You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/07/26 20:33:20 UTC

(grails-intellij-plugin) branch main updated (623a757 -> 696da9f)

This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git


    from 623a757  Merge pull request #6 from fernando88to/main
     new 8adc98a  Port build-hygiene tooling from grails-core
     new 30fadfb  Document build-from-scratch and wrapper bootstrap in README
     new 6be9d41  Fix release workflow hardcoding the pre-rename plugin ZIP name
     new 78171a4  Add release verification tooling and document the release process
     new ecfd3f0  Add release-abort workflow and drop the stale test.idea.home.path default
     new 0e3a95c  Drop grails-core references from the release docs
     new 88ae0a2  Generate the vote, result, and announcement emails from checked-in templates
     new 63fa492  Convert the artefact api and impl packages from Kotlin to Java
     new ba98364  Convert the projectView package from Kotlin to Java
     new 134d0df  Convert build-logic to Groovy precompiled script plugins
     new cb817c4  Add the Groovy convention plugins and convert hibernate as a pilot
     new 0b9505e  Convert every build file to Groovy DSL and pin all artifact names
     new e71acf3  Split compiler plugins into their own tier, distinct from libraries
     new 369fa57  Nest the compiler tier under libs and complete the group hierarchy
     new 2d2d126  Flatten the compiler namespace so no tier is a prefix of another
     new a26b552  Document every subproject, and restore the grails-rt agent manifest
     new 02f4218  Track the dormant grails-rt code in the improvement plan
     new 33967ba  Move the six content modules into pluginModules/ with standard source layout
     new 897b2e0  Move libs and compiler tiers, and drop the unused raster icons
     new 9250ef6  Promote compilers/ to a top-level tier alongside libs/
     new 88f836a  Move the plugin into plugin/, leaving the root a pure aggregator
     new b40eece  Centralize repository declarations in a settings plugin
     new b2f4c44  Rename the six content modules into the org.apache.grails namespace
     new 83356c5  Convert the structure package from Kotlin to Java
     new abc719b  Convert the gson package from Kotlin to Java
     new e74cae5  Convert the runner package and coverage module to Java
     new fa7001c  Convert the editor toolbar and action-context helpers to Java
     new f4b2666  Convert the gsp parsing, plugin discovery and test-runner setup to Java
     new 58f5e0b  Convert the last of the main-source Kotlin to Java
     new 8af0a48  Convert the five Kotlin test classes to Java
     new c315ab1  Update the build-hygiene tooling and docs for the restructure
     new c97f99a  Stop GSP taglib lookup throwing IndexNotReadyException during indexing
     new e0395ce  Update the plans to reflect what has shipped
     new 2f37ce3  Resolve a spec back to its artefact across modules and via the unit-test trait
     new 696da9f  Merge main into gradle-restructure

The 35 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .asf.yaml                                          |   3 +
 .gitattributes                                     |   8 +
 .sdkmanrc => .github/dependabot.yml                |  34 +-
 .github/vote_templates/announce.txt                |  27 ++
 .github/vote_templates/staged.txt                  |  52 +++
 .github/vote_templates/vote_succeeded.txt          |  12 +
 .github/workflows/{rat.yml => codeql.yml}          |  43 +-
 .github/workflows/{rat.yml => coverage.yml}        |  32 +-
 .github/workflows/gradle.yml                       |   8 +-
 .github/workflows/release-abort.yml                | 133 +++++++
 .github/workflows/release.yml                      | 117 +++++-
 .github/workflows/vulnerability-scan.yml           |  85 ++++
 .gitignore                                         |   2 +
 AGENTS.md                                          |  52 ++-
 IMPROVEMENT-PLAN.md                                |  59 ++-
 INSTALL                                            |  78 +++-
 MIGRATION-PLAN.md                                  | 425 ++++++--------------
 README.md                                          | 114 +++++-
 RELEASE.md                                         | 435 +++++++++++++++++++++
 build-logic/README.md                              |  74 ++++
 build-logic/build.gradle                           |  40 ++
 build-logic/settings.gradle                        |  27 ++
 .../org.apache.grails.intellij.build.base.gradle   |  28 ++
 ...rg.apache.grails.intellij.build.compiler.gradle |  59 +++
 ...ails.intellij.build.coverage-aggregation.gradle |  53 +++
 ...pache.grails.intellij.build.intellij-lib.gradle |  52 +++
 ...he.grails.intellij.build.intellij-module.gradle |  50 +++
 ....grails.intellij.build.intellij-platform.gradle |  39 ++
 ...he.grails.intellij.build.intellij-plugin.gradle |  98 +++++
 .../org.apache.grails.intellij.build.jacoco.gradle |  44 +++
 ...apache.grails.intellij.build.java-legacy.gradle |  39 ++
 .../org.apache.grails.intellij.build.java.gradle   |  45 +++
 .../org.apache.grails.intellij.build.kotlin.gradle |  34 ++
 .../org.apache.grails.intellij.build.lib.gradle    |  40 ++
 .../org.apache.grails.intellij.build.rat.gradle    |  98 +++++
 ...ils.intellij.build.repositories.settings.gradle |  48 +++
 ...pache.grails.intellij.build.reproducible.gradle |  33 ++
 .../org.apache.grails.intellij.build.test.gradle   |  31 ++
 ...grails.intellij.build.vulnerability-scan.gradle |  77 ++++
 build.gradle                                       |  42 ++
 build.gradle.kts                                   | 248 ------------
 .sdkmanrc => codecov.yml                           |  28 +-
 compatibilityResources/icons/grails.png            | Bin 312 -> 0 bytes
 compatibilityResources/icons/grails@2x.png         | Bin 588 -> 0 bytes
 compatibilityResources/icons/grails_13.png         | Bin 300 -> 0 bytes
 compatibilityResources/icons/grails_13@2x.png      | Bin 562 -> 0 bytes
 compatibilityResources/icons/grails_app.png        | Bin 346 -> 0 bytes
 compatibilityResources/icons/grails_app@2x.png     | Bin 672 -> 0 bytes
 .../icons/grails_app@2x_dark.png                   | Bin 671 -> 0 bytes
 compatibilityResources/icons/grails_app_dark.png   | Bin 347 -> 0 bytes
 compatibilityResources/icons/grails_sdk.png        | Bin 281 -> 0 bytes
 compatibilityResources/icons/grails_sdk@2x.png     | Bin 519 -> 0 bytes
 compatibilityResources/icons/grails_test.png       | Bin 399 -> 0 bytes
 compatibilityResources/icons/grails_test@2x.png    | Bin 740 -> 0 bytes
 compatibilityResources/icons/gsp_logo.png          | Bin 216 -> 0 bytes
 compatibilityResources/icons/gsp_logo@2x.png       | Bin 384 -> 0 bytes
 compatibilityResources/icons/taglib.png            | Bin 289 -> 0 bytes
 compatibilityResources/icons/taglib@2x.png         | Bin 501 -> 0 bytes
 compilers/grails-compiler-patch/README.md          |  36 ++
 compilers/grails-compiler-patch/build.gradle       |  43 ++
 .../grails/compiler/EmptyGrailsAwarePatcher.java   |   0
 .../grails/compiler/Grails2_0_JUnitPatcher.java    |   0
 .../grails/compiler/GrailsCompilerRtMarker.java    |   0
 .../grails/compiler/GrailsDomainClassPatcher.java  |   0
 .../groovy/grails/compiler/GrailsJUnitPatcher.java |   0
 .../src/main}/resources/log4j.properties           |   0
 compilers/jps-plugin/README.md                     |  30 ++
 .../jps-plugin/build.gradle                        |  19 +-
 .../jps/grails/builder/GrailsBuilderExtension.java |   0
 ...s.jps.incremental.groovy.GroovyBuilderExtension |   0
 copyright/build.gradle.kts                         |  64 ---
 coverage/build.gradle.kts                          |  69 ----
 etc/bin/Dockerfile                                 |  48 +++
 etc/bin/download-release-artifacts.sh              |  70 ++++
 etc/bin/test-reproducible-build.sh                 | 132 +++++++
 etc/bin/verify-distributions.sh                    | 173 ++++++++
 etc/bin/verify-reproducible.sh                     | 126 ++++++
 etc/bin/verify.sh                                  | 122 ++++++
 gradle-tooling/build.gradle.kts                    |  58 ---
 gradle.properties                                  |   5 +-
 grails-compiler-patch/build.gradle.kts             |  71 ----
 grails-rt/build.gradle.kts                         |  47 ---
 grails-rt/resources/META-INF/MANIFEST.MF           |   2 -
 hibernate/build.gradle.kts                         |  61 ---
 i18n/build.gradle.kts                              |  70 ----
 jps-plugin/build.gradle.kts                        |  59 ---
 langInjection/build.gradle.kts                     |  55 ---
 libs/gradle-tooling/README.md                      |  25 ++
 .../gradle-tooling/build.gradle                    |  19 +-
 .../gradle/tooling/builder/GrailsModule.java       |   0
 .../gradle/tooling/builder/GrailsModuleImpl.java   |   0
 .../builder/GrailsModuleModelBuilderImpl.java      |   0
 ...ains.plugins.gradle.tooling.ModelBuilderService |   0
 libs/grails-rt/README.md                           |  54 +++
 libs/grails-rt/build.gradle                        |  40 ++
 .../rt/AddAgentJarToClassPathTransformer.java      |   0
 .../org/jetbrains/groovy/grails/rt/Agent.java      |   0
 .../org/jetbrains/groovy/grails/rt/AgentUtils.java |   0
 .../groovy/grails/rt/ForkListenerTransformer.java  |   0
 .../groovy/grails/rt/GrailsIdeaTestListener.java   |   0
 .../jetbrains/groovy/grails/rt/GrailsRtMarker.java |   0
 .../commands/intellij-command-proxy.groovy         |   0
 .../main}/resources/idea-injected-traits.groovy    |   0
 libs/testFramework/README.md                       |  31 ++
 libs/testFramework/build.gradle                    |  49 +++
 .../service/execution/TestUnknownSdkResolver.kt    |   0
 .../externalSystem/test/JavaCompileTestUtil.kt     |   0
 .../test/JavaExternalSystemImportingTestCase.kt    |   0
 .../util/ExternalSystemFileTestUtil.kt             |   0
 .../openapi/roots/ui/configuration/SdkTestCase.kt  |   0
 .../testFramework/eelJava/EelTestJdkProvider.kt    |   0
 .../platform/testFramework/eelJava/EelTestUtil.kt  |   0
 .../gradle/importing/GradleImportingTestCase.kt    |   0
 .../importing/TestGradleBuildScripBuilder.kt       |   0
 .../importing/TestGradleSettingsScriptHelper.kt    |   0
 .../plugins/gradle/tooling/GradleJvmResolver.kt    |   0
 .../plugins/gradle/tooling/JavaVersionMatcher.kt   |   0
 .../gradle/tooling/JavaVersionRestriction.kt       |   0
 .../gradle/tooling/TargetJavaVersionWatcher.java   |   0
 .../plugins/gradle/tooling/VersionMatcherRule.java |   0
 .../tooling/annotation/TargetJavaVersion.java      |   0
 .../gradle/tooling/annotation/TargetVersions.java  |   0
 ...adleBrokenJvmSerialisationVersionRestriction.kt |   0
 .../jvm/GradleToolingApi9VersionRestriction.kt     |   0
 .../gradle/tooling/util/VersionMatcher.java        |   0
 .../plugins/gradle/util/testWrapperUtil.kt         |   0
 .../plugins/groovy/CompoundTestLibrary.java        |   0
 .../plugins/groovy/GroovyProjectDescriptors.java   |   0
 .../groovy/LibraryLightProjectDescriptor.java      |   0
 .../plugins/groovy/LightGroovyTestCase.java        |   0
 .../plugins/groovy/RepositoryTestLibrary.java      |   0
 .../org/jetbrains/plugins/groovy/TestLibrary.java  |   0
 .../groovy/completion/CompletionTestBase.java      |   0
 .../plugins/groovy/grails/Grails14TestCase.java    |   0
 .../plugins/groovy/grails/GrailsTestCase.java      |   0
 .../plugins/groovy/grails/GrailsTestUtil.java      |   0
 .../plugins/groovy/grails/HddGrailsTestCase.java   |   0
 .../grails/TestGrailsApplicationProvider.java      |   0
 .../groovy/lang/GroovyFormatterTestCase.java       |   0
 .../lang/highlighting/GrHighlightingTestBase.java  |   0
 .../jetbrains/plugins/groovy/util/TestUtils.java   |   0
 maven/build.gradle.kts                             |  72 ----
 .../maven/GrailsMavenWorkspaceConfigurator.kt      |  90 -----
 .../groovy/grails/maven/MavenNodeProvider.kt       |  36 --
 plugin/build.gradle                                |  78 ++++
 .../jetbrains/plugins/grails/GroovyMvcIcons.java   |   0
 .../lang/gsp/lexer/core/_GspDirectiveLexer.java    |   0
 .../grails/lang/gsp/lexer/core/_GspLexer.java      |   0
 .../jetbrains/plugins/grails/GantFileDetector.java |   0
 .../grails/GrailsAttachSourcesProvider.java        |   0
 .../org/jetbrains/plugins/grails/GrailsBundle.java |   0
 .../grails/GrailsCompletionContributor.java        |   0
 .../grails/GrailsConsoleFilterProvider.java        |   0
 .../grails/GrailsFlashMapContentProvider.java      |   0
 .../GrailsGroovyCodeReferenceContributor.java      |   0
 .../grails/GrailsGspReferenceContributor.java      |   0
 .../grails/GrailsHtmlReferenceContributor.java     |   0
 .../grails/GrailsImplicitUsageProvider.java        |   0
 .../GrailsPluginFieldCompletionProvider.java       |   0
 .../GrailsStaticFieldCompletionProvider.java       |   0
 .../jetbrains/plugins/grails/GsonConstants.java    |   0
 .../plugins/grails/GspCompletionContributor.java   |   0
 .../plugins/grails/GspFindUsagesProvider.java      |   0
 .../plugins/grails/GspTemplateContextType.java     |   0
 .../grails/TemplateHighlightErrorFilter.java       |   0
 .../plugins/grails/actions/ArtefactData.java       |  85 ++++
 .../grails/actions/DomainFieldIntention.java       |   0
 .../grails/actions/GenerateViewIntention.java      |   0
 .../plugins/grails/actions/GrailsActionGroup.java  |   0
 .../plugins/grails/actions/GrailsActionUtil.java   | 108 +++++
 .../grails/actions/GrailsConfigureSDKAction.java   |   2 +-
 .../GrailsCreateActionByUsageIntention.java        |   0
 .../grails/actions/GrailsGenerateViewAction.java   |   0
 .../grails/actions/GrailsGoToClassContributor.java |   0
 .../grails/actions/GrailsGotoRelatedProvider.java  |   0
 .../actions/GrailsGroovySourceFolderDetector.java  |   0
 .../grails/actions/GrailsRunCommandAction.java     |   2 +-
 .../plugins/grails/actions/GrailsWarAction.java    |   4 +-
 .../grails/actions/MakeNullableIntention.java      |   0
 .../grails/actions/MakeUniqueIntention.java        |   0
 .../grails/actions/NewGrailsControllerAction.java  |   0
 .../grails/actions/NewGrailsDomainClassAction.java |   0
 .../grails/actions/NewGrailsFilterAction.java      |   0
 .../grails/actions/NewGrailsInterceptorAction.java |   0
 .../grails/actions/NewGrailsScriptAction.java      |   0
 .../grails/actions/NewGrailsServiceAction.java     |   0
 .../grails/actions/NewGrailsTagLibAction.java      |   0
 .../plugins/grails/actions/NewGrailsXXXAction.java |  12 +-
 .../plugins/grails/actions/NewGspAction.java       |   2 +-
 .../grails/actions/UpdateGrailsSettingsAction.java |   4 +-
 .../grails/addins/GrailsIntegrationUtil.java       |   0
 .../grails/addins/js/CssIntegrationUtil.java       |   0
 .../grails/addins/js/GrailsJavaScriptInjector.java |   0
 .../grails/addins/js/GspJsInjectionTagBean.java    |   0
 .../addins/js/JavaScriptIntegrationUtil.java       |   0
 .../grails/artefact/api/ArtefactHandlers.java      |  67 ++++
 .../grails/artefact/api/GrailsArtefactHandler.java |  22 +-
 .../api/GrailsDisplayableArtefactHandler.java      |  40 +-
 .../plugins/grails/artefact/api/HandlerCache.java  |  66 ++++
 .../plugins/grails/artefact/api/IconOwner.java     |   9 +-
 .../artefact/impl/ControllerArtefactHandler.java   |  80 ++++
 .../artefact/impl/DomainArtefactHandler.java       |  77 ++++
 .../artefact/impl/FilterArtefactHandler.java       |  30 +-
 .../grails/artefact/impl/GrailsArtefacts.java      | 288 ++++++++++++++
 .../artefact/impl/InterceptorArtefactHandler.java  |  66 ++++
 .../artefact/impl/ServiceArtefactHandler.java      |  60 +++
 .../artefact/impl/TaglibArtefactHandler.java       |  67 ++++
 .../impl/controllers/ControllerActions.java        |  86 ++++
 .../grails/commands/Grails3CommandProvider.java    |   0
 .../commands/GrailsCommandCompletionUtil.java      |   0
 .../grails/commands/GrailsCommandProvider.java     |   0
 .../grails/commands/OldGrailsCommandProvider.java  |   0
 .../grails/config/AbstractConfigSupport.java       |   0
 .../BuildConfigClosureMemberContributor.java       |   0
 .../config/BuildConfigMemberContributor.java       |   0
 .../plugins/grails/config/BuildConfigSupport.java  |   0
 .../grails/config/ConfigMemberContributor.java     |   0
 .../plugins/grails/config/ConfigSupport.java       |   0
 .../plugins/grails/config/DataSourcesSupport.java  |   0
 .../grails/config/GradleParentProjectForm.form     |   0
 .../grails/config/GradleParentProjectForm.java     |   0
 .../grails/config/GrailsAbstractConfigSupport.java |   0
 .../config/GrailsConfigUnresolvedFileFilter.java   |   0
 .../plugins/grails/config/GrailsConfigUtils.java   |   0
 .../plugins/grails/config/GrailsConstants.java     |   0
 .../GrailsEnvironmentClosureMemberContributor.java |   0
 .../plugins/grails/config/GrailsFramework.java     |   0
 .../config/GrailsGradleAwareModuleBuilder.java     | 158 ++++++++
 .../grails/config/GrailsIgnoredProvider.java       |  55 +++
 .../config/GrailsLibraryPresentationProvider.java  |   0
 .../plugins/grails/config/GrailsModuleBuilder.java |   0
 .../grails/config/GrailsModuleStructureUtil.java   |   0
 .../grails/config/GrailsOptionsWizardStep.form     |   0
 .../grails/config/GrailsOptionsWizardStep.java     |   0
 .../GrailsPluginConfigMethodContributor.java       |   0
 .../GrailsPluginNameCompletionContributor.java     |   0
 .../GrailsPluginWebHelpReferenceProvider.java      |   0
 .../config/GrailsProjectStructureDetector.java     |   0
 .../grails/config/GrailsPropertiesFileCache.java   |   0
 .../grails/config/GrailsSettingSynchronizer.java   |   0
 .../plugins/grails/config/GrailsSettings.java      |   0
 .../grails/config/GrailsSettingsService.java       |   0
 .../plugins/grails/config/GrailsStructure.java     |   0
 .../plugins/grails/config/GroovyConfigReader.java  |   0
 .../config/PrintGrailsSettingsConstants.java       |   0
 .../grails/database/GrailsDataSourceDetector.java  |   0
 .../plugins/grails/editor/GenerateTestsAction.java |   4 +-
 .../grails/editor/GrailsEditorDecorator.java       |  54 +++
 .../plugins/grails/editor/GrailsEditorToolbar.java | 107 +++++
 .../editor/ViewGrailsEditorToolbarAction.java      |  63 +++
 .../selection/GrailsDependencySelectioner.java     |   0
 .../GrailsGroovyInjectionSelectioner.java          |   0
 .../grails/editor/toolbar/GenerateActionBase.java  | 102 +++++
 .../toolbar/GenerateAsyncControllerAction.java     |  10 +-
 .../editor/toolbar/GenerateControllerAction.java   |  10 +-
 .../toolbar/GenerateControllerActionBase.java      |  47 +++
 .../grails/editor/toolbar/GenerateViewsAction.java |  14 +-
 .../editor/toolbar/GoToControllerAction.java       |  73 ++++
 .../grails/editor/toolbar/GoToDomainAction.java    |  28 +-
 .../grails/editor/toolbar/GoToServiceAction.java   |  28 +-
 .../grails/editor/toolbar/GoToTestAction.java      |  94 +++++
 .../grails/editor/toolbar/GoToViewAction.java      |  83 ++++
 .../toolbar/GrailsGoToArtefactActionBase.java      |  75 ++++
 .../editor/toolbar/GrailsToolbarActionBase.java    |  86 ++++
 .../editor/toolbar/GrailsToolbarTargetAction.java  |  68 ++++
 .../editor/toolbar/GrailsToolbarVfileAction.java   |  44 +++
 .../feature/forge/GrailsForgeModuleBuilder.kt      |   0
 .../plugins/grails/feature/linkable/Linkable.java  |  23 +-
 .../linkable/LinkableNamedArgumentsProvider.java   | 134 +++++++
 .../linkable/LinkableTransformationSupport.java    |  56 +++
 .../plugins/grails/fileType/GspFileType.java       |   0
 .../plugins/grails/gorm/GormClassNames.java        |   0
 .../jetbrains/plugins/grails/gorm/GormVersion.java |  76 ++++
 .../plugins/grails/gradle/GrailsModuleData.java    |   0
 .../gradle/GrailsModuleGradleDataService.java      |   0
 .../gradle/GrailsProjectResolverExtension.java     |   0
 .../grails/gradle/GrailsTaskManagerExtension.java  |   0
 .../GrailsStreamingJsonBuilderContributor.java     |   0
 .../grails/gson/GsonControllerReference.java       |  47 +++
 .../grails/gson/GsonExpectedPackageProvider.java   |  33 ++
 .../plugins/grails/gson/GsonImportContributor.java |  33 +-
 .../grails/gson/GsonModelArgumentsProvider.java    |  37 +-
 .../gson/GsonModelFieldArgumentDescriptor.java     |  54 +++
 .../plugins/grails/gson/GsonPatterns.java          | 100 +++++
 .../grails/gson/GsonPositionManagerDelegate.java   |  97 +++++
 .../plugins/grails/gson/GsonRWAccessDetector.java  |  49 +++
 .../grails/gson/GsonReferenceContributor.java      |   8 +-
 .../gson/GsonRenderNamedArgumentsProvider.java     |  54 +++
 .../plugins/grails/gson/GsonTemplateReference.java |  74 ++++
 .../grails/gson/GsonTemplateReferenceProvider.java |  15 +-
 .../gson/GsonTemplateReferenceQueryExecutor.java   |  54 +++
 .../gson/GsonTemplateRendererContributor.java      |  51 ++-
 .../grails/gson/GsonTransformationSupport.java     |  64 +++
 .../jetbrains/plugins/grails/gson/GsonUtils.java   | 117 ++++++
 .../inspections/GrailsMethodMayBeStaticFilter.java |   0
 .../grails/inspections/GspAndGroovyInspection.java |   0
 .../grails/inspections/GspInspectionUtil.java      |   0
 .../grails/inspections/GspSuppressionProvider.java |   0
 .../grails/lang/DirtyCheckableContributor.java     |  73 ++++
 .../plugins/grails/lang/GormTraitContributor.java  | 184 +++++++++
 .../lang/Grails3TraitInjectorContributor.java      |   0
 .../plugins/grails/lang/gsp/GspCssInjector.java    |   0
 .../plugins/grails/lang/gsp/GspDirectiveKind.java  |   0
 .../plugins/grails/lang/gsp/GspErrorFilter.java    |   0
 .../grails/lang/gsp/GspFileViewProvider.java       |   0
 .../lang/gsp/GspFileviewProviderFactory.java       |   0
 .../plugins/grails/lang/gsp/GspLanguage.java       |   0
 .../lang/gsp/GspTagDocumentationProvider.java      |   0
 .../plugins/grails/lang/gsp/GspTagInspection.java  |   0
 .../gsp/completion/GspXmlFileNSInfoProvider.java   |   0
 .../lang/gsp/debug/GspCodeFragmentFactory.java     |   0
 .../grails/lang/gsp/debug/GspPositionManager.java  |   0
 .../lang/gsp/debug/GspPositionManagerFactory.java  |   0
 .../gsp/editor/actions/GspEditorActionsUtil.java   |   0
 .../lang/gsp/editor/actions/GspEnterHandler.java   |   0
 .../lang/gsp/editor/actions/GspTypedHandler.java   |   0
 .../grails/lang/gsp/folding/GspFoldingBuilder.java |   0
 .../lang/gsp/formatter/AbstractGspBlock.java       |   0
 .../lang/gsp/formatter/ForeignLanguageBlock.java   |   0
 .../grails/lang/gsp/formatter/GspBlock.java        |   0
 .../lang/gsp/formatter/GspBlockGenerator.java      |   0
 .../lang/gsp/formatter/GspFormattingHelper.java    |   0
 .../gsp/formatter/GspFormattingModelBuilder.java   |   0
 .../grails/lang/gsp/formatter/GspGroovyBlock.java  |   0
 .../grails/lang/gsp/formatter/GspHtmlBlock.java    |   0
 .../lang/gsp/formatter/GspTextSyntheticBlock.java  |   0
 .../formatter/processors/GspIndentProcessor.java   |   0
 .../formatter/processors/GspSpacingProcessor.java  |   0
 .../settings/GspCodeStyleSettingsProvider.java     |   0
 .../GspLanguageCodeStyleSettingsProvider.java      |   0
 .../grails/lang/gsp/gspIndex/GspIncludeIndex.java  |   0
 .../grails/lang/gsp/gspIndex/GspIncludeInfo.java   |   0
 .../grails/lang/gsp/highlighter/GspCommenter.java  |   0
 .../gsp/highlighter/GspDirectiveHighlighter.java   |   0
 .../highlighter/GspDirectiveHighlightingLexer.java |   0
 .../lang/gsp/highlighter/GspEditorHighlighter.java |   0
 .../highlighter/GspEditorHighlighterProvider.java  |  30 +-
 .../lang/gsp/highlighter/GspFileBraceMatcher.java  |   0
 .../gsp/highlighter/GspIndexPatternBuilder.java    |   0
 .../gsp/highlighter/GspPairedBraceMatcher.java     |   0
 .../lang/gsp/highlighter/GspSyntaxHighlighter.java |   0
 .../lang/gsp/lexer/GspDirectiveFlexLexer.java      |   0
 .../grails/lang/gsp/lexer/GspFlexLexer.java        |   0
 .../grails/lang/gsp/lexer/GspTokenTypesEx.java     |   0
 .../grails/lang/gsp/lexer/IGspElementType.java     |   0
 .../grails/lang/gsp/lexer/core/GspTokenTypes.java  |   0
 .../plugins/grails/lang/gsp/lexer/core/gsp.flex    |   0
 .../grails/lang/gsp/lexer/core/gsp_directive.flex  |   0
 .../grails/lang/gsp/parsing/GspElementTypes.java   |   0
 .../lang/gsp/parsing/GspGroovyElementTypes.java    |   0
 .../grails/lang/gsp/parsing/GspPsiCreator.java     |   0
 .../parsing/composite/GspCompositeElementType.java |   0
 .../gsp/parsing/groovy/GspAwareGroovyParser.java   |   0
 .../lang/gsp/parsing/groovy/GspGroovyParser.java   |   0
 .../gsp/parsing/groovy/GspTemplateStmtParsing.java |   0
 .../chameleons/GroovyDeclarationsElementType.java  |  18 +-
 .../GroovyDeclarationsInGspFileRoot.java           |   0
 .../chameleons/GroovyExpressionElementType.java    |  18 +-
 .../chameleons/GroovyMapAttributeValueType.java    |  18 +-
 .../chameleons/GspGroovyLazyElementType.java       |  27 +-
 .../gsp/parsing/groovy/lexer/GspGroovyLexer.java   |   0
 .../grails/lang/gsp/parsing/gsp/GrailsTag.java     |   0
 .../grails/lang/gsp/parsing/gsp/GspParser.java     |   0
 .../lang/gsp/parsing/gsp/GspParserDefinition.java  |   0
 .../gsp/chameleons/GspDirectiveElement.java        |   0
 .../lang/gsp/parsing/gsp/lexer/GspLexer.java       |   0
 .../gsp/parsing/html/GspHtmlTemplateRootType.java  |   0
 .../gsp/parsing/html/elements/GspXmlDocument.java  |   0
 .../parsing/outers/GspGroovyCodeElementType.java   |   0
 .../gsp/parsing/outers/GspInHtmlElementType.java   |   0
 .../parsing/outers/GspTemplateDataElementType.java |   0
 .../grails/lang/gsp/psi/GspPsiElementFactory.java  |   0
 .../lang/gsp/psi/GspPsiElementFactoryImpl.java     |   0
 .../plugins/grails/lang/gsp/psi/GspPsiUtil.java    |   0
 .../grails/lang/gsp/psi/groovy/GspLazyElement.java |   0
 .../grails/lang/gsp/psi/groovy/api/GrGspClass.java |   0
 .../gsp/psi/groovy/api/GrGspDeclarationHolder.java |   0
 .../gsp/psi/groovy/api/GrGspExprInjection.java     |   0
 .../lang/gsp/psi/groovy/api/GrGspRunBlock.java     |   0
 .../lang/gsp/psi/groovy/api/GrGspRunMethod.java    |   0
 .../gsp/psi/groovy/api/GrMapAttributeValue.java    |   0
 .../lang/gsp/psi/groovy/api/GspGroovyFile.java     |   0
 .../gsp/psi/groovy/api/GspOuterGroovyElement.java  |   0
 .../gsp/psi/groovy/api/GspOuterHtmlElement.java    |   0
 .../groovy/api/template/GspTemplateStatement.java  |   0
 .../lang/gsp/psi/groovy/impl/GrGspClassImpl.java   |   0
 .../groovy/impl/GrGspDeclarationHolderImpl.java    |   0
 .../psi/groovy/impl/GrGspExprInjectionImpl.java    |   0
 .../gsp/psi/groovy/impl/GrGspRunBlockImpl.java     |   0
 .../gsp/psi/groovy/impl/GrGspRunMethodImpl.java    |   0
 .../gsp/psi/groovy/impl/GrMapAttrValueImpl.java    |   0
 .../gsp/psi/groovy/impl/GspGroovyFileImpl.java     |   0
 .../gsp/psi/groovy/impl/GspModelVariableModel.java |   0
 .../psi/groovy/impl/GspOuterGroovyElementImpl.java |   0
 .../psi/groovy/impl/GspOuterHtmlElementImpl.java   |   0
 .../impl/GspOuterHtmlElementManipulator.java       |   0
 .../lang/gsp/psi/groovy/impl/GspPageSkeleton.java  |   0
 .../impl/ModelVariableDeclarationSearcher.java     |   0
 .../impl/template/GspTemplateStatementImpl.java    |   0
 .../lang/gsp/psi/gsp/api/GspDeclarationTag.java    |   0
 .../lang/gsp/psi/gsp/api/GspExpressionTag.java     |   0
 .../grails/lang/gsp/psi/gsp/api/GspFile.java       |   0
 .../lang/gsp/psi/gsp/api/GspScriptletTag.java      |   0
 .../grails/lang/gsp/psi/gsp/api/GspTag.java        |   0
 .../grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java |   0
 .../gsp/psi/gsp/api/directive/GspDirective.java    |   0
 .../gsp/api/directive/GspDirectiveAttribute.java   |   0
 .../api/directive/GspDirectiveAttributeValue.java  |   0
 .../lang/gsp/psi/gsp/api/gtag/GspAttribute.java    |   0
 .../gsp/psi/gsp/api/gtag/GspAttributeValue.java    |   0
 .../lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java    |   0
 .../gsp/psi/gsp/api/gtag/GspTaggedElement.java     |   0
 .../lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java   |   0
 .../gsp/psi/gsp/impl/GspDeclarationTagImpl.java    |   0
 .../lang/gsp/psi/gsp/impl/GspExprTagImpl.java      |   0
 .../grails/lang/gsp/psi/gsp/impl/GspFileImpl.java  |   0
 .../lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java |   0
 .../lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java   |   0
 .../impl/directive/GspDirectiveAttributeImpl.java  |   0
 .../directive/GspDirectiveAttributeValueImpl.java  |   0
 .../psi/gsp/impl/directive/GspDirectiveImpl.java   |   0
 .../gsp/psi/gsp/impl/gtag/GspAttributeImpl.java    |   0
 .../psi/gsp/impl/gtag/GspAttributeValueImpl.java   |   0
 .../impl/gtag/GspContentTagDescriptorProvider.java |   0
 .../gsp/impl/gtag/GspElementDescriptorBase.java    |   0
 .../gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java    |   0
 .../gsp/impl/gtag/GspLinkElementDescriptor.java    |   0
 .../gsp/impl/gtag/GspLinkNamespaceDescriptor.java  |   0
 .../psi/gsp/impl/gtag/GspNamespaceDescriptor.java  |   0
 .../impl/gtag/GspPropertyElementDescriptor.java    |   0
 .../psi/gsp/impl/gtag/GspSdkElementDescriptor.java |   0
 .../psi/gsp/impl/gtag/GspTagDescriptorService.java |   0
 .../gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java  |   0
 .../gtag/GspTagVariableDeclarationSearcher.java    |   0
 .../gsp/impl/gtag/GspTmplNamespaceDescriptor.java  |   0
 .../gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java   |   7 +-
 .../DefaultCodecDirectiveReferenceProvider.java    |   0
 .../GspCssClassReferenceContributor.java           |   0
 .../lang/gsp/psi/html/api/GspHtmlOuterElement.java |   0
 .../grails/lang/gsp/psi/html/api/GspLikeFile.java  |   0
 .../lang/gsp/psi/html/impl/GspHtmlFileImpl.java    |   0
 .../gsp/psi/html/impl/GspHtmlOuterElementImpl.java |   0
 .../gsp/psi/html/impl/GspHtmlXmlExtension.java     |   0
 .../grails/lang/gsp/psi/impl/GspImportUtil.java    |  89 +++++
 .../taglib/BuiltInTagReferenceSearcher.java        |   0
 .../taglib/GrailsTaglibNamedArgumentProvider.java  |   0
 .../lang/gsp/resolve/taglib/GspTagLibUtil.java     |  25 +-
 .../resolve/taglib/TagLibNamespaceDescriptor.java  |   0
 .../gsp/search/GspControllerReferenceSearcher.java |   0
 .../lang/gsp/search/GspPageReferenceSearcher.java  |   0
 .../lang/gsp/search/JspPageReferenceSearcher.java  |   0
 .../plugins/grails/lang/gsp/util/GspUtil.java      |   0
 .../perspectives/DomainClassNodeRenderer.java      |   0
 .../perspectives/DomainClassesRelationsEditor.java |   0
 .../DomainClassesRelationsEditorProvider.java      |   0
 .../PerspectiveFileEditorComponent.java            |   0
 .../perspectives/create/CreateNewRelation.form     |   0
 .../perspectives/create/CreateNewRelation.java     |   0
 .../create/RelationsCreationsProvider.java         |   0
 .../delete/RelationDeleteProvider.java             |   0
 .../DataModelAndSelectionModificationTracker.java  |   0
 .../graph/DomainClassDependencyPresentation.java   |   0
 .../grails/perspectives/graph/DomainClassNode.java |   0
 .../graph/DomainClassRelationsInfo.java            |   0
 .../graph/DomainClassesRelationsDataModel.java     |   0
 .../assetPipeline/AssetReferenceContributor.java   |  51 +++
 .../assetPipeline/AssetReferenceProvider.java      |   8 +-
 .../assetPipeline/AssetScriptInjector.java         |  62 +++
 .../assetPipeline/AssetsFileReferenceSet.java      | 124 ++++++
 .../GrailsBuildTestDataMemberProvider.java         |   0
 .../resources/GrailsResourceModuleReference.java   |   0
 .../resources/GrailsResourceReferenceSearcher.java |   0
 .../GrailsResourcesClosureMethodContributor.java   |   0
 .../GrailsResourcesPomDeclarationSearcher.java     |   0
 .../GrailsResourcesReferenceProvider.java          |   0
 .../resources/GrailsResourcesUtil.java             |   0
 .../seachable/GrailsSearchableMemberProvider.java  |   0
 .../GrailsSearchableReturnTypeCalculator.java      |   0
 .../seachable/GrailsSearchableUtil.java            |   0
 .../SearchableFieldReferenceProvider.java          |   0
 .../SearchableMemberMethodContributor.java         |   0
 .../GrailsShiroAccessControlMethodProvider.java    |   0
 .../shiro/GrailsShiroReferenceProvider.java        |   0
 .../pluginSupport/spock/GrailsSpockEnhancer.java   |   0
 .../WebFlowEventDefinitionMethodContributor.java   |   0
 .../webflow/WebFlowMethodContributor.java          |   0
 .../webflow/WebFlowPomDeclarationSearcher.java     |   0
 .../webflow/WebFlowStateNameReferenceProvider.java |   0
 .../grails/pluginSupport/webflow/WebFlowUtils.java |   0
 .../plugins/Grails3CompiledPluginDescriptor.java   |  47 +++
 .../plugins/Grails3SourcePluginDescriptor.java     |  47 +++
 .../grails/plugins/GrailsPluginDescriptor.java     |  39 ++
 .../plugins/grails/plugins/GrailsPlugins.java      | 109 ++++++
 .../grails/projectView/GrailsIconProvider.java     |   4 +-
 .../grails/projectView/GrailsPluginsNode.java      |  69 ++++
 .../grails/projectView/GrailsProjectViewPane.java  | 132 +++++++
 .../grails/projectView/GrailsProjectViewPanes.java |  54 +++
 .../projectView/GrailsTreeStructureProvider.java   |  70 ++++
 .../plugins/grails/projectView/NodeWeights.java    |   0
 .../projectView/api/GrailsSingleNodeProvider.java  |  23 +-
 .../projectView/api/GrailsViewNodeProvider.java    |  15 +-
 .../projectView/impl/Grails3NodeProvider.java      |  77 ++++
 .../projectView/impl/GrailsAppNodeProvider.java    |  67 ++++
 .../projectView/impl/GrailsNodeComparator.java     |  75 ++++
 .../grails/projectView/impl/GrailsViewItems.java   |  81 ++++
 .../projectView/impl/OldGrailsNodeProvider.java    | 135 +++++++
 .../grails/projectView/nodes/CompactedFqn.java     | 119 ++++++
 .../grails/projectView/nodes/GrailsActionNode.java |  60 +++
 .../projectView/nodes/GrailsApplicationNode.java   |  75 ++++
 .../nodes/GrailsArtefactHandlerNode.java           |  58 +++
 .../nodes/GrailsArtefactHandlerNodeBase.java       |  50 +++
 .../nodes/GrailsArtefactPackageNode.java           |  72 ++++
 .../projectView/nodes/GrailsControllerNode.java    |  62 +++
 .../projectView/nodes/GrailsFlatPackageNode.java   |  77 ++++
 .../grails/projectView/nodes/GrailsNodes.java      | 186 +++++++++
 .../projectView/nodes/GrailsPackageValue.java      |  10 +-
 .../grails/projectView/nodes/GrailsPluginNode.java |  78 ++++
 .../projectView/nodes/GrailsPsiDirectoryNode.java  |  85 ++++
 .../nodes/GrailsRegularPackageNode.java            | 104 +++++
 .../grails/projectView/nodes/GrailsRootNode.java   |  54 +++
 .../projectView/nodes/OldGrailsPluginsNode.java    |  74 ++++
 .../nodes/OtherGrailsAppSourcesNode.java           | 114 ++++++
 .../grails/projectView/nodes/TreeNodeUtil.java     |  60 +++
 .../references/CodecNonCodeMemberContributor.java  |   0
 .../grails/references/EnhancesTraitsCollector.java |  90 +++++
 .../GrailsArtifactNonCodeMemberProcessor.java      |   0
 .../references/GrailsArtifactRenameProcessor.java  |   0
 .../references/GrailsClosureMemberContributor.java |   0
 ...GrailsMethodNamedArgumentReferenceProvider.java |   0
 .../GrailsResourceClassMemberContributor.java      |   0
 .../plugins/grails/references/MemberProvider.java  |   0
 .../grails/references/TraitInjectorService.kt      |   6 +-
 .../bootstrap/GrailsBootStrapMemberProvider.java   |   0
 .../BuildConfigFileReferenceProvider.java          |   0
 .../common/ArtifactFieldReferenceSearcher.java     |   0
 .../ArtifactPropertyGetterReferenceSearcher.java   |   0
 .../grails/references/common/CodecReference.java   |   0
 .../references/common/ContentTypeReference.java    |   0
 .../common/ContentTypeReferenceProvider.java       |   0
 .../common/ContextPathReferenceProvider.java       |   0
 .../common/GrailsEncodingReferenceProvider.java    |   0
 .../common/GrailsFileReferenceSetBase.java         |   0
 .../common/GrailsLayoutFileReferenceSet.java       |   0
 .../common/GrailsRootBasedFileReferenceSet.java    |   0
 .../common/GrailsViewFileReferenceSet.java         |   0
 .../common/GroovyGspAttributeWrapper.java          |   0
 .../references/common/GroovyGspTagWrapper.java     |   0
 .../references/common/GspAttributeWrapper.java     |   0
 .../grails/references/common/GspTagWrapper.java    |   0
 .../grails/references/common/PluginReference.java  |   0
 .../common/PluginSupportFileReferenceSet.java      |   0
 .../ResourceDirAttributeFileReferenceSet.java      |   0
 .../common/TemplateFileReferenceSet.java           |   0
 .../common/WebAppFolderFileReferenceSet.java       |   0
 .../references/common/XmlGspAttributeWrapper.java  |   0
 .../grails/references/common/XmlGspTagWrapper.java |   0
 .../references/constraints/GormRegexInjector.java  |   0
 ...GrailsConstraintGroupNamedArgumentProvider.java |   0
 .../GrailsConstraintNamedArgumentProvider.java     |   0
 .../GrailsConstraintsClosureMemberProvider.java    |   0
 ...railsConstraintsGroupClosureMemberProvider.java |   0
 .../GrailsConstraintsSharedReferenceProvider.java  |   0
 .../constraints/GrailsConstraintsUtil.java         |   0
 .../GrailsValidationMethodsContributor.java        |   0
 .../GrailsValidatorParamTypeEnhancer.java          |   0
 .../references/controller/ActionReference.java     |   0
 .../controller/ActionRenameProcessor.java          |   0
 .../ControllerAllowedMethodCompletionProvider.java |   0
 .../ControllerAllowedMethodReferenceProvider.java  |   0
 .../ControllerFieldReferenceProvider.java          |   0
 .../ControllerLayoutReferenceProvider.java         |   0
 .../controller/ControllerMembersProvider.java      |   0
 .../references/controller/ControllerReference.java |   0
 .../controller/ControllerReferenceProvider.java    |   0
 .../controller/ControllerRenameProcessor.java      |   0
 .../ControllerRenderTemplateRefProvider.java       |   0
 .../controller/GrailsNamespaceReference.java       |   0
 .../GrailsWithFormatClosureMethodContributor.java  |   0
 .../controller/GspTagMethodComparator.java         |   0
 .../controller/GspTagMethodOverloadResolver.java   |  17 +-
 .../controller/HttpRequestMemberContributor.java   |   0
 .../controller/HttpResponseMemberContributor.java  |   0
 .../grails/references/domain/DomainClassUtils.java |   0
 .../grails/references/domain/DomainDescriptor.java |   0
 .../domain/DomainFieldRenameProcessor.java         |   0
 .../references/domain/DomainMembersProvider.java   |   0
 .../DomainStaticMemberCompletionProvider.java      |   0
 .../domain/GormAddToNamedArgumentProvider.java     |   0
 .../domain/GormAstTransformationContributor.java   |   3 +-
 .../domain/GormConstraintMethodProvider.java       |   0
 .../GormDynamicFinderCompletionProvider.java       |   0
 .../GormEmbeddedPropertyListReferenceProvider.java |   0
 .../domain/GormFetchModeCompletionProvider.java    |   0
 .../domain/GormFetchModeReferenceProvider.java     |   0
 ...GormGetPersistentValueReturnTypeCalculator.java |   0
 .../grails/references/domain/GormHqlInjector.java  |   0
 .../domain/GormMappingMethodProvider.java          |   0
 .../domain/GormNamedArgumentReferenceProvider.java |   0
 .../GormPersistentPropertiesNamedArgProvider.java  |   0
 ...rmPropertiesListReferenceReferenceProvider.java |   0
 .../GormPropertyConstraintReferenceProvider.java   |   0
 .../references/domain/GormPropertyReference.java   |   0
 .../domain/GormPropertyReferenceUnique.java        |   0
 .../domain/GormSortNamedArgumentProvider.java      |   0
 .../GormUniqueConstraintReferenceProvider.java     |   0
 .../grails/references/domain/GormUtils.java        |   0
 .../GrailsHasManyBelongsToReferencesProvider.java  |   0
 ...lsHasManyBelongsToValuesReferencesProvider.java |   0
 .../GrailsSyntheticFieldDeclarationSearcher.java   |   0
 .../CriteriaBuilderImplicitMemberContributor.java  |   0
 .../CriteriaBuilderNamedArgumentProvider.java      |   0
 .../domain/criteria/CriteriaBuilderUtil.java       |   0
 .../criteria/CriteriaClosureMemberContributor.java |   0
 .../CriteriaPropertyReferenceProvider.java         |   0
 .../criteria/CriteriaReturnTypeCalculator.java     |   0
 .../DetachedCriteriaClosureMemberProvider.java     |   0
 .../DetachedCriteriaDelegatesToProvider.java       |  58 +++
 .../DetachedCriteriaMemberProvider.java            |   0
 .../DetachedCriteriaPropertyNamedArgProvider.java  |   0
 .../DetachedCriteriaReferenceProvider.java         |   0
 .../detachedCriteria/DetachedCriteriaUtil.java     |   0
 .../GormNamedQueryCallTypeCalculator.java          |   0
 .../GormNamedQueryDeclarationSearcher.java         |   0
 .../NamedCriteriaProxyMembersContributor.java      |   0
 .../NamedCriteriaProxyNamedArgumentProvider.java   |   0
 .../NamedCriteriaProxyTypeCalculator.java          |   0
 .../domain/namedQuery/NamedQueryDescriptor.java    |   0
 .../domain/persistent/GormBasicAttribute.java      |   0
 .../domain/persistent/GormCollectionAttribute.java |   0
 .../domain/persistent/GormEmbeddedAttribute.java   |   0
 .../references/domain/persistent/GormEntity.java   |   0
 .../domain/persistent/GormIdAttribute.java         |   0
 .../domain/persistent/GormPersistenceMapping.java  |   0
 .../domain/persistent/GormPersistentAttribute.java |   0
 .../filter/FilterClosureMemberProvider.java        |   0
 .../references/filter/FilterMemberProvider.java    |   0
 .../references/filter/FilterReferenceProvider.java |   0
 .../GrailsFilterClosureMemberContributor.java      |   0
 .../grails/references/filter/GrailsFilterUtil.java |   0
 .../references/jobs/JobClosureMethodProvider.java  |   0
 .../grails/references/jobs/JobsMemberProvider.java |   0
 ...ailsTemplateNamedArgumentReferenceProvider.java |   0
 .../GrailsPluginClassMemberContributor.java        |   0
 .../GrailsPluginExcludeReferenceProvider.java      |   0
 .../GrailsPluginListReferenceProvider.java         |   0
 .../pluginClass/GrailsPluginParameterEnhancer.java |   0
 .../providers/GspImportListReferenceProvider.java  |   0
 .../tagSupport/CodecAttributeSupport.java          |   0
 .../tagSupport/GspActionAttributeSupport.java      |   0
 .../GspApplyLayoutNameAttributeSupport.java        |   0
 .../tagSupport/GspContextPathAttributeSupport.java |   0
 .../tagSupport/GspControllerAttributeSupport.java  |   0
 .../tagSupport/GspFieldValueTagSupport.java        |   0
 .../tagSupport/GspIncludeViewAttributeSupport.java |   0
 .../tagSupport/GspLayoutReferenceProvider.java     |   0
 .../tagSupport/GspMappingAttributeSupport.java     |   0
 .../references/tagSupport/GspMetaTagSupport.java   |   0
 .../tagSupport/GspPluginAttributeSupport.java      |   0
 .../tagSupport/GspRResourceModuleAttrSupport.java  |   0
 .../GspRResourceRequireTagModulesAttrSupport.java  |   0
 .../GspRResourceUriAttributeSupport.java           |   0
 .../GspResourceContextPathAttributeSupport.java    |   0
 .../tagSupport/GspResourceDirAttributeSupport.java |   0
 .../GspResourceFileAttributeSupport.java           |   0
 .../references/tagSupport/GspSelectTagSupport.java |   0
 .../tagSupport/GspSrcJsAttributeSupport.java       |   0
 .../GspTagSupportGspReferenceProvider.java         |   0
 .../tagSupport/GspTemplateAttributeSupport.java    |   0
 .../tagSupport/GspUpdateAttributeSupport.java      |   0
 .../tagSupport/TagAttributeReferenceProvider.java  |   0
 .../taglib/TaglibClosureParamEnhancer.java         |   0
 .../references/taglib/TaglibMembersProvider.java   |   0
 .../UrlMappingConstraintClosureMemberProvider.java |   0
 .../urlMappings/UrlMappingMemberProvider.java      |   0
 .../UrlMappingNamedArgumentProvider.java           |   0
 .../urlMappings/UrlMappingReferenceProvider.java   |   0
 .../UrlMappingUnresolvedHighlightingFilter.java    |   0
 .../references/urlMappings/UrlMappingUtil.java     |   0
 .../plugins/grails/references/util/CodecUtil.java  |   0
 .../grails/references/util/PsiFieldReference.java  |   0
 .../runner/GrailsAppEngineDebuggerRunner.java      |   0
 .../grails/runner/GrailsCommandExecutor.java       |   0
 .../grails/runner/GrailsCommandExecutorUtil.java   |   0
 .../grails/runner/GrailsCommandLineExecutor.java   |   0
 .../grails/runner/GrailsConfigurationFactory.java  |   0
 .../plugins/grails/runner/GrailsConsole.java       |   0
 .../plugins/grails/runner/GrailsConsoleState.java  |  55 +++
 .../grails/runner/GrailsDebuggerRunner.java        | 147 +++++++
 .../grails/runner/GrailsInstallationExecutor.java  |   0
 .../runner/GrailsRerunFailedTestsAction.java       |   0
 .../grails/runner/GrailsRunConfiguration.java      |   0
 .../runner/GrailsRunConfigurationExtension.java    |   0
 .../grails/runner/GrailsRunConfigurationType.java  |   0
 .../plugins/grails/runner/GrailsRunnerSetup.java   | 131 +++++++
 .../GrailsScriptRunConfigurationProducer.java      |   4 +-
 ...atableRunConfigurationWithCommonParameters.java |   0
 .../runner/impl/BaseGrailsCommandLineState.java    |  53 +++
 .../runner/impl/Grails3GradleCommandExecutor.java  |   2 +-
 .../impl/Grails3InstallationCommandExecutor.java   |   2 +-
 .../runner/impl/Grails3TestCommandLineState.java   | 114 ++++++
 .../grails/runner/impl/GrailsCommandLineState.java |   4 +-
 .../impl/GrailsInstallationCommandExecutor.java    |   0
 .../runner/impl/GrailsRunAppCommandLineState.java  |  48 +++
 .../runner/impl/GrailsTestAppCommandLineState.java |  35 ++
 .../runner/impl/GrailsTestCommandLineState.java    |   0
 .../runner/ui/GrailsApplicationCombobox.java       |   2 +-
 .../runner/ui/GrailsRunConfigurationEditor.form    |   0
 .../runner/ui/GrailsRunConfigurationEditor.java    |   0
 .../ui/GrailsRunConfigurationEditorListener.java   |   0
 .../GrailsRunConfigurationEditorWithListener.java  |   0
 .../grails/runner/util/GrailsExecutionUtils.java   |   0
 .../jetbrains/plugins/grails/sdk/GrailsSDK.java    |   0
 .../plugins/grails/sdk/GrailsSDKManager.java       |   0
 .../grails/service/GrailsBackgroundService.java    |   0
 .../spring/GrLightVariableRenameProcessor.java     |   0
 .../grails/spring/GrailsCustomSpringComponent.java |   0
 .../GrailsInjectedBeanCompletionContributor.java   |   0
 .../grails/spring/GrailsResourceBeanExtractor.java |   0
 .../GrailsResourcesGroovyMemberContributor.java    |   0
 .../grails/spring/GrailsSpringBeanDiscoverer.java  |   4 +-
 .../grails/spring/GrailsSpringFacetProvider.java   |   0
 .../GrailsSpringMethodReferenceProvider.java       |   0
 .../grails/spring/GrailsSpringSemContributor.java  |  43 ++
 ...ilsSpringStereotypeAliasingPsiTargetMapper.java |   0
 ...ilsWebApplicationContextMembersContributor.java |   0
 .../grails/spring/InjectedSpringBeanProvider.java  |   0
 .../grails/spring/SpringBeanReferenceSearcher.java |   0
 .../grails/spring/SpringGrailsTargetMapper.java    |   0
 .../grails/spring/SpringInjectionAnnotator.java    |   0
 .../grails/structure/Grails3Application.java       |   9 +-
 .../grails/structure/GrailsApplication.java        |  58 +++
 .../grails/structure/GrailsApplicationBase.java    |  86 ++++
 .../structure/GrailsApplicationListener.java       |   0
 .../grails/structure/GrailsApplicationManager.java |   0
 .../GrailsApplicationManagerRefresher.java         |   0
 .../structure/GrailsApplicationProvider.java       |   0
 .../grails/structure/GrailsApplications.java       |  47 +++
 .../grails/structure/GrailsCommonClassNames.java   | 143 +++++++
 .../grails/structure/GrailsSDKListener.java        |   0
 .../grails/structure/OldGrailsApplication.java     |  11 +-
 .../grails/structure/impl/Grails2Application.java  |  73 ++++
 .../structure/impl/Grails3ApplicationBase.java     |  91 +++++
 .../structure/impl/Grails3ApplicationProvider.java |  64 +++
 .../impl/Grails3MultiModuleApplication.java        |  93 +++++
 .../impl/Grails3SingleModuleApplication.java       |  57 +++
 .../impl/GrailsModuleBasedApplication.java         |  77 ++++
 .../grails/structure/impl/GrailsProperties.java    |  24 +-
 .../impl/OldGrailsModuleBasedApplication.java      |  81 ++++
 .../impl/PlainGrailsApplicationProvider.java       |   0
 .../sync/GrailsApplicationBackgroundTask.java      |   0
 .../sync/GrailsMvcStructureRefresher.java          |   0
 .../structure/sync/GrailsRunConfigurationTask.java |   0
 .../grails/structure/sync/GrailsSdkCheckTask.java  |   0
 .../structure/sync/GrailsStartupActivity.java      |   4 +-
 .../grails/tests/GrailsJUnitRecognizer.java        |   0
 .../grails/tests/GrailsTestMemberContributor.java  |   0
 .../plugins/grails/tests/GrailsTestUtils.java      |  91 ++++-
 .../grails/tests/GrailsTestVariableEnhancer.java   |   0
 .../runner/GrailsTestConfigurationProducer.java    |   4 +-
 .../tests/runner/GrailsTestConfigurationSetup.java | 228 +++++++++++
 .../grails/tests/runner/GrailsUrlProvider.java     |   0
 .../grails/ui/EditorComboBoxWithHistory.java       |   0
 .../grails/ui/GrailsConfigureSDKDialog.form        |   0
 .../grails/ui/GrailsConfigureSDKDialog.java        |   0
 .../plugins/grails/ui/GrailsRunCommandDialog.form  |   0
 .../plugins/grails/ui/GrailsRunCommandDialog.java  |   0
 .../plugins/grails/ui/GrailsSDKHomeForm.form       |   0
 .../plugins/grails/ui/GrailsSDKHomeForm.java       |   0
 .../plugins/grails/util/DelegateReference.java     |   0
 .../plugins/grails/util/GrailsArtifact.java        |  15 +-
 .../util/GrailsArtifactTransformerUtils.java       |   0
 .../plugins/grails/util/GrailsCachedValue.java     |   0
 .../plugins/grails/util/GrailsFacetProvider.java   |   0
 .../plugins/grails/util/GrailsNameUtils.java       |   0
 .../plugins/grails/util/GrailsPatterns.java        |   0
 .../plugins/grails/util/GrailsPluginCondition.java |   0
 .../plugins/grails/util/GrailsPsiUtil.java         |   0
 .../jetbrains/plugins/grails/util/GrailsUtils.java |   0
 .../grails/util/GrailsVersionCondition.java        |   0
 .../grails/util/GrailsWebFacetProvider.java        |   0
 .../plugins/grails/util/ModuleCachedValue.java     |   0
 .../util/PredefinedTypeReferenceEnhancer.java      |   0
 .../grails/util/ReferenceGutterIconRenderer.java   |   0
 .../plugins/grails/util/SafeReference.java         |   0
 .../plugins/grails/util/version/Range.java         |   0
 .../plugins/grails/util/version/Version.java       |   1 +
 .../plugins/grails/util/version/VersionImpl.java   |   0
 .../plugins/groovy/dsl/GrailsScriptProvider.java   |   0
 .../annotator/GrailsControllerAnnotator.java       |   0
 .../grails/annotator/GrailsDomainAnnotator.java    |   0
 .../annotator/GrailsForkSettingsNotificator.java   |   0
 .../GrailsSynchronizeStructureAnnotator.java       |   0
 .../groovy/mvc/ConsoleProcessDescriptor.java       |   0
 .../jetbrains/plugins/groovy/mvc/MvcCommand.java   |   0
 .../groovy/mvc/MvcModuleStructureSynchronizer.java |   0
 .../plugins/groovy/mvc/MvcModuleStructureUtil.java |   0
 .../plugins/groovy/mvc/MvcPathMacros.java          |   0
 .../plugins/groovy/mvc/MvcProjectStructure.java    |   0
 .../plugins/groovy/mvc/MvcWatchedRootProvider.java |   0
 .../groovy/mvc/plugins/AvailablePluginsModel.java  |   0
 .../mvc/plugins/InstallUninstallPluginsDialog.java |   0
 .../groovy/mvc/plugins/MvcPluginColumnInfo.java    |   0
 .../groovy/mvc/plugins/MvcPluginDescriptor.java    |   0
 .../plugins/MvcPluginIsInstalledColumnInfo.java    |   0
 .../plugins/groovy/mvc/plugins/MvcPluginUtil.java  |   0
 .../groovy/mvc/plugins/MvcPluginsAction.java       |   6 +-
 .../plugins/groovy/mvc/plugins/MvcPluginsMain.form |   0
 .../plugins/groovy/mvc/plugins/MvcPluginsMain.java |   0
 .../groovy/mvc/plugins/MvcPluginsTable.java        |   0
 .../mvc/plugins/actions/AddCustomPluginAction.java |   0
 .../plugins/actions/ReloadMvcPluginListAction.java |   0
 .../plugins/groovy/mvc/plugins/proxySettings.kt    |   0
 .../mvc/util/MvcTargetDialogCompletionUtils.java   |   0
 .../resources}/IdeaPrintProjectSettings.groovy     |   0
 .../resources}/META-INF/grails-css-integration.xml |   0
 .../resources}/META-INF/grails-js-integration.xml  |   0
 .../META-INF/grails-method-descriptors.xml         |   0
 .../src/main/resources}/META-INF/plugin.xml        |  19 +-
 .../src/main/resources}/META-INF/pluginIcon.svg    |   0
 .../internal/Groovy Server Page.gsp.ft             |   0
 .../internal/Groovy Server Page.gsp.html           |   0
 .../src/main/resources}/icons/action_method.svg    |   0
 .../src/main/resources}/icons/grails.svg           |   0
 .../src/main/resources}/icons/grails_13.svg        |   0
 .../src/main/resources}/icons/grails_13_dark.svg   |   0
 .../src/main/resources}/icons/grails_app.svg       |   0
 .../src/main/resources}/icons/grails_sdk.svg       |   0
 .../src/main/resources}/icons/grails_test.svg      |   0
 .../main/resources}/icons/groovy_mvc_plugin.svg    |   0
 .../src/main/resources}/icons/gsp_logo.svg         |   0
 .../src/main/resources}/icons/taglib.svg           |   0
 .../inspectionDescriptions/GspInspection.html      |   0
 .../inspectionDescriptions/metaInformation.json    |   0
 .../GenerateViewIntention/after.gsp.template       |   0
 .../GenerateViewIntention/before.groovy.template   |   0
 .../GenerateViewIntention/description.html         |   0
 .../after.groovy.template                          |   0
 .../before.groovy.template                         |   0
 .../description.html                               |   0
 .../MakeNullableIntention/after.groovy.template    |   0
 .../MakeNullableIntention/before.groovy.template   |   0
 .../MakeNullableIntention/description.html         |   0
 .../MakeUniqueIntention/after.groovy.template      |   0
 .../MakeUniqueIntention/before.groovy.template     |   0
 .../MakeUniqueIntention/description.html           |   0
 .../src/main/resources}/liveTemplates/gsp.xml      |   0
 .../resources}/messages/GrailsBundle.properties    |   0
 .../grails/lang/gsp/resolve/taglib/tld/grails.tld  |   0
 .../grails/copyright/GspCopyrightUpdaterTest.java  |   0
 .../plugins/groovy/grails/DoublePluginTest.java    |   0
 .../plugins/groovy/grails/GrEditorActionTest.java  |   0
 .../plugins/groovy/grails/GrailsActionsTest.java   |   0
 .../plugins/groovy/grails/GrailsCodecTest.java     |   0
 .../groovy/grails/GrailsConsoleFilterTest.java     |   0
 ...HighlightGrailsSpecifiedFieldsAsUnusedTest.java |   0
 .../plugins/groovy/grails/GrailsGantTest.java      |   0
 .../groovy/grails/GrailsHighlightingTest.java      |   0
 .../groovy/grails/GrailsLightVariableInfoTest.java |   0
 .../groovy/grails/GrailsLinkGeneratorTest.java     |   0
 .../groovy/grails/GrailsLogVariableTest.java       |   0
 .../groovy/grails/GrailsMavenImporterTest.java     |  69 ++--
 .../groovy/grails/GrailsPageRendererTest.java      |   0
 .../groovy/grails/GrailsPluginClassTest.java       |   0
 .../groovy/grails/GrailsProjectStructureTest.java  |   0
 .../grails/GrailsResolveInConfigFileTest.java      |   0
 .../plugins/groovy/grails/GrailsResolveTest.java   |   0
 .../grails/GrailsRunConfigurationProducerTest.java |   0
 .../grails/GrailsRunTargetCompletionTest.java      |   0
 .../groovy/grails/GspLiveTemplatesTest.java        |   0
 .../plugins/groovy/grails/JamInGroovyTest.java     |   0
 .../grails/VersionInDynamicMemberUtils14Test.java  |   0
 .../grails/VersionInDynamicMemberUtilsTest.java    |   0
 .../grails/action/DomainFieldIntentionsTest.java   | 275 +++++++------
 .../grails/action/GenerateViewByActionTest.java    |   0
 .../grails/action/GrailsGenerateAction2Test.java   |   0
 .../grails/action/GrailsGenerateActionTest.java    |   0
 .../ControllerReferenceCompletionTest.java         |   0
 .../DomainClassMethodsCompletionTest.java          |   0
 .../grails/completion/GrailsCompletionTest.java    |   0
 .../completion/GrailsNamedAttributeTest.java       |   0
 ...GrailsTestAutoCompletionHardReferencesTest.java |   0
 .../completion/GspTagReferenceCompletionTest.java  |   0
 .../grails/completion/ServiceCompletionTest.java   |   0
 .../TaglibClassMethodsCompletionTest.java          |   0
 .../config/GrailsBuildConfigResolveTest.java       |   0
 .../groovy/grails/config/GrailsConfigFileTest.java |   0
 .../grails/config/GrailsConfigSelectWordTest.java  |   0
 .../grails/config/GrailsConfigSlurperTest.java     |   0
 .../grails/config/GrailsConstraintGroupTest.java   |   0
 .../GrailsPluginDependencyCompletionTest.java      |   0
 .../groovy/grails/domain/GormApiFromJavaTest.java  |   0
 .../groovy/grails/domain/GormBinding14Test.java    |   0
 .../groovy/grails/domain/GormBindingTest.java      |   0
 .../grails/domain/GormDetachedCriteriaTest.java    |   0
 .../grails/domain/GormDomainConstructorTest.java   |   0
 .../grails/domain/GormDomainFinderMethodTest.java  |   0
 .../groovy/grails/domain/GormFetchModeTest.java    |   0
 .../domain/GormFinderMethodsCompletionTest.java    |   0
 .../GormGetPersistentValueReturnTypeTest.java      |   0
 .../grails/domain/GormHasManyRelationTypeTest.java |   0
 .../groovy/grails/domain/GormHqlInjectionTest.java |   0
 .../groovy/grails/domain/GormMappingSortTest.java  |   0
 .../grails/domain/GormMultiDefinePropertyTest.java |   0
 ...amedArgumentForHibernateMappingBuilderTest.java |   0
 .../grails/domain/GormPersistentFacetTest.java     |   0
 .../grails/domain/GormPropertyAccessTest.java      |   0
 .../groovy/grails/domain/GormStandaloneTest.java   |   0
 .../grails/domain/GormSyntheticFieldTest.java      |   0
 .../domain/GormUniqueConstraintValueTest.java      |   0
 .../grails/domain/GrailsAddToMethodTest.java       |   0
 .../grails/domain/GrailsCriteriaBuilder14Test.java |   0
 .../grails/domain/GrailsCriteriaBuilderTest.java   |   0
 .../GrailsCriteriaBuilderToManyRelationTest.java   |   0
 .../domain/GrailsCriteriaProjections14Test.java    |   0
 .../domain/GrailsCriteriaProjectionsTest.java      |   0
 .../grails/domain/GrailsDomainConstraintTest.java  |   0
 .../domain/GrailsDomainFinderMethodsTest.java      |   0
 .../domain/GrailsDomainNamedAttribute14Test.java   |   0
 .../domain/GrailsDomainNamedAttributeTest.java     |   0
 .../GrailsDomainPropertiesFromParentTest.java      |   0
 .../grails/domain/GrailsDomainPropertiesTest.java  |   0
 .../domain/GrailsFindWhereParameterTest.java       |   0
 .../grails/domain/GrailsGormGetIdMethodTest.java   |   0
 .../grails/domain/GrailsInjectRegexTest.java       |   0
 .../grails/domain/GrailsNamedQuery14Test.java      |   0
 .../groovy/grails/domain/GrailsNamedQueryTest.java |   0
 .../domain/GrailsRenameDomainPropertyTest.java     |   0
 .../grails/domain/GrailsRenameDomainTest.java      |   0
 .../grails/domain/GrailsTransientPropertyTest.java |   0
 .../grails/domain/GrormEmbeddedFieldTest.java      |   0
 .../NamedArgumentsInFindOrCreateMethodTest.java    |   0
 .../grails/gradle/GradleGrailsImportingTest.java   |   0
 .../gsp/AssetPipelineScriptInjectionTest.java      |   0
 .../grails/gsp/GspActionToViewNavigation2Test.java |   0
 .../grails/gsp/GspActionToViewNavigationTest.java  |   0
 .../grails/gsp/GspAttributeFromJavadocTest.java    |   0
 .../groovy/grails/gsp/GspBuiltInTagTest.java       |   0
 .../groovy/grails/gsp/GspCharsetReferenceTest.java |   0
 .../gsp/GspClearCompletionBehaviourTest.java       |   0
 .../grails/gsp/GspCompletionEmptyTagTest.java      |   0
 .../GspCompletionInNonexistingNamespaceTest.java   |   0
 .../groovy/grails/gsp/GspContentTagTest.java       |   0
 .../grails/gsp/GspControllerReferenceTest.java     |   0
 .../grails/gsp/GspCreateTemplateQuickFixTest.java  |   0
 .../grails/gsp/GspCreateVariableQuickFixTest.java  |   0
 .../plugins/groovy/grails/gsp/GspCssTest.java      |   0
 .../grails/gsp/GspDirectiveDefaultCodecTest.java   |   0
 ...mentManipulatorForOuterLanguageElementTest.java |   0
 .../groovy/grails/gsp/GspFormatterTest.java        |   0
 .../groovy/grails/gsp/GspGroovyInspectionTest.java |   0
 .../groovy/grails/gsp/GspHighlightingTest.java     |   0
 .../groovy/grails/gsp/GspHtmlAttributesTest.java   |   0
 .../groovy/grails/gsp/GspIDEA68710Test.java        |   0
 .../plugins/groovy/grails/gsp/GspImportTest.java   |   0
 .../groovy/grails/gsp/GspJavascriptTest.java       |   0
 .../groovy/grails/gsp/GspJsFormatterTest.java      |   0
 .../groovy/grails/gsp/GspJsInjectionTagEpTest.java |   0
 .../grails/gsp/GspLanguageInjectionTest.java       |   0
 .../groovy/grails/gsp/GspLayoutTagTest.java        |   0
 .../groovy/grails/gsp/GspNamedMappingTest.java     |   0
 .../gsp/GspNamespacePrefixIsNotIdentifierTest.java |   0
 .../grails/gsp/GspNamespacePriorityTest.java       |   0
 .../grails/gsp/GspPropertyResolvePriorityTest.java |   0
 .../groovy/grails/gsp/GspResolveIndexesTest.java   |   0
 .../gsp/GspResolveVariablesFromModelTest.java      |   0
 .../groovy/grails/gsp/GspSdkTagCompletionTest.java |   0
 .../groovy/grails/gsp/GspSelectWordTest.java       |   0
 .../groovy/grails/gsp/GspTagDocumentationTest.java |   0
 .../groovy/grails/gsp/GspTagLibDumbModeTest.java   |  95 +++++
 .../grails/gsp/GspThisAndSuperReferenceTest.java   |   0
 .../plugins/groovy/grails/gsp/GspTypingTest.java   |   0
 .../groovy/grails/gsp/GstringAttributeTest.java    |   0
 .../grails/i18n/GrailsGroovyI18nIntentionTest.java | 115 ++++++
 .../grails/i18n/GrailsI18nInspectionTest.java      | 131 ++++---
 .../groovy/grails/i18n/GspI18nIntentionTest.java   | 116 ++++++
 .../GrailsMethodMayBeStaticInspectionTest.java     |   0
 .../groovy/grails/parser/GspGroovyParserTest.java  |   0
 .../groovy/grails/parser/GspHtmlParserTest.java    |   0
 .../groovy/grails/parser/GspParserTest.java        |   0
 .../groovy/grails/parser/GspParsingTestCase.java   |   0
 .../pluginSupport/GrailsBuildTestDataTest.java     |   0
 .../pluginSupport/GrailsResourcePluginTest.java    |   0
 .../grails/pluginSupport/GrailsSearchableTest.java |   0
 .../pluginSupport/GrailsShiroPluginTest.java       |   0
 .../grails/pluginSupport/GrailsWebflowTest.java    |   0
 .../pluginSupport/SpockMethodReturnTypeTest.java   |   0
 .../reference/GrailsBeanNameConventionTest.java    |   0
 .../groovy/grails/reference/GrailsFilterTest.java  |   0
 .../groovy/grails/reference/GrailsJobTest.java     |   0
 .../grails/reference/GrailsViewReferenceTest.java  |   0
 .../reference/bootstrap/GrailsBootStrapTest.java   |   0
 .../controller/Grails14MimePluginTest.java         |   0
 .../reference/controller/GrailsAction14Test.java   |   0
 .../controller/GrailsActionWithExtensionTest.java  |   0
 .../controller/GrailsController14Test.java         |   0
 .../GrailsControllerActionReferenceTest.java       |   0
 .../GrailsControllerAllowedMethodsTest.java        |   0
 ...ilsControllerAmbiguousMethodInspectionTest.java |   0
 .../GrailsControllerLayoutReferenceTest.java       |   0
 .../controller/GrailsControllerRenameTest.java     |   0
 .../GrailsControllerRespondMethodTest.java         |   0
 .../controller/GrailsHttpRequestTest.java          |   0
 .../controller/GrailsHttpResponseTest.java         |   0
 .../reference/controller/GrailsMimePluginTest.java |   0
 .../reference/controller/GrailsNamespaceTest.java  |   0
 .../controller/GrailsRenderMethodTest.java         |   0
 .../reference/spring/GrailsSpringDSLTest.java      |   0
 .../spring/GrailsSpringIntegrationTest.java        |   0
 .../taglib/GrailsTagLibNamedArgumentsTest.java     |   0
 .../reference/taglib/GrailsTaglib14Test.java       |   0
 .../taglib/GrailsTaglibParamTypeTest.java          |   0
 .../grails/reference/taglib/GspCustomTagTest.java  |   0
 .../GspResolveTagsFromSameNamespaceTest.java       |   0
 .../reference/urlMapping/GrailsUrlMappingTest.java |   0
 .../GrailsUrlMappingUnresolvedReferenceTest.java   |   0
 .../grails/tagSupport/AssetPipelineSrcTest.java    |   0
 .../grails/tagSupport/GspActionAttributeTest.java  |   0
 .../grails/tagSupport/GspCodecAttributeTest.java   |   0
 .../tagSupport/GspControllerAttributeTest.java     |   0
 .../groovy/grails/tagSupport/GspFieldTagTest.java  |   0
 .../tagSupport/GspIncludeViewAttributeTest.java    |   0
 .../groovy/grails/tagSupport/GspJsSrcTest.java     |   0
 .../groovy/grails/tagSupport/GspMetaTagTest.java   |   0
 .../grails/tagSupport/GspPluginAttributeTest.java  |   0
 .../groovy/grails/tagSupport/GspRenderTagTest.java |   0
 .../grails/tagSupport/GspResourceTagTest.java      |   0
 .../groovy/grails/tagSupport/GspSelectTagTest.java |   0
 .../grails/tagSupport/GspUpdateAttributeTest.java  |   0
 .../grails/tests/GrailsMockAnnotationTest.java     |   0
 .../grails/tests/GrailsTestForAnnotationTest.java  |   0
 .../tests/GrailsTestMixinAnnotationTest.java       |   0
 .../groovy/grails/tests/GrailsTestUtilTest.java    |   0
 .../groovy/grails/tests/GrailsTestedClassTest.java |  90 +++++
 .../grails/tests/GrailsVariableEnhance14Test.java  |   0
 .../grails/tests/GrailsVariableEnhanceTest.java    |   0
 .../groovy/grails/v3/GrailsEnhancesTest.java       |   0
 .../groovy/grails/v3/GrailsProjectDescriptor.java  |   0
 .../grails/validation/GrailsImportFromTest.java    |   0
 .../validation/GrailsValidatedObjectTest.java      |   0
 .../standardDsls/GrailsStandardDSL.gdsl            |   0
 .../testdata}/grails/actions/GspBlockComment.gsp   |   0
 .../grails/actions/GspBlockComment_after.gsp       |   0
 .../testdata}/grails/actions/GspLineComment.gsp    |   0
 .../grails/actions/GspLineComment_after.gsp        |   0
 .../testdata}/grails/actions/GspLineUncomment.gsp  |   0
 .../grails/actions/GspLineUncomment_after.gsp      |   0
 .../AllFinderMethodsInDomainClass.groovy           |   0
 .../completion/AttributeNameFinishWithEq.gsp       |   0
 .../completion/AttributeNameFinishWithEq_after.gsp |   0
 .../ConstrainsPropertyInDomainClass.groovy         |   0
 ...nstrainsPropertyInDomainClassConstraints.groovy |   0
 .../ConstrainsPropertyInDomainClass_after.groovy   |   0
 .../completion/DomainClassDynamicMethods.groovy    |   0
 .../completion/DomainClassNonStaticMethods.groovy  |   0
 .../completion/DomainClassStaticProperties.groovy  |   0
 .../DomainClassStaticProperties_after.groovy       |   0
 .../completion/FindByMethodInDomainClass.groovy    |   0
 .../FindByMethodWithThreeArgsInDomainClass.groovy  |   0
 .../completion/FinderMethodsInDomainClass.groovy   |   0
 .../grails/completion/FinderMethodsInGsp.groovy    |   0
 .../grails/completion/FinderMethodsInGsp.gsp       |   0
 .../grails/completion/FindersWithBelongsTo.groovy  |   0
 .../grails/completion/FindersWithHasMany.groovy    |   0
 .../grails/completion/GspInjectedJavaScript.gsp    |   0
 .../completion/GspInjectedJavaScript_after.gsp     |   0
 .../grails/completion/JspTagCompletion.gsp         |   0
 .../grails/completion/JspTagCompletion_after.gsp   |   0
 .../completion/ModifiersInGspGroovyDeclaration.gsp |   0
 .../grails/completion/OverrideStandardTag.gsp      |   0
 .../grails/completion/ServiceInjection.groovy      |   0
 .../completion/ServiceInjection_after.groovy       |   0
 .../completion/UnpackagedDomainClassInGsp.groovy   |   0
 .../completion/UnpackagedDomainClassInGsp.gsp      |   0
 .../UnpackagedDomainClassInGsp_after.gsp           |   0
 .../testdata}/grails/enterAction/dir.test          |   0
 .../testdata}/grails/enterAction/simple_type.test  |   0
 {testdata => plugin/testdata}/grails/fmt.tld       |   0
 .../grails/formatter/comments/schulz1.test         |   0
 .../testdata}/grails/formatter/comments/ven1.test  |   0
 .../testdata}/grails/formatter/groovy/groovy.test  |   0
 .../testdata}/grails/formatter/groovy/plain1.test  |   0
 .../testdata}/grails/formatter/groovy/sim1.test    |   0
 .../testdata}/grails/formatter/gsp/EA29587.test    |   0
 .../testdata}/grails/formatter/gsp/gsp1.test       |   0
 .../testdata}/grails/formatter/gsp/gsp2.test       |   0
 .../testdata}/grails/formatter/gsp/gsp3.test       |   0
 .../testdata}/grails/formatter/gsp/gsp8.test       |   0
 .../testdata}/grails/formatter/gsp/gsp9.test       |   0
 .../grails/formatter/html/error/giga_werle.test    |   0
 .../grails/formatter/html/error/nik1.test          |   0
 .../grails/formatter/html/error/show.test          |   0
 .../grails/formatter/html/error/werle1.test        |   0
 .../grails/formatter/html/error/werle30.test       |   0
 .../grails/formatter/html/inner/GRVY_1165.test     |   0
 .../grails/formatter/html/inner/GRVY_1165_1.test   |   0
 .../grails/formatter/html/inner/GRVY_876.test      |   0
 .../grails/formatter/html/inner/inner2.test        |   0
 .../grails/formatter/html/inner/inner3.test        |   0
 .../grails/formatter/html/megabug/GRVY_1046.test   |   0
 .../grails/formatter/html/megabug/bigTest.test     |   0
 .../grails/formatter/html/megabug/megap2.test      |   0
 .../grails/formatter/html/megabug/mmm3.test        |   0
 .../grails/formatter/html/megabug/peter.test       |   0
 .../grails/formatter/html/megabug/peter2.test      |   0
 .../grails/formatter/html/megabug/peter3.test      |   0
 .../grails/formatter/html/megabug/range1.test      |   0
 .../grails/formatter/html/nested/attr1.test        |   0
 .../grails/formatter/html/nested/gsp6.test         |   0
 .../grails/formatter/html/nested/gsp7.test         |   0
 .../grails/formatter/html/nested/peter7926.test    |   0
 .../grails/formatter/html/nested/peter_simple.test |   0
 .../grails/formatter/html/nested/range.test        |   0
 .../grails/formatter/html/nested/range2.test       |   0
 .../grails/formatter/html/nested/range4.test       |   0
 .../grails/formatter/html/nested/range5.test       |   0
 .../grails/formatter/html/simple/GRVY_1146.test    |   0
 .../formatter/html/simple/attributeSpaces.test     |   0
 .../grails/formatter/html/simple/gsp4.test         |   0
 .../grails/formatter/html/simple/gsp8.test         |   0
 .../grails/formatter/html/simple/htm1.test         |   0
 .../grails/formatter/html/simple/htm2.test         |   0
 .../grails/formatter/html/simple/range3.test       |   0
 .../grails/formatter/html/simple/sim2.test         |   0
 .../formatter/html/simple/tagNameSpaces.test       |   0
 .../grails/formatter/html/trash/bug1.test          |   0
 .../grails/formatter/html/trash/bug2.test          |   0
 .../grails/formatter/html/trash/bug3.test          |   0
 .../grails/formatter/html/trash/bug4.test          |   0
 .../grails/formatter/html/trash/bug5.test          |   0
 .../grails/formatter/html/trash/trash1.test        |   0
 .../grails/formatter/hyphenInTagName.test          |   0
 .../testdata}/grails/formatter/js/t1.test          |   0
 .../testdata}/grails/formatter/js/t2.test          |   0
 .../grails/formatter/spacing/groovy/gr1.test       |   0
 .../grails/formatter/spacing/groovy/gr2.test       |   0
 .../testdata}/grails/formatter/spacing/htm3.test   |   0
 .../testdata}/grails/formatter/spacing/spac2.test  |   0
 .../testdata}/grails/formatter/spacing/spac3.test  |   0
 .../testdata}/grails/formatter/spacing/tags2.test  |   0
 .../testdata}/grails/gsp/TestController.groovy     |   0
 .../DomainFindersWithInheritedProperties.groovy    |   0
 .../FileReferenceWithGspInjections.gsp             |   0
 .../grails/highlighting/FinderMethods.groovy       |   0
 .../grails/highlighting/HasManyBelongsTo.groovy    |   0
 .../highlighting/JavascriptWithGspInjections.gsp   |   0
 .../testdata}/grails/highlighting/JspTagInGsp.gsp  |   0
 .../testdata}/grails/highlighting/MappedBy.groovy  |   0
 .../grails/highlighting/MappedByErrors1.groovy     |   0
 .../grails/highlighting/Properties.groovy          |   0
 .../grails/highlighting/UnknownArgsAttribute.gsp   |   0
 .../controllers/contr1Controller.test              |   0
 .../controllers/contr2Controller.test              |   0
 .../controllers/contr3Controller.test              |   0
 .../grails/oldCompletion/domain/dyn1.test          |   0
 .../grails/oldCompletion/domain/finder10.test      |   0
 .../grails/oldCompletion/domain/finder11.test      |   0
 .../grails/oldCompletion/domain/finder2.test       |   0
 .../grails/oldCompletion/domain/finder3.test       |   0
 .../grails/oldCompletion/domain/finder4.test       |   0
 .../grails/oldCompletion/domain/finder5.test       |   0
 .../grails/oldCompletion/domain/finder6.test       |   0
 .../grails/oldCompletion/domain/finder7.test       |   0
 .../grails/oldCompletion/domain/finder8.test       |   0
 .../grails/oldCompletion/domain/finder_full.test   |   0
 .../grails/oldCompletion/domain/list_order.test    |   0
 .../grails/oldCompletion/domain/not_ref.test       |   0
 .../grails/oldCompletion/domain/qulaified.test     |   0
 .../grails/oldCompletion/domain/static.test        |   0
 .../testdata}/grails/oldCompletion/gsp/attr1.test  |   0
 .../grails/oldCompletion/gsp/custom1.test          |   0
 .../oldCompletion/gsp/customNamespacePrefix.test   |   0
 .../grails/oldCompletion/gsp/dirname.test          |   0
 .../grails/oldCompletion/gsp/endTagTest1.test      |   0
 .../grails/oldCompletion/gsp/endTagTest2.test      |   0
 .../grails/oldCompletion/gsp/endTagTest3.test      |   0
 .../grails/oldCompletion/gsp/endTagTest4.test      |   0
 .../grails/oldCompletion/gsp/endTagTest5.test      |   0
 .../oldCompletion/gsp/excludeNotATagGroovy.test    |   0
 .../oldCompletion/gsp/excludeNotATagHTML.test      |   0
 .../testdata}/grails/oldCompletion/gsp/g1.test     |   0
 .../testdata}/grails/oldCompletion/gsp/get1.test   |   0
 .../testdata}/grails/oldCompletion/gsp/groo1.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo2.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo3.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo4.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo5.test  |   0
 .../testdata}/grails/oldCompletion/gsp/html1.test  |   0
 .../grails/oldCompletion/gsp/htmlWithDoctype.test  |   0
 .../testdata}/grails/oldCompletion/gsp/link.test   |   0
 .../testdata}/grails/oldCompletion/gsp/link2.test  |   0
 .../testdata}/grails/oldCompletion/gsp/my1.test    |   0
 .../grails/oldCompletion/gsp/nonStatic.test        |   0
 .../grails/oldCompletion/gsp/packageTagLib1.test   |   0
 .../grails/oldCompletion/gsp/packageTagLib2.test   |   0
 .../testdata}/grails/oldCompletion/gsp/tail1.test  |   0
 .../testdata}/grails/oldCompletion/gsp/tail2.test  |   0
 .../testdata}/grails/oldCompletion/gsp/tail3.test  |   0
 .../testdata}/grails/oldCompletion/gsp/tail4.test  |   0
 .../testdata}/grails/parser/gsp/dir/dir1.test      |   0
 .../grails/parser/gsp/hyphenInTagName.test         |   0
 .../grails/parser/gsp/inject/GRVY_943.test         |   0
 .../grails/parser/gsp/inject/escaped1.test         |   0
 .../testdata}/grails/parser/gsp/simple/bubug1.test |   0
 .../testdata}/grails/parser/gsp/simple/clos1.test  |   0
 .../testdata}/grails/parser/gsp/simple/clos2.test  |   0
 .../testdata}/grails/parser/gsp/simple/common.test |   0
 .../testdata}/grails/parser/gsp/simple/form1.test  |   0
 .../testdata}/grails/parser/gsp/simple/megap1.test |   0
 .../testdata}/grails/parser/gsp/simple/mmm1.test   |   0
 .../testdata}/grails/parser/gsp/simple/peter2.test |   0
 .../testdata}/grails/parser/gsp/tags/act1.test     |   0
 .../testdata}/grails/parser/gsp/tags/act2.test     |   0
 .../testdata}/grails/parser/gsp/tags/gps9.test     |   0
 .../testdata}/grails/parser/gsp/tags/orph1.test    |   0
 .../testdata}/grails/parser/gsp/tags/tag3.test     |   0
 .../grails/parser/gsp/tags/tagWithSlash.test       |   0
 .../grails/parser/gsp/tags/uglyAttributeName.test  |   0
 .../grails/parser/gsp/tags/unendedAttrList.test    |   0
 .../grails/parser/gspGroovy/comments/com.test      |   0
 .../grails/parser/gspGroovy/comments/comm1.test    |   0
 .../grails/parser/gspGroovy/comments/commm2.test   |   0
 .../grails/parser/gspGroovy/control/common.test    |   0
 .../grails/parser/gspGroovy/control/for1.test      |   0
 .../grails/parser/gspGroovy/control/for2.test      |   0
 .../grails/parser/gspGroovy/control/foreach1.test  |   0
 .../grails/parser/gspGroovy/control/if1.test       |   0
 .../grails/parser/gspGroovy/control/if2.test       |   0
 .../grails/parser/gspGroovy/control/swit1.test     |   0
 .../grails/parser/gspGroovy/control/swit2.test     |   0
 .../grails/parser/gspGroovy/custom/tag1.test       |   0
 .../grails/parser/gspGroovy/custom/tag2.test       |   0
 .../grails/parser/gspGroovy/custom/tag3.test       |   0
 .../grails/parser/gspGroovy/declarations/dec1.test |   0
 .../grails/parser/gspGroovy/declarations/dec2.test |   0
 .../grails/parser/gspGroovy/declarations/dec3.test |   0
 .../grails/parser/gspGroovy/declarations/dec4.test |   0
 .../grails/parser/gspGroovy/direct/dir1.test       |   0
 .../grails/parser/gspGroovy/errors/err1.test       |   0
 .../grails/parser/gspGroovy/errors/err2.test       |   0
 .../grails/parser/gspGroovy/errors/err3.test       |   0
 .../grails/parser/gspGroovy/errors/err4.test       |   0
 .../grails/parser/gspGroovy/simple/clos1.test      |   0
 .../grails/parser/gspGroovy/simple/inj1.test       |   0
 .../grails/parser/gspGroovy/simple/inj2.test       |   0
 .../grails/parser/gspGroovy/simple/nl.test         |   0
 .../grails/parser/gspGroovy/simple/stat1.test      |   0
 .../grails/parser/gspHtml/afterWhitespace.test     |   0
 .../testdata}/grails/parser/gspHtml/alone.test     |   0
 .../testdata}/grails/parser/gspHtml/common.test    |   0
 .../testdata}/grails/parser/gspHtml/html1.test     |   0
 .../testdata}/grails/parser/gspHtml/inject1.test   |   0
 .../testdata}/grails/parser/gspHtml/mmm2.test      |   0
 .../testdata}/grails/parser/gspHtml/mmm5.test      |   0
 .../testdata}/grails/parser/gspHtml/peter1.test    |   0
 .../testdata}/grails/parser/gspHtml/peter2.test    |   0
 .../grails/parser/gspHtml/range_parse.test         |   0
 .../testdata}/grails/parser/gspHtml/werle.test     |   0
 .../testdata}/grails/parser/gspHtml/werle28.test   |   0
 .../testdata}/grails/parser/gspHtml/werle29.test   |   0
 .../grails/pluginList/plugins-list-grailsCore.xml  |   0
 .../resolve/ResolvePropertiesFromBelongsTo.groovy  |   0
 .../resolve/ResolvePropertiesFromBelongsTo2.groovy |   0
 .../resolve/ResolvePropertiesFromHasMany.groovy    |   0
 .../resolve/ResolvePropertyFromMappedBy.groovy     |   0
 .../testdata}/grails/resolve/StaticsOk.groovy      |   0
 .../grails/shiro/AccessControlBuilder.groovy       |   0
 .../mockDomainDir/grails-app/domain/Product.groovy |   0
 .../grails-app/domain/aaa/Book.groovy              |   0
 .../mockGrails11/conf/groovy-starter.conf          |   0
 .../testdata}/mockGrails11/conf/webdefault.xml     |   0
 .../mockGrails11/dist/grails-bootstrap-1.1.jar     | Bin
 .../mockGrails11/dist/grails-core-1.1.jar          | Bin
 .../mockGrails11/dist/grails-core-1.3.1.jar        | Bin
 .../mockGrails11/dist/grails-gorm-1.3.1.jar        | Bin
 .../dist/grails-plugin-url-mappings-2.0.4.jar      | Bin
 .../mockGrails11/dist/grails-scripts-1.3.7.jar     | Bin
 .../mockGrails11/dist/grails-spring-1.3.1.jar      | Bin
 .../mockGrails11/dist/grails-test-1.3.1.jar        | Bin
 .../mockGrails11/dist/grails-web-1.3.1.jar         | Bin
 .../mockGrails11/dist/grails-webflow-1.3.7.jar     | Bin
 .../testdata}/mockGrails11/lib/groovy-all.jar      | Bin
 .../mockGrails11/lib/hibernate-core-3.3.1.GA.jar   | Bin
 .../testdata}/mockGrails11/lib/junit-4.8.1.jar     | Bin
 .../org.springframework.beans-3.0.3.RELEASE.jar    | Bin
 .../org.springframework.context-3.0.3.RELEASE.jar  | Bin
 .../lib/org.springframework.test-3.0.2.RELEASE.jar | Bin
 .../testdata}/mockGrails11/lib/servlet-api-2.5.jar | Bin
 .../lib/spock-grails-support-0.5-groovy-1.7.jar    | Bin
 .../mockGrails11/lib/spring-core-3.0.3.RELEASE.jar | Bin
 .../mockGrails11/scripts/Bootstrap.groovy          |   0
 .../mockGrails11/scripts/BugReport.groovy          |   0
 .../testdata}/mockGrails11/scripts/Clean.groovy    |   0
 .../testdata}/mockGrails11/scripts/Compile.groovy  |   0
 .../testdata}/mockGrails11/scripts/Console.groovy  |   0
 .../mockGrails11/scripts/CreateApp_.groovy         |   0
 .../mockGrails11/scripts/CreateController.groovy   |   0
 .../mockGrails11/scripts/CreateDomainClass.groovy  |   0
 .../mockGrails11/scripts/CreateFilters.groovy      |   0
 .../scripts/CreateIntegrationTest.groovy           |   0
 .../mockGrails11/scripts/CreatePlugin_.groovy      |   0
 .../mockGrails11/scripts/CreateScript.groovy       |   0
 .../mockGrails11/scripts/CreateService.groovy      |   0
 .../mockGrails11/scripts/CreateTagLib.groovy       |   0
 .../mockGrails11/scripts/CreateUnitTest.groovy     |   0
 .../testdata}/mockGrails11/scripts/Doc.groovy      |   0
 .../mockGrails11/scripts/GenerateAll.groovy        |   0
 .../mockGrails11/scripts/GenerateController.groovy |   0
 .../mockGrails11/scripts/GenerateViews.groovy      |   0
 .../testdata}/mockGrails11/scripts/Help_.groovy    |   0
 .../testdata}/mockGrails11/scripts/Init.groovy     |   0
 .../mockGrails11/scripts/InstallPlugin.groovy      |   0
 .../mockGrails11/scripts/InstallTemplates.groovy   |   0
 .../mockGrails11/scripts/ListPlugins_.groovy       |   0
 .../testdata}/mockGrails11/scripts/Package.groovy  |   0
 .../mockGrails11/scripts/PackagePlugin_.groovy     |   0
 .../mockGrails11/scripts/PluginInfo_.groovy        |   0
 .../mockGrails11/scripts/ReleasePlugin.groovy      |   0
 .../testdata}/mockGrails11/scripts/RunApp.groovy   |   0
 .../testdata}/mockGrails11/scripts/RunWar.groovy   |   0
 .../mockGrails11/scripts/SchemaExport.groovy       |   0
 .../mockGrails11/scripts/SetProxy_.groovy          |   0
 .../mockGrails11/scripts/SetVersion.groovy         |   0
 .../testdata}/mockGrails11/scripts/Shell.groovy    |   0
 .../testdata}/mockGrails11/scripts/Stats.groovy    |   0
 .../testdata}/mockGrails11/scripts/TestApp.groovy  |   0
 .../mockGrails11/scripts/UninstallPlugin.groovy    |   0
 .../testdata}/mockGrails11/scripts/Upgrade.groovy  |   0
 .../testdata}/mockGrails11/scripts/War.groovy      |   0
 .../mockGrails11/scripts/_GrailsArgParsing.groovy  |   0
 .../mockGrails11/scripts/_GrailsBootstrap.groovy   |   0
 .../mockGrails11/scripts/_GrailsClasspath.groovy   |   0
 .../mockGrails11/scripts/_GrailsClean.groovy       |   0
 .../mockGrails11/scripts/_GrailsCompile.groovy     |   0
 .../scripts/_GrailsCreateArtifacts.groovy          |   0
 .../scripts/_GrailsCreateProject.groovy            |   0
 .../mockGrails11/scripts/_GrailsDocs.groovy        |   0
 .../mockGrails11/scripts/_GrailsEvents.groovy      |   0
 .../mockGrails11/scripts/_GrailsGenerate.groovy    |   0
 .../mockGrails11/scripts/_GrailsInit.groovy        |   0
 .../mockGrails11/scripts/_GrailsPackage.groovy     |   0
 .../mockGrails11/scripts/_GrailsPluginDev.groovy   |   0
 .../mockGrails11/scripts/_GrailsPlugins.groovy     |   0
 .../mockGrails11/scripts/_GrailsProxy.groovy       |   0
 .../mockGrails11/scripts/_GrailsRun.groovy         |   0
 .../mockGrails11/scripts/_GrailsSettings.groovy    |   0
 .../mockGrails11/scripts/_GrailsTest.groovy        |   0
 .../mockGrails11/scripts/_GrailsWar.groovy         |   0
 .../mockGrails11/scripts/_PackagePlugins.groovy    |   0
 .../scripts/_PluginDependencies.groovy             |   0
 .../mockGrails11/scripts/log4j.properties          |   0
 .../mockGrails14/grails-bootstrap-2.0.0.M2.jar     | Bin
 .../mockGrails14/grails-core-2.0.0.M1.jar          | Bin
 .../grails-datastore-gorm-1.0.0.RC1.jar            | Bin
 .../mockGrails14/grails-hibernate-2.0.0.M1.jar     | Bin
 .../grails-plugin-controllers-1.4.0.M1.jar         | Bin
 .../grails-plugin-converters-2.2.1.jar             | Bin
 .../grails-plugin-gsp-2.0.0.RC1-sources.jar        | Bin
 .../mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar   | Bin
 .../grails-plugin-mimetypes-1.4.0.M1.jar           | Bin
 .../mockGrails14/grails-plugin-rest-2.3.0.jar      | Bin
 .../mockGrails14/grails-plugin-servlets-2.2.1.jar  | Bin
 .../grails-plugin-testing-1.4.0.M1.jar             | Bin
 .../testdata}/mockGrails14/grails-spring-2.2.3.jar | Bin
 .../mockGrails14/grails-test-2.0.0.M2.jar          | Bin
 .../testdata}/mockGrails14/grails-web-2.0.0.M1.jar | Bin
 .../mockGrails14/spring-test-3.1.0.M2.jar          | Bin
 .../mockTagLib/grails-app/taglib/MyTagLib.groovy   |   0
 .../grails-app/taglib/NonStaticTagLib.groovy       |   0
 .../grails-app/taglib/UnnamedTagLib.groovy         |   0
 .../grails-app/taglib/apackage/PppTagLib.groovy    |   0
 pluginModules/copyright/README.md                  |  20 +
 .../copyright/build.gradle                         |  18 +-
 .../copyright/UpdateGspCopyrightsProvider.java     |   0
 .../grails/copyright/UpdateGspFileCopyright.java   |   0
 ...org.apache.grails.intellij.module.copyright.xml |   0
 pluginModules/coverage/README.md                   |  17 +
 .../coverage/build.gradle                          |  10 +-
 .../grails/coverage/GrailsCoverageExtension.java   |  21 +-
 .../org.apache.grails.intellij.module.coverage.xml |   0
 pluginModules/hibernate/README.md                  |  16 +
 .../hibernate/build.gradle                         |  15 +-
 .../hibernate/GormHibernateFacetProvider.java      |   0
 .../grails/hibernate/GormSessionFactory.java       |   0
 .../hibernate/GormSessionFactoryContributor.java   |   0
 .../hibernate/Grails3HibernateFacetActivity.java   |   0
 ...org.apache.grails.intellij.module.hibernate.xml |   0
 pluginModules/i18n/README.md                       |  20 +
 .../i18n/build.gradle                              |  16 +-
 .../i18n/GrailsI18nGroovyQuickFixHandler.java      |   0
 .../groovy/grails/i18n/GrailsI18nInspection.java   |   0
 .../i18n/GrailsI18nPropertyReferenceProvider.java  |   0
 .../grails/i18n/GrailsI18nQuickFixHandler.java     |   0
 .../i18n/GrailsI18nReferenceContributor.java       |   0
 .../groovy/grails/i18n/GrailsI18nizeProvider.java  |   0
 .../grails/i18n/GrailsI18nizeQuickFixDialog.java   |   0
 .../groovy/grails/i18n/GspI18nIntention.java       |   0
 .../I18nized GSP Expression with injections.gsp.ft |   0
 ...18nized GSP Expression with injections.gsp.html |   0
 .../code/I18nized GSP Expression.gsp.ft            |   0
 .../code/I18nized GSP Expression.gsp.html          |   0
 .../I18nized Groovy string with injections.gsp.ft  |   0
 ...I18nized Groovy string with injections.gsp.html |   0
 .../code/I18nized Groovy string.gsp.ft             |   0
 .../code/I18nized Groovy string.gsp.html           |   0
 .../InvalidI18nProperty.html                       |   0
 .../inspectionDescriptions/metaInformation.json    |   0
 .../GspI18nIntention/after.gsp.template            |   0
 .../GspI18nIntention/before.gsp.template           |   0
 .../GspI18nIntention/description.html              |   0
 .../org.apache.grails.intellij.module.i18n.xml     |   0
 pluginModules/langInjection/README.md              |  20 +
 .../langInjection/build.gradle                     |  10 +-
 .../resources/META-INF/languageInjections.xml      |   0
 ...apache.grails.intellij.module.langInjection.xml |   0
 pluginModules/maven/README.md                      |  27 ++
 .../maven/build.gradle                             |  19 +-
 .../grails/maven/GrailsMavenApplication.java       |   0
 .../maven/GrailsMavenApplicationProvider.java      |   0
 .../grails/maven/GrailsMavenDebuggerRunner.java    |   0
 .../maven/GrailsMavenWorkspaceConfigurator.java    | 103 +++++
 .../groovy/grails/maven/MavenCommandExecutor.java  |   0
 .../groovy/grails/maven/MavenNodeProvider.java     |  44 +++
 .../org.apache.grails.intellij.module.maven.xml    |   0
 settings.gradle                                    |  44 +++
 settings.gradle.kts                                |  28 --
 .../plugins/grails/actions/GrailsActionUtil.kt     |  98 -----
 .../grails/artefact/api/GrailsArtefactHandler.kt   |  32 --
 .../api/GrailsDisplayableArtefactHandler.kt        |  36 --
 .../plugins/grails/artefact/api/IconOwner.kt       |  25 --
 .../plugins/grails/artefact/api/constants.kt       |  68 ----
 .../artefact/impl/ControllerArtefactHandler.kt     |  46 ---
 .../grails/artefact/impl/DomainArtefactHandler.kt  |  43 --
 .../grails/artefact/impl/FilterArtefactHandler.kt  |  34 --
 .../artefact/impl/InterceptorArtefactHandler.kt    |  40 --
 .../grails/artefact/impl/ServiceArtefactHandler.kt |  37 --
 .../grails/artefact/impl/TaglibArtefactHandler.kt  |  39 --
 .../grails/artefact/impl/controllers/functions.kt  |  61 ---
 .../jetbrains/plugins/grails/artefact/impl/util.kt | 203 ----------
 .../config/GrailsGradleAwareModuleBuilder.kt       | 112 ------
 .../plugins/grails/config/GrailsIgnoredProvider.kt |  43 --
 .../plugins/grails/editor/GrailsEditorDecorator.kt | 102 -----
 .../grails/editor/ViewGrailsEditorToolbarAction.kt |  52 ---
 .../grails/editor/toolbar/GrailsGenerateActions.kt |  87 -----
 .../editor/toolbar/GrailsGoToArtefactActionBase.kt |  49 ---
 .../editor/toolbar/GrailsToolbarActionBase.kt      |  59 ---
 .../grails/editor/toolbar/GrailsToolbarActions.kt  | 146 -------
 .../editor/toolbar/GrailsToolbarTargetAction.kt    |  51 ---
 .../editor/toolbar/GrailsToolbarVfileAction.kt     |  33 --
 .../grails/feature/linkable/namedArguments.kt      |  85 ----
 .../plugins/grails/feature/linkable/transform.kt   |  52 ---
 .../jetbrains/plugins/grails/gorm/gormVersion.kt   |  87 -----
 .../grails/gson/GsonControllerReferences.kt        |  87 -----
 .../grails/gson/GsonExpectedPackageProvider.kt     |  27 --
 .../gson/GsonModelFieldArgumentDescriptor.kt       |  35 --
 .../grails/gson/GsonPositionManagerDelegate.kt     |  70 ----
 .../grails/gson/GsonReferenceContributor.kt        |  27 --
 .../plugins/grails/gson/GsonRenderReferences.kt    | 164 --------
 .../grails/gson/GsonTemplateRendererContributor.kt |  51 ---
 .../grails/gson/GsonTransformationSupport.kt       |  51 ---
 src/org/jetbrains/plugins/grails/gson/GsonUtils.kt |  79 ----
 .../grails/lang/DirtyCheckableContributor.kt       |  67 ----
 .../plugins/grails/lang/GormTraitContributor.kt    | 132 -------
 .../highlighter/GspEditorHighlighterProvider.kt    |  33 --
 .../groovy/chameleons/GspGroovyLazyElementType.kt  |  36 --
 .../grails/lang/gsp/psi/impl/gspImportUtil.kt      |  80 ----
 .../assetPipeline/AssetScriptInjector.kt           |  56 ---
 .../pluginSupport/assetPipeline/references.kt      | 113 ------
 .../plugins/grails/plugins/descriptors.kt          |  46 ---
 src/org/jetbrains/plugins/grails/plugins/impl.kt   |  67 ----
 .../grails/projectView/GrailsPluginsNode.kt        |  48 ---
 .../grails/projectView/GrailsProjectViewPane.kt    |  64 ---
 .../projectView/GrailsTreeStructureProvider.kt     |  58 ---
 .../projectView/api/GrailsSingleNodeProvider.kt    |  30 --
 .../projectView/api/GrailsViewNodeProvider.kt      |  26 --
 .../jetbrains/plugins/grails/projectView/api/ep.kt |  21 -
 .../grails/projectView/impl/Grails3NodeProvider.kt |  59 ---
 .../projectView/impl/GrailsAppNodeProvider.kt      |  51 ---
 .../projectView/impl/GrailsNodeComparator.kt       |  60 ---
 .../projectView/impl/OldGrailsNodeProvider.kt      | 101 -----
 .../plugins/grails/projectView/impl/impl.kt        |  52 ---
 .../grails/projectView/nodes/CompactedFqn.kt       |  65 ---
 .../grails/projectView/nodes/GrailsActionNode.kt   |  44 ---
 .../projectView/nodes/GrailsApplicationNode.kt     |  59 ---
 .../projectView/nodes/GrailsArtefactHandlerNode.kt |  41 --
 .../nodes/GrailsArtefactHandlerNodeBase.kt         |  37 --
 .../projectView/nodes/GrailsArtefactPackageNode.kt |  48 ---
 .../projectView/nodes/GrailsControllerNode.kt      |  39 --
 .../projectView/nodes/GrailsFlatPackageNode.kt     |  53 ---
 .../grails/projectView/nodes/GrailsPluginNode.kt   |  56 ---
 .../projectView/nodes/GrailsPsiDirectoryNode.kt    |  42 --
 .../projectView/nodes/GrailsRegularPackageNode.kt  |  80 ----
 .../grails/projectView/nodes/GrailsRootNode.kt     |  35 --
 .../projectView/nodes/OldGrailsPluginsNode.kt      |  52 ---
 .../projectView/nodes/OtherGrailsAppSourcesNode.kt |  87 -----
 .../plugins/grails/projectView/nodes/impl.kt       | 112 ------
 .../plugins/grails/projectView/nodes/util.kt       |  46 ---
 .../plugins/grails/projectView/showHide.kt         |  41 --
 .../grails/references/EnhancesTraitsCollector.kt   |  63 ---
 .../DetachedCriteriaDelegatesToProvider.kt         |  53 ---
 .../plugins/grails/runner/GrailsConsoleState.kt    |  21 -
 .../plugins/grails/runner/GrailsDebuggerRunner.kt  | 111 ------
 .../runner/impl/BaseGrailsCommandLineState.kt      |  37 --
 .../runner/impl/Grails3TestCommandLineState.kt     |  88 -----
 .../runner/impl/GrailsRunAppCommandLineState.kt    |  43 --
 .../runner/impl/GrailsTestAppCommandLineState.kt   |  27 --
 src/org/jetbrains/plugins/grails/runner/setup.kt   |  97 -----
 .../grails/spring/GrailsSpringSemContributor.kt    |  37 --
 .../plugins/grails/structure/Grails3Application.kt |  24 --
 .../plugins/grails/structure/GrailsApplication.kt  |  54 ---
 .../grails/structure/GrailsApplicationBase.kt      |  58 ---
 .../grails/structure/GrailsCommonClassNames.kt     |  84 ----
 .../grails/structure/OldGrailsApplication.kt       |  27 --
 .../grails/structure/impl/Grails2Application.kt    |  46 ---
 .../structure/impl/Grails3ApplicationBase.kt       |  55 ---
 .../structure/impl/Grails3ApplicationProvider.kt   |  54 ---
 .../impl/Grails3MultiModuleApplication.kt          |  55 ---
 .../impl/Grails3SingleModuleApplication.kt         |  51 ---
 .../structure/impl/GrailsModuleBasedApplication.kt |  60 ---
 .../impl/OldGrailsModuleBasedApplication.kt        |  69 ----
 .../plugins/grails/structure/impl/util.kt          |  21 -
 src/org/jetbrains/plugins/grails/structure/util.kt |  27 --
 .../plugins/grails/tests/GrailsTestUtils.kt        |  37 --
 .../plugins/grails/tests/runner/byClass.kt         |  95 -----
 .../plugins/grails/tests/runner/byDirectory.kt     | 106 -----
 .../plugins/grails/tests/runner/oldUtils.kt        |  36 --
 .../plugins/grails/util/version/versions.kt        |  21 -
 .../grails/i18n/GrailsGroovyI18nIntentionTest.kt   | 105 -----
 .../groovy/grails/i18n/GspI18nIntentionTest.kt     | 118 ------
 testFramework/README.md                            |   2 -
 testFramework/build.gradle.kts                     |  85 ----
 1530 files changed, 12960 insertions(+), 8366 deletions(-)
 create mode 100644 .gitattributes
 copy .sdkmanrc => .github/dependabot.yml (51%)
 create mode 100644 .github/vote_templates/announce.txt
 create mode 100644 .github/vote_templates/staged.txt
 create mode 100644 .github/vote_templates/vote_succeeded.txt
 copy .github/workflows/{rat.yml => codeql.yml} (52%)
 copy .github/workflows/{rat.yml => coverage.yml} (72%)
 create mode 100644 .github/workflows/release-abort.yml
 create mode 100644 .github/workflows/vulnerability-scan.yml
 create mode 100644 RELEASE.md
 create mode 100644 build-logic/README.md
 create mode 100644 build-logic/build.gradle
 create mode 100644 build-logic/settings.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.jacoco.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.java-legacy.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.java.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.kotlin.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.repositories.settings.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.reproducible.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.test.gradle
 create mode 100644 build-logic/src/main/groovy/org.apache.grails.intellij.build.vulnerability-scan.gradle
 create mode 100644 build.gradle
 delete mode 100644 build.gradle.kts
 copy .sdkmanrc => codecov.yml (67%)
 delete mode 100644 compatibilityResources/icons/grails.png
 delete mode 100644 compatibilityResources/icons/grails@2x.png
 delete mode 100644 compatibilityResources/icons/grails_13.png
 delete mode 100644 compatibilityResources/icons/grails_13@2x.png
 delete mode 100644 compatibilityResources/icons/grails_app.png
 delete mode 100644 compatibilityResources/icons/grails_app@2x.png
 delete mode 100644 compatibilityResources/icons/grails_app@2x_dark.png
 delete mode 100644 compatibilityResources/icons/grails_app_dark.png
 delete mode 100644 compatibilityResources/icons/grails_sdk.png
 delete mode 100644 compatibilityResources/icons/grails_sdk@2x.png
 delete mode 100644 compatibilityResources/icons/grails_test.png
 delete mode 100644 compatibilityResources/icons/grails_test@2x.png
 delete mode 100644 compatibilityResources/icons/gsp_logo.png
 delete mode 100644 compatibilityResources/icons/gsp_logo@2x.png
 delete mode 100644 compatibilityResources/icons/taglib.png
 delete mode 100644 compatibilityResources/icons/taglib@2x.png
 create mode 100644 compilers/grails-compiler-patch/README.md
 create mode 100644 compilers/grails-compiler-patch/build.gradle
 rename {grails-compiler-patch/src => compilers/grails-compiler-patch/src/main/java}/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java (100%)
 rename {grails-compiler-patch/src => compilers/grails-compiler-patch/src/main/java}/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java (100%)
 rename {grails-compiler-patch/src => compilers/grails-compiler-patch/src/main/java}/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java (100%)
 rename {grails-compiler-patch/src => compilers/grails-compiler-patch/src/main/java}/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java (100%)
 rename {grails-compiler-patch/src => compilers/grails-compiler-patch/src/main/java}/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java (100%)
 rename {grails-compiler-patch => compilers/grails-compiler-patch/src/main}/resources/log4j.properties (100%)
 create mode 100644 compilers/jps-plugin/README.md
 copy src/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java => compilers/jps-plugin/build.gradle (64%)
 rename {jps-plugin/src => compilers/jps-plugin/src/main/java}/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java (100%)
 rename {jps-plugin => compilers/jps-plugin/src/main}/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension (100%)
 delete mode 100644 copyright/build.gradle.kts
 delete mode 100644 coverage/build.gradle.kts
 create mode 100644 etc/bin/Dockerfile
 create mode 100755 etc/bin/download-release-artifacts.sh
 create mode 100755 etc/bin/test-reproducible-build.sh
 create mode 100755 etc/bin/verify-distributions.sh
 create mode 100755 etc/bin/verify-reproducible.sh
 create mode 100755 etc/bin/verify.sh
 delete mode 100644 gradle-tooling/build.gradle.kts
 delete mode 100644 grails-compiler-patch/build.gradle.kts
 delete mode 100644 grails-rt/build.gradle.kts
 delete mode 100644 grails-rt/resources/META-INF/MANIFEST.MF
 delete mode 100644 hibernate/build.gradle.kts
 delete mode 100644 i18n/build.gradle.kts
 delete mode 100644 jps-plugin/build.gradle.kts
 delete mode 100644 langInjection/build.gradle.kts
 create mode 100644 libs/gradle-tooling/README.md
 copy src/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java => libs/gradle-tooling/build.gradle (68%)
 rename {gradle-tooling/src => libs/gradle-tooling/src/main/java}/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModule.java (100%)
 rename {gradle-tooling/src => libs/gradle-tooling/src/main/java}/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleImpl.java (100%)
 rename {gradle-tooling/src => libs/gradle-tooling/src/main/java}/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleModelBuilderImpl.java (100%)
 rename {gradle-tooling => libs/gradle-tooling/src/main}/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService (100%)
 create mode 100644 libs/grails-rt/README.md
 create mode 100644 libs/grails-rt/build.gradle
 rename {grails-rt/src => libs/grails-rt/src/main/java}/org/jetbrains/groovy/grails/rt/AddAgentJarToClassPathTransformer.java (100%)
 rename {grails-rt/src => libs/grails-rt/src/main/java}/org/jetbrains/groovy/grails/rt/Agent.java (100%)
 copy {grails-rt/src => libs/grails-rt/src/main/java}/org/jetbrains/groovy/grails/rt/AgentUtils.java (100%)
 rename {grails-rt/src => libs/grails-rt/src/main/java}/org/jetbrains/groovy/grails/rt/ForkListenerTransformer.java (100%)
 rename {grails-rt/src => libs/grails-rt/src/main/java}/org/jetbrains/groovy/grails/rt/GrailsIdeaTestListener.java (100%)
 copy {grails-rt/src => libs/grails-rt/src/main/java}/org/jetbrains/groovy/grails/rt/GrailsRtMarker.java (100%)
 rename {grails-rt => libs/grails-rt/src/main}/resources/META-INF/commands/intellij-command-proxy.groovy (100%)
 rename {grails-rt => libs/grails-rt/src/main}/resources/idea-injected-traits.groovy (100%)
 create mode 100644 libs/testFramework/README.md
 create mode 100644 libs/testFramework/build.gradle
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/openapi/externalSystem/service/execution/TestUnknownSdkResolver.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/openapi/externalSystem/test/JavaCompileTestUtil.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/openapi/externalSystem/test/JavaExternalSystemImportingTestCase.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/openapi/externalSystem/util/ExternalSystemFileTestUtil.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/platform/testFramework/eelJava/EelTestJdkProvider.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/com/intellij/platform/testFramework/eelJava/EelTestUtil.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/importing/GradleImportingTestCase.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/importing/TestGradleBuildScripBuilder.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/importing/TestGradleSettingsScriptHelper.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/GradleJvmResolver.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/JavaVersionMatcher.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/JavaVersionRestriction.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/TargetJavaVersionWatcher.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/VersionMatcherRule.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/annotation/TargetJavaVersion.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/annotation/TargetVersions.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/jvm/GradleBrokenJvmSerialisationVersionRestriction.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/jvm/GradleToolingApi9VersionRestriction.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/tooling/util/VersionMatcher.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/gradle/util/testWrapperUtil.kt (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/CompoundTestLibrary.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/GroovyProjectDescriptors.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/LibraryLightProjectDescriptor.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/LightGroovyTestCase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/RepositoryTestLibrary.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/TestLibrary.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/completion/CompletionTestBase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/grails/Grails14TestCase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/grails/GrailsTestCase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/grails/GrailsTestUtil.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/grails/HddGrailsTestCase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/grails/TestGrailsApplicationProvider.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/lang/GroovyFormatterTestCase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/lang/highlighting/GrHighlightingTestBase.java (100%)
 rename {testFramework/src => libs/testFramework/src/main/java}/org/jetbrains/plugins/groovy/util/TestUtils.java (100%)
 delete mode 100644 maven/build.gradle.kts
 delete mode 100644 maven/src/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt
 delete mode 100644 maven/src/com/intellij/groovy/grails/maven/MavenNodeProvider.kt
 create mode 100644 plugin/build.gradle
 rename {gen => plugin/src/main/gen}/org/jetbrains/plugins/grails/GroovyMvcIcons.java (100%)
 rename {gen => plugin/src/main/gen}/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspDirectiveLexer.java (100%)
 rename {gen => plugin/src/main/gen}/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspLexer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GantFileDetector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsAttachSourcesProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsBundle.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsCompletionContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsConsoleFilterProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsFlashMapContentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsGroovyCodeReferenceContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsGspReferenceContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsHtmlReferenceContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsImplicitUsageProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsPluginFieldCompletionProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GrailsStaticFieldCompletionProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GsonConstants.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GspCompletionContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GspFindUsagesProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/GspTemplateContextType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/TemplateHighlightErrorFilter.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/actions/ArtefactData.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/DomainFieldIntention.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GenerateViewIntention.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsActionGroup.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java (95%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsCreateActionByUsageIntention.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsGenerateViewAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsGoToClassContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsGotoRelatedProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsGroovySourceFolderDetector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java (97%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/GrailsWarAction.java (92%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/MakeNullableIntention.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/MakeUniqueIntention.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsControllerAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsDomainClassAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsFilterAction.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsInterceptorAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsScriptAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsServiceAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsTagLibAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java (94%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/NewGspAction.java (97%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java (88%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/addins/GrailsIntegrationUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/addins/js/CssIntegrationUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/addins/js/GrailsJavaScriptInjector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/addins/js/GspJsInjectionTagBean.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/addins/js/JavaScriptIntegrationUtil.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java
 copy src/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java (58%)
 copy src/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java (50%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java
 copy src/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java => plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/IconOwner.java (78%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java
 rename src/org/jetbrains/plugins/grails/actions/NewGrailsInterceptorAction.java => plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java (52%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/ControllerActions.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/commands/Grails3CommandProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/commands/GrailsCommandCompletionUtil.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/AbstractConfigSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/BuildConfigClosureMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/BuildConfigMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/BuildConfigSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/ConfigMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/ConfigSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/DataSourcesSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GradleParentProjectForm.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GradleParentProjectForm.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsAbstractConfigSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsConfigUnresolvedFileFilter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsConfigUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsConstants.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsEnvironmentClosureMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsFramework.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsLibraryPresentationProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsModuleBuilder.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsModuleStructureUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsPluginConfigMethodContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsPluginNameCompletionContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsPluginWebHelpReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsProjectStructureDetector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsPropertiesFileCache.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsSettingSynchronizer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsSettings.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsSettingsService.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GrailsStructure.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/GroovyConfigReader.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/config/PrintGrailsSettingsConstants.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/database/GrailsDataSourceDetector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java (96%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorToolbar.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/editor/selection/GrailsDependencySelectioner.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/editor/selection/GrailsGroovyInjectionSelectioner.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateActionBase.java
 copy src/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java => plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateAsyncControllerAction.java (65%)
 copy test/org/jetbrains/plugins/groovy/grails/domain/GormBinding14Test.java => plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerAction.java (67%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerActionBase.java
 copy grails-rt/src/org/jetbrains/groovy/grails/rt/AgentUtils.java => plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateViewsAction.java (63%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToControllerAction.java
 copy src/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java => plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToDomainAction.java (52%)
 copy src/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java => plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToServiceAction.java (52%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToTestAction.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToViewAction.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/feature/forge/GrailsForgeModuleBuilder.kt (100%)
 copy src/org/jetbrains/plugins/grails/util/ModuleCachedValue.java => plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/Linkable.java (57%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableNamedArgumentsProvider.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableTransformationSupport.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/fileType/GspFileType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/gorm/GormClassNames.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gorm/GormVersion.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/gradle/GrailsModuleData.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/gradle/GrailsModuleGradleDataService.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/gradle/GrailsProjectResolverExtension.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/gradle/GrailsTaskManagerExtension.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/gson/GrailsStreamingJsonBuilderContributor.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReference.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.java
 rename src/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt => plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.java (50%)
 rename src/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt => plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.java (51%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPatterns.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRWAccessDetector.java
 copy i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nReferenceContributor.java => plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.java (74%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderNamedArgumentsProvider.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReference.java
 copy src/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceProvider.java (57%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceQueryExecutor.java
 copy src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaMemberProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.java (51%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/inspections/GspAndGroovyInspection.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/inspections/GspSuppressionProvider.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/Grails3TraitInjectorContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspCssInjector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspDirectiveKind.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspErrorFilter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspFileViewProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspFileviewProviderFactory.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspLanguage.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspTagDocumentationProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/GspTagInspection.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/completion/GspXmlFileNSInfoProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/debug/GspCodeFragmentFactory.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManager.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManagerFactory.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEditorActionsUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEnterHandler.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspTypedHandler.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/folding/GspFoldingBuilder.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/ForeignLanguageBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlockGenerator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingHelper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingModelBuilder.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspGroovyBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspHtmlBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/GspTextSyntheticBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspIndentProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspSpacingProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspCodeStyleSettingsProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspLanguageCodeStyleSettingsProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeIndex.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeInfo.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspCommenter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlighter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlightingLexer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighter.java (100%)
 copy src/org/jetbrains/plugins/grails/references/tagSupport/CodecAttributeSupport.java => plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.java (50%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspFileBraceMatcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspIndexPatternBuilder.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspPairedBraceMatcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspSyntaxHighlighter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/GspDirectiveFlexLexer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/GspFlexLexer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/GspTokenTypesEx.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/IGspElementType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/core/GspTokenTypes.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp.flex (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp_directive.flex (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/GspElementTypes.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/GspGroovyElementTypes.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/GspPsiCreator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/composite/GspCompositeElementType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspAwareGroovyParser.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspGroovyParser.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspTemplateStmtParsing.java (100%)
 rename src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt => plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.java (62%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsInGspFileRoot.java (100%)
 rename src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt => plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.java (63%)
 rename src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt => plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.java (63%)
 rename src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsInGspFileRoot.java => plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.java (58%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/lexer/GspGroovyLexer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GrailsTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParser.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParserDefinition.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/chameleons/GspDirectiveElement.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/lexer/GspLexer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/html/GspHtmlTemplateRootType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/html/elements/GspXmlDocument.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspGroovyCodeElementType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspInHtmlElementType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspTemplateDataElementType.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactory.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactoryImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/GspLazyElement.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspClass.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspDeclarationHolder.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspExprInjection.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunBlock.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunMethod.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrMapAttributeValue.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspGroovyFile.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterGroovyElement.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterHtmlElement.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/template/GspTemplateStatement.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspClassImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspDeclarationHolderImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspExprInjectionImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunBlockImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunMethodImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrMapAttrValueImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspGroovyFileImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspModelVariableModel.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterGroovyElementImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementManipulator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspPageSkeleton.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/ModelVariableDeclarationSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/template/GspTemplateStatementImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspDeclarationTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspExpressionTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspFile.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspScriptletTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirective.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttributeValue.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttributeValue.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspTaggedElement.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspDeclarationTagImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspExprTagImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspFileImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeValueImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeValueImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspContentTagDescriptorProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspElementDescriptorBase.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkElementDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkNamespaceDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspNamespaceDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspPropertyElementDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspSdkElementDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagDescriptorService.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagVariableDeclarationSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTmplNamespaceDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java (95%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/DefaultCodecDirectiveReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/GspCssClassReferenceContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspHtmlOuterElement.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspLikeFile.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlFileImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlOuterElementImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlXmlExtension.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/GspImportUtil.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/BuiltInTagReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GrailsTaglibNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java (92%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/TagLibNamespaceDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/search/GspControllerReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/search/GspPageReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/search/JspPageReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/lang/gsp/util/GspUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/DomainClassNodeRenderer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditorProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/PerspectiveFileEditorComponent.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/create/RelationsCreationsProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/delete/RelationDeleteProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/graph/DataModelAndSelectionModificationTracker.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/graph/DomainClassDependencyPresentation.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/graph/DomainClassNode.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/graph/DomainClassRelationsInfo.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/perspectives/graph/DomainClassesRelationsDataModel.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceContributor.java
 copy src/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceProvider.java (83%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetsFileReferenceSet.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/buildTestData/GrailsBuildTestDataMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceModuleReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesClosureMethodContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesPomDeclarationSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableReturnTypeCalculator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableFieldReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableMemberMethodContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroAccessControlMethodProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/spock/GrailsSpockEnhancer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowEventDefinitionMethodContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowMethodContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowPomDeclarationSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowStateNameReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowUtils.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3CompiledPluginDescriptor.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3SourcePluginDescriptor.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPluginDescriptor.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPlugins.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java (90%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/projectView/NodeWeights.java (100%)
 rename src/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java (50%)
 rename src/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java => plugin/src/main/java/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java (52%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java
 rename src/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspLikeFile.java => plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java (72%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/CodecNonCodeMemberContributor.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/GrailsArtifactNonCodeMemberProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/GrailsArtifactRenameProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/GrailsClosureMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/GrailsMethodNamedArgumentReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/GrailsResourceClassMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/MemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/TraitInjectorService.kt (97%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/bootstrap/GrailsBootStrapMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/buildConfig/BuildConfigFileReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/ArtifactFieldReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/ArtifactPropertyGetterReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/CodecReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/ContentTypeReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/ContextPathReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GrailsEncodingReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GrailsFileReferenceSetBase.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GrailsLayoutFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GrailsRootBasedFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GrailsViewFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GroovyGspAttributeWrapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GroovyGspTagWrapper.java (100%)
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GspAttributeWrapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/GspTagWrapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/PluginReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/PluginSupportFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/ResourceDirAttributeFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/TemplateFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/WebAppFolderFileReferenceSet.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/XmlGspAttributeWrapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/common/XmlGspTagWrapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GormRegexInjector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintGroupNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsClosureMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsGroupClosureMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsSharedReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsValidationMethodsContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/constraints/GrailsValidatorParamTypeEnhancer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ActionReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ActionRenameProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodCompletionProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerFieldReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerLayoutReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerMembersProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerRenameProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/ControllerRenderTemplateRefProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/GrailsNamespaceReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/GrailsWithFormatClosureMethodContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/GspTagMethodComparator.java (100%)
 rename src/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt => plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.java (60%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/HttpRequestMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/controller/HttpResponseMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/DomainClassUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/DomainDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/DomainFieldRenameProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/DomainMembersProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/DomainStaticMemberCompletionProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormAddToNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java (98%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormConstraintMethodProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormDynamicFinderCompletionProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormEmbeddedPropertyListReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormFetchModeCompletionProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormFetchModeReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormGetPersistentValueReturnTypeCalculator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormHqlInjector.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormMappingMethodProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormNamedArgumentReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormPersistentPropertiesNamedArgProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormPropertiesListReferenceReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormPropertyConstraintReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormPropertyReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormPropertyReferenceUnique.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormSortNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormUniqueConstraintReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GormUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToReferencesProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToValuesReferencesProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/GrailsSyntheticFieldDeclarationSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderImplicitMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaClosureMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaPropertyReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaReturnTypeCalculator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaClosureMemberProvider.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaPropertyNamedArgProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryCallTypeCalculator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryDeclarationSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyMembersContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyTypeCalculator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedQueryDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormBasicAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormCollectionAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormEmbeddedAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormEntity.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormIdAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistenceMapping.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistentAttribute.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/filter/FilterClosureMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/filter/FilterMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/filter/FilterReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/filter/GrailsFilterClosureMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/filter/GrailsFilterUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/jobs/JobClosureMethodProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/jobs/JobsMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/other/GrailsTemplateNamedArgumentReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginClassMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginExcludeReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginListReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginParameterEnhancer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/providers/GspImportListReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/CodecAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspActionAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspApplyLayoutNameAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspContextPathAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspControllerAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspFieldValueTagSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspIncludeViewAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspLayoutReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspMappingAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspMetaTagSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspPluginAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceModuleAttrSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceRequireTagModulesAttrSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceUriAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspResourceContextPathAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspResourceDirAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspResourceFileAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspSelectTagSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspSrcJsAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspTagSupportGspReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspTemplateAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/GspUpdateAttributeSupport.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/tagSupport/TagAttributeReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/taglib/TaglibClosureParamEnhancer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/taglib/TaglibMembersProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingConstraintClosureMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingMemberProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingNamedArgumentProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingReferenceProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUnresolvedHighlightingFilter.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/util/CodecUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/references/util/PsiFieldReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsAppEngineDebuggerRunner.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsCommandExecutor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsCommandExecutorUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsCommandLineExecutor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsConfigurationFactory.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsConsole.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsInstallationExecutor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsRerunFailedTestsAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsRunConfiguration.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationExtension.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationType.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunnerSetup.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java (95%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/LocatableRunConfigurationWithCommonParameters.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java (97%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java (98%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java (97%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/impl/GrailsInstallationCommandExecutor.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/impl/GrailsTestCommandLineState.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java (97%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorListener.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorWithListener.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/runner/util/GrailsExecutionUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/sdk/GrailsSDK.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/sdk/GrailsSDKManager.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/service/GrailsBackgroundService.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrLightVariableRenameProcessor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsCustomSpringComponent.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsInjectedBeanCompletionContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsResourceBeanExtractor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsResourcesGroovyMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java (98%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsSpringFacetProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsSpringMethodReferenceProvider.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsSpringStereotypeAliasingPsiTargetMapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/GrailsWebApplicationContextMembersContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/InjectedSpringBeanProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/SpringBeanReferenceSearcher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/SpringGrailsTargetMapper.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/spring/SpringInjectionAnnotator.java (100%)
 copy src/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java => plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.java (75%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/GrailsApplicationListener.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/GrailsApplicationManager.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/GrailsApplicationManagerRefresher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/GrailsApplicationProvider.java (100%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplications.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.java
 copy {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java (100%)
 rename src/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java => plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.java (70%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.java
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.java
 rename src/org/jetbrains/plugins/grails/references/common/GspAttributeWrapper.java => plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsProperties.java (56%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/impl/PlainGrailsApplicationProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/sync/GrailsApplicationBackgroundTask.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/sync/GrailsMvcStructureRefresher.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/sync/GrailsRunConfigurationTask.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/sync/GrailsSdkCheckTask.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java (94%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/tests/GrailsJUnitRecognizer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/tests/GrailsTestMemberContributor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java (68%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/tests/GrailsTestVariableEnhancer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java (97%)
 create mode 100644 plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationSetup.java
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/tests/runner/GrailsUrlProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/EditorComboBoxWithHistory.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/DelegateReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsArtifact.java (96%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsArtifactTransformerUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsCachedValue.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsFacetProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsNameUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsPatterns.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsPluginCondition.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsPsiUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsUtils.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsVersionCondition.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/GrailsWebFacetProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/ModuleCachedValue.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/PredefinedTypeReferenceEnhancer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/ReferenceGutterIconRenderer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/SafeReference.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/version/Range.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/version/Version.java (98%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/grails/util/version/VersionImpl.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/dsl/GrailsScriptProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/grails/annotator/GrailsControllerAnnotator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/grails/annotator/GrailsDomainAnnotator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/grails/annotator/GrailsForkSettingsNotificator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/grails/annotator/GrailsSynchronizeStructureAnnotator.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/ConsoleProcessDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/MvcCommand.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/MvcPathMacros.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/MvcProjectStructure.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/MvcWatchedRootProvider.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/AvailablePluginsModel.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/InstallUninstallPluginsDialog.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginColumnInfo.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginDescriptor.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginIsInstalledColumnInfo.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginUtil.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java (96%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.form (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsTable.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/actions/AddCustomPluginAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/actions/ReloadMvcPluginListAction.java (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/plugins/proxySettings.kt (100%)
 rename {src => plugin/src/main/java}/org/jetbrains/plugins/groovy/mvc/util/MvcTargetDialogCompletionUtils.java (100%)
 rename {resources => plugin/src/main/resources}/IdeaPrintProjectSettings.groovy (100%)
 rename {resources => plugin/src/main/resources}/META-INF/grails-css-integration.xml (100%)
 rename {resources => plugin/src/main/resources}/META-INF/grails-js-integration.xml (100%)
 rename {resources => plugin/src/main/resources}/META-INF/grails-method-descriptors.xml (100%)
 rename {resources => plugin/src/main/resources}/META-INF/plugin.xml (97%)
 rename {resources => plugin/src/main/resources}/META-INF/pluginIcon.svg (100%)
 rename {resources => plugin/src/main/resources}/fileTemplates/internal/Groovy Server Page.gsp.ft (100%)
 rename {resources => plugin/src/main/resources}/fileTemplates/internal/Groovy Server Page.gsp.html (100%)
 rename {resources => plugin/src/main/resources}/icons/action_method.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/grails.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/grails_13.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/grails_13_dark.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/grails_app.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/grails_sdk.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/grails_test.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/groovy_mvc_plugin.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/gsp_logo.svg (100%)
 rename {resources => plugin/src/main/resources}/icons/taglib.svg (100%)
 rename {resources => plugin/src/main/resources}/inspectionDescriptions/GspInspection.html (100%)
 rename {resources => plugin/src/main/resources}/inspectionDescriptions/metaInformation.json (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/GenerateViewIntention/after.gsp.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/GenerateViewIntention/before.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/GenerateViewIntention/description.html (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/GrailsCreateActionByUsageIntention/after.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/GrailsCreateActionByUsageIntention/before.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/GrailsCreateActionByUsageIntention/description.html (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/MakeNullableIntention/after.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/MakeNullableIntention/before.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/MakeNullableIntention/description.html (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/MakeUniqueIntention/after.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/MakeUniqueIntention/before.groovy.template (100%)
 rename {resources => plugin/src/main/resources}/intentionDescriptions/MakeUniqueIntention/description.html (100%)
 rename {resources => plugin/src/main/resources}/liveTemplates/gsp.xml (100%)
 rename {resources => plugin/src/main/resources}/messages/GrailsBundle.properties (100%)
 rename {resources => plugin/src/main/resources}/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/tld/grails.tld (100%)
 rename {test => plugin/src/test/java}/com/intellij/groovy/grails/copyright/GspCopyrightUpdaterTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/DoublePluginTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrEditorActionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsActionsTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsCodecTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsConsoleFilterTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsDoNotHighlightGrailsSpecifiedFieldsAsUnusedTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsGantTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsHighlightingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsLightVariableInfoTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsLinkGeneratorTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsLogVariableTest.java (100%)
 rename test/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt => plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.java (68%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsPageRendererTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsPluginClassTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsProjectStructureTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsResolveInConfigFileTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsResolveTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsRunConfigurationProducerTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GrailsRunTargetCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/GspLiveTemplatesTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/JamInGroovyTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtils14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtilsTest.java (100%)
 rename test/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt => plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.java (54%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/action/GenerateViewByActionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateAction2Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateActionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/ControllerReferenceCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/DomainClassMethodsCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/GrailsCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/GrailsNamedAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/GrailsTestAutoCompletionHardReferencesTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/GspTagReferenceCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/ServiceCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/completion/TaglibClassMethodsCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/config/GrailsBuildConfigResolveTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/config/GrailsConfigFileTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSelectWordTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSlurperTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/config/GrailsConstraintGroupTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/config/GrailsPluginDependencyCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormApiFromJavaTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormBinding14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormBindingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormDetachedCriteriaTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormDomainConstructorTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormDomainFinderMethodTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormFetchModeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormFinderMethodsCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormGetPersistentValueReturnTypeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormHasManyRelationTypeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormHqlInjectionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormMappingSortTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormMultiDefinePropertyTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormNamedArgumentForHibernateMappingBuilderTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormPersistentFacetTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormPropertyAccessTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormStandaloneTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormSyntheticFieldTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GormUniqueConstraintValueTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsAddToMethodTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilder14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderToManyRelationTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjections14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjectionsTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainConstraintTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainFinderMethodsTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttribute14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesFromParentTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsFindWhereParameterTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsGormGetIdMethodTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsInjectRegexTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQuery14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQueryTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainPropertyTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrailsTransientPropertyTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/GrormEmbeddedFieldTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/domain/NamedArgumentsInFindOrCreateMethodTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gradle/GradleGrailsImportingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/AssetPipelineScriptInjectionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigation2Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigationTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspAttributeFromJavadocTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspBuiltInTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspCharsetReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspClearCompletionBehaviourTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionEmptyTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionInNonexistingNamespaceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspContentTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspControllerReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspCreateTemplateQuickFixTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspCreateVariableQuickFixTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspCssTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspDirectiveDefaultCodecTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspElementManipulatorForOuterLanguageElementTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspFormatterTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspGroovyInspectionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspHighlightingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspHtmlAttributesTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspIDEA68710Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspImportTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspJavascriptTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspJsFormatterTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspJsInjectionTagEpTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspLanguageInjectionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspLayoutTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspNamedMappingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePrefixIsNotIdentifierTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePriorityTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspPropertyResolvePriorityTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspResolveIndexesTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspResolveVariablesFromModelTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspSdkTagCompletionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspSelectWordTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspTagDocumentationTest.java (100%)
 create mode 100644 plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTagLibDumbModeTest.java
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspThisAndSuperReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GspTypingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/gsp/GstringAttributeTest.java (100%)
 create mode 100644 plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.java
 rename test/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt => plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.java (55%)
 create mode 100644 plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.java
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/inspections/GrailsMethodMayBeStaticInspectionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/parser/GspGroovyParserTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/parser/GspHtmlParserTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/parser/GspParserTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/parser/GspParsingTestCase.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsBuildTestDataTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsResourcePluginTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsSearchableTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsShiroPluginTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsWebflowTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/pluginSupport/SpockMethodReturnTypeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/GrailsBeanNameConventionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/GrailsFilterTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/GrailsJobTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/GrailsViewReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/bootstrap/GrailsBootStrapTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/Grails14MimePluginTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsAction14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsActionWithExtensionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsController14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerActionReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAllowedMethodsTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAmbiguousMethodInspectionTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerLayoutReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRenameTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRespondMethodTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpRequestTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpResponseTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsMimePluginTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsNamespaceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsRenderMethodTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringDSLTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringIntegrationTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTagLibNamedArgumentsTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglib14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglibParamTypeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/taglib/GspCustomTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/taglib/GspResolveTagsFromSameNamespaceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingUnresolvedReferenceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/AssetPipelineSrcTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspActionAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspCodecAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspControllerAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspFieldTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspIncludeViewAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspJsSrcTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspMetaTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspPluginAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspRenderTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspResourceTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspSelectTagTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tagSupport/GspUpdateAttributeTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tests/GrailsMockAnnotationTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tests/GrailsTestForAnnotationTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tests/GrailsTestMixinAnnotationTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tests/GrailsTestUtilTest.java (100%)
 create mode 100644 plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestedClassTest.java
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhance14Test.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhanceTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/v3/GrailsEnhancesTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/v3/GrailsProjectDescriptor.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/validation/GrailsImportFromTest.java (100%)
 rename {test => plugin/src/test/java}/org/jetbrains/plugins/groovy/grails/validation/GrailsValidatedObjectTest.java (100%)
 rename {resources => plugin}/standardDsls/GrailsStandardDSL.gdsl (100%)
 rename {testdata => plugin/testdata}/grails/actions/GspBlockComment.gsp (100%)
 rename {testdata => plugin/testdata}/grails/actions/GspBlockComment_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/actions/GspLineComment.gsp (100%)
 rename {testdata => plugin/testdata}/grails/actions/GspLineComment_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/actions/GspLineUncomment.gsp (100%)
 rename {testdata => plugin/testdata}/grails/actions/GspLineUncomment_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/AllFinderMethodsInDomainClass.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/AttributeNameFinishWithEq.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/AttributeNameFinishWithEq_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/ConstrainsPropertyInDomainClass.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/ConstrainsPropertyInDomainClassConstraints.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/ConstrainsPropertyInDomainClass_after.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/DomainClassDynamicMethods.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/DomainClassNonStaticMethods.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/DomainClassStaticProperties.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/DomainClassStaticProperties_after.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/FindByMethodInDomainClass.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/FindByMethodWithThreeArgsInDomainClass.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/FinderMethodsInDomainClass.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/FinderMethodsInGsp.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/FinderMethodsInGsp.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/FindersWithBelongsTo.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/FindersWithHasMany.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/GspInjectedJavaScript.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/GspInjectedJavaScript_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/JspTagCompletion.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/JspTagCompletion_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/ModifiersInGspGroovyDeclaration.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/OverrideStandardTag.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/ServiceInjection.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/ServiceInjection_after.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/UnpackagedDomainClassInGsp.groovy (100%)
 rename {testdata => plugin/testdata}/grails/completion/UnpackagedDomainClassInGsp.gsp (100%)
 rename {testdata => plugin/testdata}/grails/completion/UnpackagedDomainClassInGsp_after.gsp (100%)
 rename {testdata => plugin/testdata}/grails/enterAction/dir.test (100%)
 rename {testdata => plugin/testdata}/grails/enterAction/simple_type.test (100%)
 rename {testdata => plugin/testdata}/grails/fmt.tld (100%)
 rename {testdata => plugin/testdata}/grails/formatter/comments/schulz1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/comments/ven1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/groovy/groovy.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/groovy/plain1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/groovy/sim1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/gsp/EA29587.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/gsp/gsp1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/gsp/gsp2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/gsp/gsp3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/gsp/gsp8.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/gsp/gsp9.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/error/giga_werle.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/error/nik1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/error/show.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/error/werle1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/error/werle30.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/inner/GRVY_1165.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/inner/GRVY_1165_1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/inner/GRVY_876.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/inner/inner2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/inner/inner3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/GRVY_1046.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/bigTest.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/megap2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/mmm3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/peter.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/peter2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/peter3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/megabug/range1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/attr1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/gsp6.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/gsp7.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/peter7926.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/peter_simple.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/range.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/range2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/range4.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/nested/range5.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/GRVY_1146.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/attributeSpaces.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/gsp4.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/gsp8.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/htm1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/htm2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/range3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/sim2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/simple/tagNameSpaces.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/trash/bug1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/trash/bug2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/trash/bug3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/trash/bug4.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/trash/bug5.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/html/trash/trash1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/hyphenInTagName.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/js/t1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/js/t2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/spacing/groovy/gr1.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/spacing/groovy/gr2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/spacing/htm3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/spacing/spac2.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/spacing/spac3.test (100%)
 rename {testdata => plugin/testdata}/grails/formatter/spacing/tags2.test (100%)
 rename {testdata => plugin/testdata}/grails/gsp/TestController.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/DomainFindersWithInheritedProperties.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/FileReferenceWithGspInjections.gsp (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/FinderMethods.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/HasManyBelongsTo.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/JavascriptWithGspInjections.gsp (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/JspTagInGsp.gsp (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/MappedBy.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/MappedByErrors1.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/Properties.groovy (100%)
 rename {testdata => plugin/testdata}/grails/highlighting/UnknownArgsAttribute.gsp (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/controllers/contr1Controller.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/controllers/contr2Controller.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/controllers/contr3Controller.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/dyn1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder10.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder11.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder2.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder3.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder4.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder5.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder6.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder7.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder8.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/finder_full.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/list_order.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/not_ref.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/qulaified.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/domain/static.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/attr1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/custom1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/customNamespacePrefix.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/dirname.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/endTagTest1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/endTagTest2.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/endTagTest3.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/endTagTest4.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/endTagTest5.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/excludeNotATagGroovy.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/excludeNotATagHTML.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/g1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/get1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/groo1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/groo2.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/groo3.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/groo4.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/groo5.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/html1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/htmlWithDoctype.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/link.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/link2.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/my1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/nonStatic.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/packageTagLib1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/packageTagLib2.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/tail1.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/tail2.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/tail3.test (100%)
 rename {testdata => plugin/testdata}/grails/oldCompletion/gsp/tail4.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/dir/dir1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/hyphenInTagName.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/inject/GRVY_943.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/inject/escaped1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/bubug1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/clos1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/clos2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/common.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/form1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/megap1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/mmm1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/simple/peter2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/act1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/act2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/gps9.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/orph1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/tag3.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/tagWithSlash.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/uglyAttributeName.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gsp/tags/unendedAttrList.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/comments/com.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/comments/comm1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/comments/commm2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/common.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/for1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/for2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/foreach1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/if1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/if2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/swit1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/control/swit2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/custom/tag1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/custom/tag2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/custom/tag3.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/declarations/dec1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/declarations/dec2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/declarations/dec3.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/declarations/dec4.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/direct/dir1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/errors/err1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/errors/err2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/errors/err3.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/errors/err4.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/simple/clos1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/simple/inj1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/simple/inj2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/simple/nl.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspGroovy/simple/stat1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/afterWhitespace.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/alone.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/common.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/html1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/inject1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/mmm2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/mmm5.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/peter1.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/peter2.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/range_parse.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/werle.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/werle28.test (100%)
 rename {testdata => plugin/testdata}/grails/parser/gspHtml/werle29.test (100%)
 rename {testdata => plugin/testdata}/grails/pluginList/plugins-list-grailsCore.xml (100%)
 rename {testdata => plugin/testdata}/grails/resolve/ResolvePropertiesFromBelongsTo.groovy (100%)
 rename {testdata => plugin/testdata}/grails/resolve/ResolvePropertiesFromBelongsTo2.groovy (100%)
 rename {testdata => plugin/testdata}/grails/resolve/ResolvePropertiesFromHasMany.groovy (100%)
 rename {testdata => plugin/testdata}/grails/resolve/ResolvePropertyFromMappedBy.groovy (100%)
 rename {testdata => plugin/testdata}/grails/resolve/StaticsOk.groovy (100%)
 rename {testdata => plugin/testdata}/grails/shiro/AccessControlBuilder.groovy (100%)
 rename {testdata => plugin/testdata}/mockDomainDir/grails-app/domain/Product.groovy (100%)
 rename {testdata => plugin/testdata}/mockDomainDir/grails-app/domain/aaa/Book.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/conf/groovy-starter.conf (100%)
 rename {testdata => plugin/testdata}/mockGrails11/conf/webdefault.xml (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-bootstrap-1.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-core-1.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-core-1.3.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-gorm-1.3.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-plugin-url-mappings-2.0.4.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-scripts-1.3.7.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-spring-1.3.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-test-1.3.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-web-1.3.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/dist/grails-webflow-1.3.7.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/groovy-all.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/hibernate-core-3.3.1.GA.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/junit-4.8.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/org.springframework.beans-3.0.3.RELEASE.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/org.springframework.context-3.0.3.RELEASE.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/org.springframework.test-3.0.2.RELEASE.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/servlet-api-2.5.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/spock-grails-support-0.5-groovy-1.7.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/lib/spring-core-3.0.3.RELEASE.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Bootstrap.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/BugReport.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Clean.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Compile.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Console.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateApp_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateController.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateDomainClass.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateFilters.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateIntegrationTest.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreatePlugin_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateScript.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateService.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateTagLib.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/CreateUnitTest.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Doc.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/GenerateAll.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/GenerateController.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/GenerateViews.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Help_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Init.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/InstallPlugin.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/InstallTemplates.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/ListPlugins_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Package.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/PackagePlugin_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/PluginInfo_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/ReleasePlugin.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/RunApp.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/RunWar.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/SchemaExport.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/SetProxy_.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/SetVersion.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Shell.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Stats.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/TestApp.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/UninstallPlugin.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/Upgrade.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/War.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsArgParsing.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsBootstrap.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsClasspath.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsClean.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsCompile.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsCreateArtifacts.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsCreateProject.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsDocs.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsEvents.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsGenerate.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsInit.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsPackage.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsPluginDev.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsPlugins.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsProxy.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsRun.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsSettings.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsTest.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_GrailsWar.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_PackagePlugins.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/_PluginDependencies.groovy (100%)
 rename {testdata => plugin/testdata}/mockGrails11/scripts/log4j.properties (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-bootstrap-2.0.0.M2.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-core-2.0.0.M1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-datastore-gorm-1.0.0.RC1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-hibernate-2.0.0.M1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-controllers-1.4.0.M1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-converters-2.2.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-gsp-2.0.0.RC1-sources.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-mimetypes-1.4.0.M1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-rest-2.3.0.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-servlets-2.2.1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-plugin-testing-1.4.0.M1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-spring-2.2.3.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-test-2.0.0.M2.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/grails-web-2.0.0.M1.jar (100%)
 rename {testdata => plugin/testdata}/mockGrails14/spring-test-3.1.0.M2.jar (100%)
 rename {testdata => plugin/testdata}/mockTagLib/grails-app/taglib/MyTagLib.groovy (100%)
 rename {testdata => plugin/testdata}/mockTagLib/grails-app/taglib/NonStaticTagLib.groovy (100%)
 rename {testdata => plugin/testdata}/mockTagLib/grails-app/taglib/UnnamedTagLib.groovy (100%)
 rename {testdata => plugin/testdata}/mockTagLib/grails-app/taglib/apackage/PppTagLib.groovy (100%)
 create mode 100644 pluginModules/copyright/README.md
 copy grails-rt/src/org/jetbrains/groovy/grails/rt/AgentUtils.java => pluginModules/copyright/build.gradle (61%)
 rename {copyright/src => pluginModules/copyright/src/main/java}/com/intellij/groovy/grails/copyright/UpdateGspCopyrightsProvider.java (100%)
 rename {copyright/src => pluginModules/copyright/src/main/java}/com/intellij/groovy/grails/copyright/UpdateGspFileCopyright.java (100%)
 rename copyright/resources/intellij.groovy.grails.copyright.xml => pluginModules/copyright/src/main/resources/org.apache.grails.intellij.module.copyright.xml (100%)
 create mode 100644 pluginModules/coverage/README.md
 rename src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrMapAttributeValue.java => pluginModules/coverage/build.gradle (77%)
 rename coverage/src/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt => pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.java (58%)
 rename coverage/resources/intellij.groovy.grails.coverage.xml => pluginModules/coverage/src/main/resources/org.apache.grails.intellij.module.coverage.xml (100%)
 create mode 100644 pluginModules/hibernate/README.md
 copy src/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java => pluginModules/hibernate/build.gradle (76%)
 rename {hibernate/src => pluginModules/hibernate/src/main/java}/com/intellij/groovy/grails/hibernate/GormHibernateFacetProvider.java (100%)
 rename {hibernate/src => pluginModules/hibernate/src/main/java}/com/intellij/groovy/grails/hibernate/GormSessionFactory.java (100%)
 rename {hibernate/src => pluginModules/hibernate/src/main/java}/com/intellij/groovy/grails/hibernate/GormSessionFactoryContributor.java (100%)
 rename {hibernate/src => pluginModules/hibernate/src/main/java}/com/intellij/groovy/grails/hibernate/Grails3HibernateFacetActivity.java (100%)
 rename hibernate/resources/intellij.groovy.grails.hibernate.xml => pluginModules/hibernate/src/main/resources/org.apache.grails.intellij.module.hibernate.xml (100%)
 create mode 100644 pluginModules/i18n/README.md
 rename src/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java => pluginModules/i18n/build.gradle (72%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nGroovyQuickFixHandler.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nInspection.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nPropertyReferenceProvider.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nQuickFixHandler.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nReferenceContributor.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nizeProvider.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GrailsI18nizeQuickFixDialog.java (100%)
 rename {i18n/src => pluginModules/i18n/src/main/java}/com/intellij/groovy/grails/i18n/GspI18nIntention.java (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.ft (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.html (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized GSP Expression.gsp.ft (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized GSP Expression.gsp.html (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.ft (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.html (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized Groovy string.gsp.ft (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/fileTemplates/code/I18nized Groovy string.gsp.html (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/inspectionDescriptions/InvalidI18nProperty.html (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/inspectionDescriptions/metaInformation.json (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/intentionDescriptions/GspI18nIntention/after.gsp.template (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/intentionDescriptions/GspI18nIntention/before.gsp.template (100%)
 rename {i18n => pluginModules/i18n/src/main}/resources/intentionDescriptions/GspI18nIntention/description.html (100%)
 rename i18n/resources/intellij.groovy.grails.i18n.xml => pluginModules/i18n/src/main/resources/org.apache.grails.intellij.module.i18n.xml (100%)
 create mode 100644 pluginModules/langInjection/README.md
 rename grails-rt/src/org/jetbrains/groovy/grails/rt/GrailsRtMarker.java => pluginModules/langInjection/build.gradle (78%)
 rename {langInjection => pluginModules/langInjection/src/main}/resources/META-INF/languageInjections.xml (100%)
 rename langInjection/resources/intellij.groovy.grails.langInjection.xml => pluginModules/langInjection/src/main/resources/org.apache.grails.intellij.module.langInjection.xml (100%)
 create mode 100644 pluginModules/maven/README.md
 rename grails-rt/src/org/jetbrains/groovy/grails/rt/AgentUtils.java => pluginModules/maven/build.gradle (63%)
 rename {maven/src => pluginModules/maven/src/main/java}/com/intellij/groovy/grails/maven/GrailsMavenApplication.java (100%)
 rename {maven/src => pluginModules/maven/src/main/java}/com/intellij/groovy/grails/maven/GrailsMavenApplicationProvider.java (100%)
 rename {maven/src => pluginModules/maven/src/main/java}/com/intellij/groovy/grails/maven/GrailsMavenDebuggerRunner.java (100%)
 create mode 100644 pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.java
 rename {maven/src => pluginModules/maven/src/main/java}/com/intellij/groovy/grails/maven/MavenCommandExecutor.java (100%)
 create mode 100644 pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.java
 rename maven/resources/intellij.groovy.grails.maven.xml => pluginModules/maven/src/main/resources/org.apache.grails.intellij.module.maven.xml (100%)
 create mode 100644 settings.gradle
 delete mode 100644 settings.gradle.kts
 delete mode 100644 src/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/api/IconOwner.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/api/constants.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/artefact/impl/util.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/feature/linkable/transform.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gorm/gormVersion.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/gson/GsonUtils.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/plugins/descriptors.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/plugins/impl.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/api/ep.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/impl/impl.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/impl.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/nodes/util.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/projectView/showHide.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/runner/setup.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/Grails3Application.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/GrailsApplication.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/impl/util.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/structure/util.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/tests/runner/byClass.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt
 delete mode 100644 src/org/jetbrains/plugins/grails/util/version/versions.kt
 delete mode 100644 test/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt
 delete mode 100644 test/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt
 delete mode 100644 testFramework/README.md
 delete mode 100644 testFramework/build.gradle.kts


(grails-intellij-plugin) 19/35: Move libs and compiler tiers, and drop the unused raster icons

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 897b2e068a7fd7bb722370ac04beae25b2da6d26
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 12:12:06 2026 -0400

    Move libs and compiler tiers, and drop the unused raster icons
    
    Step 5 of the restructure. gradle-tooling, grails-rt and testFramework move to
    libs/*; jps-plugin and grails-compiler-patch move to libs/compiler/*, with
    src/ -> src/main/java and resources/ -> src/main/resources throughout. Project
    names carry their subpath (:libs-grails-rt, :libs-compiler-jps-plugin) with
    directories remapped via projectDir.
    
    The invariants all held across the move, checked rather than assumed:
    
      packaged artifact names   byte-identical to the pre-move clean build
      compileServer classpath   both entries still resolve to real jars in lib/
      bytecode levels           52/52/52 and 55, unchanged
      grails-rt agent manifest  Premain-Class still present
      tests                     987 / 0 failures
      RAT                       clean
    
    RAT needed a fourth depth glob: libs/compiler/<project>/build sits one level
    deeper than anything before it, and Ant patterns do not cross '/'. Without
    '*/*/*/build/**' the audit would have started scanning compiler build output.
    
    Also removes compatibilityResources/ -- 16 raster icons that shipped but were
    unreachable. Evidence: the generated GroovyMvcIcons loads only .svg paths, an
    exhaustive sweep of src/gen/resources/pluginModules/libs finds exactly nine
    icons/ references and all nine are .svg, XML refers to icons through the
    generated accessor rather than by path, and there are no IconLoader call sites at
    all, so nothing can resolve an icon by base name. @2x variants are a raster-only
    concept -- SVG scales intrinsically -- and 12 of the 16 files were @2x or _dark
    variants, so they only ever made sense in the pre-SVG scheme. sinceBuild is 262,
    years past the platform's SVG migration, so no supported target needs fallbacks.
    They can be regenerated from the SVGs if that ever changes, and git retains them.
    
    Verified after removal that all nine icon paths GroovyMvcIcons loads still
    resolve inside the packaged jar: PNG count 16 -> 0, SVG count unchanged at 10.
    
    That was the last non-standard resources root, so legacy-layout is now down to
    src/, gen/, resources/ and test/ for the plugin project alone; it can be deleted
    once that project moves.
---
 ...pache.grails.intellij.build.legacy-layout.gradle |   5 ++---
 .../org.apache.grails.intellij.build.rat.gradle     |   6 ++++--
 build.gradle                                        |  20 ++++++++++----------
 compatibilityResources/icons/grails.png             | Bin 312 -> 0 bytes
 compatibilityResources/icons/grails@2x.png          | Bin 588 -> 0 bytes
 compatibilityResources/icons/grails_13.png          | Bin 300 -> 0 bytes
 compatibilityResources/icons/grails_13@2x.png       | Bin 562 -> 0 bytes
 compatibilityResources/icons/grails_app.png         | Bin 346 -> 0 bytes
 compatibilityResources/icons/grails_app@2x.png      | Bin 672 -> 0 bytes
 compatibilityResources/icons/grails_app@2x_dark.png | Bin 671 -> 0 bytes
 compatibilityResources/icons/grails_app_dark.png    | Bin 347 -> 0 bytes
 compatibilityResources/icons/grails_sdk.png         | Bin 281 -> 0 bytes
 compatibilityResources/icons/grails_sdk@2x.png      | Bin 519 -> 0 bytes
 compatibilityResources/icons/grails_test.png        | Bin 399 -> 0 bytes
 compatibilityResources/icons/grails_test@2x.png     | Bin 740 -> 0 bytes
 compatibilityResources/icons/gsp_logo.png           | Bin 216 -> 0 bytes
 compatibilityResources/icons/gsp_logo@2x.png        | Bin 384 -> 0 bytes
 compatibilityResources/icons/taglib.png             | Bin 289 -> 0 bytes
 compatibilityResources/icons/taglib@2x.png          | Bin 501 -> 0 bytes
 .../compiler/grails-compiler-patch}/README.md       |   0
 .../compiler/grails-compiler-patch}/build.gradle    |   1 -
 .../grails/compiler/EmptyGrailsAwarePatcher.java    |   0
 .../grails/compiler/Grails2_0_JUnitPatcher.java     |   0
 .../grails/compiler/GrailsCompilerRtMarker.java     |   0
 .../grails/compiler/GrailsDomainClassPatcher.java   |   0
 .../groovy/grails/compiler/GrailsJUnitPatcher.java  |   0
 .../src/main}/resources/log4j.properties            |   0
 {jps-plugin => libs/compiler/jps-plugin}/README.md  |   0
 .../compiler/jps-plugin}/build.gradle               |   3 +--
 .../jps/grails/builder/GrailsBuilderExtension.java  |   0
 ...ns.jps.incremental.groovy.GroovyBuilderExtension |   0
 {gradle-tooling => libs/gradle-tooling}/README.md   |   0
 .../gradle-tooling}/build.gradle                    |   1 -
 .../grails/gradle/tooling/builder/GrailsModule.java |   0
 .../gradle/tooling/builder/GrailsModuleImpl.java    |   0
 .../builder/GrailsModuleModelBuilderImpl.java       |   0
 ...rains.plugins.gradle.tooling.ModelBuilderService |   0
 {grails-rt => libs/grails-rt}/README.md             |   0
 {grails-rt => libs/grails-rt}/build.gradle          |   1 -
 .../rt/AddAgentJarToClassPathTransformer.java       |   0
 .../java}/org/jetbrains/groovy/grails/rt/Agent.java |   0
 .../org/jetbrains/groovy/grails/rt/AgentUtils.java  |   0
 .../groovy/grails/rt/ForkListenerTransformer.java   |   0
 .../groovy/grails/rt/GrailsIdeaTestListener.java    |   0
 .../jetbrains/groovy/grails/rt/GrailsRtMarker.java  |   0
 .../META-INF/commands/intellij-command-proxy.groovy |   0
 .../src/main}/resources/idea-injected-traits.groovy |   0
 {testFramework => libs/testFramework}/README.md     |   0
 {testFramework => libs/testFramework}/build.gradle  |   1 -
 .../service/execution/TestUnknownSdkResolver.kt     |   0
 .../externalSystem/test/JavaCompileTestUtil.kt      |   0
 .../test/JavaExternalSystemImportingTestCase.kt     |   0
 .../util/ExternalSystemFileTestUtil.kt              |   0
 .../openapi/roots/ui/configuration/SdkTestCase.kt   |   0
 .../testFramework/eelJava/EelTestJdkProvider.kt     |   0
 .../platform/testFramework/eelJava/EelTestUtil.kt   |   0
 .../gradle/importing/GradleImportingTestCase.kt     |   0
 .../gradle/importing/TestGradleBuildScripBuilder.kt |   0
 .../importing/TestGradleSettingsScriptHelper.kt     |   0
 .../plugins/gradle/tooling/GradleJvmResolver.kt     |   0
 .../plugins/gradle/tooling/JavaVersionMatcher.kt    |   0
 .../gradle/tooling/JavaVersionRestriction.kt        |   0
 .../gradle/tooling/TargetJavaVersionWatcher.java    |   0
 .../plugins/gradle/tooling/VersionMatcherRule.java  |   0
 .../tooling/annotation/TargetJavaVersion.java       |   0
 .../gradle/tooling/annotation/TargetVersions.java   |   0
 ...radleBrokenJvmSerialisationVersionRestriction.kt |   0
 .../jvm/GradleToolingApi9VersionRestriction.kt      |   0
 .../plugins/gradle/tooling/util/VersionMatcher.java |   0
 .../plugins/gradle/util/testWrapperUtil.kt          |   0
 .../plugins/groovy/CompoundTestLibrary.java         |   0
 .../plugins/groovy/GroovyProjectDescriptors.java    |   0
 .../groovy/LibraryLightProjectDescriptor.java       |   0
 .../plugins/groovy/LightGroovyTestCase.java         |   0
 .../plugins/groovy/RepositoryTestLibrary.java       |   0
 .../org/jetbrains/plugins/groovy/TestLibrary.java   |   0
 .../groovy/completion/CompletionTestBase.java       |   0
 .../plugins/groovy/grails/Grails14TestCase.java     |   0
 .../plugins/groovy/grails/GrailsTestCase.java       |   0
 .../plugins/groovy/grails/GrailsTestUtil.java       |   0
 .../plugins/groovy/grails/HddGrailsTestCase.java    |   0
 .../grails/TestGrailsApplicationProvider.java       |   0
 .../groovy/lang/GroovyFormatterTestCase.java        |   0
 .../lang/highlighting/GrHighlightingTestBase.java   |   0
 .../jetbrains/plugins/groovy/util/TestUtils.java    |   0
 pluginModules/maven/build.gradle                    |   2 +-
 settings.gradle                                     |  15 ++++++++++-----
 87 files changed, 28 insertions(+), 27 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
index 663f4f0..8f29e3d 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
@@ -18,13 +18,13 @@
  */
 
 // TRANSITIONAL. Declares the pre-restructure source layout inherited from the JetBrains JPS
-// monorepo (src/, gen/, resources/, compatibilityResources/, test/) so that the convention
+// monorepo (src/, gen/, resources/, test/) so that the convention
 // plugins can be introduced before any files move.
 //
 // Every project applies this while the restructure is in flight; it is deleted once all three
 // tiers have been normalized to src/main/java, src/main/resources and src/test/java. Declaring
 // a srcDir that does not exist is harmless, so one shared block covers all projects even
-// though only the plugin project has gen/, compatibilityResources/ and test/.
+// though only the plugin project has gen/ and test/.
 //
 // Do NOT add anything here that is not purely about the old layout.
 
@@ -32,7 +32,6 @@ sourceSets.named('main') { main ->
     main.java.srcDir 'src'
     main.java.srcDir 'gen'
     main.resources.srcDir 'resources'
-    main.resources.srcDir 'compatibilityResources'
 }
 
 sourceSets.named('test') { test ->
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
index 883d127..b862c40 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
@@ -82,11 +82,13 @@ tasks.named('rat') { task ->
             '**/META-INF/MANIFEST.MF',
             // build outputs are generated and never shipped in the source distro. Anchored to
             // explicit depths rather than "**/build/**" so a source package named "build" would
-            // still be audited. Three levels are needed: the root, the tier directories, and the
-            // nested projects under pluginModules/ and libs/.
+            // still be audited. Four levels are needed: the root, the tier directories, the
+            // projects under pluginModules/ and libs/, and the compiler projects one level
+            // deeper again at libs/compiler/<project>/build.
             'build/**',
             '*/build/**',
             '*/*/build/**',
+            '*/*/*/build/**',
     ])
     // Never let a license audit be skipped. upToDateWhen alone is not enough: RatTask is
     // @CacheableTask and org.gradle.caching=true, so a build-cache hit would skip the audit
diff --git a/build.gradle b/build.gradle
index 665ee4c..8ba5ac2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -67,27 +67,27 @@ dependencies {
 
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.assertj:assertj-core:4.0.0-M1'
-    testImplementation project(':testFramework')
+    testImplementation project(':libs-testFramework')
     testCompileOnly project(':pluginModules-i18n')
     testCompileOnly project(':pluginModules-copyright')
 
-    implementation project(':gradle-tooling')
-    implementation project(':grails-rt')
+    implementation project(':libs-gradle-tooling')
+    implementation project(':libs-grails-rt')
 
     // JPS/build
-    runtimeOnly project(':grails-compiler-patch')
-    runtimeOnly project(':jps-plugin')
+    runtimeOnly project(':libs-compiler-grails-compiler-patch')
+    runtimeOnly project(':libs-compiler-jps-plugin')
 
     // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
     jacocoAggregation project(':pluginModules-copyright')
     jacocoAggregation project(':pluginModules-coverage')
-    jacocoAggregation project(':gradle-tooling')
-    jacocoAggregation project(':grails-compiler-patch')
-    jacocoAggregation project(':grails-rt')
+    jacocoAggregation project(':libs-gradle-tooling')
+    jacocoAggregation project(':libs-compiler-grails-compiler-patch')
+    jacocoAggregation project(':libs-grails-rt')
     jacocoAggregation project(':pluginModules-hibernate')
     jacocoAggregation project(':pluginModules-i18n')
-    jacocoAggregation project(':jps-plugin')
+    jacocoAggregation project(':libs-compiler-jps-plugin')
     jacocoAggregation project(':pluginModules-langInjection')
     jacocoAggregation project(':pluginModules-maven')
-    jacocoAggregation project(':testFramework')
+    jacocoAggregation project(':libs-testFramework')
 }
diff --git a/compatibilityResources/icons/grails.png b/compatibilityResources/icons/grails.png
deleted file mode 100644
index 3a6c2c6..0000000
Binary files a/compatibilityResources/icons/grails.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails@2x.png b/compatibilityResources/icons/grails@2x.png
deleted file mode 100644
index c56b209..0000000
Binary files a/compatibilityResources/icons/grails@2x.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_13.png b/compatibilityResources/icons/grails_13.png
deleted file mode 100644
index 3c2e54a..0000000
Binary files a/compatibilityResources/icons/grails_13.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_13@2x.png b/compatibilityResources/icons/grails_13@2x.png
deleted file mode 100644
index 1370386..0000000
Binary files a/compatibilityResources/icons/grails_13@2x.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_app.png b/compatibilityResources/icons/grails_app.png
deleted file mode 100644
index 017bf63..0000000
Binary files a/compatibilityResources/icons/grails_app.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_app@2x.png b/compatibilityResources/icons/grails_app@2x.png
deleted file mode 100644
index 4a1dae5..0000000
Binary files a/compatibilityResources/icons/grails_app@2x.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_app@2x_dark.png b/compatibilityResources/icons/grails_app@2x_dark.png
deleted file mode 100644
index 0bd70b7..0000000
Binary files a/compatibilityResources/icons/grails_app@2x_dark.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_app_dark.png b/compatibilityResources/icons/grails_app_dark.png
deleted file mode 100644
index 8d9c8f2..0000000
Binary files a/compatibilityResources/icons/grails_app_dark.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_sdk.png b/compatibilityResources/icons/grails_sdk.png
deleted file mode 100644
index 9b7b783..0000000
Binary files a/compatibilityResources/icons/grails_sdk.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_sdk@2x.png b/compatibilityResources/icons/grails_sdk@2x.png
deleted file mode 100644
index 731d253..0000000
Binary files a/compatibilityResources/icons/grails_sdk@2x.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_test.png b/compatibilityResources/icons/grails_test.png
deleted file mode 100644
index 830e260..0000000
Binary files a/compatibilityResources/icons/grails_test.png and /dev/null differ
diff --git a/compatibilityResources/icons/grails_test@2x.png b/compatibilityResources/icons/grails_test@2x.png
deleted file mode 100644
index d2ec854..0000000
Binary files a/compatibilityResources/icons/grails_test@2x.png and /dev/null differ
diff --git a/compatibilityResources/icons/gsp_logo.png b/compatibilityResources/icons/gsp_logo.png
deleted file mode 100644
index c38ce30..0000000
Binary files a/compatibilityResources/icons/gsp_logo.png and /dev/null differ
diff --git a/compatibilityResources/icons/gsp_logo@2x.png b/compatibilityResources/icons/gsp_logo@2x.png
deleted file mode 100644
index d30e6c6..0000000
Binary files a/compatibilityResources/icons/gsp_logo@2x.png and /dev/null differ
diff --git a/compatibilityResources/icons/taglib.png b/compatibilityResources/icons/taglib.png
deleted file mode 100644
index 8e5bbb7..0000000
Binary files a/compatibilityResources/icons/taglib.png and /dev/null differ
diff --git a/compatibilityResources/icons/taglib@2x.png b/compatibilityResources/icons/taglib@2x.png
deleted file mode 100644
index 814c2cf..0000000
Binary files a/compatibilityResources/icons/taglib@2x.png and /dev/null differ
diff --git a/grails-compiler-patch/README.md b/libs/compiler/grails-compiler-patch/README.md
similarity index 100%
rename from grails-compiler-patch/README.md
rename to libs/compiler/grails-compiler-patch/README.md
diff --git a/grails-compiler-patch/build.gradle b/libs/compiler/grails-compiler-patch/build.gradle
similarity index 96%
rename from grails-compiler-patch/build.gradle
rename to libs/compiler/grails-compiler-patch/build.gradle
index f5c8e94..ed8cd5c 100644
--- a/grails-compiler-patch/build.gradle
+++ b/libs/compiler/grails-compiler-patch/build.gradle
@@ -16,7 +16,6 @@
 plugins {
     id 'org.apache.grails.intellij.build.compiler'
     id 'org.apache.grails.intellij.build.java-legacy'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 // runs inside user Grails/JPS builds
diff --git a/grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java b/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java
similarity index 100%
rename from grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java
rename to libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java
diff --git a/grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java b/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java
similarity index 100%
rename from grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java
rename to libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java
diff --git a/grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java b/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java
similarity index 100%
rename from grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java
rename to libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java
diff --git a/grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java b/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java
similarity index 100%
rename from grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java
rename to libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java
diff --git a/grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java b/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java
similarity index 100%
rename from grails-compiler-patch/src/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java
rename to libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java
diff --git a/grails-compiler-patch/resources/log4j.properties b/libs/compiler/grails-compiler-patch/src/main/resources/log4j.properties
similarity index 100%
rename from grails-compiler-patch/resources/log4j.properties
rename to libs/compiler/grails-compiler-patch/src/main/resources/log4j.properties
diff --git a/jps-plugin/README.md b/libs/compiler/jps-plugin/README.md
similarity index 100%
rename from jps-plugin/README.md
rename to libs/compiler/jps-plugin/README.md
diff --git a/jps-plugin/build.gradle b/libs/compiler/jps-plugin/build.gradle
similarity index 89%
rename from jps-plugin/build.gradle
rename to libs/compiler/jps-plugin/build.gradle
index 145fc86..f4681e2 100644
--- a/jps-plugin/build.gradle
+++ b/libs/compiler/jps-plugin/build.gradle
@@ -16,7 +16,6 @@
 plugins {
     id 'org.apache.grails.intellij.build.compiler'
     id 'org.apache.grails.intellij.build.java-legacy'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 // runs inside the end user's JPS build process
@@ -29,5 +28,5 @@ dependencies {
         bundledPlugin 'org.intellij.groovy'
     }
 
-    compileOnly project(':grails-compiler-patch')
+    compileOnly project(':libs-compiler-grails-compiler-patch')
 }
diff --git a/jps-plugin/src/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java b/libs/compiler/jps-plugin/src/main/java/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java
similarity index 100%
rename from jps-plugin/src/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java
rename to libs/compiler/jps-plugin/src/main/java/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java
diff --git a/jps-plugin/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension b/libs/compiler/jps-plugin/src/main/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension
similarity index 100%
rename from jps-plugin/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension
rename to libs/compiler/jps-plugin/src/main/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension
diff --git a/gradle-tooling/README.md b/libs/gradle-tooling/README.md
similarity index 100%
rename from gradle-tooling/README.md
rename to libs/gradle-tooling/README.md
diff --git a/gradle-tooling/build.gradle b/libs/gradle-tooling/build.gradle
similarity index 94%
rename from gradle-tooling/build.gradle
rename to libs/gradle-tooling/build.gradle
index 3a99610..2bd87c0 100644
--- a/gradle-tooling/build.gradle
+++ b/libs/gradle-tooling/build.gradle
@@ -16,7 +16,6 @@
 plugins {
     id 'org.apache.grails.intellij.build.intellij-lib'
     id 'org.apache.grails.intellij.build.java-legacy'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 // runs inside user Grails/JPS builds
diff --git a/gradle-tooling/src/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModule.java b/libs/gradle-tooling/src/main/java/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModule.java
similarity index 100%
rename from gradle-tooling/src/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModule.java
rename to libs/gradle-tooling/src/main/java/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModule.java
diff --git a/gradle-tooling/src/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleImpl.java b/libs/gradle-tooling/src/main/java/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleImpl.java
similarity index 100%
rename from gradle-tooling/src/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleImpl.java
rename to libs/gradle-tooling/src/main/java/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleImpl.java
diff --git a/gradle-tooling/src/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleModelBuilderImpl.java b/libs/gradle-tooling/src/main/java/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleModelBuilderImpl.java
similarity index 100%
rename from gradle-tooling/src/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleModelBuilderImpl.java
rename to libs/gradle-tooling/src/main/java/org/jetbrains/plugins/grails/gradle/tooling/builder/GrailsModuleModelBuilderImpl.java
diff --git a/gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService b/libs/gradle-tooling/src/main/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService
similarity index 100%
rename from gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService
rename to libs/gradle-tooling/src/main/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService
diff --git a/grails-rt/README.md b/libs/grails-rt/README.md
similarity index 100%
rename from grails-rt/README.md
rename to libs/grails-rt/README.md
diff --git a/grails-rt/build.gradle b/libs/grails-rt/build.gradle
similarity index 96%
rename from grails-rt/build.gradle
rename to libs/grails-rt/build.gradle
index f237080..d819b73 100644
--- a/grails-rt/build.gradle
+++ b/libs/grails-rt/build.gradle
@@ -16,7 +16,6 @@
 plugins {
     id 'org.apache.grails.intellij.build.lib'
     id 'org.apache.grails.intellij.build.java-legacy'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 // runs inside user Grails builds; located at runtime via GrailsRtMarker, not by name
diff --git a/grails-rt/src/org/jetbrains/groovy/grails/rt/AddAgentJarToClassPathTransformer.java b/libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/AddAgentJarToClassPathTransformer.java
similarity index 100%
rename from grails-rt/src/org/jetbrains/groovy/grails/rt/AddAgentJarToClassPathTransformer.java
rename to libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/AddAgentJarToClassPathTransformer.java
diff --git a/grails-rt/src/org/jetbrains/groovy/grails/rt/Agent.java b/libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/Agent.java
similarity index 100%
rename from grails-rt/src/org/jetbrains/groovy/grails/rt/Agent.java
rename to libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/Agent.java
diff --git a/grails-rt/src/org/jetbrains/groovy/grails/rt/AgentUtils.java b/libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/AgentUtils.java
similarity index 100%
rename from grails-rt/src/org/jetbrains/groovy/grails/rt/AgentUtils.java
rename to libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/AgentUtils.java
diff --git a/grails-rt/src/org/jetbrains/groovy/grails/rt/ForkListenerTransformer.java b/libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/ForkListenerTransformer.java
similarity index 100%
rename from grails-rt/src/org/jetbrains/groovy/grails/rt/ForkListenerTransformer.java
rename to libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/ForkListenerTransformer.java
diff --git a/grails-rt/src/org/jetbrains/groovy/grails/rt/GrailsIdeaTestListener.java b/libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/GrailsIdeaTestListener.java
similarity index 100%
rename from grails-rt/src/org/jetbrains/groovy/grails/rt/GrailsIdeaTestListener.java
rename to libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/GrailsIdeaTestListener.java
diff --git a/grails-rt/src/org/jetbrains/groovy/grails/rt/GrailsRtMarker.java b/libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/GrailsRtMarker.java
similarity index 100%
rename from grails-rt/src/org/jetbrains/groovy/grails/rt/GrailsRtMarker.java
rename to libs/grails-rt/src/main/java/org/jetbrains/groovy/grails/rt/GrailsRtMarker.java
diff --git a/grails-rt/resources/META-INF/commands/intellij-command-proxy.groovy b/libs/grails-rt/src/main/resources/META-INF/commands/intellij-command-proxy.groovy
similarity index 100%
rename from grails-rt/resources/META-INF/commands/intellij-command-proxy.groovy
rename to libs/grails-rt/src/main/resources/META-INF/commands/intellij-command-proxy.groovy
diff --git a/grails-rt/resources/idea-injected-traits.groovy b/libs/grails-rt/src/main/resources/idea-injected-traits.groovy
similarity index 100%
rename from grails-rt/resources/idea-injected-traits.groovy
rename to libs/grails-rt/src/main/resources/idea-injected-traits.groovy
diff --git a/testFramework/README.md b/libs/testFramework/README.md
similarity index 100%
rename from testFramework/README.md
rename to libs/testFramework/README.md
diff --git a/testFramework/build.gradle b/libs/testFramework/build.gradle
similarity index 97%
rename from testFramework/build.gradle
rename to libs/testFramework/build.gradle
index b301a2f..da54ded 100644
--- a/testFramework/build.gradle
+++ b/libs/testFramework/build.gradle
@@ -19,7 +19,6 @@ import org.jetbrains.intellij.platform.gradle.TestFrameworkType
 plugins {
     id 'org.apache.grails.intellij.build.intellij-lib'
     id 'org.apache.grails.intellij.build.kotlin'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/testFramework/src/com/intellij/openapi/externalSystem/service/execution/TestUnknownSdkResolver.kt b/libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/service/execution/TestUnknownSdkResolver.kt
similarity index 100%
rename from testFramework/src/com/intellij/openapi/externalSystem/service/execution/TestUnknownSdkResolver.kt
rename to libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/service/execution/TestUnknownSdkResolver.kt
diff --git a/testFramework/src/com/intellij/openapi/externalSystem/test/JavaCompileTestUtil.kt b/libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/test/JavaCompileTestUtil.kt
similarity index 100%
rename from testFramework/src/com/intellij/openapi/externalSystem/test/JavaCompileTestUtil.kt
rename to libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/test/JavaCompileTestUtil.kt
diff --git a/testFramework/src/com/intellij/openapi/externalSystem/test/JavaExternalSystemImportingTestCase.kt b/libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/test/JavaExternalSystemImportingTestCase.kt
similarity index 100%
rename from testFramework/src/com/intellij/openapi/externalSystem/test/JavaExternalSystemImportingTestCase.kt
rename to libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/test/JavaExternalSystemImportingTestCase.kt
diff --git a/testFramework/src/com/intellij/openapi/externalSystem/util/ExternalSystemFileTestUtil.kt b/libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/util/ExternalSystemFileTestUtil.kt
similarity index 100%
rename from testFramework/src/com/intellij/openapi/externalSystem/util/ExternalSystemFileTestUtil.kt
rename to libs/testFramework/src/main/java/com/intellij/openapi/externalSystem/util/ExternalSystemFileTestUtil.kt
diff --git a/testFramework/src/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt b/libs/testFramework/src/main/java/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt
similarity index 100%
rename from testFramework/src/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt
rename to libs/testFramework/src/main/java/com/intellij/openapi/roots/ui/configuration/SdkTestCase.kt
diff --git a/testFramework/src/com/intellij/platform/testFramework/eelJava/EelTestJdkProvider.kt b/libs/testFramework/src/main/java/com/intellij/platform/testFramework/eelJava/EelTestJdkProvider.kt
similarity index 100%
rename from testFramework/src/com/intellij/platform/testFramework/eelJava/EelTestJdkProvider.kt
rename to libs/testFramework/src/main/java/com/intellij/platform/testFramework/eelJava/EelTestJdkProvider.kt
diff --git a/testFramework/src/com/intellij/platform/testFramework/eelJava/EelTestUtil.kt b/libs/testFramework/src/main/java/com/intellij/platform/testFramework/eelJava/EelTestUtil.kt
similarity index 100%
rename from testFramework/src/com/intellij/platform/testFramework/eelJava/EelTestUtil.kt
rename to libs/testFramework/src/main/java/com/intellij/platform/testFramework/eelJava/EelTestUtil.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/importing/GradleImportingTestCase.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/importing/GradleImportingTestCase.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/importing/GradleImportingTestCase.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/importing/GradleImportingTestCase.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/importing/TestGradleBuildScripBuilder.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/importing/TestGradleBuildScripBuilder.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/importing/TestGradleBuildScripBuilder.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/importing/TestGradleBuildScripBuilder.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/importing/TestGradleSettingsScriptHelper.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/importing/TestGradleSettingsScriptHelper.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/importing/TestGradleSettingsScriptHelper.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/importing/TestGradleSettingsScriptHelper.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/GradleJvmResolver.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/GradleJvmResolver.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/GradleJvmResolver.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/GradleJvmResolver.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/JavaVersionMatcher.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/JavaVersionMatcher.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/JavaVersionMatcher.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/JavaVersionMatcher.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/JavaVersionRestriction.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/JavaVersionRestriction.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/JavaVersionRestriction.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/JavaVersionRestriction.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/TargetJavaVersionWatcher.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/TargetJavaVersionWatcher.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/TargetJavaVersionWatcher.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/TargetJavaVersionWatcher.java
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/VersionMatcherRule.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/VersionMatcherRule.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/VersionMatcherRule.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/VersionMatcherRule.java
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/annotation/TargetJavaVersion.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/annotation/TargetJavaVersion.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/annotation/TargetJavaVersion.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/annotation/TargetJavaVersion.java
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/annotation/TargetVersions.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/annotation/TargetVersions.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/annotation/TargetVersions.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/annotation/TargetVersions.java
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/jvm/GradleBrokenJvmSerialisationVersionRestriction.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/jvm/GradleBrokenJvmSerialisationVersionRestriction.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/jvm/GradleBrokenJvmSerialisationVersionRestriction.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/jvm/GradleBrokenJvmSerialisationVersionRestriction.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/jvm/GradleToolingApi9VersionRestriction.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/jvm/GradleToolingApi9VersionRestriction.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/jvm/GradleToolingApi9VersionRestriction.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/jvm/GradleToolingApi9VersionRestriction.kt
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/tooling/util/VersionMatcher.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/util/VersionMatcher.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/tooling/util/VersionMatcher.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/tooling/util/VersionMatcher.java
diff --git a/testFramework/src/org/jetbrains/plugins/gradle/util/testWrapperUtil.kt b/libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/util/testWrapperUtil.kt
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/gradle/util/testWrapperUtil.kt
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/gradle/util/testWrapperUtil.kt
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/CompoundTestLibrary.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/CompoundTestLibrary.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/CompoundTestLibrary.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/CompoundTestLibrary.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/GroovyProjectDescriptors.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/GroovyProjectDescriptors.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/GroovyProjectDescriptors.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/GroovyProjectDescriptors.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/LibraryLightProjectDescriptor.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/LibraryLightProjectDescriptor.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/LibraryLightProjectDescriptor.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/LibraryLightProjectDescriptor.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/LightGroovyTestCase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/LightGroovyTestCase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/LightGroovyTestCase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/LightGroovyTestCase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/RepositoryTestLibrary.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/RepositoryTestLibrary.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/RepositoryTestLibrary.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/RepositoryTestLibrary.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/TestLibrary.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/TestLibrary.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/TestLibrary.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/TestLibrary.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/completion/CompletionTestBase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/completion/CompletionTestBase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/completion/CompletionTestBase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/completion/CompletionTestBase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/grails/Grails14TestCase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/Grails14TestCase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/grails/Grails14TestCase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/Grails14TestCase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/grails/GrailsTestCase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/GrailsTestCase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/grails/GrailsTestCase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/GrailsTestCase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/grails/GrailsTestUtil.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/GrailsTestUtil.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/grails/GrailsTestUtil.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/GrailsTestUtil.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/grails/HddGrailsTestCase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/HddGrailsTestCase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/grails/HddGrailsTestCase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/HddGrailsTestCase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/grails/TestGrailsApplicationProvider.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/TestGrailsApplicationProvider.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/grails/TestGrailsApplicationProvider.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/grails/TestGrailsApplicationProvider.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/lang/GroovyFormatterTestCase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/lang/GroovyFormatterTestCase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/lang/GroovyFormatterTestCase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/lang/GroovyFormatterTestCase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/lang/highlighting/GrHighlightingTestBase.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/lang/highlighting/GrHighlightingTestBase.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/lang/highlighting/GrHighlightingTestBase.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/lang/highlighting/GrHighlightingTestBase.java
diff --git a/testFramework/src/org/jetbrains/plugins/groovy/util/TestUtils.java b/libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/util/TestUtils.java
similarity index 100%
rename from testFramework/src/org/jetbrains/plugins/groovy/util/TestUtils.java
rename to libs/testFramework/src/main/java/org/jetbrains/plugins/groovy/util/TestUtils.java
diff --git a/pluginModules/maven/build.gradle b/pluginModules/maven/build.gradle
index 79197d1..02d7032 100644
--- a/pluginModules/maven/build.gradle
+++ b/pluginModules/maven/build.gradle
@@ -25,6 +25,6 @@ dependencies {
         bundledPlugin 'com.intellij.java'
     }
 
-    compileOnly project(':grails-rt')
+    compileOnly project(':libs-grails-rt')
     compileOnly project(':')
 }
diff --git a/settings.gradle b/settings.gradle
index 8537e86..e0ee8f7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,8 +26,13 @@ rootProject.name = 'grails-intellij-plugin'
     project(":pluginModules-$name").projectDir = file("pluginModules/$name")
 }
 
-include 'gradle-tooling'
-include 'grails-compiler-patch'
-include 'grails-rt'
-include 'jps-plugin'
-include 'testFramework'
\ No newline at end of file
+
+['gradle-tooling', 'grails-rt', 'testFramework'].each { name ->
+    include "libs-$name"
+    project(":libs-$name").projectDir = file("libs/$name")
+}
+
+['jps-plugin', 'grails-compiler-patch'].each { name ->
+    include "libs-compiler-$name"
+    project(":libs-compiler-$name").projectDir = file("libs/compiler/$name")
+}


(grails-intellij-plugin) 15/35: Flatten the compiler namespace so no tier is a prefix of another

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 2d2d1265b96ea85697155be4bf3c69ab4e1770c8
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:31:58 2026 -0400

    Flatten the compiler namespace so no tier is a prefix of another
    
    org.apache.grails.intellij.lib.compiler nested the compiler tier inside the lib
    namespace, making one project's coordinates a prefix of another's. The tier
    namespaces are now all siblings:
    
      org.apache.grails.intellij.build       build-logic
      org.apache.grails.intellij.plugin      the IntelliJ plugin
      org.apache.grails.intellij.module      the six content modules
      org.apache.grails.intellij.lib         gradle-tooling, grails-rt, testFramework
      org.apache.grails.intellij.compiler    jps-plugin, grails-compiler-patch
    
    Checked mechanically: no tier namespace is a prefix of any other.
    
    The directory layout is unaffected -- compiler plugins stay at libs/compiler/*
    so the source tiers keep mirroring the packaged layout (pluginModules/* ship to
    lib/modules/, everything under libs/ ships to lib/). Only the namespace is a
    sibling rather than a child, so on-disk grouping and coordinates are allowed to
    differ where they answer different questions.
    
    Convention plugin renamed build.lib-compiler -> build.compiler to match, and the
    compileServer.plugin classpath in plugin.xml updated to the new jar names;
    verified every entry still resolves to a real jar in lib/.
---
 ...le => org.apache.grails.intellij.build.compiler.gradle} | 14 ++++++++------
 grails-compiler-patch/build.gradle                         |  2 +-
 jps-plugin/build.gradle                                    |  2 +-
 resources/META-INF/plugin.xml                              |  2 +-
 4 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib-compiler.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
similarity index 76%
rename from build-logic/src/main/groovy/org.apache.grails.intellij.build.lib-compiler.gradle
rename to build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
index 03f8e29..2f72f99 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib-compiler.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
@@ -20,10 +20,12 @@
 // A compiler plugin: code that runs inside the end user's JPS build process rather than in the
 // IDE. These are the jars named by <compileServer.plugin classpath="..."/> in plugin.xml.
 //
-// It lives under the lib tier (libs/compiler/*) rather than beside it, because the source tiers
-// mirror the packaged layout: pluginModules/* are content modules and ship to lib/modules/, while
-// everything in libs/ -- compiler plugins included -- ships to lib/. The compiler subdirectory is
-// a sub-grouping within that tier, not a separate destination.
+// It sits at libs/compiler/* on disk, because the source tiers mirror the packaged layout:
+// pluginModules/* are content modules and ship to lib/modules/, while everything under libs/ --
+// compiler plugins included -- ships to lib/.
+//
+// Its NAMESPACE is a sibling of lib rather than a child of it (org.apache.grails.intellij.compiler,
+// not ...intellij.lib.compiler), so no project's coordinates are a prefix of another's.
 //
 // It is still called out separately because JPS resolves the classpath attribute by file name and
 // silently skips entries it cannot find, so these jar names are part of the plugin's contract and
@@ -36,12 +38,12 @@ plugins {
 
 def shortName = projectDir.name
 
-group = 'org.apache.grails.intellij.lib.compiler'
+group = 'org.apache.grails.intellij.compiler'
 base.archivesName = shortName
 
 // Pinned, not derived. IPGP's default is "${rootProject.name}.${project.path with ':' -> '.'}",
 // which tracks both the root project name and the project's depth in the tree; that is exactly
 // how the descriptor's classpath attribute went stale before.
 tasks.named('composedJar') { task ->
-    task.archiveBaseName = "org.apache.grails.intellij.lib.compiler.$shortName"
+    task.archiveBaseName = "org.apache.grails.intellij.compiler.$shortName"
 }
diff --git a/grails-compiler-patch/build.gradle b/grails-compiler-patch/build.gradle
index 6bf6df5..f5c8e94 100644
--- a/grails-compiler-patch/build.gradle
+++ b/grails-compiler-patch/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.lib-compiler'
+    id 'org.apache.grails.intellij.build.compiler'
     id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
diff --git a/jps-plugin/build.gradle b/jps-plugin/build.gradle
index 061ecac..145fc86 100644
--- a/jps-plugin/build.gradle
+++ b/jps-plugin/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.lib-compiler'
+    id 'org.apache.grails.intellij.build.compiler'
     id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index 7536939..ca6f492 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -323,7 +323,7 @@
          builders with nothing more than a line in idea.log. These names are pinned by the
          org.apache.grails.intellij.build.intellij-lib convention plugin rather than derived from
          rootProject.name, which is what let this attribute go stale previously. -->
-    <compileServer.plugin classpath="org.apache.grails.intellij.lib.compiler.jps-plugin.jar;org.apache.grails.intellij.lib.compiler.grails-compiler-patch.jar"/>
+    <compileServer.plugin classpath="org.apache.grails.intellij.compiler.jps-plugin.jar;org.apache.grails.intellij.compiler.grails-compiler-patch.jar"/>
 
     <multiHostInjector implementation="org.jetbrains.plugins.grails.lang.gsp.GspCssInjector"/>
     <multiHostInjector implementation="org.jetbrains.plugins.grails.references.constraints.GormRegexInjector"/>


(grails-intellij-plugin) 02/35: Document build-from-scratch and wrapper bootstrap in README

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 30fadfb488eb0839ddbebf06632488d49c7e13cc
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sat Jul 18 22:09:52 2026 -0400

    Document build-from-scratch and wrapper bootstrap in README
    
    Expand the README's build section with tooling setup (.sdkmanrc via SDKMAN),
    the gradle-bootstrap step needed when building from a source distribution that
    ships without the wrapper jar, and the new hygiene tasks (coverage, ossIndexAudit),
    mirroring grails-core's from-source guidance.
---
 README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 50 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index c10abb1..97d7ef9 100644
--- a/README.md
+++ b/README.md
@@ -13,23 +13,66 @@ This codebase was originally developed by JetBrains s.r.o. and imported from
 - IntelliJ IDEA Ultimate 2026.2+ (`sinceBuild` 262)
 - Java 25 (the `grails-rt`, `grails-compiler-patch`, and `jps-plugin` modules target Java 8/11)
 
-## Building
+## Building from source
+
+For building, using, and running from a **source distribution**, see the
+[INSTALL](INSTALL) document. The steps below cover a clone of this repository.
+
+### 1. Set up the tooling (JDK + Gradle)
+
+The required JDK and Gradle versions are pinned in [`.sdkmanrc`](.sdkmanrc). The
+recommended way to install them is with [SDKMAN!](https://sdkman.io); from the source
+root run:
+
+```
+sdk env install
+```
+
+This installs the pinned Java (Liberica 25) and Gradle versions and selects them for the
+current shell. You may also install those versions by any other means you prefer.
+
+### 2. Bootstrap the Gradle wrapper (only if `gradlew` is missing)
+
+A clone of this repository already contains the Gradle Wrapper, so you can skip this step
+and use `./gradlew` directly.
+
+A **source distribution** ships *without* the Gradle Wrapper jar (per ASF source-release
+policy), so it must be regenerated once. With a local Gradle installed (from step 1), run:
+
+```
+cd gradle-bootstrap
+gradle bootstrap
+cd ..
+```
+
+This pins the wrapper to the version in `.sdkmanrc` and installs `gradlew` /
+`gradlew.bat` at the source root. Afterwards, use `./gradlew` for every command below.
+
+### 3. Build the plugin
 
 ```
 ./gradlew buildPlugin
 ```
 
-The plugin ZIP is written to `build/distributions/`.
+The plugin ZIP is written to `build/distributions/` and can be installed in IntelliJ IDEA
+via **Settings > Plugins > Install Plugin from Disk**.
 
-Other useful tasks:
+> The first build downloads the IntelliJ Platform (IntelliJ IDEA Ultimate) and the bundled
+> plugins it compiles against — expect network access and several GB of disk usage.
+
+### Other useful tasks
 
 ```
-./gradlew check          # compile and run tests
-./gradlew verifyPlugin   # IntelliJ Plugin Verifier
-./gradlew rat            # Apache RAT license audit
-./gradlew runIde         # launch a sandbox IDE with the plugin
+./gradlew check                  # compile and run tests
+./gradlew verifyPlugin           # IntelliJ Plugin Verifier
+./gradlew rat                    # Apache RAT license audit
+./gradlew runIde                 # launch a sandbox IDE with the plugin
+./gradlew testCodeCoverageReport # aggregate JaCoCo coverage report
+./gradlew ossIndexAudit          # Sonatype OSS Index vulnerability scan (needs SONATYPE_GUIDE_TOKEN)
 ```
 
+To verify the build is reproducible, see [RELEASE.md](RELEASE.md#verifying-a-reproducible-build).
+
 ## Links
 
 - [Apache Grails](https://grails.apache.org/)


(grails-intellij-plugin) 29/35: Convert the last of the main-source Kotlin to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 58f5e0ba0788c163d3577aa13f789bdc7f3747a9
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 15:12:09 2026 -0400

    Convert the last of the main-source Kotlin to Java
    
    Thirteen files: gorm, feature/linkable, the two lang AST contributors, the
    Gradle-aware module builder, and both maven-module files. Everything under
    plugin/src/main and pluginModules is now Java apart from the three files that
    cannot be rewritten.
    
    Four places needed more than transcription:
    
      - namedArguments.kt used interface delegation (`by delegate`), so
        LinkDescriptor now forwards all five NamedArgumentDescriptor members
        explicitly and overrides only createReference, as the delegation did.
      - transform.kt built methods through MemberBuilder.method(name) { ... },
        whose only signature takes a kotlin.jvm.functions.Function1. Decompiling
        it showed the body is just `GrLightMethodBuilder(context.manager, name)`
        followed by the block, with no hidden defaults, so the Java constructs the
        builder directly and needs no Kotlin stdlib.
      - GrailsGradleAwareModuleBuilder.getModuleRoot throws the checked
        ExecutionException, which linkModule cannot declare because
        GrailsModuleBuilder passes it as a java.util.function.Consumer. It wraps
        instead; nothing on that path catches ExecutionException, so only the type
        reaching the log changes. Kotlin's invariantSeparatorsPathString is
        reproduced locally rather than approximated with
        FileUtil.toSystemIndependentName.
      - MavenWorkspaceConfigurator.getMavenProjectsWithModules returns a
        kotlin.sequences.Sequence, which is not Iterable, so the loop drives its
        iterator. It resolves from the platform classpath, so the maven module
        compiles with no Kotlin plugin at all -- verified by removing it.
    
    GormVersion's two lookup functions become static factories on the enum rather
    than a separate holder, since the enum and the functions shared one Kotlin file
    and there were only two call sites.
    
    Verified: 186 classes / 987 tests / 0 failures / 0 errors, matching baseline;
    rat clean; all 220 descriptor-referenced classes resolve; the built ZIP carries
    all six content-module jars and every compileServer.plugin entry resolves in
    lib/.
---
 .../config/GrailsGradleAwareModuleBuilder.java     | 158 ++++++++++++++++++
 .../config/GrailsGradleAwareModuleBuilder.kt       | 112 -------------
 .../plugins/grails/feature/linkable/Linkable.java  |  28 ++--
 .../linkable/LinkableNamedArgumentsProvider.java   | 134 +++++++++++++++
 .../linkable/LinkableTransformationSupport.java    |  56 +++++++
 .../grails/feature/linkable/namedArguments.kt      |  85 ----------
 .../plugins/grails/feature/linkable/transform.kt   |  52 ------
 .../jetbrains/plugins/grails/gorm/GormVersion.java |  76 +++++++++
 .../jetbrains/plugins/grails/gorm/gormVersion.kt   |  87 ----------
 .../grails/lang/DirtyCheckableContributor.java     |  73 ++++++++
 .../grails/lang/DirtyCheckableContributor.kt       |  67 --------
 .../plugins/grails/lang/GormTraitContributor.java  | 184 +++++++++++++++++++++
 .../plugins/grails/lang/GormTraitContributor.kt    | 132 ---------------
 .../domain/GormAstTransformationContributor.java   |   3 +-
 pluginModules/maven/build.gradle                   |   1 -
 .../maven/GrailsMavenWorkspaceConfigurator.java    | 103 ++++++++++++
 .../maven/GrailsMavenWorkspaceConfigurator.kt      |  90 ----------
 .../groovy/grails/maven/MavenNodeProvider.java     |  44 +++++
 .../groovy/grails/maven/MavenNodeProvider.kt       |  36 ----
 19 files changed, 845 insertions(+), 676 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.java
new file mode 100644
index 0000000..3271f22
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.config;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.ide.util.projectWizard.ModuleBuilder;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder;
+import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
+import com.intellij.openapi.externalSystem.model.project.ProjectData;
+import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
+import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.gradle.service.project.wizard.AbstractGradleModuleBuilder;
+import org.jetbrains.plugins.gradle.settings.DistributionType;
+import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
+import org.jetbrains.plugins.gradle.util.GradleConstants;
+import org.jetbrains.plugins.grails.GrailsBundle;
+import org.jetbrains.plugins.grails.runner.GrailsConsole;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public abstract class GrailsGradleAwareModuleBuilder extends ModuleBuilder {
+
+  protected static final Logger LOG = Logger.getInstance(GrailsGradleAwareModuleBuilder.class);
+
+  private @Nullable ProjectData myParentProject;
+  private boolean myIsCreatingNewProject;
+
+  public @Nullable ProjectData getParentProject() {
+    return myParentProject;
+  }
+
+  public void setParentProject(@Nullable ProjectData parentProject) {
+    myParentProject = parentProject;
+  }
+
+  public boolean isCreatingNewProject() {
+    return myIsCreatingNewProject;
+  }
+
+  public void setCreatingNewProject(boolean creatingNewProject) {
+    myIsCreatingNewProject = creatingNewProject;
+  }
+
+  @Override
+  protected void setupModule(@NotNull Module module) throws ConfigurationException {
+    super.setupModule(module);
+    if (myIsCreatingNewProject) {
+      module.getProject().putUserData(ExternalSystemDataKeys.NEWLY_CREATED_PROJECT, Boolean.TRUE);
+    }
+  }
+
+  protected void linkModule(@NotNull Module module) {
+    Project project = module.getProject();
+    VirtualFile root;
+    try {
+      root = getModuleRoot(module);
+    }
+    catch (ExecutionException e) {
+      // linkModule is handed to GrailsModuleBuilder as a java.util.function.Consumer, so the
+      // checked exception cannot be declared here. Nothing on that path catches ExecutionException,
+      // so wrapping changes only the type that reaches the log.
+      throw new RuntimeException(e);
+    }
+
+    Path rootProjectPath;
+    String linkedPath = myParentProject == null ? null : myParentProject.getLinkedExternalProjectPath();
+    if (linkedPath != null) {
+      rootProjectPath = Paths.get(linkedPath);
+    }
+    else if (myIsCreatingNewProject) {
+      rootProjectPath = Paths.get(module.getProject().getBasePath());
+    }
+    else {
+      rootProjectPath = root.toNioPath();
+    }
+
+    boolean fresh = myIsCreatingNewProject || myParentProject == null;
+    ApplicationManager.getApplication().runWriteAction(() -> {
+      try {
+        AbstractGradleModuleBuilder.setupGradleSettingsFile(rootProjectPath, root, project.getName(), module.getName(), fresh, false);
+        LOG.debug("'settings.gradle' file set up for module: " + module);
+      }
+      catch (ConfigurationException e) {
+        LOG.debug(e);
+        GrailsConsole.NOTIFICATION_GROUP
+          .createNotification(GrailsBundle.message("failed.to.create.settings.gradle.notification.title"),
+                              e.getMessage() == null ? "" : e.getMessage(),
+                              NotificationType.WARNING)
+          .notify(project);
+      }
+    });
+
+    linkGradleProject(fresh, rootProjectPath, project);
+  }
+
+  private void linkGradleProject(boolean fresh, @NotNull Path rootProjectPath, @NotNull Project project) {
+    if (fresh) {
+      GradleProjectSettings gradleSettings = new GradleProjectSettings();
+      gradleSettings.setDistributionType(DistributionType.DEFAULT_WRAPPED);
+      gradleSettings.setExternalProjectPath(invariantSeparatorsPathString(rootProjectPath));
+      gradleSettings.setGradleJvm("#USE_PROJECT_JDK");
+
+      ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).linkProject(gradleSettings);
+      LOG.debug("Project '" + project + "' linked with " + gradleSettings);
+    }
+
+    if (!myIsCreatingNewProject) {
+      FileDocumentManager.getInstance().saveAllDocuments();
+      LOG.debug("Starting refreshing the project: " + rootProjectPath);
+    }
+
+    ExternalSystemUtil.refreshProject(invariantSeparatorsPathString(rootProjectPath),
+                                     new ImportSpecBuilder(project, GradleConstants.SYSTEM_ID));
+  }
+
+  /**
+   * Gradle wants forward slashes in linked project paths regardless of platform. Stands in for
+   * Kotlin's {@code Path.invariantSeparatorsPathString}.
+   */
+  private static @NotNull String invariantSeparatorsPathString(@NotNull Path path) {
+    String separator = path.getFileSystem().getSeparator();
+    String asString = path.toString();
+    return "/".equals(separator) ? asString : asString.replace(separator, "/");
+  }
+
+  protected @NotNull VirtualFile getModuleRoot(@NotNull Module module) throws ExecutionException {
+    VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots();
+    if (roots.length == 1) {
+      return roots[0];
+    }
+    throw new ExecutionException(GrailsBundle.message("dialog.message.no.module.root"));
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt
deleted file mode 100644
index dcc5fcf..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.config
-
-import com.intellij.execution.ExecutionException
-import com.intellij.ide.util.projectWizard.ModuleBuilder
-import com.intellij.notification.NotificationType
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.diagnostic.logger
-import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
-import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys
-import com.intellij.openapi.externalSystem.model.project.ProjectData
-import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
-import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
-import com.intellij.openapi.fileEditor.FileDocumentManager
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.options.ConfigurationException
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.roots.ModuleRootManager
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.gradle.service.project.wizard.AbstractGradleModuleBuilder.setupGradleSettingsFile
-import org.jetbrains.plugins.gradle.settings.DistributionType.DEFAULT_WRAPPED
-import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
-import org.jetbrains.plugins.gradle.util.GradleConstants
-import org.jetbrains.plugins.grails.GrailsBundle
-import org.jetbrains.plugins.grails.runner.GrailsConsole
-import java.nio.file.Path
-import java.nio.file.Paths
-import kotlin.io.path.invariantSeparatorsPathString
-
-abstract class GrailsGradleAwareModuleBuilder : ModuleBuilder() {
-  companion object {
-    val LOG = logger<GrailsGradleAwareModuleBuilder>()
-  }
-
-  var parentProject: ProjectData? = null
-  var isCreatingNewProject: Boolean = false
-
-  @Throws(ConfigurationException::class)
-  override fun setupModule(module: Module) {
-    super.setupModule(module)
-    if (isCreatingNewProject) {
-      module.project.putUserData(ExternalSystemDataKeys.NEWLY_CREATED_PROJECT, java.lang.Boolean.TRUE)
-    }
-  }
-
-  protected fun linkModule(module: Module) {
-    val project = module.project
-    val root = getModuleRoot(module)
-
-    val rootProjectPath: Path = parentProject?.linkedExternalProjectPath?.let { Paths.get(it) }
-                          ?: (if (isCreatingNewProject) Paths.get(module.project.basePath!!) else root.toNioPath())
-
-    val fresh = isCreatingNewProject || parentProject == null
-    ApplicationManager.getApplication().runWriteAction {
-      try {
-        setupGradleSettingsFile(rootProjectPath, root, project.name, module.name, fresh, false)
-        LOG.debug("'settings.gradle' file set up for module: $module")
-      }
-      catch (e: ConfigurationException) {
-        LOG.debug(e)
-        GrailsConsole.NOTIFICATION_GROUP
-          .createNotification(GrailsBundle.message("failed.to.create.settings.gradle.notification.title"), e.message ?: "", NotificationType.WARNING)
-          .notify(project)
-      }
-    }
-
-    linkGradleProject(fresh, rootProjectPath, project)
-  }
-
-  private fun linkGradleProject(fresh: Boolean, rootProjectPath: Path, project: Project) {
-    if (fresh) {
-      val gradleSettings = GradleProjectSettings()
-      gradleSettings.distributionType = DEFAULT_WRAPPED
-      gradleSettings.externalProjectPath = rootProjectPath.invariantSeparatorsPathString
-      gradleSettings.gradleJvm = "#USE_PROJECT_JDK"
-
-      ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).linkProject(gradleSettings)
-      LOG.debug("Project '$project' linked with $gradleSettings")
-    }
-
-    if (!isCreatingNewProject) {
-      FileDocumentManager.getInstance().saveAllDocuments()
-      LOG.debug("Starting refreshing the project: $rootProjectPath")
-    }
-
-    ExternalSystemUtil.refreshProject(rootProjectPath.invariantSeparatorsPathString, ImportSpecBuilder(project, GradleConstants.SYSTEM_ID))
-  }
-
-  @Throws(ExecutionException::class)
-  protected fun getModuleRoot(module: Module): VirtualFile {
-    val roots = ModuleRootManager.getInstance(module).contentRoots
-    if (roots.size == 1) {
-      return roots[0]
-    }
-    throw ExecutionException(GrailsBundle.message("dialog.message.no.module.root"))
-  }
-}
diff --git a/pluginModules/maven/build.gradle b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/Linkable.java
similarity index 53%
copy from pluginModules/maven/build.gradle
copy to plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/Linkable.java
index 7ab37e9..752c74b 100644
--- a/pluginModules/maven/build.gradle
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/Linkable.java
@@ -13,18 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-plugins {
-    id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.kotlin'
-}
+package org.jetbrains.plugins.grails.feature.linkable;
+
+import com.intellij.openapi.util.Key;
+
+/**
+ * Shared between the transformation that synthesises {@code link(Map)} and the provider that
+ * supplies its named arguments: the marker lets the provider recognise the synthetic method.
+ */
+final class Linkable {
+
+  private Linkable() {
+  }
+
+  static final String LINK_FQN = "grails.rest.Link";
 
-dependencies {
-    intellijPlatform {
-        bundledPlugin 'org.jetbrains.idea.maven'
-        // java debugger classes moved into the java plugin modules in 2026.2
-        bundledPlugin 'com.intellij.java'
-    }
+  static final Key<Object> LINK_METHOD_KEY = Key.create("grails.linkable.method.key");
 
-    compileOnly project(':libs-grails-rt')
-    compileOnly project(':plugin')
+  static final Object LINK_METHOD_MARKER = new Object();
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableNamedArgumentsProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableNamedArgumentsProvider.java
new file mode 100644
index 0000000..5219ed5
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableNamedArgumentsProvider.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.feature.linkable;
+
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.patterns.PsiMethodPattern;
+import com.intellij.psi.CommonClassNames;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiPolyVariantReference;
+import com.intellij.psi.PsiPolyVariantReferenceBase;
+import com.intellij.psi.PsiSubstitutor;
+import com.intellij.psi.PsiType;
+import com.intellij.psi.ResolveResult;
+import com.intellij.psi.infos.CandidateInfo;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider;
+import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor;
+import org.jetbrains.plugins.groovy.extensions.impl.StringTypeCondition;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
+import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
+
+import java.util.Map;
+
+public final class LinkableNamedArgumentsProvider extends GroovyNamedArgumentProvider {
+
+  private static final NamedArgumentDescriptor STRING_DESCRIPTOR = new LinkDescriptor(NamedArgumentDescriptor.TYPE_STRING);
+  private static final NamedArgumentDescriptor BOOL_DESCRIPTOR = new LinkDescriptor(NamedArgumentDescriptor.TYPE_BOOL);
+
+  private static final Map<String, NamedArgumentDescriptor> LINK_NAMED_ARGS = Map.of(
+    "rel", STRING_DESCRIPTOR,
+    "href", STRING_DESCRIPTOR,
+    "hreflang", new LinkDescriptor(new StringTypeCondition("java.util.Locale")),
+    "contentType", STRING_DESCRIPTOR,
+    "title", STRING_DESCRIPTOR,
+    "deprecated", BOOL_DESCRIPTOR,
+    "templated", BOOL_DESCRIPTOR
+  );
+
+  // grails.rest.Link#createLink(java.util.Map)
+  private static final PsiMethodPattern LINK_METHOD_PATTERN = new PsiMethodPattern()
+    .withName("createLink")
+    .definedInClass(Linkable.LINK_FQN)
+    .withParameters(CommonClassNames.JAVA_UTIL_MAP);
+
+  @Override
+  public void getNamedArguments(@NotNull GrCall call,
+                               @NotNull GroovyResolveResult resolveResult,
+                               @Nullable String argumentName,
+                               boolean forCompletion,
+                               @NotNull Map<String, NamedArgumentDescriptor> result) {
+    PsiElement resolved = resolveResult.getElement();
+    boolean isSynthetic = resolved != null && resolved.getUserData(Linkable.LINK_METHOD_KEY) == Linkable.LINK_METHOD_MARKER;
+    if (isSynthetic || LINK_METHOD_PATTERN.accepts(resolved)) {
+      result.putAll(LINK_NAMED_ARGS);
+    }
+  }
+
+  /**
+   * Delegating wrapper that only replaces reference creation, so each label resolves to the matching
+   * field on {@code grails.rest.Link}. Kotlin expressed this with interface delegation.
+   */
+  private static final class LinkDescriptor implements NamedArgumentDescriptor {
+
+    private final NamedArgumentDescriptor myDelegate;
+
+    private LinkDescriptor(NamedArgumentDescriptor delegate) {
+      myDelegate = delegate;
+    }
+
+    @Override
+    public Priority getPriority() {
+      return myDelegate.getPriority();
+    }
+
+    @Override
+    public boolean checkType(@NotNull PsiType type, @NotNull GroovyPsiElement context) {
+      return myDelegate.checkType(type, context);
+    }
+
+    @Override
+    public @Nullable PsiElement getNavigationElement() {
+      return myDelegate.getNavigationElement();
+    }
+
+    @Override
+    public LookupElement customizeLookupElement(@NotNull LookupElementBuilder builder) {
+      return myDelegate.customizeLookupElement(builder);
+    }
+
+    @Override
+    public @NotNull PsiPolyVariantReference createReference(@NotNull GrArgumentLabel label) {
+      return new LinkReference(label);
+    }
+  }
+
+  private static final class LinkReference extends PsiPolyVariantReferenceBase<GrArgumentLabel> {
+
+    private LinkReference(@NotNull GrArgumentLabel label) {
+      super(label);
+    }
+
+    @Override
+    public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
+      String name = getElement().getName();
+      if (name == null) return ResolveResult.EMPTY_ARRAY;
+      PsiClass clazz = JavaPsiFacade.getInstance(getElement().getProject())
+        .findClass(Linkable.LINK_FQN, getElement().getResolveScope());
+      if (clazz == null) return ResolveResult.EMPTY_ARRAY;
+      PsiField field = clazz.findFieldByName(name, false);
+      if (field == null) return ResolveResult.EMPTY_ARRAY;
+      return new ResolveResult[]{new CandidateInfo(field, PsiSubstitutor.EMPTY)};
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableTransformationSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableTransformationSupport.java
new file mode 100644
index 0000000..58af73d
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/LinkableTransformationSupport.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.feature.linkable;
+
+import com.intellij.psi.CommonClassNames;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiTypes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
+import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport;
+import org.jetbrains.plugins.groovy.transformations.TransformationContext;
+
+public final class LinkableTransformationSupport implements AstTransformationSupport {
+
+  @Override
+  public void applyTransformation(@NotNull TransformationContext context) {
+    addLinkMethods(context, "grails.rest.Linkable");
+    addLinkMethods(context, "grails.rest.Resource");
+  }
+
+  private static void addLinkMethods(@NotNull TransformationContext context, @NotNull String fqn) {
+    PsiAnnotation annotation = context.getAnnotation(fqn);
+    if (annotation == null) return;
+
+    // public void link(Map)
+    GrLightMethodBuilder linkMap = new GrLightMethodBuilder(context.getManager(), "link");
+    linkMap.addModifier(GrModifierFlags.PUBLIC_MASK);
+    linkMap.setReturnType(PsiTypes.voidType());
+    linkMap.addParameter("link", CommonClassNames.JAVA_UTIL_MAP);
+    linkMap.setNavigationElement(annotation);
+    linkMap.putUserData(Linkable.LINK_METHOD_KEY, Linkable.LINK_METHOD_MARKER);
+    context.addMethod(linkMap);
+
+    // public void link(Link)
+    GrLightMethodBuilder linkTyped = new GrLightMethodBuilder(context.getManager(), "link");
+    linkTyped.addModifier(GrModifierFlags.PUBLIC_MASK);
+    linkTyped.setReturnType(PsiTypes.voidType());
+    linkTyped.addParameter("link", Linkable.LINK_FQN);
+    linkTyped.setNavigationElement(annotation);
+    context.addMethod(linkTyped);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt
deleted file mode 100644
index d5bde7f..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.feature.linkable
-
-import com.intellij.openapi.util.Key
-import com.intellij.patterns.PsiMethodPattern
-import com.intellij.psi.CommonClassNames
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiPolyVariantReferenceBase
-import com.intellij.psi.PsiSubstitutor
-import com.intellij.psi.ResolveResult
-import com.intellij.psi.infos.CandidateInfo
-import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider
-import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor
-import org.jetbrains.plugins.groovy.extensions.impl.StringTypeCondition
-import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall
-
-internal const val LINK_FQN = "grails.rest.Link"
-
-internal val LINK_METHOD_KEY = Key.create<Any>("grails.linkable.method.key")
-internal val LINK_METHOD_MARKER = Object()
-
-private val STRING_DESCRIPTOR = LinkDescriptor(NamedArgumentDescriptor.TYPE_STRING)
-private val BOOL_DESCRIPTOR = LinkDescriptor(NamedArgumentDescriptor.TYPE_BOOL)
-private val LINK_NAMED_ARGS = mapOf(
-    "rel" to STRING_DESCRIPTOR,
-    "href" to STRING_DESCRIPTOR,
-    "hreflang" to LinkDescriptor(StringTypeCondition("java.util.Locale")),
-    "contentType" to STRING_DESCRIPTOR,
-    "title" to STRING_DESCRIPTOR,
-    "deprecated" to BOOL_DESCRIPTOR,
-    "templated" to BOOL_DESCRIPTOR
-)
-
-// grails.rest.Link#createLink(java.util.Map)
-private val LINK_METHOD_PATTERN = PsiMethodPattern().withName("createLink").definedInClass(
-    LINK_FQN
-).withParameters(CommonClassNames.JAVA_UTIL_MAP)
-
-private class LinkDescriptor(delegate: NamedArgumentDescriptor) : NamedArgumentDescriptor by delegate {
-
-  override fun createReference(label: GrArgumentLabel) = LinkReference(label)
-}
-
-private class LinkReference(label: GrArgumentLabel) : PsiPolyVariantReferenceBase<GrArgumentLabel>(label) {
-
-  override fun multiResolve(incompleteCode: Boolean): Array<out ResolveResult> {
-    val name = element.name ?: return emptyArray()
-    val clazz = JavaPsiFacade.getInstance(element.project).findClass(LINK_FQN, element.resolveScope) ?: return emptyArray()
-    return clazz.findFieldByName(name, false)?.let {
-      arrayOf(CandidateInfo(it, PsiSubstitutor.EMPTY))
-    } ?: emptyArray()
-  }
-}
-
-class LinkableNamedArgumentsProvider : GroovyNamedArgumentProvider() {
-
-  override fun getNamedArguments(call: GrCall,
-                                 resolveResult: GroovyResolveResult,
-                                 argumentName: String?,
-                                 forCompletion: Boolean,
-                                 result: MutableMap<String, NamedArgumentDescriptor>) {
-    val resolved = resolveResult.element
-    if (resolved?.getUserData(LINK_METHOD_KEY) == LINK_METHOD_MARKER || LINK_METHOD_PATTERN.accepts(resolved)) {
-      result += LINK_NAMED_ARGS
-    }
-  }
-}
-
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/transform.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/transform.kt
deleted file mode 100644
index 2770063..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/transform.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.feature.linkable
-
-import com.intellij.psi.CommonClassNames
-import com.intellij.psi.PsiTypes
-import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags
-import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport
-import org.jetbrains.plugins.groovy.transformations.TransformationContext
-
-class LinkableTransformationSupport : AstTransformationSupport {
-
-  override fun applyTransformation(context: TransformationContext) {
-    addLinkMethods(context, "grails.rest.Linkable")
-    addLinkMethods(context, "grails.rest.Resource")
-  }
-}
-
-private fun addLinkMethods(context: TransformationContext, fqn: String) {
-  context.getAnnotation(fqn)?.let {
-    // public void link(Map)
-    context.addMethod(context.memberBuilder.method("link") {
-      addModifier(GrModifierFlags.PUBLIC_MASK)
-      returnType = PsiTypes.voidType()
-      addParameter("link", CommonClassNames.JAVA_UTIL_MAP)
-      navigationElement = it
-      putUserData(LINK_METHOD_KEY, LINK_METHOD_MARKER)
-    })
-
-    // public void link(Link)
-    context.addMethod(context.memberBuilder.method("link") {
-      addModifier(GrModifierFlags.PUBLIC_MASK)
-      returnType = PsiTypes.voidType()
-      addParameter("link", LINK_FQN)
-      navigationElement = it
-    })
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/GormVersion.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/GormVersion.java
new file mode 100644
index 0000000..86c74a9
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/GormVersion.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gorm;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.references.domain.DomainDescriptor;
+
+public enum GormVersion {
+  /**
+   * GormApi compile time injection
+   */
+  BELOW_4,
+  /**
+   * GormEntity trait injection
+   */
+  IS_4,
+  /**
+   * GormEntity trait injection with custom traits for different DBs (Mongo, Cassandra, etc)
+   */
+  IS_5,
+  /**
+   * Custom traits for different DBs which are checked for availability in current context
+   */
+  IS_6;
+
+  public static @Nullable GormVersion forElement(@Nullable PsiElement element) {
+    return element == null ? null : forModule(ModuleUtilCore.findModuleForPsiElement(element));
+  }
+
+  public static @Nullable GormVersion forModule(@Nullable Module module) {
+    if (module == null) return null;
+    return CachedValuesManager.getManager(module.getProject()).getCachedValue(
+      module,
+      () -> Result.create(detect(module), ProjectRootManager.getInstance(module.getProject())));
+  }
+
+  private static @Nullable GormVersion detect(Module module) {
+    JavaPsiFacade facade = JavaPsiFacade.getInstance(module.getProject());
+    GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module);
+
+    // present since GORM 6
+    if (facade.findClass(GormClassNames.QUERY_CREATOR, scope) != null) return IS_6;
+
+    // present since GORM 5
+    if (facade.findClass(GormClassNames.ENTITY_ANNO, scope) != null) return IS_5;
+
+    // present since GORM 4
+    if (facade.findClass(GormClassNames.ENTITY_TRAIT, scope) != null) return IS_4;
+
+    // GormInstanceApi class is present in all versions, so we check it last
+    if (facade.findClass(DomainDescriptor.GORM_INSTANCE_API_CLASS, scope) != null) return BELOW_4;
+
+    return null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/gormVersion.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/gormVersion.kt
deleted file mode 100644
index d856b39..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/gormVersion.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gorm
-
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.module.ModuleUtilCore
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiElement
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import org.jetbrains.plugins.grails.gorm.GormClassNames.ENTITY_ANNO
-import org.jetbrains.plugins.grails.gorm.GormClassNames.ENTITY_TRAIT
-import org.jetbrains.plugins.grails.gorm.GormClassNames.QUERY_CREATOR
-import org.jetbrains.plugins.grails.references.domain.DomainDescriptor.GORM_INSTANCE_API_CLASS
-
-enum class GormVersion {
-  /**
-   * GormApi compile time injection
-   */
-  BELOW_4,
-  /**
-   * GormEntity trait injection
-   */
-  IS_4,
-  /**
-   * GormEntity trait injection with custom traits for different DBs (Mongo, Cassandra, etc)
-   */
-  IS_5,
-  /**
-   * Custom traits for different DBs which are checked for availability in current context
-   */
-  IS_6
-}
-
-fun getGormVersion(element: PsiElement?): GormVersion? = element?.let {
-  getGormVersion(ModuleUtilCore.findModuleForPsiElement(it))
-}
-
-fun getGormVersion(module: Module?): GormVersion? = module?.let {
-  CachedValuesManager.getManager(it.project).getCachedValue(it) {
-    Result.create(doGetGormVersion(it), ProjectRootManager.getInstance(it.project))
-  }
-}
-
-private fun doGetGormVersion(module: Module): GormVersion? {
-  val project = module.project
-  val facade = JavaPsiFacade.getInstance(project)
-  val scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module)
-
-  // present since GORM 6
-  facade.findClass(QUERY_CREATOR, scope)?.let {
-    return GormVersion.IS_6
-  }
-
-  // present since GORM 5
-  facade.findClass(ENTITY_ANNO, scope)?.let {
-    return GormVersion.IS_5
-  }
-
-  // present since GORM 4
-  facade.findClass(ENTITY_TRAIT, scope)?.let {
-    return GormVersion.IS_4
-  }
-
-  // GormInstanceApi class is present in all versions, so we check it last
-  facade.findClass(GORM_INSTANCE_API_CLASS, scope)?.let {
-    return GormVersion.BELOW_4
-  }
-
-  return null
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.java
new file mode 100644
index 0000000..2ab7afc
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.lang;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiMethod;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrMethodWrapper;
+import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
+import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport;
+import org.jetbrains.plugins.groovy.transformations.TransformationContext;
+
+public final class DirtyCheckableContributor implements AstTransformationSupport {
+
+  public static final String DIRTY_CHECK_FQN = "grails.gorm.dirty.checking.DirtyCheck";
+  public static final String DIRTY_CHECKABLE_FQN = "org.grails.datastore.mapping.dirty.checking.DirtyCheckable";
+  public static final String ORIGIN_INFO = "via @DirtyCheck";
+
+  /**
+   * DirtyCheckable is added in compile time when:
+   *
+   * <ol>
+   *   <li>class annotated explicitly -&gt; org.codehaus.groovy.grails.compiler.gorm.DirtyCheckTransformation</li>
+   *   <li>class is a GORM domain</li>
+   * </ol>
+   */
+  private static boolean check(@NotNull GrTypeDefinition clazz) {
+    return AnnotationUtil.isAnnotated(clazz, DIRTY_CHECK_FQN, 0)
+           || GrailsUtils.calculateArtifactType(clazz) == GrailsArtifact.DOMAIN;
+  }
+
+  /**
+   * In GORM 5, DirtyCheckable was introduced as a trait.
+   * If it is a trait, then we do not collect its methods here,
+   * because they will be collected in
+   * {@link org.jetbrains.plugins.groovy.transformations.impl.TraitTransformationSupport TraitTransformationSupport}.
+   * If it is a simple interface, then we are dealing with pre GORM 5, where all its methods are added one by one to the target class.
+   */
+  @Override
+  public void applyTransformation(@NotNull TransformationContext context) {
+    GrTypeDefinition clazz = context.getCodeClass();
+    if (!check(clazz)) return;
+    context.addInterface(DIRTY_CHECKABLE_FQN);
+    PsiClass dirtyCheckable = JavaPsiFacade.getInstance(clazz.getProject())
+      .findClass(DIRTY_CHECKABLE_FQN, clazz.getResolveScope());
+    if (dirtyCheckable == null || !dirtyCheckable.isInterface() || GrTraitUtil.isTrait(dirtyCheckable)) return;
+    for (PsiMethod interfaceMethod : dirtyCheckable.getMethods()) {
+      GrMethodWrapper wrapper = GrMethodWrapper.wrap(interfaceMethod);
+      wrapper.getModifierList().removeModifier(GrModifierFlags.ABSTRACT_MASK);
+      wrapper.setOriginInfo(ORIGIN_INFO);
+      context.addMethod(wrapper);
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt
deleted file mode 100644
index deacfad..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.lang
-
-import com.intellij.codeInsight.AnnotationUtil
-import com.intellij.psi.JavaPsiFacade
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.grails.util.GrailsUtils
-import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrMethodWrapper
-import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil
-import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport
-import org.jetbrains.plugins.groovy.transformations.TransformationContext
-
-class DirtyCheckableContributor : AstTransformationSupport {
-
-  companion object {
-    const val DIRTY_CHECK_FQN: String = "grails.gorm.dirty.checking.DirtyCheck"
-    const val DIRTY_CHECKABLE_FQN: String = "org.grails.datastore.mapping.dirty.checking.DirtyCheckable"
-    const val ORIGIN_INFO: String = "via @DirtyCheck"
-  }
-
-  /**
-   * DirtyCheckable is added in compile time when:
-   *
-   * 1. class annotated explicitly -> org.codehaus.groovy.grails.compiler.gorm.DirtyCheckTransformation
-   * 2. class is a GORM domain
-   */
-  private fun check(clazz: GrTypeDefinition) =
-    AnnotationUtil.isAnnotated(clazz, DIRTY_CHECK_FQN, 0) || GrailsUtils.calculateArtifactType(clazz) === GrailsArtifact.DOMAIN
-
-  /**
-   * In GORM 5, DirtyCheckable was introduced as a trait.
-   * If it is a trait, then we do not collect its methods here,
-   * because they will be collected in
-   * [TraitTransformationSupport][org.jetbrains.plugins.groovy.transformations.impl.TraitTransformationSupport].
-   * If it is a simple interface, then we are dealing with pre GORM 5, where all its methods are added one by one to the target class.
-   */
-  override fun applyTransformation(context: TransformationContext) {
-    val clazz = context.codeClass
-    if (!check(clazz)) return
-    context.addInterface(DIRTY_CHECKABLE_FQN)
-    val dirtyCheckable = JavaPsiFacade.getInstance(clazz.project).findClass(DIRTY_CHECKABLE_FQN, clazz.resolveScope)
-    if (dirtyCheckable == null || !dirtyCheckable.isInterface || GrTraitUtil.isTrait(dirtyCheckable)) return
-    for (interfaceMethod in dirtyCheckable.methods) {
-      val wrapper = GrMethodWrapper.wrap(interfaceMethod)
-      wrapper.modifierList.removeModifier(GrModifierFlags.ABSTRACT_MASK)
-      wrapper.originInfo = ORIGIN_INFO
-      context.addMethod(wrapper)
-    }
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.java
new file mode 100644
index 0000000..1d1836f
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.lang;
+
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.searches.ClassInheritorsSearch;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.util.PsiModificationTracker;
+import it.unimi.dsi.fastutil.Hash;
+import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.gorm.GormClassNames;
+import org.jetbrains.plugins.grails.gorm.GormVersion;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
+import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyDirectInheritorsSearcher;
+import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
+import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport;
+import org.jetbrains.plugins.groovy.transformations.TransformationContext;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public final class GormTraitContributor implements AstTransformationSupport {
+
+  private static final Map<String, String> MARKER_CLASSES = Map.of(
+    "grails.mongodb.MongoEntity", "com.mongodb.MongoClient",
+    "grails.neo4j.Neo4jEntity", "org.neo4j.driver.v1.Driver"
+  );
+
+  @Override
+  public void applyTransformation(@NotNull TransformationContext context) {
+    GrTypeDefinition clazz = context.getCodeClass();
+    if (GrailsUtils.calculateArtifactType(clazz) != GrailsArtifact.DOMAIN) return;
+
+    GormVersion gormVersion = GormVersion.forElement(clazz);
+    if (gormVersion == null) return;
+    if (gormVersion.compareTo(GormVersion.IS_5) < 0) return;
+    if (gormVersion.compareTo(GormVersion.IS_6) >= 0 && context.isInheritor("grails.gorm.rx.RxEntity")) return;
+
+    /*
+    Repeat org.grails.compiler.gorm.GormEntityTransformation#pickGormEntityTrait().
+     */
+    String mapWith = computeMapWithValue(clazz);
+    boolean hibernatePresent = JavaPsiFacade.getInstance(clazz.getProject())
+                                 .findClass("org.hibernate.Hibernate", clazz.getResolveScope()) != null;
+    String traitFqn;
+    if (hibernatePresent && mapWith == null) {
+      traitFqn = GormClassNames.ENTITY_TRAIT;
+    }
+    else {
+      Collection<PsiClass> providers = findTraitsFromProviders(clazz);
+      if (providers.isEmpty()) {
+        traitFqn = GormClassNames.ENTITY_TRAIT;
+      }
+      else if (mapWith == null || mapWith.isEmpty()) {
+        traitFqn = providers.size() == 1 ? qualifiedNameOrEntityTrait(providers.iterator().next())
+                                         : GormClassNames.ENTITY_TRAIT;
+      }
+      else {
+        PsiClass traitFromProvider = null;
+        for (PsiClass provider : providers) {
+          String name = provider.getName();
+          if (name != null && name.startsWith(mapWith)) {
+            traitFromProvider = provider;
+            break;
+          }
+        }
+        traitFqn = traitFromProvider == null ? GormClassNames.ENTITY_TRAIT : qualifiedNameOrEntityTrait(traitFromProvider);
+      }
+    }
+    Grails3TraitInjectorContributor.injectTraits(clazz, context, List.of(traitFqn));
+  }
+
+  private static @NotNull String qualifiedNameOrEntityTrait(@NotNull PsiClass clazz) {
+    String qualifiedName = clazz.getQualifiedName();
+    return qualifiedName == null ? GormClassNames.ENTITY_TRAIT : qualifiedName;
+  }
+
+  private static @Nullable String computeMapWithValue(@NotNull GrTypeDefinition clazz) {
+    return CachedValuesManager.getCachedValue(
+      clazz,
+      () -> Result.create(doComputeMapWithValue(clazz), PsiModificationTracker.MODIFICATION_COUNT));
+  }
+
+  private static @Nullable String doComputeMapWithValue(@NotNull GrTypeDefinition clazz) {
+    PsiField mapWith = clazz.findCodeFieldByName("mapWith", true);
+    if (mapWith instanceof GrField grField) {
+      GrExpression initializer = grField.getInitializerGroovy();
+      if (initializer instanceof GrLiteral literal && literal.getValue() instanceof String value) {
+        if (value.isEmpty()) return value;
+        return Character.toUpperCase(value.charAt(0)) + value.substring(1);
+      }
+    }
+    return null;
+  }
+
+  private static @NotNull Collection<PsiClass> findTraitsFromProviders(@NotNull PsiElement context) {
+    return CachedValuesManager.getCachedValue(
+      context,
+      () -> Result.create(doFindTraitsFromProviders(context), ProjectRootManager.getInstance(context.getProject())));
+  }
+
+  /**
+   * Grails loads GormEntityTraitProvider using Java Services API and calls getEntityTrait().
+   * To reduce processing time we assume that GormEntity's inheritors are registered in META-INF/services via GormEntityTraitProvider.
+   * I.e. we do not run script with user classpath to determine traits, but just search for GormEntity inheritors.
+   */
+  private static @NotNull Collection<PsiClass> doFindTraitsFromProviders(@NotNull PsiElement context) {
+    GlobalSearchScope scope = context.getResolveScope();
+    JavaPsiFacade facade = JavaPsiFacade.getInstance(context.getProject());
+    PsiClass gormEntity = facade.findClass(GormClassNames.ENTITY_TRAIT, scope);
+    if (gormEntity == null) return Collections.emptyList();
+    GormVersion version = GormVersion.forElement(context);
+    if (version == null) return Collections.emptyList();
+
+    List<PsiClass> traits = new ArrayList<>();
+    gormEntity.putUserData(GroovyDirectInheritorsSearcher.IGNORE_INHERITANCE_CHECK, true);
+    try {
+      for (PsiClass candidate : ClassInheritorsSearch.search(gormEntity, scope, true).findAll()) {
+        if (GrTraitUtil.isTrait(candidate)) traits.add(candidate);
+      }
+    }
+    finally {
+      gormEntity.putUserData(GroovyDirectInheritorsSearcher.IGNORE_INHERITANCE_CHECK, null);
+    }
+
+    // Dedupe by qualified name: the same trait can be seen through several scopes.
+    Collection<PsiClass> result = new ObjectOpenCustomHashSet<>(new Hash.Strategy<>() {
+      @Override
+      public int hashCode(@Nullable PsiClass o) {
+        String qualifiedName = o == null ? null : o.getQualifiedName();
+        return qualifiedName == null ? 0 : qualifiedName.hashCode();
+      }
+
+      @Override
+      public boolean equals(@Nullable PsiClass o1, @Nullable PsiClass o2) {
+        if (o1 == o2) return true;
+        String q1 = o1 == null ? null : o1.getQualifiedName();
+        String q2 = o2 == null ? null : o2.getQualifiedName();
+        return Objects.equals(q1, q2);
+      }
+    });
+
+    for (PsiClass trait : traits) {
+      // From GORM 6 a DB-specific trait only applies when its driver is on the classpath.
+      if (version.compareTo(GormVersion.IS_6) >= 0) {
+        String markerFqn = MARKER_CLASSES.get(trait.getQualifiedName());
+        if (markerFqn != null && facade.findClass(markerFqn, scope) == null) continue;
+      }
+      result.add(trait);
+    }
+
+    return result;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt
deleted file mode 100644
index 3d024cb..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.lang
-
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.util.removeUserData
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiElement
-import com.intellij.psi.search.searches.ClassInheritorsSearch
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import com.intellij.psi.util.PsiModificationTracker
-import it.unimi.dsi.fastutil.Hash
-import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet
-import org.jetbrains.plugins.grails.gorm.GormClassNames.ENTITY_TRAIT
-import org.jetbrains.plugins.grails.gorm.GormVersion
-import org.jetbrains.plugins.grails.gorm.getGormVersion
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.grails.util.GrailsUtils
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
-import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyDirectInheritorsSearcher
-import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil
-import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport
-import org.jetbrains.plugins.groovy.transformations.TransformationContext
-
-class GormTraitContributor : AstTransformationSupport {
-
-  override fun applyTransformation(context: TransformationContext) {
-    val clazz = context.codeClass
-    if (GrailsUtils.calculateArtifactType(clazz) !== GrailsArtifact.DOMAIN) return
-
-    val gormVersion = getGormVersion(clazz) ?: return
-    if (gormVersion < GormVersion.IS_5) return
-    if (gormVersion >= GormVersion.IS_6 && context.isInheritor("grails.gorm.rx.RxEntity")) return
-
-    /*
-    Repeat org.grails.compiler.gorm.GormEntityTransformation#pickGormEntityTrait().
-     */
-    val mapWith = computeMapWithValue(clazz)
-    val hibernatePresent = JavaPsiFacade.getInstance(clazz.project).findClass("org.hibernate.Hibernate", clazz.resolveScope) != null
-    val traitFqn = if (hibernatePresent && mapWith == null) {
-      ENTITY_TRAIT
-    }
-    else {
-      val providers = findTraitsFromProviders(clazz)
-      if (providers.isEmpty()) {
-        ENTITY_TRAIT
-      }
-      else if (mapWith == null || mapWith.isEmpty()) {
-        providers.singleOrNull()?.qualifiedName ?: ENTITY_TRAIT
-      }
-      else {
-        val traitFromProvider = providers.find { it.name?.startsWith(mapWith) ?: false }
-        traitFromProvider?.qualifiedName ?: ENTITY_TRAIT
-      }
-    }
-    Grails3TraitInjectorContributor.injectTraits(clazz, context, listOf(traitFqn))
-  }
-
-  private fun computeMapWithValue(clazz: GrTypeDefinition) = CachedValuesManager.getCachedValue(clazz) {
-    Result.create(doComputeMapWithValue(clazz), PsiModificationTracker.MODIFICATION_COUNT)
-  }
-
-  private fun doComputeMapWithValue(clazz: GrTypeDefinition): String? {
-    val mapWith = clazz.findCodeFieldByName("mapWith", true)
-    if (mapWith is GrField) {
-      val initializer = mapWith.initializerGroovy
-      if (initializer is GrLiteral) {
-        val value = initializer.value
-        if (value is String) {
-          return value.replaceFirstChar { it.uppercaseChar() }
-        }
-      }
-    }
-    return null
-  }
-
-  private fun findTraitsFromProviders(context: PsiElement) = CachedValuesManager.getCachedValue(context) {
-    Result.create(doFindTraitsFromProviders(context), ProjectRootManager.getInstance(context.project))
-  }
-
-  /**
-   * Grails loads GormEntityTraitProvider using Java Services API and calls getEntityTrait().
-   * To reduce processing time we assume that GormEntity's inheritors are registered in META-INF/services via GormEntityTraitProvider.
-   * I.e. we do not run script with user classpath to determine traits, but just search for GormEntity inheritors.
-   */
-  private fun doFindTraitsFromProviders(context: PsiElement): Collection<PsiClass> {
-    val scope = context.resolveScope
-    val facade = JavaPsiFacade.getInstance(context.project)
-    val gormEntity = facade.findClass(ENTITY_TRAIT, scope) ?: return emptyList()
-    val version = getGormVersion(context) ?: return emptyList()
-    val result = ObjectOpenCustomHashSet(object : Hash.Strategy<PsiClass> {
-      override fun hashCode(`object`: PsiClass?) = `object`?.qualifiedName?.hashCode() ?: 0
-      override fun equals(o1: PsiClass?, o2: PsiClass?) = o1 === o2 || o1?.qualifiedName == o2?.qualifiedName
-    })
-    gormEntity.putUserData(GroovyDirectInheritorsSearcher.IGNORE_INHERITANCE_CHECK, true)
-    val query = ClassInheritorsSearch.search(gormEntity, scope, true)
-    val traits = query.findAll().asSequence().filter {
-      GrTraitUtil.isTrait(it)
-    }
-    gormEntity.removeUserData(GroovyDirectInheritorsSearcher.IGNORE_INHERITANCE_CHECK)
-    val availableTraits = if (version < GormVersion.IS_6) traits
-    else traits.filter {
-      val markerFqn = markerClasses[it.qualifiedName] ?: return@filter true
-      facade.findClass(markerFqn, scope) != null
-    }
-
-    return availableTraits.toCollection(result)
-  }
-
-  private val markerClasses = mapOf(
-      "grails.mongodb.MongoEntity" to "com.mongodb.MongoClient",
-      "grails.neo4j.Neo4jEntity" to "org.neo4j.driver.v1.Driver"
-  )
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java
index e058189..f12b900 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java
@@ -29,7 +29,6 @@ import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.grails.gorm.GormVersion;
-import org.jetbrains.plugins.grails.gorm.GormVersionKt;
 import org.jetbrains.plugins.grails.util.GrailsArtifactTransformerUtils;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
@@ -74,7 +73,7 @@ public final class GormAstTransformationContributor implements AstTransformation
     final PsiType myMapType = TypesUtil.createTypeByFQClassName(CommonClassNames.JAVA_UTIL_MAP, aClass);
     context.addMethod(new GrLightMethodBuilder(aClass).addParameter("namedArgs", myMapType));
 
-    GormVersion version = GormVersionKt.getGormVersion(aClass);
+    GormVersion version = GormVersion.forElement(aClass);
     if (version != GormVersion.BELOW_4) return;
 
     List<PsiMethod> methods = new ArrayList<>();
diff --git a/pluginModules/maven/build.gradle b/pluginModules/maven/build.gradle
index 7ab37e9..9235ca5 100644
--- a/pluginModules/maven/build.gradle
+++ b/pluginModules/maven/build.gradle
@@ -15,7 +15,6 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.kotlin'
 }
 
 dependencies {
diff --git a/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.java b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.java
new file mode 100644
index 0000000..de0e653
--- /dev/null
+++ b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.intellij.groovy.grails.maven;
+
+import com.intellij.facet.FacetManager;
+import com.intellij.facet.ModifiableFacetModel;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.idea.maven.importing.MavenStaticSyncAware;
+import org.jetbrains.idea.maven.importing.MavenWorkspaceConfigurator;
+import org.jetbrains.idea.maven.project.MavenProject;
+import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
+import org.jetbrains.plugins.grails.config.GrailsFramework;
+import org.jetbrains.plugins.grails.util.GrailsFacetProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * Replaces the legacy {@link org.jetbrains.idea.maven.importing.MavenImporter}-based
+ * GrailsMavenImporter, which is no longer invoked by the workspace-model Maven import
+ * (2026.2): registers the Grails source/test/resource roots and the Grails facets for
+ * projects built with org.grails:grails-maven-plugin.
+ */
+public final class GrailsMavenWorkspaceConfigurator implements MavenWorkspaceConfigurator, MavenStaticSyncAware {
+
+  private static boolean isApplicable(@NotNull MavenProject mavenProject) {
+    return mavenProject.findPlugin("org.grails", "grails-maven-plugin") != null;
+  }
+
+  @Override
+  public @NotNull Stream<AdditionalFolder> getAdditionalFolders(@NotNull FoldersContext context) {
+    if (!isApplicable(context.getMavenProject())) {
+      return Stream.empty();
+    }
+    List<AdditionalFolder> folders = new ArrayList<>();
+    for (JpsModuleSourceRootType<?> rootType : GrailsFramework.GRAILS_SOURCE_FOLDERS.keySet()) {
+      FolderType folderType = toFolderType(rootType);
+      for (String path : GrailsFramework.GRAILS_SOURCE_FOLDERS.get(rootType)) {
+        folders.add(new AdditionalFolder(path, folderType));
+      }
+    }
+    // the static preimport repairs default source dirs but not resources
+    // (MavenProjectStaticImporter.resolveDirectories), so restore the Maven defaults here
+    if (context.getMavenProject().getResources().isEmpty()) {
+      folders.add(new AdditionalFolder("src/main/resources", FolderType.RESOURCE));
+    }
+    if (context.getMavenProject().getTestResources().isEmpty()) {
+      folders.add(new AdditionalFolder("src/test/resources", FolderType.TEST_RESOURCE));
+    }
+    return folders.stream();
+  }
+
+  @Override
+  public void afterModelApplied(@NotNull AppliedModelContext context) {
+    // getMavenProjectsWithModules() is a kotlin.sequences.Sequence, which is not Iterable, so it
+    // cannot drive a for-each from Java.
+    var projects = context.getMavenProjectsWithModules().iterator();
+    while (projects.hasNext()) {
+      var projectWithModules = projects.next();
+      MavenProject mavenProject = projectWithModules.getMavenProject();
+      if (!isApplicable(mavenProject)) continue;
+
+      for (var moduleWithType : projectWithModules.getModules()) {
+        Module module = moduleWithType.getModule();
+        List<Consumer<ModifiableFacetModel>> actions = new ArrayList<>();
+        for (GrailsFacetProvider provider : GrailsFacetProvider.EP_NAME.getExtensions()) {
+          provider.addFacets(actions, module, List.of(mavenProject.getDirectoryFile()));
+        }
+        if (actions.isEmpty()) continue;
+
+        ApplicationManager.getApplication().invokeAndWait(
+          () -> ApplicationManager.getApplication().runWriteAction(() -> {
+            if (module.isDisposed()) return;
+            ModifiableFacetModel facetModel = FacetManager.getInstance(module).createModifiableModel();
+            for (Consumer<ModifiableFacetModel> action : actions) {
+              action.consume(facetModel);
+            }
+            facetModel.commit();
+          }));
+      }
+    }
+  }
+}
diff --git a/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt
deleted file mode 100644
index 3258e86..0000000
--- a/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package com.intellij.groovy.grails.maven
-
-import com.intellij.facet.FacetManager
-import com.intellij.facet.ModifiableFacetModel
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.util.Consumer
-import org.jetbrains.idea.maven.importing.MavenStaticSyncAware
-import org.jetbrains.idea.maven.importing.MavenWorkspaceConfigurator
-import org.jetbrains.idea.maven.project.MavenProject
-import org.jetbrains.plugins.grails.config.GrailsFramework
-import org.jetbrains.plugins.grails.util.GrailsFacetProvider
-import java.util.stream.Stream
-
-/**
- * Replaces the legacy [org.jetbrains.idea.maven.importing.MavenImporter]-based
- * GrailsMavenImporter, which is no longer invoked by the workspace-model Maven import
- * (2026.2): registers the Grails source/test/resource roots and the Grails facets for
- * projects built with org.grails:grails-maven-plugin.
- */
-internal class GrailsMavenWorkspaceConfigurator : MavenWorkspaceConfigurator, MavenStaticSyncAware {
-
-  private fun isApplicable(mavenProject: MavenProject): Boolean =
-    mavenProject.findPlugin("org.grails", "grails-maven-plugin") != null
-
-  override fun getAdditionalFolders(
-    context: MavenWorkspaceConfigurator.FoldersContext,
-  ): Stream<MavenWorkspaceConfigurator.AdditionalFolder> {
-    if (!isApplicable(context.mavenProject)) {
-      return Stream.empty()
-    }
-    val folders = ArrayList<MavenWorkspaceConfigurator.AdditionalFolder>()
-    for (rootType in GrailsFramework.GRAILS_SOURCE_FOLDERS.keySet()) {
-      val folderType = rootType.toFolderType()
-      GrailsFramework.GRAILS_SOURCE_FOLDERS.get(rootType)
-        .mapTo(folders) { MavenWorkspaceConfigurator.AdditionalFolder(it, folderType) }
-    }
-    // the static preimport repairs default source dirs but not resources
-    // (MavenProjectStaticImporter.resolveDirectories), so restore the Maven defaults here
-    if (context.mavenProject.resources.isEmpty()) {
-      folders.add(MavenWorkspaceConfigurator.AdditionalFolder("src/main/resources", MavenWorkspaceConfigurator.FolderType.RESOURCE))
-    }
-    if (context.mavenProject.testResources.isEmpty()) {
-      folders.add(MavenWorkspaceConfigurator.AdditionalFolder("src/test/resources", MavenWorkspaceConfigurator.FolderType.TEST_RESOURCE))
-    }
-    return folders.stream()
-  }
-
-  override fun afterModelApplied(context: MavenWorkspaceConfigurator.AppliedModelContext) {
-    for (projectWithModules in context.mavenProjectsWithModules) {
-      val mavenProject = projectWithModules.mavenProject
-      if (!isApplicable(mavenProject)) continue
-
-      for (moduleWithType in projectWithModules.modules) {
-        val module = moduleWithType.module
-        val actions = ArrayList<Consumer<ModifiableFacetModel>>()
-        for (provider in GrailsFacetProvider.EP_NAME.extensions) {
-          provider.addFacets(actions, module, listOf(mavenProject.directoryFile))
-        }
-        if (actions.isEmpty()) continue
-
-        ApplicationManager.getApplication().invokeAndWait {
-          ApplicationManager.getApplication().runWriteAction {
-            if (module.isDisposed) return@runWriteAction
-            val facetModel = FacetManager.getInstance(module).createModifiableModel()
-            actions.forEach { it.consume(facetModel) }
-            facetModel.commit()
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.java b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.java
new file mode 100644
index 0000000..49641da
--- /dev/null
+++ b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.groovy.grails.maven;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.maven.model.MavenConstants;
+import org.jetbrains.plugins.grails.projectView.api.GrailsSingleNodeProvider;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+/** Shows the {@code pom.xml} of a Maven-built Grails application in the Grails project view. */
+public final class MavenNodeProvider extends GrailsSingleNodeProvider {
+
+  @Override
+  public @Nullable AbstractTreeNode<?> createNode(@NotNull GrailsApplication application, @NotNull ViewSettings settings) {
+    if (!(application instanceof GrailsMavenApplication)) return null;
+    Project project = application.getProject();
+    VirtualFile pom = application.getRoot().findChild(MavenConstants.POM_XML);
+    if (pom == null) return null;
+    PsiFile psiFile = PsiManager.getInstance(project).findFile(pom);
+    if (psiFile == null) return null;
+    return new PsiFileNode(project, psiFile, settings);
+  }
+}
diff --git a/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.kt b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.kt
deleted file mode 100644
index 21798d9..0000000
--- a/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.intellij.groovy.grails.maven
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.psi.PsiManager
-import org.jetbrains.idea.maven.model.MavenConstants
-import org.jetbrains.plugins.grails.projectView.api.GrailsSingleNodeProvider
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-internal class MavenNodeProvider : GrailsSingleNodeProvider() {
-
-  override fun createNode(application: GrailsApplication, settings: ViewSettings): AbstractTreeNode<*>? {
-    if (application !is GrailsMavenApplication) return null
-    val project = application.project
-    return application.root.findChild(MavenConstants.POM_XML)
-      ?.let { PsiManager.getInstance(project).findFile(it) }
-      ?.let { PsiFileNode(project, it, settings) }
-  }
-}
\ No newline at end of file


(grails-intellij-plugin) 07/35: Generate the vote, result, and announcement emails from checked-in templates

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 88ae0a2653569f45921483290b661b8c70508476
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 07:32:27 2026 -0400

    Generate the vote, result, and announcement emails from checked-in templates
    
    Follows the precedent set for Grails releases: the release workflow renders the
    emails instead of leaving the release manager to write them by hand.
    
    Adds .github/vote_templates/{staged,vote_succeeded,announce}.txt, expanded with
    envsubst and printed to the job log with recipient, subject, and body.
    
    The upload job now records the dist.apache.org SVN revision the artifacts were
    committed at and resolves the tag commit id (dereferencing annotated tags), so
    the [VOTE] email cites exactly what was staged. It gains contents:read to check
    out the templates and query the tag.
    
    The release job prints the [RESULT][VOTE] and [ANNOUNCE] emails in place of the
    previous one-line reminder.
    
    RELEASE.md now points at the templates as the single source of truth rather than
    duplicating a hand-written vote email.
---
 .github/vote_templates/announce.txt       |  27 ++++++++
 .github/vote_templates/staged.txt         |  52 +++++++++++++++
 .github/vote_templates/vote_succeeded.txt |  12 ++++
 .github/workflows/release.yml             | 103 +++++++++++++++++++++++++++---
 RELEASE.md                                |  49 ++++++--------
 5 files changed, 204 insertions(+), 39 deletions(-)

diff --git a/.github/vote_templates/announce.txt b/.github/vote_templates/announce.txt
new file mode 100644
index 0000000..112e64b
--- /dev/null
+++ b/.github/vote_templates/announce.txt
@@ -0,0 +1,27 @@
+The Apache Grails community is pleased to announce the release of ${PROJECT_NAME} ${VERSION}.
+
+${PROJECT_DESC}
+
+<IF MILESTONE>
+This is a pre-release of a new version for ${PROJECT_NAME}. We greatly appreciate any feedback you can give us when using this version.
+</IF MILESTONE>
+
+The release notes are available here:
+https://github.com/${REPO_SLUG}/releases/tag/${TAG}
+
+For the complete list of changes:
+https://github.com/${REPO_SLUG}/compare/v<PREVIOUS_VERSION>...${TAG}
+
+The plugin can be installed from inside IntelliJ IDEA Ultimate via
+Settings > Plugins > Marketplace, or from the JetBrains Marketplace:
+https://plugins.jetbrains.com/plugin/org.intellij.grails
+
+Apache Grails website: https://grails.apache.org/
+
+Grails Resources:
+- Grails IntelliJ Plugin GitHub repo: https://github.com/${REPO_SLUG}
+- Issues: https://github.com/${REPO_SLUG}/issues
+- Mailing lists: https://grails.apache.org/community.html
+
+Happy Coding,
+The Apache Grails Team
diff --git a/.github/vote_templates/staged.txt b/.github/vote_templates/staged.txt
new file mode 100644
index 0000000..12741f3
--- /dev/null
+++ b/.github/vote_templates/staged.txt
@@ -0,0 +1,52 @@
+Hi Everyone,
+
+I am happy to start the VOTE thread to release ${PROJECT_NAME} ${VERSION}!
+
+Release notes for the release are here:
+https://github.com/${REPO_SLUG}/releases/tag/${TAG}
+
+The tag for this release is:
+https://github.com/${REPO_SLUG}/releases/tag/${TAG}
+Tag commit id: ${SHA}
+
+The artifacts to be voted on are located as follows (r${DIST_SVN_REVISION}):
+Source release: https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}/sources
+Binary distribution: https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}/distribution
+
+The official release is the source distribution. The plugin ZIP in the binary
+distribution is a convenience binary, signed for the JetBrains Marketplace and built
+from the same tagged source.
+
+Release artifacts are signed with a key from the following file:
+https://dist.apache.org/repos/dist/release/grails/KEYS
+
+Please vote on releasing this package as: ${PROJECT_NAME} ${VERSION}.
+
+Reminder on ASF release approval requirements for PMC members:
+https://www.apache.org/legal/release-policy.html#release-approval
+
+Hints on validating checksums/signatures (but replace md5sum with sha512sum):
+https://www.apache.org/info/verification.html
+
+Details of our release process are documented at:
+https://github.com/${REPO_SLUG}/blob/HEAD/RELEASE.md
+
+The staged artifacts can be checked out and verified in one step with the scripts
+checked into the repository:
+
+    etc/bin/verify.sh ${TAG} /tmp/grails-ij-verify
+
+That downloads the artifacts and the KEYS file, verifies the checksums and signatures,
+audits the licenses with Apache RAT, and rebuilds the plugin from the staged source
+distribution to confirm it matches the staged binary.
+
+The vote is open for a minimum of 72 hours and passes if a majority of at least
+three +1 PMC votes are cast.
+
+[ ] +1 Release ${PROJECT_NAME} ${VERSION}
+[ ]  0 I don't have a strong opinion about this, but I assume it's ok
+[ ] -1 Do not release ${PROJECT_NAME} ${VERSION} because...
+
+Here is my vote:
+
++1 (binding)
diff --git a/.github/vote_templates/vote_succeeded.txt b/.github/vote_templates/vote_succeeded.txt
new file mode 100644
index 0000000..f722d5d
--- /dev/null
+++ b/.github/vote_templates/vote_succeeded.txt
@@ -0,0 +1,12 @@
+The vote has passed with <X> +1 binding votes and <Y> +1 additional votes.
+
+<IF NEW EMAIL>
+Vote thread: <VOTE THREAD PERMALINK>
+</IF NEW EMAIL>
+
+I'll proceed with the release and announce it shortly.
+
+Thanks to everyone who participated in the vote!
+
+Regards
+<NAME>
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5273b52..04a2aaa 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,6 +25,11 @@ env:
   DIST_NAME: apache-grails-intellij-plugin
   JAVA_DISTRIBUTION: liberica # .sdkmanrc pins librca = Liberica
   PROJECT_NAME: Apache Grails IntelliJ Plugin
+  PROJECT_DESC: >-
+    The Apache Grails IntelliJ Plugin adds Apache Grails support to IntelliJ IDEA Ultimate:
+    GSP language support, Grails project structure and navigation, run configurations,
+    taglib and domain class support, and integrations for i18n, coverage, Hibernate, Maven,
+    and language injection.
   REPO_SLUG: ${{ github.repository }}
   SVN_FOLDER: INTELLIJ
   SVN_PROJECT: grails
@@ -158,6 +163,8 @@ jobs:
   upload:
     name: "Upload Distributions to dist.apache.org"
     needs: [ publish, source ]
+    permissions:
+      contents: read # to check out the vote templates and resolve the tag SHA
     runs-on: ubuntu-latest
     env:
       SVN_USERNAME: ${{ secrets.SVC_DIST_GRAILS_USERNAME }}
@@ -166,7 +173,7 @@ jobs:
       - name: "📝 Establish release version"
         run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
       - name: "🛠️ Install tools"
-        run: sudo apt-get install -y subversion tree
+        run: sudo apt-get install -y subversion tree gettext-base # gettext-base provides envsubst for the vote email
       - name: "👀 Ensure dev/${{ env.SVN_PROJECT }}/${{ env.SVN_FOLDER }} folder exists"
         run: |
           set -e
@@ -215,11 +222,52 @@ jobs:
           tree
           svn add ${VERSION} --force
           svn commit -m "Upload ${PROJECT_NAME} distribution files for ${VERSION}" --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" --non-interactive
-      - name: "🗳 MANUAL - Start PMC Vote"
+      - name: "💾 Store Distribution SVN revision in a file"
+        # The vote email cites the SVN revision so voters can confirm they are looking at
+        # the same artifacts that were staged, even if the folder is touched afterwards.
+        run: |
+          cd dev-repo
+          svn info ${VERSION} > DIST_SVN_REVISION.txt
+          cat DIST_SVN_REVISION.txt
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        with:
+          path: repo
+          ref: ${{ env.TAG }}
+      - name: "📝 Resolve the tag commit id"
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        # An annotated tag points at a tag object, so dereference it to the commit.
+        run: |
+          set -euo pipefail
+          ref_json=$(gh api "repos/${REPO_SLUG}/git/ref/tags/${TAG}")
+          type=$(jq -r '.object.type' <<< "$ref_json")
+          sha=$(jq -r '.object.sha' <<< "$ref_json")
+          if [ "$type" = "tag" ]; then
+            sha=$(gh api "repos/${REPO_SLUG}/git/tags/${sha}" --jq '.object.sha')
+          fi
+          echo "Found sha: $sha"
+          echo "SHA=${sha}" >> "$GITHUB_ENV"
+      - name: "📧 Print Grails PMC Vote Email"
         run: |
-          echo "::group::Manual Vote"
-          echo "Distributions staged at https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}"
-          echo "Start a [VOTE] thread for ${PROJECT_NAME} ${VERSION} on dev@grails.apache.org."
+          export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt)
+
+          echo "::group::Grails PMC Vote Email"
+          echo ""
+          echo "TO:"
+          echo "*************************************************"
+          echo "dev@grails.apache.org"
+          echo "*************************************************"
+          echo ""
+          echo "Subject:"
+          echo "*************************************************"
+          echo "[VOTE] Release ${PROJECT_NAME} ${VERSION}"
+          echo "*************************************************"
+          echo ""
+          echo "Body:"
+          echo "*************************************************"
+          cat repo/.github/vote_templates/staged.txt | envsubst
+          echo "*************************************************"
           echo "::endgroup::"
   release:
     environment: release # post-vote gate: required reviewers configured in .asf.yaml
@@ -269,9 +317,46 @@ jobs:
           echo "Update https://reporter.apache.org/addrelease.html?${SVN_PROJECT} to add"
           echo "${SVN_FOLDER}-${VERSION} as complete as of ${TODAY}"
           echo "::endgroup::"
-      - name: "📧 MANUAL - Send Vote Result & Announcement Emails"
+      - name: "📧 MANUAL - Send Vote Result Email"
+        run: |
+          echo "::group::Manual Vote Result Email"
+          echo ""
+          echo "Send a new email or reply to the original vote email by changing the subject."
+          echo ""
+          echo "TO:"
+          echo "*************************************************"
+          echo "dev@grails.apache.org"
+          echo "*************************************************"
+          echo ""
+          echo "Subject:"
+          echo "*************************************************"
+          echo "[RESULT][VOTE] ${PROJECT_NAME} ${VERSION}"
+          echo "*************************************************"
+          echo ""
+          echo "Body:"
+          echo "*************************************************"
+          cat .github/vote_templates/vote_succeeded.txt | envsubst
+          echo "*************************************************"
+          echo "::endgroup::"
+      - name: "📧 MANUAL - Send Announcement Email"
         run: |
-          echo "::group::Manual Emails"
-          echo "Send [RESULT][VOTE] to dev@grails.apache.org and"
-          echo "[ANNOUNCE] ${PROJECT_NAME} ${VERSION} to announce@apache.org, dev@grails.apache.org, users@grails.apache.org."
+          echo "::group::Announcement Email"
+          echo ""
+          echo "Announcements must be sent from your @apache.org address."
+          echo "See https://infra.apache.org/committer-email.html"
+          echo ""
+          echo "TO:"
+          echo "*************************************************"
+          echo "announce@apache.org, dev@grails.apache.org, users@grails.apache.org"
+          echo "*************************************************"
+          echo ""
+          echo "Subject:"
+          echo "*************************************************"
+          echo "[ANNOUNCE] ${PROJECT_NAME} ${VERSION}"
+          echo "*************************************************"
+          echo ""
+          echo "Body:"
+          echo "*************************************************"
+          cat .github/vote_templates/announce.txt | envsubst
+          echo "*************************************************"
           echo "::endgroup::"
diff --git a/RELEASE.md b/RELEASE.md
index 20e3ac2..c18a513 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -229,36 +229,24 @@ following the [Apache voting process](https://www.apache.org/foundation/voting.h
 Only Grails PMC votes are binding; the vote runs a minimum of 72 hours and needs at least
 three +1 votes from PMC members.
 
-Template:
+**You do not need to write the email.** The `upload` job's final step,
+*📧 Print Grails PMC Vote Email*, renders it for you and prints it to the job log with the
+recipient, subject, and body ready to copy. It fills in the version, tag, tag commit id,
+staged artifact URLs, and the `dist.apache.org` SVN revision the artifacts were committed
+at, so voters can confirm they are looking at exactly what was staged.
 
-```
-Subject: [VOTE] Release Apache Grails IntelliJ Plugin <version>
-
-Hi all,
-
-I'd like to call a vote to release Apache Grails IntelliJ Plugin <version>.
-
-The source and binary distributions are staged at:
-  https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/<version>/
-
-The release tag is:
-  https://github.com/apache/grails-intellij-plugin/releases/tag/v<version>
+The email bodies live in [`.github/vote_templates`](.github/vote_templates) and are
+expanded with `envsubst`:
 
-The KEYS file used to sign the artifacts:
-  https://dist.apache.org/repos/dist/release/grails/KEYS
+| Template | Used by | Sent as |
+| --- | --- | --- |
+| `staged.txt` | `upload` job | `[VOTE]` to `dev@grails.apache.org` |
+| `vote_succeeded.txt` | `release` job | `[RESULT][VOTE]` to `dev@grails.apache.org` |
+| `announce.txt` | `release` job | `[ANNOUNCE]` to `announce@apache.org`, `dev@`, `users@` |
 
-Verification instructions are in RELEASE.md; the artifacts can be checked with:
-  etc/bin/verify.sh v<version> /tmp/grails-ij-verify
-
-The vote is open for at least 72 hours.
-
-[ ] +1 Release this package
-[ ]  0 No opinion
-[ ] -1 Do not release this package (please provide the reason)
-
-Thanks,
-<your name>
-```
+Edit the templates rather than the workflow when the wording needs to change. Placeholders
+in angle brackets (`<X>`, `<NAME>`, `<PREVIOUS_VERSION>`) are deliberately left for the
+release manager to fill in; everything in `${...}` is substituted automatically.
 
 ## 8. Publish the release (`release` job)
 
@@ -278,9 +266,10 @@ requires the reviewers listed in `.asf.yaml`):
    JetBrains Marketplace.
 4. **MANUAL:** record the release at <https://reporter.apache.org/addrelease.html?grails>
    as `INTELLIJ-<version>`, dated the day the distributions were promoted.
-5. **MANUAL:** send `[RESULT][VOTE]` to `dev@grails.apache.org`, then `[ANNOUNCE]` to
-   `announce@apache.org`, `dev@grails.apache.org`, and `users@grails.apache.org`.
-   Announcements must come from your `@apache.org` address
+5. **MANUAL:** send the `[RESULT][VOTE]` and `[ANNOUNCE]` emails. The job's last two steps
+   print both, rendered from
+   [`.github/vote_templates`](.github/vote_templates) — copy them from the log and fill in
+   the angle-bracket placeholders. Announcements must come from your `@apache.org` address
    (see <https://infra.apache.org/committer-email.html>).
 6. Flag the GitHub Release as **latest**.
 


(grails-intellij-plugin) 26/35: Convert the runner package and coverage module to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit e74cae53e94501bb5615d14b2cd53f9d50a1b9cf
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 14:26:59 2026 -0400

    Convert the runner package and coverage module to Java
    
    Converts the seven remaining Kotlin files under runner/ plus the coverage
    module's only source file. The runner hierarchy alternated Kotlin -> Java ->
    Kotlin (BaseGrailsCommandLineState -> GrailsCommandLineState ->
    GrailsTestApp/GrailsRunApp), so it had to move as a unit.
    
    setup.kt's top-level declarations become static members of GrailsRunnerSetup;
    the three Java call sites that referenced the synthetic SetupKt class are
    repathed. Declarations that were Kotlin `internal` but cross package
    boundaries are public with @ApiStatus.Internal, matching what the earlier
    Grails2Application conversion did.
    
    Behaviour preserved deliberately in three places:
    
      - GrailsConsoleState keeps getAutoCloseEnabled/setAutoCloseEnabled and a
        no-arg constructor. Those names are the persisted XML form, and
        XmlSerializer needs the default constructor Kotlin was synthesising.
      - Grails3TestCommandLineState's proxy command stays lazy behind a
        double-checked getCommand(). Kotlin's `by lazy` deferred
        createTestInitScript(), which touches the filesystem; building it in the
        constructor would move that I/O onto the caller's thread.
      - canPassVmArgs was a Kotlin extension on GrailsApplication?, so its one
        caller in the coverage module changes to a static call. That module now
        has no Kotlin sources, so it drops the kotlin convention plugin.
    
    ProcessListener.onTextAvailable declares a raw Key, so the override is raw
    too; Kotlin's Key<*> had hidden that.
---
 .../plugins/grails/runner/GrailsConsoleState.java  |  55 ++++++++
 .../plugins/grails/runner/GrailsConsoleState.kt    |  21 ---
 .../grails/runner/GrailsDebuggerRunner.java        | 147 +++++++++++++++++++++
 .../plugins/grails/runner/GrailsDebuggerRunner.kt  | 111 ----------------
 .../plugins/grails/runner/GrailsRunnerSetup.java   | 131 ++++++++++++++++++
 .../runner/impl/BaseGrailsCommandLineState.java    |  53 ++++++++
 .../runner/impl/BaseGrailsCommandLineState.kt      |  37 ------
 .../runner/impl/Grails3GradleCommandExecutor.java  |   2 +-
 .../impl/Grails3InstallationCommandExecutor.java   |   2 +-
 .../runner/impl/Grails3TestCommandLineState.java   | 114 ++++++++++++++++
 .../runner/impl/Grails3TestCommandLineState.kt     |  88 ------------
 .../grails/runner/impl/GrailsCommandLineState.java |   4 +-
 .../runner/impl/GrailsRunAppCommandLineState.java  |  48 +++++++
 .../runner/impl/GrailsRunAppCommandLineState.kt    |  43 ------
 .../runner/impl/GrailsTestAppCommandLineState.java |  35 +++++
 .../runner/impl/GrailsTestAppCommandLineState.kt   |  27 ----
 .../org/jetbrains/plugins/grails/runner/setup.kt   |  97 --------------
 pluginModules/coverage/build.gradle                |   1 -
 ...geExtension.kt => GrailsCoverageExtension.java} |  21 +--
 19 files changed, 599 insertions(+), 438 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.java
new file mode 100644
index 0000000..b3ea7ee
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner;
+
+/**
+ * Persisted state of {@link GrailsConsole}. The accessor names are part of the serialized form —
+ * {@code autoCloseEnabled} is written as an {@code <option>} element — so do not rename them.
+ */
+public final class GrailsConsoleState {
+
+  private boolean autoCloseEnabled = true;
+
+  public GrailsConsoleState() {
+  }
+
+  public GrailsConsoleState(boolean autoCloseEnabled) {
+    this.autoCloseEnabled = autoCloseEnabled;
+  }
+
+  public boolean getAutoCloseEnabled() {
+    return autoCloseEnabled;
+  }
+
+  public void setAutoCloseEnabled(boolean autoCloseEnabled) {
+    this.autoCloseEnabled = autoCloseEnabled;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o || o instanceof GrailsConsoleState other && autoCloseEnabled == other.autoCloseEnabled;
+  }
+
+  @Override
+  public int hashCode() {
+    return Boolean.hashCode(autoCloseEnabled);
+  }
+
+  @Override
+  public String toString() {
+    return "GrailsConsoleState(autoCloseEnabled=" + autoCloseEnabled + ")";
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt
deleted file mode 100644
index e17e29c..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner
-
-data class GrailsConsoleState(
-    var autoCloseEnabled: Boolean = true
-)
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.java
new file mode 100644
index 0000000..d3b7744
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner;
+
+import com.intellij.debugger.impl.GenericDebuggerRunner;
+import com.intellij.debugger.impl.RemoteConnectionBuilder;
+import com.intellij.debugger.settings.DebuggerSettings;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.JavaParameters;
+import com.intellij.execution.configurations.RemoteConnection;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.executors.DefaultDebugExecutor;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.ui.RunContentDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.PathsList;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.runner.impl.GrailsCommandLineState;
+import org.jetbrains.plugins.grails.runner.impl.GrailsRunAppCommandLineState;
+import org.jetbrains.plugins.grails.runner.impl.GrailsTestAppCommandLineState;
+import org.jetbrains.plugins.grails.runner.impl.GrailsTestCommandLineState;
+import org.jetbrains.plugins.grails.structure.Grails3Application;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.impl.Grails2Application;
+import org.jetbrains.plugins.grails.util.version.Version;
+
+import java.util.List;
+
+public class GrailsDebuggerRunner extends GenericDebuggerRunner {
+
+  private static final String DEBUG_KEY_V2_SINGLE = "--debug";
+  private static final String DEBUG_KEY_V2_FORKED = "--debug-fork";
+  private static final String DEBUG_KEY_V3 = "--debug-jvm";
+  private static final long POLL_TIMEOUT = 5 * 60 * 1000L; // 5 minutes
+
+  @Override
+  public @NotNull String getRunnerId() {
+    return getClass().getName();
+  }
+
+  @Override
+  public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
+    if (!DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) || !(profile instanceof GrailsRunConfiguration configuration)) {
+      return false;
+    }
+    GrailsApplication application = configuration.getGrailsApplicationNullable();
+    return application instanceof Grails3Application || application instanceof Grails2Application;
+  }
+
+  @Override
+  protected @Nullable RunContentDescriptor createContentDescriptor(@NotNull RunProfileState state,
+                                                                   @NotNull ExecutionEnvironment environment)
+    throws ExecutionException {
+    if (!(state instanceof GrailsCommandLineState commandLineState)
+        || !(state instanceof GrailsRunAppCommandLineState || state instanceof GrailsTestAppCommandLineState)) {
+      return super.createContentDescriptor(state, environment);
+    }
+
+    RemoteConnection connection;
+    GrailsApplication application = commandLineState.getApplication();
+    if (application instanceof Grails2Application) {
+      if (GrailsRunnerSetup.isForkedDebug(commandLineState)) {
+        // Grails forks the JVM itself, so the debug agent options have to be handed to it as a
+        // build property rather than passed on our own command line.
+        commandLineState.getCommand().getArgs().removeIf(DEBUG_KEY_V2_FORKED::equals);
+        JavaParameters javaParams = commandLineState.getJavaParameters();
+        DebugOptions options = createOptionsAndConnection(javaParams, false, environment.getProject());
+        String propertyName = commandLineState instanceof GrailsTestCommandLineState
+                              ? "grails.project.fork.test.debugArgs"
+                              : "grails.project.fork.run.debugArgs";
+        javaParams.getVMParametersList().addProperty(propertyName, options.vmOptions());
+        connection = options.connection();
+      }
+      else {
+        commandLineState.getCommand().getArgs().removeIf(DEBUG_KEY_V2_SINGLE::equals);
+        JavaParameters javaParams = commandLineState.getJavaParameters();
+        DebugOptions options = createOptionsAndConnection(javaParams, true, environment.getProject());
+        javaParams.getVMParametersList().addParametersString(options.vmOptions());
+        connection = options.connection();
+      }
+    }
+    else if (application instanceof Grails3Application grails3) {
+      if (grails3.getGrailsVersion().isAtLeast(Version.GRAILS_3_1_5) && !Registry.is("grails.simple.debug")) {
+        commandLineState.getCommand().getArgs().removeIf(DEBUG_KEY_V3::equals);
+        JavaParameters javaParams = commandLineState.getJavaParameters();
+        DebugOptions options = createOptionsAndConnection(javaParams, true, environment.getProject());
+        String existing = javaParams.getEnv().get(GrailsRunnerSetup.OPTS_KEY);
+        javaParams.getEnv().put(GrailsRunnerSetup.OPTS_KEY,
+                                StringUtil.isEmptyOrSpaces(existing)
+                                ? options.vmOptions()
+                                : existing + " " + options.vmOptions());
+        connection = options.connection();
+      }
+      else {
+        // Let Grails open the debug port itself and just attach to its fixed default.
+        List<String> args = commandLineState.getCommand().getArgs();
+        if (!args.contains(DEBUG_KEY_V3)) args.add(DEBUG_KEY_V3);
+        connection = new RemoteConnection(true, "localhost", "5005", false);
+      }
+    }
+    else {
+      return null;
+    }
+
+    return attachVirtualMachine(commandLineState, environment, connection, POLL_TIMEOUT);
+  }
+
+  private static @NotNull DebugOptions createOptionsAndConnection(@NotNull JavaParameters javaParams,
+                                                                  boolean asyncDebugger,
+                                                                  @NotNull Project project) throws ExecutionException {
+    // RemoteConnectionBuilder writes the agent options into the parameters it is given; hand it a
+    // delegate so only the resulting option string is taken, not the mutations.
+    JavaParameters javaParamsDelegate = new JavaParameters() {
+      @Override
+      public PathsList getClassPath() {
+        return javaParams.getClassPath();
+      }
+    };
+    javaParamsDelegate.setJdk(javaParams.getJdk());
+    RemoteConnection connection = new RemoteConnectionBuilder(false, DebuggerSettings.SOCKET_TRANSPORT, null)
+      .suspend(false)
+      .asyncAgent(asyncDebugger)
+      .project(project)
+      .create(javaParamsDelegate);
+    return new DebugOptions(connection, javaParamsDelegate.getVMParametersList().getParametersString());
+  }
+
+  private record DebugOptions(@NotNull RemoteConnection connection, @NotNull String vmOptions) {
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt
deleted file mode 100644
index 95d0681..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner
-
-import com.intellij.debugger.impl.GenericDebuggerRunner
-import com.intellij.debugger.impl.RemoteConnectionBuilder
-import com.intellij.debugger.settings.DebuggerSettings
-import com.intellij.execution.configurations.JavaParameters
-import com.intellij.execution.configurations.RemoteConnection
-import com.intellij.execution.configurations.RunProfile
-import com.intellij.execution.configurations.RunProfileState
-import com.intellij.execution.executors.DefaultDebugExecutor
-import com.intellij.execution.runners.ExecutionEnvironment
-import com.intellij.execution.ui.RunContentDescriptor
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.registry.Registry
-import org.jetbrains.plugins.grails.runner.impl.GrailsCommandLineState
-import org.jetbrains.plugins.grails.runner.impl.GrailsRunAppCommandLineState
-import org.jetbrains.plugins.grails.runner.impl.GrailsTestAppCommandLineState
-import org.jetbrains.plugins.grails.runner.impl.GrailsTestCommandLineState
-import org.jetbrains.plugins.grails.structure.Grails3Application
-import org.jetbrains.plugins.grails.structure.impl.Grails2Application
-import org.jetbrains.plugins.grails.util.version.Version.GRAILS_3_1_5
-
-class GrailsDebuggerRunner : GenericDebuggerRunner() {
-
-  private val DEBUG_KEY_v2_SINGLE = "--debug"
-  private val DEBUG_KEY_v2_FORKED = "--debug-fork"
-  private val DEBUG_KEY_v3 = "--debug-jvm"
-  private val POLL_TIMEOUT = 5 * 60 * 1000L // 5 minutes
-
-  override fun getRunnerId(): String = javaClass.name
-
-  override fun canRun(executorId: String, profile: RunProfile): Boolean =
-    executorId == DefaultDebugExecutor.EXECUTOR_ID &&
-    profile is GrailsRunConfiguration &&
-    profile.grailsApplicationNullable.let { it is Grails3Application || it is Grails2Application }
-
-  override fun createContentDescriptor(state: RunProfileState, environment: ExecutionEnvironment): RunContentDescriptor? {
-    if (state !is GrailsCommandLineState || state !is GrailsRunAppCommandLineState && state !is GrailsTestAppCommandLineState) {
-      return super.createContentDescriptor(state, environment)
-    }
-
-    val connection: RemoteConnection = when (val application = state.application) {
-      is Grails2Application -> if (isForkedDebug(state)) {
-        state.command.args.removeIf { it == DEBUG_KEY_v2_FORKED }
-        val javaParams = state.javaParameters
-        val (connection, vmOptions) = createOptionsAndConnection(javaParams, false, environment.project)
-        val propertyName = if (state is GrailsTestCommandLineState) {
-          "grails.project.fork.test.debugArgs"
-        }
-        else {
-          "grails.project.fork.run.debugArgs"
-        }
-        javaParams.vmParametersList.addProperty(propertyName, vmOptions)
-        connection
-      }
-      else {
-        state.command.args.removeIf { it == DEBUG_KEY_v2_SINGLE }
-        val javaParams = state.javaParameters
-        val (connection, vmOptions) = createOptionsAndConnection(javaParams, true, environment.project)
-        javaParams.vmParametersList.addParametersString(vmOptions)
-        connection
-      }
-      is Grails3Application -> if (application.grailsVersion >= GRAILS_3_1_5 && !Registry.`is`("grails.simple.debug")) {
-        state.command.args.removeIf { it == DEBUG_KEY_v3 }
-        val javaParams = state.javaParameters
-        val (connection, vmOptions) = createOptionsAndConnection(javaParams, true, environment.project)
-        val existing = javaParams.env[OPTS_KEY]
-        javaParams.env[OPTS_KEY] = if (existing.isNullOrBlank()) vmOptions else "$existing $vmOptions"
-        connection
-      }
-      else {
-        state.command.args.apply { if (!contains(DEBUG_KEY_v3)) add(DEBUG_KEY_v3) }
-        RemoteConnection(true, "localhost", "5005", false)
-      }
-      else -> return null
-    }
-
-    return attachVirtualMachine(state, environment, connection, POLL_TIMEOUT)
-  }
-
-  private fun createOptionsAndConnection(javaParams: JavaParameters, asyncDebugger: Boolean, project: Project): Pair<RemoteConnection, String> {
-    val javaParamsDelegate = object : JavaParameters() {
-      override fun getClassPath() = javaParams.classPath
-    }
-    javaParamsDelegate.jdk = javaParams.jdk
-    val connection = RemoteConnectionBuilder(false, DebuggerSettings.SOCKET_TRANSPORT, null)
-      .suspend(false)
-      .asyncAgent(asyncDebugger)
-      .project(project)
-      .create(javaParamsDelegate)
-    val vmOptions = javaParamsDelegate.vmParametersList.parametersString
-    return Pair(connection, vmOptions)
-  }
-
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunnerSetup.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunnerSetup.java
new file mode 100644
index 0000000..df53c96
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunnerSetup.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.JavaRunConfigurationExtensionManager;
+import com.intellij.execution.configurations.JavaParameters;
+import com.intellij.execution.configurations.ParametersList;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.gradle.service.execution.GradleInitScriptUtil;
+import org.jetbrains.plugins.grails.runner.impl.GrailsCommandLineState;
+import org.jetbrains.plugins.grails.runner.impl.GrailsTestCommandLineState;
+import org.jetbrains.plugins.grails.structure.Grails3Application;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.impl.Grails2Application;
+import org.jetbrains.plugins.grails.util.version.Version;
+
+/**
+ * Wiring between a {@link GrailsRunConfiguration} and the {@link JavaParameters} the platform
+ * actually launches — chiefly how VM options reach a forked Grails process, which differs per
+ * Grails generation.
+ */
+public final class GrailsRunnerSetup {
+
+  private GrailsRunnerSetup() {
+  }
+
+  @ApiStatus.Internal
+  public static final String OPTS_KEY = "GRAILS_FORK_OPTS";
+
+  private static final String PLAIN_OUTPUT_KEY_V3 = "--plain-output";
+
+  @ApiStatus.Internal
+  public static final String TEST_FRAMEWORK_NAME = "GrailsTests";
+
+  @ApiStatus.Internal
+  public static final String PATH_TO_GRADLE_INIT_SCRIPT_KEY = "INTELLIJ_GRADLE_INIT_SCRIPT";
+
+  /** Written on first use only: creating the init script touches the filesystem. */
+  private static final class InitScriptHolder {
+    static final String PATH = GradleInitScriptUtil.createTestInitScript().toString();
+  }
+
+  @ApiStatus.Internal
+  public static @NotNull String getPathToGradleInitScript() {
+    return InitScriptHolder.PATH;
+  }
+
+  public static boolean canPassVmArgs(@Nullable GrailsApplication application) {
+    return application instanceof Grails2Application
+           || application instanceof Grails3Application grails3
+              && grails3.getGrailsVersion().isAtLeast(Version.GRAILS_3_1_5);
+  }
+
+  public static void setupJavaParameters(@NotNull GrailsRunConfiguration configuration,
+                                        @NotNull GrailsCommandLineState state,
+                                        @NotNull JavaParameters params) throws ExecutionException {
+    // Run the extensions against a throwaway JavaParameters so we can collect the VM options they
+    // contribute and forward them the way this Grails version expects, rather than letting them
+    // land on the launcher process.
+    JavaParameters delegate = new JavaParameters();
+    delegate.setJdk(params.getJdk());
+    delegate.getClassPath().addAll(params.getClassPath().getPathList());
+    JavaRunConfigurationExtensionManager.getInstance()
+      .updateJavaParameters(configuration, delegate, state.getRunnerSettings(), state.getEnvironment().getExecutor());
+    String vmOptions = delegate.getVMParametersList().getParametersString();
+
+    GrailsApplication application = configuration.getGrailsApplication();
+    if (application instanceof Grails2Application) {
+      if (isForkedDebug(state)) {
+        String propertyName = state instanceof GrailsTestCommandLineState
+                              ? "grails.project.fork.test.debugArgs"
+                              : "grails.project.fork.run.debugArgs";
+        params.getVMParametersList().addProperty(propertyName, vmOptions);
+      }
+      else {
+        params.getVMParametersList().addParametersString(vmOptions);
+      }
+    }
+    else if (application instanceof Grails3Application grails3
+             && grails3.getGrailsVersion().isAtLeast(Version.GRAILS_3_1_5)) {
+      String existing = params.getEnv().get(OPTS_KEY);
+      params.getEnv().put(OPTS_KEY, StringUtil.isEmptyOrSpaces(existing) ? vmOptions : existing + " " + vmOptions);
+    }
+  }
+
+  @ApiStatus.Internal
+  public static boolean isForkedDebug(@NotNull GrailsCommandLineState state) {
+    if (!(state.getApplication() instanceof Grails2Application application)) return false;
+    Version version = application.getGrailsVersion();
+    boolean forTests = state instanceof GrailsTestCommandLineState;
+    // There is a bug (https://github.com/grails/grails-core/issues/5641) in grails < 2.3.10
+    // that doesn't allow us to debug "test-app" in forked mode.
+    // We should have ability to debug "run-*" commands in forked mode starting from 2.3.0.
+    //
+    // But there is another bug (https://github.com/grails/grails-core/issues/3294)
+    // that makes no sense to disable forked mode only for tests,
+    // i.e. forked mode must be disabled for "run-*" too.
+    //
+    // Finally we will allow debug tests for >=2.3.10 and debug "run-*" for >=2.3.5,
+    // and force users to disable forked mode in older versions.
+    if (forTests) {
+      return version.isAtLeast(Version.GRAILS_2_3_10) && application.isTestForked();
+    }
+    return version.isAtLeast(Version.GRAILS_2_3_5) && application.isRunForked();
+  }
+
+  @ApiStatus.Internal
+  public static void addPlainOutput(@NotNull ParametersList parameters) {
+    if (Registry.is("grails.add.plain.output") && !parameters.hasParameter(PLAIN_OUTPUT_KEY_V3)) {
+      parameters.add(PLAIN_OUTPUT_KEY_V3);
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.java
new file mode 100644
index 0000000..900c974
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner.impl;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.JavaRunConfigurationExtensionManager;
+import com.intellij.execution.configurations.JavaCommandLineState;
+import com.intellij.execution.process.KillableColoredProcessHandler;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessTerminatedListener;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
+
+public abstract class BaseGrailsCommandLineState extends JavaCommandLineState {
+
+  private final @NotNull GrailsRunConfiguration myConfiguration;
+
+  protected BaseGrailsCommandLineState(@NotNull ExecutionEnvironment environment,
+                                       @NotNull GrailsRunConfiguration configuration) {
+    super(environment);
+    myConfiguration = configuration;
+  }
+
+  public @NotNull GrailsRunConfiguration getConfiguration() {
+    return myConfiguration;
+  }
+
+  /**
+   * @see com.intellij.execution.application.BaseJavaApplicationCommandLineState#startProcess()
+   */
+  @Override
+  protected @NotNull OSProcessHandler startProcess() throws ExecutionException {
+    KillableColoredProcessHandler handler = new KillableColoredProcessHandler(createCommandLine());
+    ProcessTerminatedListener.attach(handler);
+    JavaRunConfigurationExtensionManager.getInstance()
+      .attachExtensionsToProcess(getConfiguration(), handler, getRunnerSettings());
+    return handler;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt
deleted file mode 100644
index 588c19e..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner.impl
-
-import com.intellij.execution.ExecutionException
-import com.intellij.execution.JavaRunConfigurationExtensionManager
-import com.intellij.execution.configurations.JavaCommandLineState
-import com.intellij.execution.process.KillableColoredProcessHandler
-import com.intellij.execution.process.OSProcessHandler
-import com.intellij.execution.process.ProcessTerminatedListener
-import com.intellij.execution.runners.ExecutionEnvironment
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-
-abstract class BaseGrailsCommandLineState(environment: ExecutionEnvironment, val configuration: GrailsRunConfiguration) : JavaCommandLineState(environment) {
-  /**
-   * @see com.intellij.execution.application.BaseJavaApplicationCommandLineState.startProcess()
-   */
-  @Throws(ExecutionException::class)
-  override fun startProcess(): OSProcessHandler = KillableColoredProcessHandler(createCommandLine()).apply {
-    ProcessTerminatedListener.attach(this)
-    JavaRunConfigurationExtensionManager.instance.attachExtensionsToProcess(configuration, this, runnerSettings)
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java
index 1ff1d35..551be4b 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java
@@ -28,7 +28,7 @@ import org.jetbrains.plugins.grails.structure.Grails3Application;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 import org.jetbrains.plugins.groovy.mvc.MvcCommand;
 
-import static org.jetbrains.plugins.grails.runner.SetupKt.addPlainOutput;
+import static org.jetbrains.plugins.grails.runner.GrailsRunnerSetup.addPlainOutput;
 import static org.jetbrains.plugins.grails.runner.util.GrailsExecutionUtils.addCommonJvmOptions;
 
 public final class Grails3GradleCommandExecutor extends GrailsCommandLineExecutor {
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java
index 5fe583e..051b654 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java
@@ -40,7 +40,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import static org.jetbrains.plugins.grails.runner.SetupKt.addPlainOutput;
+import static org.jetbrains.plugins.grails.runner.GrailsRunnerSetup.addPlainOutput;
 import static org.jetbrains.plugins.grails.runner.util.GrailsExecutionUtils.addCommonJvmOptions;
 
 public final class Grails3InstallationCommandExecutor extends GrailsCommandLineExecutor implements GrailsInstallationExecutor {
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.java
new file mode 100644
index 0000000..6e7cb0a
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner.impl;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil;
+import com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerEventsConvertor;
+import com.intellij.execution.testframework.sm.runner.SMTestProxy;
+import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerUIActionsHandler;
+import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
+import com.intellij.execution.ui.ConsoleView;
+import com.intellij.openapi.util.Key;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.gradle.execution.test.runner.GradleConsoleProperties;
+import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsole;
+import org.jetbrains.plugins.gradle.execution.test.runner.events.GradleTestsExecutionConsoleOutputProcessor;
+import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor;
+import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
+import org.jetbrains.plugins.grails.runner.GrailsRunnerSetup;
+import org.jetbrains.plugins.groovy.mvc.MvcCommand;
+
+public class Grails3TestCommandLineState extends GrailsTestAppCommandLineState {
+
+  private volatile MvcCommand myProxyCommand;
+
+  public Grails3TestCommandLineState(@NotNull ExecutionEnvironment environment,
+                                     @NotNull GrailsRunConfiguration configuration,
+                                     @NotNull GrailsCommandLineExecutor executor) throws ExecutionException {
+    super(environment, configuration, executor);
+  }
+
+  @Override
+  public @NotNull MvcCommand getCommand() {
+    MvcCommand cached = myProxyCommand;
+    if (cached != null) return cached;
+    synchronized (this) {
+      if (myProxyCommand == null) {
+        myProxyCommand = createProxyCommand();
+      }
+      return myProxyCommand;
+    }
+  }
+
+  /**
+   * Wraps the real command in Grails' {@code intellij-command-proxy}, which runs it through Gradle
+   * with our init script attached so test events come back in a form the Gradle test console reads.
+   */
+  private @NotNull MvcCommand createProxyCommand() {
+    MvcCommand originalCommand = super.getCommand();
+    MvcCommand command = new MvcCommand("intellij-command-proxy");
+    command.getArgs().add(originalCommand.getCommand());
+    command.getArgs().addAll(originalCommand.getArgs());
+    command.setEnv(originalCommand.getEnv());
+    command.setVmOptions(originalCommand.getVmOptions());
+    command.setEnvVariables(originalCommand.getEnvVariables());
+    command.setPassParentEnvs(originalCommand.isPassParentEnvs());
+    command.getEnvVariables().put(GrailsRunnerSetup.PATH_TO_GRADLE_INIT_SCRIPT_KEY,
+                                 GrailsRunnerSetup.getPathToGradleInitScript());
+    return command;
+  }
+
+  @Override
+  protected @NotNull ConsoleView createConsole(@NotNull Executor executor) {
+    String splitterPropertyName = SMTestRunnerConnectionUtil.getSplitterPropertyName(GrailsRunnerSetup.TEST_FRAMEWORK_NAME);
+    GradleConsoleProperties consoleProperties =
+      new GradleConsoleProperties(getConfiguration(), GrailsRunnerSetup.TEST_FRAMEWORK_NAME, executor);
+    GradleTestsExecutionConsole console = new GradleTestsExecutionConsole(consoleProperties, splitterPropertyName);
+    console.setHelpId("reference.runToolWindow.testResultsTab");
+    console.initUI();
+    console.addAttachToProcessListener(handler -> {
+      SMTestRunnerResultsForm resultsViewer = console.getResultsViewer();
+      resultsViewer.addEventsListener(new SMTRunnerUIActionsHandler(consoleProperties));
+
+      SMTestProxy.SMRootTestProxy rootNode = resultsViewer.getTestsRootNode();
+      rootNode.setHandler(handler);
+
+      GeneralToSMTRunnerEventsConvertor eventsProcessor =
+        new GeneralToSMTRunnerEventsConvertor(consoleProperties.getProject(), rootNode, GrailsRunnerSetup.TEST_FRAMEWORK_NAME);
+      eventsProcessor.addEventsListener(resultsViewer);
+      eventsProcessor.onStartTesting();
+
+      handler.addProcessListener(new ProcessAdapter() {
+        @Override
+        public void processTerminated(@NotNull ProcessEvent event) {
+          eventsProcessor.onFinishTesting();
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes") // ProcessListener declares a raw Key
+        public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
+          GradleTestsExecutionConsoleOutputProcessor.onOutput(console, event.getText(), outputType);
+        }
+      });
+    });
+    return console;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt
deleted file mode 100644
index d260c33..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner.impl
-
-import com.intellij.execution.Executor
-import com.intellij.execution.process.ProcessAdapter
-import com.intellij.execution.process.ProcessEvent
-import com.intellij.execution.runners.ExecutionEnvironment
-import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil.getSplitterPropertyName
-import com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerEventsConvertor
-import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerUIActionsHandler
-import com.intellij.execution.ui.ConsoleView
-import com.intellij.openapi.util.Key
-import org.jetbrains.plugins.gradle.execution.test.runner.GradleConsoleProperties
-import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsole
-import org.jetbrains.plugins.gradle.execution.test.runner.events.GradleTestsExecutionConsoleOutputProcessor
-import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-import org.jetbrains.plugins.grails.runner.pathToGradleInitScript
-import org.jetbrains.plugins.grails.runner.pathToGradleInitScriptKey
-import org.jetbrains.plugins.grails.runner.testFrameworkName
-import org.jetbrains.plugins.groovy.mvc.MvcCommand
-
-class Grails3TestCommandLineState(
-  environment: ExecutionEnvironment,
-  configuration: GrailsRunConfiguration,
-  executor: GrailsCommandLineExecutor
-) : GrailsTestAppCommandLineState(environment, configuration, executor) {
-
-  private val myCommand by lazy {
-    val originalCommand = super.getCommand()
-    MvcCommand("intellij-command-proxy").apply {
-      args += originalCommand.command
-      args += originalCommand.args
-      env = originalCommand.env
-      vmOptions = originalCommand.vmOptions
-      envVariables = originalCommand.envVariables
-      isPassParentEnvs = originalCommand.isPassParentEnvs
-      envVariables[pathToGradleInitScriptKey] = pathToGradleInitScript!!
-    }
-  }
-
-  override fun getCommand(): MvcCommand = myCommand
-
-  override fun createConsole(executor: Executor): ConsoleView {
-    val splitterPropertyName = getSplitterPropertyName(testFrameworkName)
-    val consoleProperties = GradleConsoleProperties(configuration, testFrameworkName, executor)
-    val console = GradleTestsExecutionConsole(consoleProperties, splitterPropertyName)
-    console.setHelpId("reference.runToolWindow.testResultsTab")
-    console.initUI()
-    console.addAttachToProcessListener { handler ->
-      val resultsViewer = console.resultsViewer
-      resultsViewer.addEventsListener(SMTRunnerUIActionsHandler(consoleProperties))
-
-      val rootNode = resultsViewer.testsRootNode
-      rootNode.handler = handler
-
-      val eventsProcessor = GeneralToSMTRunnerEventsConvertor(consoleProperties.project, rootNode, testFrameworkName)
-      eventsProcessor.addEventsListener(resultsViewer)
-      eventsProcessor.onStartTesting()
-
-      handler.addProcessListener(object : ProcessAdapter() {
-        override fun processTerminated(event: ProcessEvent) {
-          eventsProcessor.onFinishTesting()
-        }
-
-        override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
-          GradleTestsExecutionConsoleOutputProcessor.onOutput(console, event.text, outputType)
-        }
-      })
-    }
-    return console
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java
index 38f36da..37a96e6 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java
@@ -29,7 +29,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor;
 import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
 import org.jetbrains.plugins.grails.runner.GrailsRunConfigurationExtension;
-import org.jetbrains.plugins.grails.runner.SetupKt;
+import org.jetbrains.plugins.grails.runner.GrailsRunnerSetup;
 import org.jetbrains.plugins.grails.structure.Grails3Application;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 import org.jetbrains.plugins.grails.util.version.Version;
@@ -97,7 +97,7 @@ public class GrailsCommandLineState extends BaseGrailsCommandLineState {
       }
     }
 
-    SetupKt.setupJavaParameters(getConfiguration(), this, parameters);
+    GrailsRunnerSetup.setupJavaParameters(getConfiguration(), this, parameters);
     return parameters;
   }
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.java
new file mode 100644
index 0000000..b7fc6af
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner.impl;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.ExecutionResult;
+import com.intellij.execution.Executor;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.runners.ProgramRunner;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor;
+import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
+import org.jetbrains.plugins.grails.runner.util.GrailsExecutionUtils;
+
+public class GrailsRunAppCommandLineState extends GrailsCommandLineState {
+
+  public GrailsRunAppCommandLineState(@NotNull ExecutionEnvironment environment,
+                                      @NotNull GrailsRunConfiguration configuration,
+                                      @NotNull GrailsCommandLineExecutor executor) throws ExecutionException {
+    super(environment, configuration, executor);
+  }
+
+  @Override
+  public @NotNull ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner<?> runner)
+    throws ExecutionException {
+    ExecutionResult result = super.execute(executor, runner);
+    ProcessHandler handler = result.getProcessHandler();
+    if (handler != null && getConfiguration().isLaunchBrowser()) {
+      handler.addProcessListener(
+        GrailsExecutionUtils.getBrowserLaunchListener(handler, getConfiguration().getLaunchBrowserUrl()));
+    }
+    return result;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt
deleted file mode 100644
index c24800b..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner.impl
-
-import com.intellij.execution.ExecutionException
-import com.intellij.execution.ExecutionResult
-import com.intellij.execution.Executor
-import com.intellij.execution.runners.ExecutionEnvironment
-import com.intellij.execution.runners.ProgramRunner
-import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-import org.jetbrains.plugins.grails.runner.util.GrailsExecutionUtils
-
-class GrailsRunAppCommandLineState(
-  environment: ExecutionEnvironment,
-  configuration: GrailsRunConfiguration,
-  executor: GrailsCommandLineExecutor
-) : GrailsCommandLineState(environment, configuration, executor) {
-
-  @Throws(ExecutionException::class)
-  override fun execute(executor: Executor, runner: ProgramRunner<*>): ExecutionResult {
-    val result = super.execute(executor, runner)
-    val handler = result.processHandler
-    if (handler != null && configuration.isLaunchBrowser) {
-      handler.addProcessListener(GrailsExecutionUtils.getBrowserLaunchListener(handler, configuration.launchBrowserUrl))
-    }
-    return result
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.java
new file mode 100644
index 0000000..8f6b8dd
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.runner.impl;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor;
+import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
+
+/**
+ * Marker base for the "test-app" states, letting the debugger runner recognise a test launch
+ * regardless of which Grails generation produced it.
+ */
+public abstract class GrailsTestAppCommandLineState extends GrailsCommandLineState {
+
+  protected GrailsTestAppCommandLineState(@NotNull ExecutionEnvironment environment,
+                                          @NotNull GrailsRunConfiguration configuration,
+                                          @NotNull GrailsCommandLineExecutor executor) throws ExecutionException {
+    super(environment, configuration, executor);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt
deleted file mode 100644
index 2b930c2..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner.impl
-
-import com.intellij.execution.runners.ExecutionEnvironment
-import org.jetbrains.plugins.grails.runner.GrailsCommandLineExecutor
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-
-abstract class GrailsTestAppCommandLineState(
-  environment: ExecutionEnvironment,
-  configuration: GrailsRunConfiguration,
-  executor: GrailsCommandLineExecutor
-) : GrailsCommandLineState(environment, configuration, executor)
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/setup.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/setup.kt
deleted file mode 100644
index 5348463..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/setup.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.runner
-
-import com.intellij.execution.JavaRunConfigurationExtensionManager
-import com.intellij.execution.configurations.JavaParameters
-import com.intellij.execution.configurations.ParametersList
-import com.intellij.openapi.util.registry.Registry
-import org.jetbrains.plugins.gradle.service.execution.createTestInitScript
-import org.jetbrains.plugins.grails.runner.impl.GrailsCommandLineState
-import org.jetbrains.plugins.grails.runner.impl.GrailsTestCommandLineState
-import org.jetbrains.plugins.grails.structure.Grails3Application
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.impl.Grails2Application
-import org.jetbrains.plugins.grails.util.version.Version.GRAILS_3_1_5
-
-internal const val OPTS_KEY = "GRAILS_FORK_OPTS"
-private const val plainOutputKeyV3 = "--plain-output"
-
-internal const val testFrameworkName = "GrailsTests"
-internal const val pathToGradleInitScriptKey = "INTELLIJ_GRADLE_INIT_SCRIPT"
-internal val pathToGradleInitScript: String by lazy {
-  createTestInitScript().toString()
-}
-
-fun GrailsApplication?.canPassVmArgs(): Boolean = this is Grails2Application || this is Grails3Application && this.grailsVersion >= GRAILS_3_1_5
-
-fun setupJavaParameters(configuration: GrailsRunConfiguration, state: GrailsCommandLineState, params: JavaParameters) {
-  val delegate = JavaParameters().apply {
-    jdk = params.jdk
-    classPath.addAll(params.classPath.pathList)
-  }
-  JavaRunConfigurationExtensionManager.instance.updateJavaParameters(configuration, delegate, state.runnerSettings,
-                                                                     state.environment.executor)
-  val vmOptions = delegate.vmParametersList.parametersString
-
-  when (val application = configuration.grailsApplication) {
-    is Grails2Application -> if (isForkedDebug(state)) {
-      val propertyName = if (state is GrailsTestCommandLineState) {
-        "grails.project.fork.test.debugArgs"
-      }
-      else {
-        "grails.project.fork.run.debugArgs"
-      }
-      params.vmParametersList.addProperty(propertyName, vmOptions)
-    }
-    else {
-      params.vmParametersList.addParametersString(vmOptions)
-    }
-    is Grails3Application -> if (application.grailsVersion >= GRAILS_3_1_5) {
-      val existing = params.env[OPTS_KEY]
-      params.env[OPTS_KEY] = if (existing.isNullOrBlank()) vmOptions else "$existing $vmOptions"
-    }
-  }
-}
-
-internal fun isForkedDebug(state: GrailsCommandLineState): Boolean {
-  val application = state.application
-  if (application !is Grails2Application) return false
-  val version = application.grailsVersion
-  val forTests = state is GrailsTestCommandLineState
-  // There is a bug (https://github.com/grails/grails-core/issues/5641) in grails < 2.3.10
-  // that doesn't allow us to debug "test-app" in forked mode.
-  // We should have ability to debug "run-*" commands in forked mode starting from 2.3.0.
-  //
-  // But there is another bug (https://github.com/grails/grails-core/issues/3294)
-  // that makes no sense to disable forked mode only for tests,
-  // i.e. forked mode must be disabled for "run-*" too.
-  //
-  // Finally we will allow debug tests for >=2.3.10 and debug "run-*" for >=2.3.5,
-  // and force users to disable forked mode in older versions.
-  return if (forTests)
-    version >= org.jetbrains.plugins.grails.util.version.Version.GRAILS_2_3_10 && application.isTestForked
-  else {
-    version >= org.jetbrains.plugins.grails.util.version.Version.GRAILS_2_3_5 && application.isRunForked
-  }
-}
-
-internal fun addPlainOutput(parameters: ParametersList) {
-  if (Registry.`is`("grails.add.plain.output") && !parameters.hasParameter(plainOutputKeyV3)) {
-    parameters.add(plainOutputKeyV3)
-  }
-}
diff --git a/pluginModules/coverage/build.gradle b/pluginModules/coverage/build.gradle
index 9339856..e8c5ffb 100644
--- a/pluginModules/coverage/build.gradle
+++ b/pluginModules/coverage/build.gradle
@@ -15,7 +15,6 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.kotlin'
 }
 
 dependencies {
diff --git a/pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt b/pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.java
similarity index 58%
rename from pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt
rename to pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.java
index 95f1329..05b7525 100644
--- a/pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt
+++ b/pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.java
@@ -13,16 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.intellij.groovy.grails.coverage;
 
-package com.intellij.groovy.grails.coverage
+import com.intellij.coverage.JavaCoverageEngineExtension;
+import com.intellij.execution.configurations.RunConfigurationBase;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
+import org.jetbrains.plugins.grails.runner.GrailsRunnerSetup;
 
-import com.intellij.coverage.JavaCoverageEngineExtension
-import com.intellij.execution.configurations.RunConfigurationBase
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-import org.jetbrains.plugins.grails.runner.canPassVmArgs
+public final class GrailsCoverageExtension extends JavaCoverageEngineExtension {
 
-internal class GrailsCoverageExtension : JavaCoverageEngineExtension() {
-  override fun isApplicableTo(conf: RunConfigurationBase<*>?): Boolean {
-    return conf is GrailsRunConfiguration && conf.grailsApplicationNullable.canPassVmArgs()
+  @Override
+  public boolean isApplicableTo(@Nullable RunConfigurationBase<?> conf) {
+    return conf instanceof GrailsRunConfiguration configuration
+           && GrailsRunnerSetup.canPassVmArgs(configuration.getGrailsApplicationNullable());
   }
-}
\ No newline at end of file
+}


(grails-intellij-plugin) 23/35: Rename the six content modules into the org.apache.grails namespace

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit b2f4c442e0bba8d21067fe61d1f90338870916f7
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 13:14:34 2026 -0400

    Rename the six content modules into the org.apache.grails namespace
    
    Final step of the restructure. intellij.groovy.grails.<x> becomes
    org.apache.grails.intellij.module.<x> across all three places the name is
    duplicated, which the platform requires to agree exactly:
    
      plugin.xml <content><module name="..."/>
      the descriptor file name at each module jar's root
      composedJar.archiveBaseName
    
    Only the third is written by hand -- the convention plugin derives it from
    projectDir.name, so the prefix changed in one place rather than six.
    
    Verified by parsing the built artifact rather than the sources, since this is the
    one change that is invisible to compilation and to the test suite:
    
      declared in <content>          6
      jars in lib/modules/           6
      declared-but-missing           none
      shipped-but-undeclared         none
      descriptor <name>.xml at root  all 6
    
    verifyPlugin passes with no INVALID_PLUGIN, MISSING_DEPENDENCIES or
    COMPATIBILITY_PROBLEMS -- only the pre-existing deprecated/internal API usages that
    the failureLevel deliberately does not gate on. 987 tests / 0 failures, RAT clean.
    
    Two things that needed care. The rename had to be scoped to `name="intellij.groovy.grails.`
    rather than the bare prefix: the Java packages are com.intellij.groovy.grails.* and
    the extension namespace appears as `intellij.grails.commandExecutor` (from
    defaultExtensionNs="org"), neither of which is a module name. And the platform
    module references in <dependencies> -- intellij.spring, intellij.regexp,
    intellij.javaee.platform and friends -- are JetBrains' own and must not be touched.
    
    <content namespace="jetbrains"> is left as-is. The modules no longer sit in a
    JetBrains namespace by name, so that attribute now reads oddly, but its semantics
    are not documented well enough to change safely and module resolution demonstrably
    works without touching it. Worth revisiting if runIde ever shows a resolution
    problem.
---
 .../org.apache.grails.intellij.build.intellij-module.gradle  |  6 +++---
 plugin/src/main/resources/META-INF/plugin.xml                | 12 ++++++------
 pluginModules/copyright/README.md                            |  4 ++--
 ...t.xml => org.apache.grails.intellij.module.copyright.xml} |  0
 pluginModules/coverage/README.md                             |  4 ++--
 ...ge.xml => org.apache.grails.intellij.module.coverage.xml} |  0
 pluginModules/hibernate/README.md                            |  4 ++--
 ...e.xml => org.apache.grails.intellij.module.hibernate.xml} |  0
 pluginModules/i18n/README.md                                 |  4 ++--
 ...s.i18n.xml => org.apache.grails.intellij.module.i18n.xml} |  0
 pluginModules/langInjection/README.md                        |  4 ++--
 ...l => org.apache.grails.intellij.module.langInjection.xml} |  0
 pluginModules/maven/README.md                                |  4 ++--
 ...maven.xml => org.apache.grails.intellij.module.maven.xml} |  0
 14 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle
index a902eac..f0ddab2 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle
@@ -43,8 +43,8 @@ def shortName = projectDir.name
 group = 'org.apache.grails.intellij.module'
 base.archivesName = shortName
 
-// Prefix is changed to 'org.apache.grails.intellij.module.' in the content-module rename step;
-// deriving it here is what keeps the three coupled strings from drifting apart again.
+// Deriving the name here is what keeps the three coupled strings from drifting apart: this must
+// equal the <module name> in plugin.xml and the descriptor file name in this project's resources.
 tasks.named('composedJar') { task ->
-    task.archiveBaseName = "intellij.groovy.grails.$shortName"
+    task.archiveBaseName = "org.apache.grails.intellij.module.$shortName"
 }
diff --git a/plugin/src/main/resources/META-INF/plugin.xml b/plugin/src/main/resources/META-INF/plugin.xml
index ca6f492..aa57a1b 100644
--- a/plugin/src/main/resources/META-INF/plugin.xml
+++ b/plugin/src/main/resources/META-INF/plugin.xml
@@ -57,12 +57,12 @@
   </dependencies>
 
   <content namespace="jetbrains">
-    <module name="intellij.groovy.grails.copyright"/>
-    <module name="intellij.groovy.grails.i18n"/>
-    <module name="intellij.groovy.grails.maven"/>
-    <module name="intellij.groovy.grails.hibernate"/>
-    <module name="intellij.groovy.grails.langInjection"/>
-    <module name="intellij.groovy.grails.coverage"/>
+    <module name="org.apache.grails.intellij.module.copyright"/>
+    <module name="org.apache.grails.intellij.module.i18n"/>
+    <module name="org.apache.grails.intellij.module.maven"/>
+    <module name="org.apache.grails.intellij.module.hibernate"/>
+    <module name="org.apache.grails.intellij.module.langInjection"/>
+    <module name="org.apache.grails.intellij.module.coverage"/>
   </content>
 
   <depends optional="true" config-file="grails-js-integration.xml">JavaScript</depends>
diff --git a/pluginModules/copyright/README.md b/pluginModules/copyright/README.md
index 1f1b693..73c283b 100644
--- a/pluginModules/copyright/README.md
+++ b/pluginModules/copyright/README.md
@@ -13,8 +13,8 @@ nothing from javaee/JSP — see that class for why the JSP original's checks wer
 
 ## Packaging
 
-Content module `intellij.groovy.grails.copyright`, shipped as
-`lib/modules/intellij.groovy.grails.copyright.jar`. The module name, the descriptor file name and the
+Content module `org.apache.grails.intellij.module.copyright`, shipped as
+`lib/modules/org.apache.grails.intellij.module.copyright.jar`. The module name, the descriptor file name and the
 jar base name are one three-way contract — see the `intellij-module` convention plugin.
 
 Requires the `com.intellij.copyright` plugin at runtime.
diff --git a/pluginModules/copyright/src/main/resources/intellij.groovy.grails.copyright.xml b/pluginModules/copyright/src/main/resources/org.apache.grails.intellij.module.copyright.xml
similarity index 100%
rename from pluginModules/copyright/src/main/resources/intellij.groovy.grails.copyright.xml
rename to pluginModules/copyright/src/main/resources/org.apache.grails.intellij.module.copyright.xml
diff --git a/pluginModules/coverage/README.md b/pluginModules/coverage/README.md
index 3a86cdb..92afbce 100644
--- a/pluginModules/coverage/README.md
+++ b/pluginModules/coverage/README.md
@@ -8,8 +8,8 @@ IntelliJ **content module** integrating Grails test runs with the IDE's code-cov
 
 ## Packaging
 
-Content module `intellij.groovy.grails.coverage`, shipped as
-`lib/modules/intellij.groovy.grails.coverage.jar`.
+Content module `org.apache.grails.intellij.module.coverage`, shipped as
+`lib/modules/org.apache.grails.intellij.module.coverage.jar`.
 
 Its descriptor declares `<plugin id="Coverage"/>`. This matters: depending on the
 `com.intellij.modules.coverage` alias instead caused a `NoClassDefFoundError` at IDE startup, because
diff --git a/pluginModules/coverage/src/main/resources/intellij.groovy.grails.coverage.xml b/pluginModules/coverage/src/main/resources/org.apache.grails.intellij.module.coverage.xml
similarity index 100%
rename from pluginModules/coverage/src/main/resources/intellij.groovy.grails.coverage.xml
rename to pluginModules/coverage/src/main/resources/org.apache.grails.intellij.module.coverage.xml
diff --git a/pluginModules/hibernate/README.md b/pluginModules/hibernate/README.md
index 53d304e..b5208a9 100644
--- a/pluginModules/hibernate/README.md
+++ b/pluginModules/hibernate/README.md
@@ -10,7 +10,7 @@ persistence facets, mapping awareness and the Hibernate console.
 
 ## Packaging
 
-Content module `intellij.groovy.grails.hibernate`, shipped as
-`lib/modules/intellij.groovy.grails.hibernate.jar`.
+Content module `org.apache.grails.intellij.module.hibernate`, shipped as
+`lib/modules/org.apache.grails.intellij.module.hibernate.jar`.
 
 Requires the `com.intellij.hibernate` plugin at runtime.
diff --git a/pluginModules/hibernate/src/main/resources/intellij.groovy.grails.hibernate.xml b/pluginModules/hibernate/src/main/resources/org.apache.grails.intellij.module.hibernate.xml
similarity index 100%
rename from pluginModules/hibernate/src/main/resources/intellij.groovy.grails.hibernate.xml
rename to pluginModules/hibernate/src/main/resources/org.apache.grails.intellij.module.hibernate.xml
diff --git a/pluginModules/i18n/README.md b/pluginModules/i18n/README.md
index 021e1c0..afe9496 100644
--- a/pluginModules/i18n/README.md
+++ b/pluginModules/i18n/README.md
@@ -10,8 +10,8 @@ extract-to-bundle intentions and inspections for hard-coded strings in GSP and G
 
 ## Packaging
 
-Content module `intellij.groovy.grails.i18n`, shipped as
-`lib/modules/intellij.groovy.grails.i18n.jar`. Its descriptor also declares
+Content module `org.apache.grails.intellij.module.i18n`, shipped as
+`lib/modules/org.apache.grails.intellij.module.i18n.jar`. Its descriptor also declares
 `<resource-bundle>messages.GrailsBundle</resource-bundle>`.
 
 Requires `com.intellij.java-i18n` and `org.intellij.groovy` at runtime.
diff --git a/pluginModules/i18n/src/main/resources/intellij.groovy.grails.i18n.xml b/pluginModules/i18n/src/main/resources/org.apache.grails.intellij.module.i18n.xml
similarity index 100%
rename from pluginModules/i18n/src/main/resources/intellij.groovy.grails.i18n.xml
rename to pluginModules/i18n/src/main/resources/org.apache.grails.intellij.module.i18n.xml
diff --git a/pluginModules/langInjection/README.md b/pluginModules/langInjection/README.md
index 4952fa7..b293961 100644
--- a/pluginModules/langInjection/README.md
+++ b/pluginModules/langInjection/README.md
@@ -12,8 +12,8 @@ at it from its descriptor with `<injectionConfig config="META-INF/languageInject
 
 ## Packaging
 
-Content module `intellij.groovy.grails.langInjection`, shipped as
-`lib/modules/intellij.groovy.grails.langInjection.jar`.
+Content module `org.apache.grails.intellij.module.langInjection`, shipped as
+`lib/modules/org.apache.grails.intellij.module.langInjection.jar`.
 
 This is the only module whose descriptor depends on a bundled **module** rather than a plugin
 (`<module name="intellij.platform.langInjection"/>`), and the only one with no Java/Kotlin sources at
diff --git a/pluginModules/langInjection/src/main/resources/intellij.groovy.grails.langInjection.xml b/pluginModules/langInjection/src/main/resources/org.apache.grails.intellij.module.langInjection.xml
similarity index 100%
rename from pluginModules/langInjection/src/main/resources/intellij.groovy.grails.langInjection.xml
rename to pluginModules/langInjection/src/main/resources/org.apache.grails.intellij.module.langInjection.xml
diff --git a/pluginModules/maven/README.md b/pluginModules/maven/README.md
index 504f3cf..c421eb2 100644
--- a/pluginModules/maven/README.md
+++ b/pluginModules/maven/README.md
@@ -20,8 +20,8 @@ goal mapping is skipped, because those versions must run tests as
 
 ## Packaging
 
-Content module `intellij.groovy.grails.maven`, shipped as
-`lib/modules/intellij.groovy.grails.maven.jar`.
+Content module `org.apache.grails.intellij.module.maven`, shipped as
+`lib/modules/org.apache.grails.intellij.module.maven.jar`.
 
 Requires `org.jetbrains.idea.maven` and `com.intellij.java` at runtime — the Java debugger classes
 moved into the java plugin modules in 2026.2.
diff --git a/pluginModules/maven/src/main/resources/intellij.groovy.grails.maven.xml b/pluginModules/maven/src/main/resources/org.apache.grails.intellij.module.maven.xml
similarity index 100%
rename from pluginModules/maven/src/main/resources/intellij.groovy.grails.maven.xml
rename to pluginModules/maven/src/main/resources/org.apache.grails.intellij.module.maven.xml


(grails-intellij-plugin) 08/35: Convert the artefact api and impl packages from Kotlin to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 63fa492daed68b96a43884b978a29588467bf50b
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 07:42:07 2026 -0400

    Convert the artefact api and impl packages from Kotlin to Java
    
    First batch of the Kotlin -> Java source conversion: 11 Kotlin files become 12
    Java files. Verified against the pre-conversion baseline -- 987 tests, 0
    failures, no test classes lost -- and the RAT audit still passes.
    
    Conversion decisions that the rest of the batches will follow:
    
    * Kotlin `object` singletons become final classes with a public static INSTANCE
      and a private constructor. Existing Java already called
      ServiceArtefactHandler.INSTANCE, so this shape is load-bearing.
    * Kotlin `val` members become getters, so remaining Kotlin callers keep working
      through synthetic property access and only bare top-level references need
      editing.
    * Top-level declarations, which the compiler otherwise exposes as <File>Kt,
      become explicitly named holders: constants.kt splits into ArtefactHandlers
      (the extension point and handler list) and HandlerCache (the @Service), and
      artefact/impl/util.kt becomes GrailsArtefacts.
    * Extension functions become static methods taking the receiver first --
      handler.getArtefacts(app, scope) is now
      GrailsArtefacts.getArtefacts(handler, app, scope).
    * HandlerCache keeps its lazily-computed maps. They stay lazy because handlers
      contributed through the extension point are not registered when the service
      is constructed; the fields are volatile so the idempotent recompute in a race
      is published safely.
    
    Call sites updated in GrailsIconProvider.java, projectView/impl/impl.kt,
    GrailsAppNodeProvider.kt, OtherGrailsAppSourcesNode.kt, and
    GrailsArtefactHandlerNodeBase.kt.
---
 .../grails/artefact/api/ArtefactHandlers.java      |  67 +++++
 .../grails/artefact/api/GrailsArtefactHandler.java |  42 +++
 .../grails/artefact/api/GrailsArtefactHandler.kt   |  32 ---
 .../api/GrailsDisplayableArtefactHandler.java      |  47 ++++
 .../api/GrailsDisplayableArtefactHandler.kt        |  36 ---
 .../plugins/grails/artefact/api/HandlerCache.java  |  66 +++++
 .../artefact/api/{IconOwner.kt => IconOwner.java}  |  11 +-
 .../plugins/grails/artefact/api/constants.kt       |  68 -----
 .../artefact/impl/ControllerArtefactHandler.java   |  80 ++++++
 .../artefact/impl/ControllerArtefactHandler.kt     |  46 ----
 .../artefact/impl/DomainArtefactHandler.java       |  77 ++++++
 .../grails/artefact/impl/DomainArtefactHandler.kt  |  43 ---
 .../impl/FilterArtefactHandler.java}               |  40 +--
 .../grails/artefact/impl/FilterArtefactHandler.kt  |  34 ---
 .../grails/artefact/impl/GrailsArtefacts.java      | 288 +++++++++++++++++++++
 .../artefact/impl/InterceptorArtefactHandler.java  |  66 +++++
 .../artefact/impl/InterceptorArtefactHandler.kt    |  40 ---
 .../artefact/impl/ServiceArtefactHandler.java      |  60 +++++
 .../grails/artefact/impl/ServiceArtefactHandler.kt |  37 ---
 .../artefact/impl/TaglibArtefactHandler.java       |  67 +++++
 .../grails/artefact/impl/TaglibArtefactHandler.kt  |  39 ---
 .../jetbrains/plugins/grails/artefact/impl/util.kt | 203 ---------------
 .../grails/projectView/GrailsIconProvider.java     |   4 +-
 .../projectView/impl/GrailsAppNodeProvider.kt      |   4 +-
 .../plugins/grails/projectView/impl/impl.kt        |   4 +-
 .../nodes/GrailsArtefactHandlerNodeBase.kt         |   4 +-
 .../projectView/nodes/OtherGrailsAppSourcesNode.kt |   4 +-
 27 files changed, 899 insertions(+), 610 deletions(-)

diff --git a/src/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java b/src/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java
new file mode 100644
index 0000000..0c39660
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.api;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.artefact.impl.ControllerArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.DomainArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.FilterArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.InterceptorArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.ServiceArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.TaglibArtefactHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class ArtefactHandlers {
+
+  private static final ExtensionPointName<GrailsArtefactHandler> EP_NAME =
+    ExtensionPointName.create("org.intellij.grails.artefactHandler");
+
+  private static final List<GrailsArtefactHandler> NAMED_HANDLERS = List.of(
+    DomainArtefactHandler.INSTANCE,
+    ControllerArtefactHandler.INSTANCE,
+    ServiceArtefactHandler.INSTANCE,
+    TaglibArtefactHandler.INSTANCE,
+    InterceptorArtefactHandler.INSTANCE,
+    FilterArtefactHandler.INSTANCE
+  );
+
+  private ArtefactHandlers() {
+  }
+
+  /**
+   * The built-in handlers followed by any contributed through the extension point. The
+   * extension list is read on every call, so handlers registered later are picked up.
+   */
+  public static @NotNull List<GrailsArtefactHandler> allHandlers() {
+    List<GrailsArtefactHandler> result = new ArrayList<>(NAMED_HANDLERS);
+    result.addAll(EP_NAME.getExtensionList());
+    return result;
+  }
+
+  public static @NotNull List<GrailsDisplayableArtefactHandler> displayableArtefactHandlers() {
+    List<GrailsDisplayableArtefactHandler> result = new ArrayList<>();
+    for (GrailsArtefactHandler handler : allHandlers()) {
+      if (handler instanceof GrailsDisplayableArtefactHandler displayable) {
+        result.add(displayable);
+      }
+    }
+    return result;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java
new file mode 100644
index 0000000..d76a8cf
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.api;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface GrailsArtefactHandler {
+
+  @NotNull String getArtefactHandlerID();
+
+  default @NotNull String getArtefactClassSuffix() {
+    return getArtefactHandlerID();
+  }
+
+  default @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return null;
+  }
+
+  default @NotNull Collection<String> getAnnotationFqns() {
+    return List.of();
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.kt
deleted file mode 100644
index 0949ea5..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.api
-
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-interface GrailsArtefactHandler {
-
-  val artefactHandlerID: String
-
-  val artefactClassSuffix: String get() = artefactHandlerID
-
-  fun getDirectory(application: GrailsApplication): VirtualFile? = null
-
-  val annotationFqns: Collection<String> get() = emptyList()
-
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java
new file mode 100644
index 0000000..d83087f
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.api;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import javax.swing.Icon;
+
+public interface GrailsDisplayableArtefactHandler extends GrailsArtefactHandler, IconOwner {
+
+  default @NotNull Icon getGroupIcon() {
+    return getIcon();
+  }
+
+  @NotNull String getTitle();
+
+  default int getWeight() {
+    return 0;
+  }
+
+  default boolean isVisible(@NotNull GrailsApplication application) {
+    return true;
+  }
+
+  default @Nullable AbstractTreeNode<?> createNode(@NotNull PsiClass artefact, @NotNull ViewSettings settings) {
+    return null;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.kt
deleted file mode 100644
index 9746810..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.api
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.psi.PsiClass
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-interface GrailsDisplayableArtefactHandler : GrailsArtefactHandler, IconOwner {
-
-  val groupIcon: Icon get() = icon
-
-  val title: String
-
-  val weight: Int get() = 0
-
-  fun isVisible(application: GrailsApplication): Boolean = true
-
-  fun createNode(artefact: PsiClass, settings: ViewSettings): AbstractTreeNode<*>? = null
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java b/src/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java
new file mode 100644
index 0000000..fee0dd7
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.api;
+
+import com.intellij.openapi.components.Service;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@ApiStatus.Internal
+@Service(Service.Level.APP)
+public final class HandlerCache {
+
+  // Computed on first use rather than in the constructor, because handlers contributed
+  // through the extension point are not necessarily registered when the service is created.
+  // The computations are idempotent, so a race just recomputes the same map; volatile is
+  // only here to publish the result safely.
+  private volatile Map<String, GrailsArtefactHandler> idToHandler;
+  private volatile Map<String, GrailsArtefactHandler> annotationToHandler;
+
+  public @NotNull Map<String, GrailsArtefactHandler> getIdToHandler() {
+    Map<String, GrailsArtefactHandler> result = idToHandler;
+    if (result == null) {
+      result = new LinkedHashMap<>();
+      for (GrailsArtefactHandler handler : ArtefactHandlers.allHandlers()) {
+        result.put(handler.getArtefactHandlerID(), handler);
+      }
+      idToHandler = result;
+    }
+    return result;
+  }
+
+  /**
+   * Maps every annotation FQN a handler claims to that handler. A handler may declare more
+   * than one annotation, and a later handler claiming the same annotation wins.
+   */
+  public @NotNull Map<String, GrailsArtefactHandler> getAnnotationToHandler() {
+    Map<String, GrailsArtefactHandler> result = annotationToHandler;
+    if (result == null) {
+      result = new LinkedHashMap<>();
+      for (GrailsArtefactHandler handler : ArtefactHandlers.allHandlers()) {
+        for (String fqn : handler.getAnnotationFqns()) {
+          result.put(fqn, handler);
+        }
+      }
+      annotationToHandler = result;
+    }
+    return result;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/IconOwner.kt b/src/org/jetbrains/plugins/grails/artefact/api/IconOwner.java
similarity index 78%
rename from src/org/jetbrains/plugins/grails/artefact/api/IconOwner.kt
rename to src/org/jetbrains/plugins/grails/artefact/api/IconOwner.java
index e0d7a78..ae52f2e 100644
--- a/src/org/jetbrains/plugins/grails/artefact/api/IconOwner.kt
+++ b/src/org/jetbrains/plugins/grails/artefact/api/IconOwner.java
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package org.jetbrains.plugins.grails.artefact.api
+package org.jetbrains.plugins.grails.artefact.api;
 
-import javax.swing.Icon
+import org.jetbrains.annotations.NotNull;
 
-interface IconOwner {
+import javax.swing.Icon;
 
-  val icon: Icon
+public interface IconOwner {
 
-}
\ No newline at end of file
+  @NotNull Icon getIcon();
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/constants.kt b/src/org/jetbrains/plugins/grails/artefact/api/constants.kt
deleted file mode 100644
index 5c59bd1..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/api/constants.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.api
-
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.extensions.ExtensionPointName
-import org.jetbrains.plugins.grails.artefact.impl.ControllerArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.DomainArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.FilterArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.InterceptorArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.ServiceArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.TaglibArtefactHandler
-
-private val EP_NAME = ExtensionPointName<GrailsArtefactHandler>("org.intellij.grails.artefactHandler")
-
-private val namedHandlers = sequenceOf<GrailsArtefactHandler>(
-  DomainArtefactHandler,
-  ControllerArtefactHandler,
-  ServiceArtefactHandler,
-  TaglibArtefactHandler,
-  InterceptorArtefactHandler,
-  FilterArtefactHandler
-)
-
-@Service(Service.Level.APP)
-internal class HandlerCache {
-  val idToHandler by lazy(LazyThreadSafetyMode.NONE) {
-    allHandlers.associateBy { it.artefactHandlerID }
-  }
-
-  val annotationToHandler: Map<String, GrailsArtefactHandler> by lazy(LazyThreadSafetyMode.NONE) {
-    allHandlers.associateByM { it.annotationFqns }
-  }
-}
-
-internal val allHandlers: Sequence<GrailsArtefactHandler>
-  get() {
-    return namedHandlers.plus(EP_NAME.extensionList)
-  }
-
-internal val displayableArtefactHandlers: Sequence<GrailsDisplayableArtefactHandler>
-  get() {
-    return allHandlers.filterIsInstance(GrailsDisplayableArtefactHandler::class.java)
-  }
-
-private inline fun <T, K> Sequence<T>.associateByM(keySelector: (T) -> Iterable<K>): Map<K, T> {
-  val result = mutableMapOf<K, T>()
-  for (handler in this) {
-    for (fqn in keySelector(handler)) {
-      result[fqn] = handler
-    }
-  }
-  return result
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java
new file mode 100644
index 0000000..eaa3fbe
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.impl;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsControllerNode;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import javax.swing.Icon;
+import java.util.Collection;
+import java.util.List;
+
+public final class ControllerArtefactHandler implements GrailsDisplayableArtefactHandler {
+
+  public static final ControllerArtefactHandler INSTANCE = new ControllerArtefactHandler();
+
+  private ControllerArtefactHandler() {
+  }
+
+  @Override
+  public @NotNull String getArtefactHandlerID() {
+    return "Controller";
+  }
+
+  @Override
+  public @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return application.getAppRoot().findChild("controllers");
+  }
+
+  @Override
+  public @NotNull Collection<String> getAnnotationFqns() {
+    return List.of("grails.web.Controller");
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return AllIcons.Nodes.Controller;
+  }
+
+  @Override
+  public @NotNull Icon getGroupIcon() {
+    return AllIcons.Nodes.KeymapTools;
+  }
+
+  @Override
+  public @NotNull String getTitle() {
+    return "Controllers";
+  }
+
+  @Override
+  public int getWeight() {
+    return NodeWeights.CONTROLLERS_FOLDER;
+  }
+
+  @Override
+  public @NotNull GrailsControllerNode createNode(@NotNull PsiClass artefact, @NotNull ViewSettings settings) {
+    return new GrailsControllerNode(artefact, settings);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.kt
deleted file mode 100644
index 13525ec..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.icons.AllIcons
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiClass
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsControllerNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-object ControllerArtefactHandler : GrailsDisplayableArtefactHandler {
-
-  override val artefactHandlerID: String = "Controller"
-
-  override fun getDirectory(application: GrailsApplication): VirtualFile? = application.appRoot.findChild("controllers")
-
-  override val annotationFqns: Collection<String> get() = listOf("grails.web.Controller")
-
-  override val icon: Icon = AllIcons.Nodes.Controller
-
-  override val groupIcon: Icon get() = AllIcons.Nodes.KeymapTools
-
-  override val title: String = "Controllers"
-
-  override val weight: Int = NodeWeights.CONTROLLERS_FOLDER
-
-  override fun createNode(artefact: PsiClass, settings: ViewSettings): GrailsControllerNode = GrailsControllerNode(artefact, settings)
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java
new file mode 100644
index 0000000..fbba46a
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.impl;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import javax.swing.Icon;
+import java.util.Collection;
+import java.util.List;
+
+public final class DomainArtefactHandler implements GrailsDisplayableArtefactHandler {
+
+  public static final DomainArtefactHandler INSTANCE = new DomainArtefactHandler();
+
+  private DomainArtefactHandler() {
+  }
+
+  @Override
+  public @NotNull String getArtefactHandlerID() {
+    return "Domain";
+  }
+
+  @Override
+  public @NotNull String getArtefactClassSuffix() {
+    return "";
+  }
+
+  @Override
+  public @NotNull Collection<String> getAnnotationFqns() {
+    return List.of("grails.persistence.Entity", "grails.gorm.annotation.Entity");
+  }
+
+  @Override
+  public @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return application.getAppRoot().findChild("domain");
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return AllIcons.Nodes.DataTables;
+  }
+
+  @Override
+  public @NotNull Icon getGroupIcon() {
+    return AllIcons.Nodes.Models;
+  }
+
+  @Override
+  public @NotNull String getTitle() {
+    return "Domain Classes";
+  }
+
+  @Override
+  public int getWeight() {
+    return NodeWeights.DOMAIN_CLASSES_FOLDER;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.kt
deleted file mode 100644
index 6181d73..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-object DomainArtefactHandler : GrailsDisplayableArtefactHandler {
-
-  override val artefactHandlerID: String = "Domain"
-
-  override val artefactClassSuffix: String = ""
-
-  override val annotationFqns: Collection<String> = listOf("grails.persistence.Entity", "grails.gorm.annotation.Entity")
-
-  override fun getDirectory(application: GrailsApplication): VirtualFile? = application.appRoot.findChild("domain")
-
-  override val icon: Icon = AllIcons.Nodes.DataTables
-
-  override val groupIcon: Icon = AllIcons.Nodes.Models
-
-  override val title: String = "Domain Classes"
-
-  override val weight: Int = NodeWeights.DOMAIN_CLASSES_FOLDER
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java b/src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java
similarity index 51%
copy from src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
copy to src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java
index 969a334..858e227 100644
--- a/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java
@@ -14,31 +14,37 @@
  * limitations under the License.
  */
 
-package org.jetbrains.plugins.grails.projectView;
+package org.jetbrains.plugins.grails.artefact.impl;
 
-import com.intellij.ide.IconProvider;
-import com.intellij.openapi.util.Iconable;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiElement;
-import com.intellij.ui.IconManager;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.vfs.VirtualFile;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler;
 import org.jetbrains.plugins.grails.artefact.api.IconOwner;
-import org.jetbrains.plugins.grails.artefact.impl.UtilKt;
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
 
 import javax.swing.Icon;
 
-final class GrailsIconProvider extends IconProvider {
+public final class FilterArtefactHandler implements GrailsArtefactHandler, IconOwner {
+
+  public static final FilterArtefactHandler INSTANCE = new FilterArtefactHandler();
+
+  private FilterArtefactHandler() {
+  }
+
+  @Override
+  public @NotNull String getArtefactHandlerID() {
+    return "Filters";
+  }
+
+  @Override
+  public @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return application.getAppRoot().findChild("conf");
+  }
+
   @Override
-  public @Nullable Icon getIcon(@NotNull PsiElement element, @Iconable.IconFlags int flags) {
-    if (element instanceof GrClassDefinition) {
-      GrailsArtefactHandler handler = UtilKt.getArtefactHandler((PsiClass)element);
-      if (handler instanceof IconOwner) {
-        return IconManager.getInstance().createLayeredIcon(element, ((IconOwner)handler).getIcon(), flags);
-      }
-    }
-    return null;
+  public @NotNull Icon getIcon() {
+    return AllIcons.General.Filter;
   }
 }
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.kt
deleted file mode 100644
index 3303c38..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler
-import org.jetbrains.plugins.grails.artefact.api.IconOwner
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-object FilterArtefactHandler : GrailsArtefactHandler, IconOwner {
-
-  override val artefactHandlerID: String = "Filters"
-
-  override fun getDirectory(application: GrailsApplication): VirtualFile? = application.appRoot.findChild("conf")
-
-  override val icon: Icon = AllIcons.General.Filter
-
-}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java b/src/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java
new file mode 100644
index 0000000..f8be845
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.impl;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiModifierList;
+import com.intellij.psi.PsiPackage;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.GlobalSearchScopesCore;
+import com.intellij.psi.search.searches.AllClassesSearch;
+import com.intellij.psi.search.searches.AnnotatedElementsSearch;
+import com.intellij.psi.util.CachedValue;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.util.PsiModificationTracker;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.util.containers.FactoryMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers;
+import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.api.HandlerCache;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+import org.jetbrains.plugins.groovy.GroovyFileType;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
+import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+public final class GrailsArtefacts {
+
+  private static final String GRAILS_ARTEFACT_ARTEFACT = "grails.artefact.Artefact";
+
+  /** One cache key per handler, so each handler's artefacts are cached independently. */
+  private static final Map<GrailsArtefactHandler, Key<CachedValue<Map<GlobalSearchScope, Collection<PsiClass>>>>> KEYS =
+    FactoryMap.create(handler -> Key.create("grails.artefact.cache." + handler.getClass().getName()));
+
+  private GrailsArtefacts() {
+  }
+
+  public static @NotNull Collection<PsiClass> getArtefacts(@NotNull GrailsArtefactHandler handler,
+                                                           @NotNull GrailsApplication application,
+                                                           @NotNull GlobalSearchScope scope) {
+    Map<GlobalSearchScope, Collection<PsiClass>> perScope = CachedValuesManager
+      .getManager(application.getProject())
+      .getCachedValue(application, KEYS.get(handler),
+                      () -> Result.create(createCache(handler, application), PsiModificationTracker.MODIFICATION_COUNT),
+                      false);
+    Collection<PsiClass> result = perScope.get(scope);
+    return result != null ? result : List.of();
+  }
+
+  private static @NotNull Map<GlobalSearchScope, Collection<PsiClass>> createCache(@NotNull GrailsArtefactHandler handler,
+                                                                                   @NotNull GrailsApplication application) {
+    return FactoryMap.create(scope -> ApplicationManager.getApplication()
+      .runReadAction((com.intellij.openapi.util.Computable<Collection<PsiClass>>)() -> doGetArtefacts(handler, application, scope)));
+  }
+
+  private static @NotNull Collection<PsiClass> doGetArtefacts(@NotNull GrailsArtefactHandler handler,
+                                                              @NotNull GrailsApplication application,
+                                                              @NotNull GlobalSearchScope scope) {
+    Collection<PsiClass> result = new LinkedHashSet<>();
+
+    Collection<PsiClass> conventional = collectConventionalArtefacts(handler, application, scope);
+    if (conventional != null) result.addAll(conventional);
+
+    Collection<PsiClass> compiled = collectCompiledArtefacts(handler, application, scope);
+    if (compiled != null) result.addAll(compiled);
+
+    Collection<PsiClass> annotated = collectAnnotatedArtefacts(handler, application, scope);
+    if (annotated != null) result.addAll(annotated);
+
+    result.addAll(collectSpecificAnnotatedArtefacts(handler, application, scope));
+
+    return result;
+  }
+
+  /**
+   * PsiClass is considered a convention artefact when all the following us true:
+   * - its name has specified suffix
+   * - its name if the same as the name of file
+   * - it is defined in a groovy file which lies under specified directory
+   */
+  private static @Nullable Collection<PsiClass> collectConventionalArtefacts(@NotNull GrailsArtefactHandler handler,
+                                                                             @NotNull GrailsApplication application,
+                                                                             @NotNull GlobalSearchScope scope) {
+    Project project = application.getProject();
+    VirtualFile directory = handler.getDirectory(application);
+    if (directory == null) return null;
+
+    GlobalSearchScope artefactDirectoryScope = GlobalSearchScopesCore.directoryScope(project, directory, true);
+    GlobalSearchScope resultScope = artefactDirectoryScope.intersectWith(scope);
+    String suffix = handler.getArtefactClassSuffix();
+
+    List<PsiClass> result = new ArrayList<>();
+    for (PsiClass artefactClass : AllClassesSearch.search(resultScope, project, className -> className.endsWith(suffix)).findAll()) {
+      VirtualFile file = virtualFileOf(artefactClass);
+      if (file != null
+          && file.getNameWithoutExtension().equals(artefactClass.getName())
+          && file.getFileType() == GroovyFileType.GROOVY_FILE_TYPE) {
+        result.add(artefactClass);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Collects classes defined in META-INF/grails-plugin.xml
+   */
+  private static @Nullable Collection<PsiClass> collectCompiledArtefacts(@NotNull GrailsArtefactHandler handler,
+                                                                         @NotNull GrailsApplication application,
+                                                                         @NotNull GlobalSearchScope scope) {
+    Project project = application.getProject();
+    JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
+    PsiPackage metaInfPackage = facade.findPackage("META-INF");
+    if (metaInfPackage == null) return null;
+
+    String suffix = handler.getArtefactClassSuffix();
+    List<PsiClass> result = new ArrayList<>();
+
+    for (PsiDirectory directory : metaInfPackage.getDirectories(scope)) {
+      if (!(directory.findFile("grails-plugin.xml") instanceof XmlFile file)) continue;
+      XmlTag rootTag = file.getRootTag();
+      if (rootTag == null) continue;
+      XmlTag[] resourcesTags = rootTag.findSubTags("resources");
+      if (resourcesTags.length == 0) continue;
+
+      for (XmlTag tag : resourcesTags[0].getSubTags()) {
+        if (!"resource".equals(tag.getName())) continue;
+        String fqn = tag.getValue().getTrimmedText();
+        if (fqn.endsWith(suffix)) {
+          PsiClass found = facade.findClass(fqn, scope);
+          if (found != null) result.add(found);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Collects classes annotated with @Artefact("artefactHandlerId"), where artefactId corresponds to the current artefact handler.
+   */
+  private static @Nullable Collection<PsiClass> collectAnnotatedArtefacts(@NotNull GrailsArtefactHandler handler,
+                                                                          @NotNull GrailsApplication application,
+                                                                          @NotNull GlobalSearchScope scope) {
+    Project project = application.getProject();
+    GlobalSearchScope annotationClassSearchScope = application.getScope(true, false);
+
+    PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(GRAILS_ARTEFACT_ARTEFACT, annotationClassSearchScope);
+    if (annotationClass == null) return null;
+
+    String suffix = handler.getArtefactClassSuffix();
+    List<PsiClass> result = new ArrayList<>();
+
+    for (PsiClass artefactClass : AnnotatedElementsSearch.searchPsiClasses(annotationClass, scope).findAll()) {
+      VirtualFile file = virtualFileOf(artefactClass);
+      if (file == null || file.getFileType() != GroovyFileType.GROOVY_FILE_TYPE) continue;
+
+      String name = artefactClass.getName();
+      if (name == null || !name.endsWith(suffix)) continue;
+
+      PsiModifierList modifierList = artefactClass.getModifierList();
+      if (modifierList == null) continue;
+      PsiAnnotation annotation = modifierList.findAnnotation(GRAILS_ARTEFACT_ARTEFACT);
+      if (annotation == null) continue;
+
+      if (handler.getArtefactHandlerID().equals(GrAnnotationUtil.inferStringAttribute(annotation, "value"))) {
+        result.add(artefactClass);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Collects classes annotated with artefact specific annotation, i.e. @Controller or @Taglib.
+   */
+  private static @NotNull Collection<PsiClass> collectSpecificAnnotatedArtefacts(@NotNull GrailsArtefactHandler handler,
+                                                                                 @NotNull GrailsApplication application,
+                                                                                 @NotNull GlobalSearchScope scope) {
+    Project project = application.getProject();
+    GlobalSearchScope annotationClassSearchScope = application.getScope(true, false);
+    String suffix = handler.getArtefactClassSuffix();
+
+    List<PsiClass> result = new ArrayList<>();
+    for (String annotationFqn : handler.getAnnotationFqns()) {
+      PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationFqn, annotationClassSearchScope);
+      if (annotationClass == null) continue;
+
+      for (PsiClass artefactClass : AnnotatedElementsSearch.searchPsiClasses(annotationClass, scope).findAll()) {
+        VirtualFile file = virtualFileOf(artefactClass);
+        String name = artefactClass.getName();
+        if (file != null
+            && file.getFileType() == GroovyFileType.GROOVY_FILE_TYPE
+            && name != null
+            && name.endsWith(suffix)) {
+          result.add(artefactClass);
+        }
+      }
+    }
+    return result;
+  }
+
+  public static @Nullable GrailsArtefactHandler getArtefactHandler(@NotNull PsiClass clazz) {
+    if (!(clazz instanceof GrTypeDefinition typeDefinition)) return null;
+    return CachedValuesManager.getCachedValue(typeDefinition,
+                                              () -> Result.create(doGetArtefactHandler(typeDefinition), typeDefinition));
+  }
+
+  private static @Nullable GrailsArtefactHandler doGetArtefactHandler(@NotNull GrTypeDefinition clazz) {
+    GrailsApplication application = GrailsApplicationManager.findApplication(clazz);
+    if (application == null) return null;
+    String name = clazz.getName();
+    if (name == null) return null;
+
+    HandlerCache handlerCache = ApplicationManager.getApplication().getService(HandlerCache.class);
+
+    // first check for @Artefact
+    PsiAnnotation artefactAnnotation = AnnotationUtil.findAnnotation(clazz, GRAILS_ARTEFACT_ARTEFACT);
+    if (artefactAnnotation != null) {
+      String id = GrAnnotationUtil.inferStringAttribute(artefactAnnotation, "value");
+      if (id != null) {
+        GrailsArtefactHandler handler = handlerCache.getIdToHandler().get(id);
+        if (handler != null) return handler;
+      }
+    }
+
+    // check for @TagLib, @Entity, etc
+    for (Map.Entry<String, GrailsArtefactHandler> entry : handlerCache.getAnnotationToHandler().entrySet()) {
+      if (AnnotationUtil.findAnnotation(clazz, entry.getKey()) != null) {
+        return entry.getValue();
+      }
+    }
+
+    VirtualFile file = virtualFileOf(clazz);
+    if (file == null) return null;
+    if (!file.getNameWithoutExtension().equals(name)) return null;
+
+    // check if it's conventional artefact
+    for (GrailsArtefactHandler handler : ArtefactHandlers.allHandlers()) {
+      if (!name.endsWith(handler.getArtefactClassSuffix())) continue;
+
+      VirtualFile artefactDirectory = handler.getDirectory(application);
+      if (artefactDirectory == null) continue;
+      if (!VfsUtilCore.isAncestor(artefactDirectory, file, true)) continue;
+
+      return handler;
+    }
+
+    return null;
+  }
+
+  private static @Nullable VirtualFile virtualFileOf(@NotNull PsiClass clazz) {
+    PsiFile containingFile = clazz.getContainingFile();
+    return containingFile != null ? containingFile.getVirtualFile() : null;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java
new file mode 100644
index 0000000..329a25d
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.impl;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.structure.Grails3Application;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import javax.swing.Icon;
+
+public final class InterceptorArtefactHandler implements GrailsDisplayableArtefactHandler {
+
+  public static final InterceptorArtefactHandler INSTANCE = new InterceptorArtefactHandler();
+
+  private InterceptorArtefactHandler() {
+  }
+
+  @Override
+  public @NotNull String getArtefactHandlerID() {
+    return "Interceptor";
+  }
+
+  @Override
+  public @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return application.getAppRoot().findChild("controllers");
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return AllIcons.General.Filter;
+  }
+
+  @Override
+  public @NotNull String getTitle() {
+    return "Interceptors";
+  }
+
+  @Override
+  public int getWeight() {
+    return NodeWeights.CONTROLLERS_FOLDER + 1;
+  }
+
+  @Override
+  public boolean isVisible(@NotNull GrailsApplication application) {
+    return application instanceof Grails3Application;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.kt
deleted file mode 100644
index 0a6ce0c..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.structure.Grails3Application
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-object InterceptorArtefactHandler : GrailsDisplayableArtefactHandler {
-
-  override val artefactHandlerID: String = "Interceptor"
-
-  override fun getDirectory(application: GrailsApplication): VirtualFile? = application.appRoot.findChild("controllers")
-
-  override val icon: Icon = AllIcons.General.Filter
-
-  override val title: String get() = "Interceptors"
-
-  override val weight: Int get() = NodeWeights.CONTROLLERS_FOLDER + 1
-
-  override fun isVisible(application: GrailsApplication): Boolean = application is Grails3Application
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java
new file mode 100644
index 0000000..1e6a6b3
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.impl;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import javax.swing.Icon;
+
+public final class ServiceArtefactHandler implements GrailsDisplayableArtefactHandler {
+
+  public static final ServiceArtefactHandler INSTANCE = new ServiceArtefactHandler();
+
+  private ServiceArtefactHandler() {
+  }
+
+  @Override
+  public @NotNull String getArtefactHandlerID() {
+    return "Service";
+  }
+
+  @Override
+  public @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return application.getAppRoot().findChild("services");
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return AllIcons.FileTypes.Config;
+  }
+
+  @Override
+  public @NotNull String getTitle() {
+    return "Services";
+  }
+
+  @Override
+  public int getWeight() {
+    return NodeWeights.SERVICES_FOLDER;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.kt
deleted file mode 100644
index a67ea29..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-object ServiceArtefactHandler : GrailsDisplayableArtefactHandler {
-
-  override val artefactHandlerID: String = "Service"
-
-  override fun getDirectory(application: GrailsApplication): VirtualFile? = application.appRoot.findChild("services")
-
-  override val icon: Icon = AllIcons.FileTypes.Config
-
-  override val title: String = "Services"
-
-  override val weight: Int = NodeWeights.SERVICES_FOLDER
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java b/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java
new file mode 100644
index 0000000..9080593
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.artefact.impl;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import javax.swing.Icon;
+import java.util.Collection;
+import java.util.List;
+
+public final class TaglibArtefactHandler implements GrailsDisplayableArtefactHandler {
+
+  public static final TaglibArtefactHandler INSTANCE = new TaglibArtefactHandler();
+
+  private TaglibArtefactHandler() {
+  }
+
+  @Override
+  public @NotNull String getArtefactHandlerID() {
+    return "TagLib";
+  }
+
+  @Override
+  public @Nullable VirtualFile getDirectory(@NotNull GrailsApplication application) {
+    return application.getAppRoot().findChild("taglib");
+  }
+
+  @Override
+  public @NotNull Collection<String> getAnnotationFqns() {
+    return List.of("grails.gsp.TagLib");
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return GroovyMvcIcons.Taglib;
+  }
+
+  @Override
+  public @NotNull String getTitle() {
+    return "Tag Libraries";
+  }
+
+  @Override
+  public int getWeight() {
+    return NodeWeights.TAGLIB_FOLDER;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.kt b/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.kt
deleted file mode 100644
index c36c398..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import javax.swing.Icon
-
-object TaglibArtefactHandler : GrailsDisplayableArtefactHandler {
-
-  override val artefactHandlerID: String = "TagLib"
-
-  override fun getDirectory(application: GrailsApplication): VirtualFile? = application.appRoot.findChild("taglib")
-
-  override val annotationFqns: Collection<String> = listOf("grails.gsp.TagLib")
-
-  override val icon: Icon = GroovyMvcIcons.Taglib
-
-  override val title: String = "Tag Libraries"
-
-  override val weight: Int = NodeWeights.TAGLIB_FOLDER
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/util.kt b/src/org/jetbrains/plugins/grails/artefact/impl/util.kt
deleted file mode 100644
index d2e9cda..0000000
--- a/src/org/jetbrains/plugins/grails/artefact/impl/util.kt
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl
-
-import com.intellij.codeInsight.AnnotationUtil
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.components.service
-import com.intellij.openapi.util.Computable
-import com.intellij.openapi.util.Key
-import com.intellij.openapi.vfs.VfsUtilCore
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiClass
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.search.GlobalSearchScopesCore
-import com.intellij.psi.search.searches.AllClassesSearch
-import com.intellij.psi.search.searches.AnnotatedElementsSearch
-import com.intellij.psi.util.CachedValue
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import com.intellij.psi.util.PsiModificationTracker
-import com.intellij.psi.xml.XmlFile
-import com.intellij.util.containers.FactoryMap
-import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler
-import org.jetbrains.plugins.grails.artefact.api.HandlerCache
-import org.jetbrains.plugins.grails.artefact.api.allHandlers
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-import org.jetbrains.plugins.groovy.GroovyFileType
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
-import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil
-
-private const val GRAILS_ARTEFACT_ARTEFACT = "grails.artefact.Artefact"
-
-fun GrailsArtefactHandler.getArtefacts(application: GrailsApplication, scope: GlobalSearchScope): Collection<PsiClass> {
-  return CachedValuesManager.getManager(application.project).getCachedValue(application, keys[this]!!, {
-    Result.create(createCache(application), PsiModificationTracker.MODIFICATION_COUNT)
-  }, false)[scope] ?: emptyList()
-}
-
-private val keys: Map<GrailsArtefactHandler, Key<CachedValue<Map<GlobalSearchScope, Collection<PsiClass>>>>> = FactoryMap.create {
-  Key.create("grails.artefact.cache." + it.javaClass.name)
-}
-
-
-private fun GrailsArtefactHandler.createCache(application: GrailsApplication): Map<GlobalSearchScope, Collection<PsiClass>> = FactoryMap.create {
-  ApplicationManager.getApplication().runReadAction(Computable {
-    this@createCache.doGetArtefacts(application, it)
-  })
-}
-
-private fun GrailsArtefactHandler.doGetArtefacts(application: GrailsApplication, scope: GlobalSearchScope): Collection<PsiClass> {
-  val result = linkedSetOf<PsiClass>()
-
-  collectConventionalArtefacts(application, scope)?.let { result += it }
-  collectCompiledArtefacts(application, scope)?.let { result += it }
-  collectAnnotatedArtefacts(application, scope)?.let { result += it }
-  collectSpecificAnnotatedArtefacts(application, scope).let { result += it }
-
-  return result
-}
-
-/**
- * PsiClass is considered a convention artefact when all the following us true:
- * - its name has specified suffix
- * - its name if the same as the name of file
- * - it is defined in a groovy file which lies under specified directory
- */
-private fun GrailsArtefactHandler.collectConventionalArtefacts(application: GrailsApplication,
-                                                               scope: GlobalSearchScope): Collection<PsiClass>? {
-  val project = application.project
-  val directory = getDirectory(application) ?: return null
-  val artefactDirectoryScope = GlobalSearchScopesCore.directoryScope(project, directory, true)
-  val resultScope = artefactDirectoryScope.intersectWith(scope)
-  return AllClassesSearch.search(resultScope, project) { className ->
-    className.endsWith(artefactClassSuffix)
-  }.findAll().filter { artefactClass ->
-    val file = artefactClass.containingFile?.virtualFile
-    file?.nameWithoutExtension == artefactClass.name && file?.fileType == GroovyFileType.GROOVY_FILE_TYPE
-  }
-}
-
-//private fun GrailsArtefactHandler.isConventionalArtefact(artefactClass: PsiClass, checkName: Boolean = false): Boolean {
-//  val name = artefactClass.name ?: return false
-//  val file = artefactClass.containingFile?.virtualFile
-//  return file?.nameWithoutExtension == name && file?.fileType == GroovyFileType.GROOVY_FILE_TYPE
-//}
-
-/**
- * Collects classes defined in META-INF/grails-plugin.xml
- */
-private fun GrailsArtefactHandler.collectCompiledArtefacts(application: GrailsApplication,
-                                                           scope: GlobalSearchScope): Collection<PsiClass>? {
-  val project = application.project
-  val facade = JavaPsiFacade.getInstance(project)
-  val metaInfPackage = facade.findPackage("META-INF") ?: return null
-  val result = mutableListOf<PsiClass>()
-
-  for (directory in metaInfPackage.getDirectories(scope)) {
-    val file = directory.findFile("grails-plugin.xml") as? XmlFile ?: continue
-    val resourcesTag = file.rootTag?.findSubTags("resources")?.firstOrNull() ?: continue
-    for (tag in resourcesTag.subTags.filter { it.name == "resource" }) {
-      val fqn = tag.value.trimmedText
-      if (fqn.endsWith(artefactClassSuffix)) {
-        facade.findClass(fqn, scope)?.let { result += it }
-      }
-    }
-  }
-
-  return result
-}
-
-/**
- * Collects classes annotated with @Artefact("artefactHandlerId"), where artefactId corresponds to the current artefact handler.
- */
-private fun GrailsArtefactHandler.collectAnnotatedArtefacts(application: GrailsApplication,
-                                                            scope: GlobalSearchScope): Collection<PsiClass>? {
-  val project = application.project
-  val annotationClassSearchScope = application.getScope(true, false)
-
-
-  val annotationClass = JavaPsiFacade.getInstance(project).findClass(GRAILS_ARTEFACT_ARTEFACT,
-                                                                     annotationClassSearchScope) ?: return null
-  return AnnotatedElementsSearch.searchPsiClasses(annotationClass, scope).findAll().filter { artefactClass ->
-    artefactClass.containingFile?.virtualFile?.fileType == GroovyFileType.GROOVY_FILE_TYPE
-    && artefactClass.name?.endsWith(artefactClassSuffix) ?: false
-    && artefactClass.modifierList?.findAnnotation(GRAILS_ARTEFACT_ARTEFACT)?.let {
-      GrAnnotationUtil.inferStringAttribute(it, "value") == artefactHandlerID
-    } ?: false
-  }
-}
-
-/**
- * Collects classes annotated with artefact specific annotation, i.e. @Controller or @Taglib.
- */
-private fun GrailsArtefactHandler.collectSpecificAnnotatedArtefacts(application: GrailsApplication,
-                                                                    scope: GlobalSearchScope) = annotationFqns.flatMap { annotationFqn ->
-  val project = application.project
-  val annotationClassSearchScope = application.getScope(true, false)
-  JavaPsiFacade.getInstance(project).findClass(annotationFqn, annotationClassSearchScope)?.let { annotationClass ->
-    AnnotatedElementsSearch.searchPsiClasses(annotationClass, scope).findAll().filter { artefactClass ->
-      artefactClass.containingFile?.virtualFile?.fileType == GroovyFileType.GROOVY_FILE_TYPE &&
-      artefactClass.name?.endsWith(artefactClassSuffix) ?: false
-    }
-  } ?: emptyList()
-}
-
-fun getArtefactHandler(clazz: PsiClass): GrailsArtefactHandler? {
-  if (clazz !is GrTypeDefinition) return null
-  return CachedValuesManager.getCachedValue(clazz) {
-    Result.create(doGetArtefactHandler(clazz), clazz)
-  }
-}
-
-private fun doGetArtefactHandler(clazz: GrTypeDefinition): GrailsArtefactHandler? {
-  val application = GrailsApplicationManager.findApplication(clazz) ?: return null
-  val name = clazz.name ?: return null
-
-  val handlerCache = service<HandlerCache>()
-  // first check for @Artefact
-  AnnotationUtil.findAnnotation(clazz, GRAILS_ARTEFACT_ARTEFACT)?.let { annotation ->
-    GrAnnotationUtil.inferStringAttribute(annotation, "value")?.let { id ->
-      handlerCache.idToHandler[id]?.let {
-        return it
-      }
-    }
-  }
-
-  // check for @TagLib, @Entity, etc
-  for ((fqn, handler) in handlerCache.annotationToHandler) {
-    if (AnnotationUtil.findAnnotation(clazz, fqn) != null) {
-      return handler
-    }
-  }
-
-  val file = clazz.containingFile?.virtualFile ?: return null
-  if (file.nameWithoutExtension != name) return null
-
-  // check if it's conventional artefact
-  for (handler in allHandlers) {
-    if (!name.endsWith(handler.artefactClassSuffix)) continue
-
-    val artefactDirectory = handler.getDirectory(application) ?: continue
-    if (!VfsUtilCore.isAncestor(artefactDirectory, file, true)) continue
-
-    return handler
-  }
-
-  return null
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java b/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
index 969a334..458edb9 100644
--- a/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
+++ b/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler;
 import org.jetbrains.plugins.grails.artefact.api.IconOwner;
-import org.jetbrains.plugins.grails.artefact.impl.UtilKt;
+import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition;
 
 import javax.swing.Icon;
@@ -34,7 +34,7 @@ final class GrailsIconProvider extends IconProvider {
   @Override
   public @Nullable Icon getIcon(@NotNull PsiElement element, @Iconable.IconFlags int flags) {
     if (element instanceof GrClassDefinition) {
-      GrailsArtefactHandler handler = UtilKt.getArtefactHandler((PsiClass)element);
+      GrailsArtefactHandler handler = GrailsArtefacts.getArtefactHandler((PsiClass)element);
       if (handler instanceof IconOwner) {
         return IconManager.getInstance().createLayeredIcon(element, ((IconOwner)handler).getIcon(), flags);
       }
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt
index 273ba60..130b6f6 100644
--- a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt
@@ -19,7 +19,7 @@ package org.jetbrains.plugins.grails.projectView.impl
 import com.intellij.ide.projectView.ViewSettings
 import com.intellij.ide.util.treeView.AbstractTreeNode
 import com.intellij.psi.PsiManager
-import org.jetbrains.plugins.grails.artefact.api.displayableArtefactHandlers
+import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers
 import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider
 import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode
 import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode
@@ -32,7 +32,7 @@ internal class GrailsAppNodeProvider : GrailsViewNodeProvider {
 
     val result = mutableListOf<AbstractTreeNode<*>>()
 
-    displayableArtefactHandlers.filter { it.isVisible(application) }.mapTo(result) {
+    ArtefactHandlers.displayableArtefactHandlers().filter { it.isVisible(application) }.mapTo(result) {
       GrailsArtefactHandlerNode(project, it, settings)
     }
 
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt b/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt
index 0c65c0f..283cbbc 100644
--- a/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt
@@ -20,7 +20,7 @@ import com.intellij.icons.AllIcons
 import com.intellij.psi.PsiFileSystemItem
 import com.intellij.psi.PsiManager
 import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.artefact.impl.getArtefactHandler
+import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts
 import org.jetbrains.plugins.grails.projectView.NodeWeights
 import org.jetbrains.plugins.grails.structure.GrailsApplication
 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
@@ -29,7 +29,7 @@ import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandle
 internal fun shouldShowItem(item: PsiFileSystemItem): Boolean {
   if (item !is GroovyFile) return true
   val clazz = item.classes.singleOrNull() ?: return true
-  val handler = getArtefactHandler(clazz)
+  val handler = GrailsArtefacts.getArtefactHandler(clazz)
   return handler !is ArtefactHandler
 }
 
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt
index 2fdaea9..3931dfe 100644
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt
@@ -21,7 +21,7 @@ import com.intellij.ide.projectView.ViewSettings
 import com.intellij.openapi.project.Project
 import com.intellij.psi.search.GlobalSearchScope
 import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.getArtefacts
+import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts
 import org.jetbrains.plugins.grails.structure.GrailsApplication
 
 abstract class GrailsArtefactHandlerNodeBase<T : Any>(
@@ -33,5 +33,5 @@ abstract class GrailsArtefactHandlerNodeBase<T : Any>(
   val grailsApplication: GrailsApplication get() = findNotNullValueOfType()
   protected val scope: GlobalSearchScope get() = grailsApplication.getScope(includeDependencies = false, testsOnly = false)
   protected abstract val artefactHandler: GrailsDisplayableArtefactHandler
-  protected val artefacts: Classes get() = artefactHandler.getArtefacts(grailsApplication, scope)
+  protected val artefacts: Classes get() = GrailsArtefacts.getArtefacts(artefactHandler, grailsApplication, scope)
 }
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt
index 4efd612..0c99d65 100644
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt
@@ -25,7 +25,7 @@ import com.intellij.psi.PsiDirectory
 import com.intellij.psi.PsiManager
 import com.intellij.util.PlatformIcons
 import com.intellij.util.lazyUnsafe
-import org.jetbrains.plugins.grails.artefact.api.displayableArtefactHandlers
+import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers
 import org.jetbrains.plugins.grails.projectView.NodeWeights
 import org.jetbrains.plugins.grails.projectView.impl.shouldShowItem
 import org.jetbrains.plugins.grails.projectView.impl.specialGrailsAppFolders
@@ -49,7 +49,7 @@ class OtherGrailsAppSourcesNode(
     val manager = PsiManager.getInstance(project)
 
     // directories from nodes that are showed separately
-    val artefactDirs = displayableArtefactHandlers.filter {
+    val artefactDirs = ArtefactHandlers.displayableArtefactHandlers().filter {
       it.isVisible(application)
     }.mapNotNullTo(HashSet()) {
       it.getDirectory(application)


(grails-intellij-plugin) 12/35: Convert every build file to Groovy DSL and pin all artifact names

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 0b9505e14f5d19980a5c3c54d526c46bf0f38ed8
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:23:07 2026 -0400

    Convert every build file to Groovy DSL and pin all artifact names
    
    Step 3 of the restructure. settings.gradle.kts, the root build and all eleven
    module build files become Groovy; each module now applies one tier convention
    plugin and carries only content. hibernate went from 68 lines to 27.
    
    Verified: 987 tests / 0 failures (baseline), RAT clean, configuration cache
    reused, and bytecode levels asserted from the class files rather than trusted:
    gradle-tooling/grails-compiler-patch/grails-rt = major 52 (Java 8),
    jps-plugin = 55 (Java 11), hibernate = 69 (Java 25).
    
    Artifact names are now pinned rather than derived, which is the point of the
    exercise. Packaged layout:
    
      apache-grails-intellij-plugin-262.0.0.zip
        lib/apache-grails-intellij-plugin-262.0.0.jar
        lib/grails-rt-262.0.0.jar
        lib/org.apache.grails.intellij.lib.{gradle-tooling,grails-compiler-patch,jps-plugin}.jar
        lib/modules/intellij.groovy.grails.{copyright,coverage,hibernate,i18n,langInjection,maven}.jar
    
    This fixes a pre-existing silent breakage: plugin.xml declared
    compileServer.plugin classpath="Grails.jps-plugin.jar;Grails.grails-compiler-patch.jar",
    which no build has produced since rootProject.name was renamed. JPS resolves
    those entries by name and silently skips what it cannot find, so the Grails JPS
    builders have simply not been loading. The names are now pinned by convention
    plugin instead of derived from rootProject.name, the attribute matches, and a
    check confirms every classpath entry resolves to a real jar in lib/.
    
    Groovy-vs-Kotlin differences that needed real fixes:
    
    * testFramework(type = X, configurationName = Y) used Kotlin named arguments to
      skip the middle `version` parameter. Groovy cannot, so all three are passed
      with the default spelled out as Constants.Constraints.CLOSEST_VERSION.
    * TestFrameworkType.Platform and friends are Kotlin objects with private
      constructors, so .INSTANCE is required -- a bare reference is a Class in
      Groovy, not a value.
    * pluginModule needs project(path: ':x'); a bare project(':x') resolves to
      Project.project(String) and returns a Project rather than a Dependency.
    * JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME is Gradle internal API, replaced
      with the 'compileOnly' literal.
    * A convention plugin that configures the intellijPlatform extensions has to
      apply org.jetbrains.intellij.platform.base itself; the consuming tier plugin
      applies IPGP too late, after this body has already run.
    * jacoco-report-aggregation auto-registers testCodeCoverageReport only for
      projects applying jvm-test-suite, so the registration is guarded and takes
      effect only once the root stops compiling code.
    
    Note the nested-extension closures do NOT need explicit parameters: once IPGP
    is applied, delegate-first resolution finds the right intellijPlatform
    extension, so the idiomatic `dependencies { intellijPlatform { ... } }` works.
---
 ...ails.intellij.build.coverage-aggregation.gradle |  11 +-
 ....grails.intellij.build.intellij-platform.gradle |  29 +--
 build.gradle                                       |  93 +++++++
 build.gradle.kts                                   | 283 ---------------------
 {hibernate => copyright}/build.gradle              |   8 +-
 copyright/build.gradle.kts                         |  67 -----
 {hibernate => coverage}/build.gradle               |   7 +-
 coverage/build.gradle.kts                          |  72 ------
 {hibernate => gradle-tooling}/build.gradle         |  16 +-
 gradle-tooling/build.gradle.kts                    |  61 -----
 grails-compiler-patch/build.gradle                 |  44 ++++
 grails-compiler-patch/build.gradle.kts             |  74 ------
 {hibernate => grails-rt}/build.gradle              |  14 +-
 grails-rt/build.gradle.kts                         |  50 ----
 hibernate/build.gradle                             |   6 +-
 {hibernate => i18n}/build.gradle                   |   7 +-
 i18n/build.gradle.kts                              |  65 -----
 {hibernate => jps-plugin}/build.gradle             |  16 +-
 jps-plugin/build.gradle.kts                        |  62 -----
 {hibernate => langInjection}/build.gradle          |   8 +-
 langInjection/build.gradle.kts                     |  58 -----
 {hibernate => maven}/build.gradle                  |  10 +-
 maven/build.gradle.kts                             |  75 ------
 resources/META-INF/plugin.xml                      |   7 +-
 settings.gradle.kts => settings.gradle             |  26 +-
 testFramework/build.gradle                         |  50 ++++
 testFramework/build.gradle.kts                     |  88 -------
 27 files changed, 282 insertions(+), 1025 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle
index 33998cc..9929cc1 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle
@@ -35,8 +35,15 @@ plugins {
     id 'jacoco-report-aggregation'
 }
 
-reporting.reports.register('testCodeCoverageReport', JacocoCoverageReport) { report ->
-    report.testSuiteName = 'test'
+// Registered only when it does not already exist. jacoco-report-aggregation auto-registers
+// <suite>CodeCoverageReport for projects that apply jvm-test-suite, which the root does today
+// because it still owns the plugin source. Once the plugin moves to its own project the root
+// compiles nothing, the auto-registration stops happening, and this branch takes over -- keeping
+// the report at the path the coverage workflow uploads either way.
+if (!reporting.reports.names.contains('testCodeCoverageReport')) {
+    reporting.reports.register('testCodeCoverageReport', JacocoCoverageReport) { report ->
+        report.testSuiteName = 'test'
+    }
 }
 
 tasks.named('testCodeCoverageReport') { task ->
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
index c660c33..5f63a28 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
@@ -19,31 +19,26 @@
 
 // Shared IntelliJ Platform wiring: the artifact repositories and the platform dependency.
 // The IPGP version comes from build-logic's classpath, so this is applied without a version.
-//
-// EVERY closure below binds its target to an explicit parameter, and that is load-bearing.
-// IPGP registers THREE extensions all named `intellijPlatform` -- one on the Project, one on
-// RepositoryHandler, one on DependencyHandler. Groovy closures resolve owner-first, so a bare
-// `intellijPlatform { }` nested inside `repositories { }` or `dependencies { }` resolves to the
-// PROJECT extension and fails with "Could not find method defaultRepositories() ... on extension
-// 'intellijPlatform'". Kotlin's DSL scopes by receiver type, so it has no such clash.
 
 plugins {
     id 'org.apache.grails.intellij.build.java'
-    // Registers the intellijPlatform extensions used below. The consuming tier plugin adds
-    // the full plugin (.platform) or module (.module) variant on top; both re-apply this base
-    // idempotently. Without it, this script's body runs before any IPGP plugin is applied and
-    // the repository/dependency extensions do not exist yet.
+    // Registers the intellijPlatform extensions used below, and it must be applied HERE rather
+    // than left to the consuming tier plugin. IPGP registers three separate extensions all named
+    // `intellijPlatform` (Project, RepositoryHandler, DependencyHandler); if none of them exists
+    // yet when this body runs, Groovy's owner fallback resolves the nested blocks against the
+    // Project and reports a misleading "Could not find method defaultRepositories() on extension
+    // 'intellijPlatform'". The tier plugins add the .platform or .module variant on top of this.
     id 'org.jetbrains.intellij.platform.base'
 }
 
-repositories { repositoryHandler ->
-    repositoryHandler.intellijPlatform { repos ->
-        repos.defaultRepositories()
+repositories {
+    intellijPlatform {
+        defaultRepositories()
     }
 }
 
-dependencies { dependencyHandler ->
-    dependencyHandler.intellijPlatform { platform ->
-        platform.intellijIdea providers.gradleProperty('platformVersion')
+dependencies {
+    intellijPlatform {
+        intellijIdea providers.gradleProperty('platformVersion')
     }
 }
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..27e6cdf
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.jetbrains.intellij.platform.gradle.Constants
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
+
+// Deliberately no allprojects/subprojects block: every project applies its own convention
+// plugins. Everything here is either content (dependency lists) or root-only wiring.
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-plugin'
+    id 'org.apache.grails.intellij.build.legacy-layout'
+    id 'org.apache.grails.intellij.build.rat'
+    id 'org.apache.grails.intellij.build.coverage-aggregation'
+}
+
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'com.intellij.javaee'
+        bundledPlugin 'com.intellij.javaee.el' // no longer transitive in 2026.2
+        bundledPlugin 'com.intellij.persistence'
+        bundledPlugin 'com.intellij.javaee.jpa'
+        bundledPlugin 'com.intellij.jsp'
+        bundledPlugin 'com.intellij.javaee.web'
+        bundledPlugin 'org.intellij.groovy'
+        bundledPlugin 'com.intellij.database'
+        bundledPlugin 'com.intellij.spring'
+        bundledPlugin 'com.intellij.gradle' // split out of org.jetbrains.plugins.gradle in 2026.2
+        bundledPlugin 'org.jetbrains.plugins.gradle'
+        bundledPlugin 'com.intellij.modules.ultimate'
+        bundledPlugin 'com.intellij.microservices.jvm'
+        bundledPlugin 'JavaScript'
+        bundledPlugin 'com.intellij.css'
+
+        // plugin modules. project(path:) is required: a bare project(':x') resolves through the
+        // owner chain to Project.project(String) and returns a Project, not a Dependency.
+        pluginModule project(path: ':copyright')
+        pluginModule project(path: ':coverage')
+        pluginModule project(path: ':hibernate')
+        pluginModule project(path: ':i18n')
+        pluginModule project(path: ':langInjection')
+        pluginModule project(path: ':maven')
+
+        // additional plugins required for testing
+        testBundledPlugin 'com.intellij.hibernate'
+        testBundledPlugin 'HtmlTools'
+        testBundledPlugin 'org.jetbrains.idea.maven'
+        testBundledPlugin 'com.intellij.copyright' // GspCopyrightUpdaterTest
+
+        // .INSTANCE because these are Kotlin objects; see testFramework/build.gradle
+        testFramework TestFrameworkType.Plugin.ExternalSystem.INSTANCE
+        testFramework TestFrameworkType.Plugin.Java.INSTANCE
+        testFramework TestFrameworkType.Plugin.Maven.INSTANCE
+        testFramework TestFrameworkType.Platform.INSTANCE
+    }
+
+    testImplementation 'junit:junit:4.13.2'
+    testImplementation 'org.assertj:assertj-core:4.0.0-M1'
+    testImplementation project(':testFramework')
+    testCompileOnly project(':i18n')
+    testCompileOnly project(':copyright')
+
+    implementation project(':gradle-tooling')
+    implementation project(':grails-rt')
+
+    // JPS/build
+    runtimeOnly project(':grails-compiler-patch')
+    runtimeOnly project(':jps-plugin')
+
+    // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
+    jacocoAggregation project(':copyright')
+    jacocoAggregation project(':coverage')
+    jacocoAggregation project(':gradle-tooling')
+    jacocoAggregation project(':grails-compiler-patch')
+    jacocoAggregation project(':grails-rt')
+    jacocoAggregation project(':hibernate')
+    jacocoAggregation project(':i18n')
+    jacocoAggregation project(':jps-plugin')
+    jacocoAggregation project(':langInjection')
+    jacocoAggregation project(':maven')
+    jacocoAggregation project(':testFramework')
+}
diff --git a/build.gradle.kts b/build.gradle.kts
deleted file mode 100644
index b20dc23..0000000
--- a/build.gradle.kts
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import org.gradle.testing.jacoco.tasks.JacocoReport
-import org.jetbrains.intellij.platform.gradle.TestFrameworkType
-import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask
-
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.jetbrains.kotlin.jvm") version "2.3.0"
-    id("org.jetbrains.intellij.platform") version "2.18.1"
-    id("org.nosphere.apache.rat") version "0.8.1"
-    // build-hygiene conventions (build-logic included build)
-    id("jacoco-report-aggregation")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-}
-
-group = "org.apache.grails.intellij"
-// project version comes from gradle.properties `version` (override with -Pversion=)
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-
-        bundledPlugin("com.intellij.javaee")
-        bundledPlugin("com.intellij.javaee.el") // no longer transitive in 2026.2
-        bundledPlugin("com.intellij.persistence")
-        bundledPlugin("com.intellij.javaee.jpa")
-        bundledPlugin("com.intellij.jsp")
-        bundledPlugin("com.intellij.javaee.web")
-        bundledPlugin("org.intellij.groovy")
-        bundledPlugin("com.intellij.database")
-        bundledPlugin("com.intellij.spring")
-        bundledPlugin("com.intellij.gradle") // split out of org.jetbrains.plugins.gradle in 2026.2
-        bundledPlugin("org.jetbrains.plugins.gradle")
-        bundledPlugin("com.intellij.modules.ultimate")
-        bundledPlugin("com.intellij.microservices.jvm")
-        bundledPlugin("JavaScript")
-        bundledPlugin("com.intellij.css")
-
-
-        // plugin modules
-        pluginModule(project(":copyright"))
-        pluginModule(project(":coverage"))
-        pluginModule(project(":hibernate"))
-        pluginModule(project(":i18n"))
-        pluginModule(project(":langInjection"))
-        pluginModule(project(":maven"))
-
-
-        // additional plugins required for testing
-        testBundledPlugin("com.intellij.hibernate")
-        testBundledPlugin("HtmlTools")
-        testBundledPlugin("org.jetbrains.idea.maven")
-        testBundledPlugin("com.intellij.copyright") // GspCopyrightUpdaterTest
-
-        testFramework(TestFrameworkType.Plugin.ExternalSystem)
-        testFramework(TestFrameworkType.Plugin.Java)
-        testFramework(TestFrameworkType.Plugin.Maven)
-        testFramework(TestFrameworkType.Platform)
-    }
-
-    testImplementation("junit:junit:4.13.2")
-    testImplementation("org.assertj:assertj-core:4.0.0-M1")
-    testImplementation(project(":testFramework"))
-    testCompileOnly(project(":i18n"))
-    testCompileOnly(project(":copyright"))
-
-    implementation(project(":gradle-tooling"))
-    implementation(project(":grails-rt"))
-
-    // JPS/build
-    runtimeOnly(project(":grails-compiler-patch"))
-    runtimeOnly(project(":jps-plugin"))
-
-    // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
-    jacocoAggregation(project(":copyright"))
-    jacocoAggregation(project(":coverage"))
-    jacocoAggregation(project(":gradle-tooling"))
-    jacocoAggregation(project(":grails-compiler-patch"))
-    jacocoAggregation(project(":grails-rt"))
-    jacocoAggregation(project(":hibernate"))
-    jacocoAggregation(project(":i18n"))
-    jacocoAggregation(project(":jps-plugin"))
-    jacocoAggregation(project(":langInjection"))
-    jacocoAggregation(project(":maven"))
-    jacocoAggregation(project(":testFramework"))
-}
-
-// jacoco-report-aggregation auto-registers testCodeCoverageReport from the default `test`
-// suite, producing build/reports/jacoco/testCodeCoverageReport/... ; ensure the XML variant
-// is emitted for Codecov upload.
-tasks.named<JacocoReport>("testCodeCoverageReport") {
-    reports {
-        xml.required.set(true)
-        html.required.set(true)
-        csv.required.set(false)
-    }
-}
-
-kotlin {
-    // no toolchain on purpose: the JDK is pinned via .sdkmanrc for reproducible builds
-    compilerOptions {
-        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(properties("platformJavaVersion"))
-    }
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-    srcDir("gen")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-    srcDir("compatibilityResources")
-}
-
-// special handling for /standardDsls/ packaging, see PrepareSandboxTask below
-sourceSets {
-    main {
-        resources {
-            exclude("standardDsls/")
-        }
-    }
-}
-
-java.sourceSets["test"].java {
-    srcDir("test")
-}
-
-intellijPlatform {
-    pluginConfiguration {
-        ideaVersion {
-            sinceBuild = properties("pluginSinceBuild")
-        }
-
-        changeNotes = ""
-    }
-
-    pluginVerification {
-        // the legacy plugin id org.intellij.grails is grandfathered on Marketplace and
-        // permanent (see MIGRATION-PLAN.md decision 2); mute the new-plugin naming check
-        freeArgs = listOf("-mute", "TemplateWordInPluginId")
-        // gate on real incompatibilities only; the inherited internal/deprecated API
-        // usages (47/67) are a tracked cleanup item, not a release blocker
-        failureLevel = listOf(
-            org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS,
-            org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.MISSING_DEPENDENCIES,
-            org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.INVALID_PLUGIN,
-        )
-        ides {
-            recommended()
-        }
-    }
-
-    // JetBrains Marketplace code signing / publishing; secrets provided by the release workflow
-    signing {
-        certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN")
-        privateKey = providers.environmentVariable("PRIVATE_KEY")
-        password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
-    }
-
-    publishing {
-        token = providers.environmentVariable("PUBLISH_TOKEN")
-    }
-}
-
-tasks {
-    withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-    test {
-        // local override for tests that need IDE sources; unset/nonexistent on CI
-        val ideaHome = project.findProperty("test.idea.home.path")?.toString()
-        if (ideaHome != null && file(ideaHome).exists()) {
-            systemProperty("idea.home.path", ideaHome)
-        }
-    }
-
-    // ASF policy: ship LICENSE and NOTICE inside the plugin jar
-    processResources {
-        from(layout.projectDirectory.file("LICENSE")) { into("META-INF") }
-        from(layout.projectDirectory.file("NOTICE")) { into("META-INF") }
-    }
-
-    // special handling for /standardDsls/ packaging
-    withType<PrepareSandboxTask> {
-        from(layout.projectDirectory.dir("resources/standardDsls")) {
-            into(pluginName.map { "$it/lib/standardDsls/"})
-        }
-    }
-}
-
-// ASF license audit (Apache RAT) — see MIGRATION-PLAN.md Phase 3.
-// Every exclude must carry a justification; no blanket excludes to make the audit pass.
-tasks.rat {
-    excludes.addAll(
-        listOf(
-            // planning documents, removed before the first release
-            "MIGRATION-PLAN.md",
-            "IMPROVEMENT-PLAN.md",
-            // documentation
-            "**/README.md",
-            // git configuration isn't code
-            "**/.gitignore",
-            "**/.gitattributes",
-            // gradle wrapper files, excluded from the source distro
-            "gradlew*",
-            "gradle/wrapper/**",
-            "**/.gradle/**",
-            // github/ASF infra configuration, not shipped in the source distro
-            ".github/**",
-            ".asf.yaml",
-            // IDE-generated files
-            ".idea/**",
-            "**/*.iml",
-            "out/**",
-            // IntelliJ Platform Gradle Plugin local cache (generated Ivy descriptors, git-ignored)
-            ".intellijPlatform/**",
-            // build-logic included build: generated output and local caches (sources are headered)
-            "build-logic/build/**",
-            "build-logic/.gradle/**",
-            // reproducible-build test output (git-ignored, regenerated by etc/bin/test-reproducible-build.sh)
-            "etc/bin/results/**",
-            // generated code (JFlex lexers; headers are injected by the generator config)
-            "gen/**",
-            // test fixtures: content *is* the test input (mock Grails projects/JARs);
-            // adding headers would break parser/position-sensitive tests
-            "testdata/**",
-            "**/testdata/**",
-            // file templates that materialize inside end-user projects
-            "**/resources/fileTemplates/**",
-            // intention/inspection description snippets and before/after templates
-            // rendered verbatim in the IDE settings UI
-            "**/intentionDescriptions/**",
-            "**/inspectionDescriptions/**",
-            // html files are IDE description snippets / template previews only
-            "**/*.html",
-            // GUI-designer .form files are machine-edited XML
-            "**/*.form",
-            // images
-            "**/*.png", "**/*.svg", "**/*.ico", "**/*.jpg", "**/*.gif",
-            // JAR manifest format has no comment syntax
-            "grails-rt/resources/META-INF/MANIFEST.MF",
-            // build outputs are generated and never shipped in the source distro. Listed as globs
-            // rather than derived from subprojects so directories Gradle does not enumerate as
-            // subprojects are covered too: included builds, and leftover build dirs from modules no
-            // longer in settings.gradle.kts. Anchored to the root and one level down instead of
-            // "**/build/**" so a source package named "build" would still be audited.
-            "build/**",
-            "*/build/**",
-        )
-    )
-    // never cache license audits
-    outputs.upToDateWhen { false }
-}
-
-
diff --git a/hibernate/build.gradle b/copyright/build.gradle
similarity index 74%
copy from hibernate/build.gradle
copy to copyright/build.gradle
index 640e48c..b00c26a 100644
--- a/hibernate/build.gradle
+++ b/copyright/build.gradle
@@ -18,9 +18,11 @@ plugins {
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+dependencies {
+    intellijPlatform {
+        // UpdateGspFileCopyright derives from this plugin's UpdatePsiFileCopyright and needs
+        // nothing from javaee/JSP; see that class for why the JSP original's checks were inert.
+        bundledPlugin 'com.intellij.copyright'
     }
 
     compileOnly project(':')
diff --git a/copyright/build.gradle.kts b/copyright/build.gradle.kts
deleted file mode 100644
index 4257796..0000000
--- a/copyright/build.gradle.kts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.copyright"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        // UpdateGspFileCopyright derives from this plugin's UpdatePsiFileCopyright and needs
-        // nothing from javaee/JSP; see that class for why the JSP original's checks were inert.
-        bundledPlugin("com.intellij.copyright")
-    }
-
-    compileOnly(project(":"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}
-
-// 2026.2 resolves content-module descriptors from lib/modules/<module-name>.jar,
-// so the composed jar must be named after the content module, not the project
-tasks.named<org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask>("composedJar") {
-    archiveBaseName = "intellij.groovy.grails.copyright"
-}
diff --git a/hibernate/build.gradle b/coverage/build.gradle
similarity index 86%
copy from hibernate/build.gradle
copy to coverage/build.gradle
index 640e48c..d9af7b2 100644
--- a/hibernate/build.gradle
+++ b/coverage/build.gradle
@@ -15,12 +15,13 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
+    id 'org.apache.grails.intellij.build.kotlin'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'Coverage'
     }
 
     compileOnly project(':')
diff --git a/coverage/build.gradle.kts b/coverage/build.gradle.kts
deleted file mode 100644
index 740371b..0000000
--- a/coverage/build.gradle.kts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.kotlin.jvm") version "2.3.0"
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.coverage"
-
-kotlin {
-    // no toolchain on purpose: the JDK is pinned via .sdkmanrc for reproducible builds
-    compilerOptions {
-        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(properties("platformJavaVersion"))
-    }
-}
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("Coverage")
-    }
-    compileOnly(project(":"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}
-
-// 2026.2 resolves content-module descriptors from lib/modules/<module-name>.jar,
-// so the composed jar must be named after the content module, not the project
-tasks.named<org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask>("composedJar") {
-    archiveBaseName = "intellij.groovy.grails.coverage"
-}
diff --git a/hibernate/build.gradle b/gradle-tooling/build.gradle
similarity index 72%
copy from hibernate/build.gradle
copy to gradle-tooling/build.gradle
index 640e48c..3a99610 100644
--- a/hibernate/build.gradle
+++ b/gradle-tooling/build.gradle
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.intellij-module'
+    id 'org.apache.grails.intellij.build.intellij-lib'
+    id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
-    }
+// runs inside user Grails/JPS builds
+legacyBytecode {
+    release = 8
+}
 
-    compileOnly project(':')
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'com.intellij.gradle'
+    }
 }
diff --git a/gradle-tooling/build.gradle.kts b/gradle-tooling/build.gradle.kts
deleted file mode 100644
index c829d64..0000000
--- a/gradle-tooling/build.gradle.kts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.gradle.tooling"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("com.intellij.gradle")
-    }
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-    // Set the JVM compatibility versions
-    withType<JavaCompile> {
-        sourceCompatibility = "8"
-        targetCompatibility = "8"
-        // IPGP 2.18+ force-sets options.release to the platform's Java version (25);
-        // this module must stay Java 8 bytecode (it runs inside user Grails/JPS builds)
-        options.release = 8
-    }
-
-}
-
diff --git a/grails-compiler-patch/build.gradle b/grails-compiler-patch/build.gradle
new file mode 100644
index 0000000..c045dc1
--- /dev/null
+++ b/grails-compiler-patch/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-lib'
+    id 'org.apache.grails.intellij.build.java-legacy'
+    id 'org.apache.grails.intellij.build.legacy-layout'
+}
+
+// runs inside user Grails/JPS builds
+legacyBytecode {
+    release = 8
+}
+
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'org.intellij.groovy'
+    }
+
+    compileOnly('org.grails:grails-core:1.2.0') {
+        exclude group: 'org.codehaus.groovy' // groovy pinned explicitly below
+    }
+    // era-correct Groovy for the Grails 1.x/2.x patchers (superset of the APIs used);
+    // must NOT be the IDE's bundled Groovy 5 — see compileClasspath exclusion below
+    compileOnly 'org.codehaus.groovy:groovy-all:2.4.21'
+}
+
+// this patch runs inside Grails 2.x builds, so it must compile against the era-correct
+// Groovy (groovy-all 1.6.7 via grails-core above), not the IDE's bundled Groovy 5
+configurations.named('compileClasspath') { config ->
+    config.exclude group: 'bundledPlugin', module: 'com.intellij.groovy.scripting'
+}
diff --git a/grails-compiler-patch/build.gradle.kts b/grails-compiler-patch/build.gradle.kts
deleted file mode 100644
index ebd59a4..0000000
--- a/grails-compiler-patch/build.gradle.kts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.compiler.patch"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("org.intellij.groovy")
-    }
-
-    compileOnly("org.grails:grails-core:1.2.0") {
-        exclude(group = "org.codehaus.groovy") // groovy pinned explicitly below
-    }
-    // era-correct Groovy for the Grails 1.x/2.x patchers (superset of the APIs used);
-    // must NOT be the IDE's bundled Groovy 5 — see compileClasspath exclusion below
-    compileOnly("org.codehaus.groovy:groovy-all:2.4.21")
-}
-
-// this patch runs inside Grails 2.x builds, so it must compile against the era-correct
-// Groovy (groovy-all 1.6.7 via grails-core above), not the IDE's bundled Groovy 5
-configurations.compileClasspath {
-    exclude(group = "bundledPlugin", module = "com.intellij.groovy.scripting")
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-    // Set the JVM compatibility versions
-    withType<JavaCompile> {
-        sourceCompatibility = "8"
-        targetCompatibility = "8"
-        // IPGP 2.18+ force-sets options.release to the platform's Java version (25);
-        // this module must stay Java 8 bytecode (it runs inside user Grails/JPS builds)
-        options.release = 8
-    }
-
-}
-
diff --git a/hibernate/build.gradle b/grails-rt/build.gradle
similarity index 70%
copy from hibernate/build.gradle
copy to grails-rt/build.gradle
index 640e48c..515f2e5 100644
--- a/hibernate/build.gradle
+++ b/grails-rt/build.gradle
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.intellij-module'
+    id 'org.apache.grails.intellij.build.lib'
+    id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
-    }
+// runs inside user Grails builds; located at runtime via GrailsRtMarker, not by name
+legacyBytecode {
+    release = 8
+}
 
-    compileOnly project(':')
+dependencies {
+    compileOnly 'org.grails:grails-bootstrap:1.2.0'
 }
diff --git a/grails-rt/build.gradle.kts b/grails-rt/build.gradle.kts
deleted file mode 100644
index 54d5472..0000000
--- a/grails-rt/build.gradle.kts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-}
-
-group = "org.apache.grails.intellij.rt"
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    compileOnly("org.grails:grails-bootstrap:1.2.0")
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-    // Set the JVM compatibility versions
-    withType<JavaCompile> {
-        sourceCompatibility = "8"
-        targetCompatibility = "8"
-        // IPGP 2.18+ force-sets options.release to the platform's Java version (25);
-        // this module must stay Java 8 bytecode (it runs inside user Grails/JPS builds)
-        options.release = 8
-    }
-
-}
diff --git a/hibernate/build.gradle b/hibernate/build.gradle
index 640e48c..c33812c 100644
--- a/hibernate/build.gradle
+++ b/hibernate/build.gradle
@@ -18,9 +18,9 @@ plugins {
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'com.intellij.hibernate'
     }
 
     compileOnly project(':')
diff --git a/hibernate/build.gradle b/i18n/build.gradle
similarity index 85%
copy from hibernate/build.gradle
copy to i18n/build.gradle
index 640e48c..5b111d7 100644
--- a/hibernate/build.gradle
+++ b/i18n/build.gradle
@@ -18,9 +18,10 @@ plugins {
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'org.intellij.groovy'
+        bundledPlugin 'com.intellij.java-i18n'
     }
 
     compileOnly project(':')
diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts
deleted file mode 100644
index c83209a..0000000
--- a/i18n/build.gradle.kts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.i18n"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("org.intellij.groovy")
-        bundledPlugin("com.intellij.java-i18n")
-    }
-
-    compileOnly(project(":"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}
-
-// 2026.2 resolves content-module descriptors from lib/modules/<module-name>.jar,
-// so the composed jar must be named after the content module, not the project
-tasks.named<org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask>("composedJar") {
-    archiveBaseName = "intellij.groovy.grails.i18n"
-}
diff --git a/hibernate/build.gradle b/jps-plugin/build.gradle
similarity index 68%
copy from hibernate/build.gradle
copy to jps-plugin/build.gradle
index 640e48c..34f3260 100644
--- a/hibernate/build.gradle
+++ b/jps-plugin/build.gradle
@@ -14,14 +14,20 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.intellij-module'
+    id 'org.apache.grails.intellij.build.intellij-lib'
+    id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+// runs inside the end user's JPS build process
+legacyBytecode {
+    release = 11
+}
+
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'org.intellij.groovy'
     }
 
-    compileOnly project(':')
+    compileOnly project(':grails-compiler-patch')
 }
diff --git a/jps-plugin/build.gradle.kts b/jps-plugin/build.gradle.kts
deleted file mode 100644
index 7b70d34..0000000
--- a/jps-plugin/build.gradle.kts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.jps"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("org.intellij.groovy")
-    }
-
-    compileOnly(project(":grails-compiler-patch"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-    // Set the JVM compatibility versions
-    withType<JavaCompile> {
-        sourceCompatibility = "11"
-        targetCompatibility = "11"
-        // IPGP 2.18+ force-sets options.release to the platform's Java version (25);
-        // this module must stay Java 11 bytecode (it runs inside user Grails/JPS builds)
-        options.release = 11
-    }
-
-}
diff --git a/hibernate/build.gradle b/langInjection/build.gradle
similarity index 83%
copy from hibernate/build.gradle
copy to langInjection/build.gradle
index 640e48c..bc3a763 100644
--- a/hibernate/build.gradle
+++ b/langInjection/build.gradle
@@ -18,10 +18,8 @@ plugins {
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+dependencies {
+    intellijPlatform {
+        bundledModule 'intellij.platform.langInjection'
     }
-
-    compileOnly project(':')
 }
diff --git a/langInjection/build.gradle.kts b/langInjection/build.gradle.kts
deleted file mode 100644
index fd0e4d1..0000000
--- a/langInjection/build.gradle.kts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.langInjection"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledModule("intellij.platform.langInjection")
-    }
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}
-
-// 2026.2 resolves content-module descriptors from lib/modules/<module-name>.jar,
-// so the composed jar must be named after the content module, not the project
-tasks.named<org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask>("composedJar") {
-    archiveBaseName = "intellij.groovy.grails.langInjection"
-}
diff --git a/hibernate/build.gradle b/maven/build.gradle
similarity index 72%
copy from hibernate/build.gradle
copy to maven/build.gradle
index 640e48c..4319350 100644
--- a/hibernate/build.gradle
+++ b/maven/build.gradle
@@ -15,13 +15,17 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
+    id 'org.apache.grails.intellij.build.kotlin'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
-dependencies { deps ->
-    deps.intellijPlatform { platform ->
-        platform.bundledPlugin 'com.intellij.hibernate'
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'org.jetbrains.idea.maven'
+        // java debugger classes moved into the java plugin modules in 2026.2
+        bundledPlugin 'com.intellij.java'
     }
 
+    compileOnly project(':grails-rt')
     compileOnly project(':')
 }
diff --git a/maven/build.gradle.kts b/maven/build.gradle.kts
deleted file mode 100644
index df4f1d2..0000000
--- a/maven/build.gradle.kts
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.kotlin.jvm") version "2.3.0"
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.maven"
-
-kotlin {
-    // no toolchain on purpose: the JDK is pinned via .sdkmanrc for reproducible builds
-    compilerOptions {
-        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(properties("platformJavaVersion"))
-    }
-}
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("org.jetbrains.idea.maven")
-        bundledPlugin("com.intellij.java") // java debugger classes moved into the java plugin modules in 2026.2
-    }
-
-    compileOnly(project(":grails-rt"))
-    compileOnly(project(":"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}
-
-
-// 2026.2 resolves content-module descriptors from lib/modules/<module-name>.jar,
-// so the composed jar must be named after the content module, not the project
-tasks.named<org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask>("composedJar") {
-    archiveBaseName = "intellij.groovy.grails.maven"
-}
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index e40249b..3f062bf 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -318,7 +318,12 @@
 
     <pom.declarationSearcher implementation="org.jetbrains.plugins.grails.pluginSupport.webflow.WebFlowPomDeclarationSearcher"/>
 
-    <compileServer.plugin classpath="Grails.jps-plugin.jar;Grails.grails-compiler-patch.jar"/>
+    <!-- Each entry must match a jar in the plugin's lib/ directory exactly; JPS resolves them by
+         name and silently skips anything it cannot find, so a stale entry disables the Grails JPS
+         builders with nothing more than a line in idea.log. These names are pinned by the
+         org.apache.grails.intellij.build.intellij-lib convention plugin rather than derived from
+         rootProject.name, which is what let this attribute go stale previously. -->
+    <compileServer.plugin classpath="org.apache.grails.intellij.lib.jps-plugin.jar;org.apache.grails.intellij.lib.grails-compiler-patch.jar"/>
 
     <multiHostInjector implementation="org.jetbrains.plugins.grails.lang.gsp.GspCssInjector"/>
     <multiHostInjector implementation="org.jetbrains.plugins.grails.references.constraints.GormRegexInjector"/>
diff --git a/settings.gradle.kts b/settings.gradle
similarity index 66%
rename from settings.gradle.kts
rename to settings.gradle
index 53b8d2d..f6cfa8b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle
@@ -14,19 +14,19 @@
  * limitations under the License.
  */
 pluginManagement {
-    includeBuild("build-logic")
+    includeBuild('build-logic')
 }
 
-rootProject.name = "Grails IntelliJ Plugin"
+rootProject.name = 'grails-intellij-plugin'
 
-include("copyright")
-include("coverage")
-include("gradle-tooling")
-include("grails-compiler-patch")
-include("grails-rt")
-include("hibernate")
-include("i18n")
-include("jps-plugin")
-include("langInjection")
-include("maven")
-include("testFramework")
\ No newline at end of file
+include 'copyright'
+include 'coverage'
+include 'gradle-tooling'
+include 'grails-compiler-patch'
+include 'grails-rt'
+include 'hibernate'
+include 'i18n'
+include 'jps-plugin'
+include 'langInjection'
+include 'maven'
+include 'testFramework'
\ No newline at end of file
diff --git a/testFramework/build.gradle b/testFramework/build.gradle
new file mode 100644
index 0000000..b301a2f
--- /dev/null
+++ b/testFramework/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.jetbrains.intellij.platform.gradle.Constants
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
+
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-lib'
+    id 'org.apache.grails.intellij.build.kotlin'
+    id 'org.apache.grails.intellij.build.legacy-layout'
+}
+
+dependencies {
+    intellijPlatform {
+        bundledPlugin 'org.intellij.groovy'
+        bundledPlugin 'com.intellij.gradle' // split out of org.jetbrains.plugins.gradle in 2026.2
+        bundledPlugin 'org.jetbrains.plugins.gradle'
+
+        // The test frameworks are added to _production_ deps here, because this project *is* the
+        // test framework for the others.
+        //
+        // Three arguments are passed explicitly. The Kotlin original used named arguments to skip
+        // the middle `version` parameter, which Groovy cannot do, so the default has to be spelled
+        // out. The types are Kotlin `object`s with private constructors, so `.INSTANCE` is required
+        // -- a bare `TestFrameworkType.Platform` is a Class reference in Groovy, not a value.
+        // 'compileOnly' replaces JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME, which is Gradle
+        // internal API.
+        testFramework TestFrameworkType.Plugin.Java.INSTANCE, Constants.Constraints.CLOSEST_VERSION, 'compileOnly'
+        testFramework TestFrameworkType.Platform.INSTANCE, Constants.Constraints.CLOSEST_VERSION, 'compileOnly'
+        testFramework TestFrameworkType.Plugin.Maven.INSTANCE, Constants.Constraints.CLOSEST_VERSION, 'compileOnly'
+        testFramework TestFrameworkType.Plugin.ExternalSystem.INSTANCE, Constants.Constraints.CLOSEST_VERSION, 'compileOnly'
+    }
+
+    compileOnly 'junit:junit:4.13.2'
+    compileOnly 'org.assertj:assertj-core:4.0.0-M1'
+
+    compileOnly project(':')
+}
diff --git a/testFramework/build.gradle.kts b/testFramework/build.gradle.kts
deleted file mode 100644
index a9cc5da..0000000
--- a/testFramework/build.gradle.kts
+++ /dev/null
@@ -1,88 +0,0 @@
-import org.gradle.api.internal.tasks.JvmConstants
-import org.jetbrains.intellij.platform.gradle.TestFrameworkType
-
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.kotlin.jvm") version "2.3.0"
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.testFramework"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-kotlin {
-    // no toolchain on purpose: the JDK is pinned via .sdkmanrc for reproducible builds
-    compilerOptions {
-        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(properties("platformJavaVersion"))
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("org.intellij.groovy")
-        bundledPlugin("com.intellij.gradle") // split out of org.jetbrains.plugins.gradle in 2026.2
-        bundledPlugin("org.jetbrains.plugins.gradle")
-
-        // add to _production_ deps
-        testFramework(
-            type = TestFrameworkType.Plugin.Java,
-            configurationName = JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME
-        )
-        testFramework(
-            type = TestFrameworkType.Platform,
-            configurationName = JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME
-        )
-        testFramework(
-            type = TestFrameworkType.Plugin.Maven,
-            configurationName = JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME
-        )
-        testFramework(
-            type = TestFrameworkType.Plugin.ExternalSystem,
-            configurationName = JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME
-        )
-
-    }
-    compileOnly("junit:junit:4.13.2")
-    compileOnly("org.assertj:assertj-core:4.0.0-M1")
-
-    compileOnly(project(":"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}


(grails-intellij-plugin) 10/35: Convert build-logic to Groovy precompiled script plugins

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 134d0df52344e7f879c869ef27bff6da13940497
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 10:57:49 2026 -0400

    Convert build-logic to Groovy precompiled script plugins
    
    First step of the Gradle restructure. build-logic moves from Kotlin DSL
    (kotlin-dsl, src/main/kotlin/*.gradle.kts) to Groovy precompiled script plugins
    (groovy-gradle-plugin, src/main/groovy/*.gradle), and the plugin ids move from
    grails-intellij.* to org.apache.grails.intellij.build.*.
    
    Verified against the pre-restructure baseline: 987 tests, 0 failures, RAT clean,
    configuration cache still reused with no problems.
    
    Two substantive changes beyond the language port:
    
    * build-logic now declares the Gradle plugins the convention plugins apply
      (IntelliJ Platform 2.18.1, Kotlin 2.3.0, RAT 0.8.1, Sonatype scan 3.1.6) as
      implementation dependencies. Today org.jetbrains.intellij.platform.module is
      applied with no version in ten modules and resolves only by inheriting the
      root build script's plugin classpath -- which would break the moment the
      version-bearing declaration moves into a nested plugin/ project. Putting the
      plugins on build-logic's classpath removes that coupling. These versions live
      in build-logic/build.gradle rather than gradle.properties because build-logic
      is a separate build and cannot read the root build's properties.
    
    * Fixed a latent configuration-cache violation in the vulnerability-scan
      plugin: it dereferenced project.path inside doFirst/doLast, which is
      unsupported at execution time, and read System.getenv eagerly. project.path
      is now captured at configuration time and the environment is read through
      providers.environmentVariable. This only escaped detection because
      ossIndexAudit is not part of check.
    
    One Groovy-vs-Kotlin DSL difference worth recording: Groovy closures resolve
    owner-first, so a bare `username = ...` inside extensions.configure(...) was
    silently assigned to the Project rather than the extension and failed with
    MissingPropertyException. The extension is now bound to an explicit closure
    parameter; Kotlin's configure<T> {} has no such fallback.
    
    Verified the ported conventions actually take effect rather than silently
    no-op'ing: archive tasks report preserveFileTimestamps=false and
    reproducibleFileOrder=true, jacoco resolves toolVersion 0.8.14, and
    ossIndexAudit is registered.
---
 build-logic/build.gradle                           | 37 +++++++++++
 build-logic/build.gradle.kts                       | 29 --------
 .../{settings.gradle.kts => settings.gradle}       |  3 +-
 ...org.apache.grails.intellij.build.jacoco.gradle} | 26 ++++----
 ...ache.grails.intellij.build.reproducible.gradle} | 13 ++--
 ...grails.intellij.build.vulnerability-scan.gradle | 77 ++++++++++++++++++++++
 .../src/main/kotlin/OssIndexAuditThrottle.kt       | 35 ----------
 .../grails-intellij.vulnerability-scan.gradle.kts  | 66 -------------------
 build.gradle.kts                                   |  6 +-
 copyright/build.gradle.kts                         |  6 +-
 coverage/build.gradle.kts                          |  6 +-
 gradle-tooling/build.gradle.kts                    |  6 +-
 grails-compiler-patch/build.gradle.kts             |  6 +-
 grails-rt/build.gradle.kts                         |  6 +-
 hibernate/build.gradle.kts                         |  6 +-
 i18n/build.gradle.kts                              |  6 +-
 jps-plugin/build.gradle.kts                        |  6 +-
 langInjection/build.gradle.kts                     |  6 +-
 maven/build.gradle.kts                             |  6 +-
 testFramework/build.gradle.kts                     |  6 +-
 20 files changed, 170 insertions(+), 188 deletions(-)

diff --git a/build-logic/build.gradle b/build-logic/build.gradle
new file mode 100644
index 0000000..91b51c2
--- /dev/null
+++ b/build-logic/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+    id 'groovy-gradle-plugin'
+}
+
+// Gradle plugins that the convention plugins apply. Declaring them here is what lets the
+// convention plugins apply them WITHOUT a version, and it removes the previous fragility
+// where 'org.jetbrains.intellij.platform.module' was applied version-free in every module
+// and resolved only by inheriting the root build script's plugin classpath.
+//
+// Versions live here rather than in the root gradle.properties because build-logic is a
+// separate build and cannot read the root build's properties.
+dependencies {
+    // Dependabot (gradle ecosystem, /build-logic) keeps these versions current.
+    implementation 'org.jetbrains.intellij.platform:intellij-platform-gradle-plugin:2.18.1'
+    implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0'
+    implementation 'org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:0.8.1'
+    implementation 'org.sonatype.gradle.plugins:scan-gradle-plugin:3.1.6'
+}
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
deleted file mode 100644
index 2ef3348..0000000
--- a/build-logic/build.gradle.kts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-plugins {
-    `kotlin-dsl`
-}
-
-// The convention plugins here are consumed by the root build and every module. The
-// vulnerability-scan plugin applies the Sonatype OSS Index plugin and references its
-// extension type, so that plugin must be on the build-logic compile classpath.
-dependencies {
-    // Dependabot (gradle ecosystem, /build-logic) keeps this version current.
-    implementation("org.sonatype.gradle.plugins:scan-gradle-plugin:3.1.6")
-}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle
similarity index 96%
rename from build-logic/settings.gradle.kts
rename to build-logic/settings.gradle
index 8334e44..b590824 100644
--- a/build-logic/settings.gradle.kts
+++ b/build-logic/settings.gradle
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 dependencyResolutionManagement {
     repositories {
         mavenCentral()
@@ -23,4 +24,4 @@ dependencyResolutionManagement {
     }
 }
 
-rootProject.name = "build-logic"
+rootProject.name = 'build-logic'
diff --git a/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts b/build-logic/src/main/groovy/org.apache.grails.intellij.build.jacoco.gradle
similarity index 54%
rename from build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts
rename to build-logic/src/main/groovy/org.apache.grails.intellij.build.jacoco.gradle
index 9011919..af4f961 100644
--- a/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.jacoco.gradle
@@ -16,31 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import org.gradle.testing.jacoco.tasks.JacocoReport
 
-// Per-project JaCoCo conventions. Ported from grails-core's GrailsJacocoPlugin (per-module
-// portion only). The cross-project aggregate is produced by the root build via Gradle's
-// built-in `jacoco-report-aggregation` plugin, which is safe under the configuration cache
-// (this repo has org.gradle.configuration-cache=true, unlike grails-core).
+// Per-project JaCoCo conventions. The cross-project aggregate is produced by the root build
+// via org.apache.grails.intellij.build.coverage-aggregation, which wraps Gradle's built-in
+// jacoco-report-aggregation plugin (configuration-cache safe, unlike a hand-rolled aggregate).
 
 plugins {
-    jacoco
+    id 'jacoco'
 }
 
 jacoco {
     // keep in sync with `jacocoVersion` in gradle.properties
-    toolVersion = (project.findProperty("jacocoVersion") ?: "0.8.14").toString()
+    toolVersion = providers.gradleProperty('jacocoVersion').getOrElse('0.8.14')
 }
 
-tasks.withType<Test>().configureEach {
-    finalizedBy(tasks.withType<JacocoReport>())
+tasks.withType(Test).configureEach {
+    finalizedBy tasks.withType(JacocoReport)
 }
 
-tasks.withType<JacocoReport>().configureEach {
-    dependsOn(tasks.withType<Test>())
+tasks.withType(JacocoReport).configureEach {
+    dependsOn tasks.withType(Test)
     reports {
-        xml.required.set(true)
-        html.required.set(true)
-        csv.required.set(false)
+        xml.required = true
+        html.required = true
+        csv.required = false
     }
 }
diff --git a/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts b/build-logic/src/main/groovy/org.apache.grails.intellij.build.reproducible.gradle
similarity index 77%
rename from build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts
rename to build-logic/src/main/groovy/org.apache.grails.intellij.build.reproducible.gradle
index 2644224..221b929 100644
--- a/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.reproducible.gradle
@@ -17,8 +17,7 @@
  * under the License.
  */
 
-// Reproducible-build conventions. Ported from grails-core's CompilePlugin.configureReproducible.
-// Applied to the root project and every module so that the plugin ZIP
+// Reproducible-build conventions. Applied to every project so that the plugin ZIP
 // (build/distributions/*.zip) and all composed/module jars are byte-for-byte stable.
 //
 // preserveFileTimestamps = false pins every archive entry to a constant timestamp, so
@@ -26,9 +25,9 @@
 // removes the only other common source of non-determinism (filesystem iteration order).
 // Fixed permissions guarantee the same entry metadata regardless of the host umask.
 
-tasks.withType<AbstractArchiveTask>().configureEach {
-    isPreserveFileTimestamps = false
-    isReproducibleFileOrder = true
-    filePermissions { unix("0644") }
-    dirPermissions { unix("0755") }
+tasks.withType(AbstractArchiveTask).configureEach {
+    preserveFileTimestamps = false
+    reproducibleFileOrder = true
+    filePermissions { unix('0644') }
+    dirPermissions { unix('0755') }
 }
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.vulnerability-scan.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.vulnerability-scan.gradle
new file mode 100644
index 0000000..caa392c
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.vulnerability-scan.gradle
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.grails.intellij.build.OssIndexAuditThrottle
+import org.sonatype.gradle.plugins.scan.ossindex.OssIndexPluginExtension
+
+// Sonatype OSS Index / Guide vulnerability scanning. Credentials come from the environment:
+//   SONATYPE_GUIDE_USERNAME - Guide account email / user token username
+//   SONATYPE_GUIDE_TOKEN    - Guide Personal Access Token (PAT)
+// The `ossIndexAudit` task fails fast when SONATYPE_GUIDE_TOKEN is absent.
+
+plugins {
+    id 'org.sonatype.gradle.plugins.scan'
+}
+
+// The OSS Index plugin guards its shared on-disk response cache with a file lock, so audit
+// tasks cannot run concurrently without hitting OverlappingFileLockException. Gate every
+// audit task on a single-usage build service to serialize them while leaving parallel
+// execution enabled for the rest of the build.
+def throttle = gradle.sharedServices.registerIfAbsent('ossIndexAuditThrottle', OssIndexAuditThrottle) {
+    maxParallelUsages = 1
+}
+
+// Read through Providers rather than System.getenv so the values are tracked inputs.
+def guideUsername = providers.environmentVariable('SONATYPE_GUIDE_USERNAME')
+def guideToken = providers.environmentVariable('SONATYPE_GUIDE_TOKEN')
+
+// The extension is named explicitly rather than configured via a bare closure: Groovy
+// closures resolve owner-first, so `username = ...` would be set on the Project instead of
+// the extension (Kotlin's configure<T> {} has no such fallback).
+extensions.configure(OssIndexPluginExtension) { OssIndexPluginExtension ossIndex ->
+    ossIndex.username = guideUsername.getOrElse('')
+    ossIndex.password = guideToken.getOrElse('')
+    // Exemptions are limited to vulnerabilities with NO available upstream fix. Everything
+    // with a published fix must be bumped instead. Each entry must name the CVE and the
+    // removal condition, and be revisited whenever a fix ships. Empty until a real,
+    // unfixable CVE is encountered.
+    ossIndex.excludeCoordinates = [] as Set
+}
+
+// Captured at configuration time: Task.project is unavailable at execution time under the
+// configuration cache, so the task actions below must not reach for it.
+def projectPath = project.path
+def projectName = project.name
+
+tasks.named('ossIndexAudit') {
+    group = 'verification'
+    description = "Scans $projectName runtime dependencies for known vulnerabilities via Sonatype Guide"
+    usesService(throttle)
+    doFirst {
+        if (!guideToken.isPresent() || guideToken.get().isEmpty()) {
+            throw new GradleException(
+                    'SONATYPE_GUIDE_TOKEN environment variable is not set. ' +
+                            'A Sonatype Guide Personal Access Token is required to run the vulnerability scan.')
+        }
+        println "##[ossIndexAudit:begin] $projectPath"
+    }
+    doLast {
+        println "##[ossIndexAudit:end] $projectPath"
+    }
+}
diff --git a/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt b/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt
deleted file mode 100644
index 4f8619f..0000000
--- a/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.grails.intellij.buildlogic
-
-import org.gradle.api.services.BuildService
-import org.gradle.api.services.BuildServiceParameters
-
-/**
- * Shared build service used solely as a concurrency gate for `ossIndexAudit` tasks.
- *
- * The Sonatype OSS Index Gradle plugin backs every audit task with a single shared
- * on-disk response cache guarded by a file lock. When Gradle runs audit tasks in parallel,
- * they contend for that lock and fail with `OverlappingFileLockException`, surfaced by the
- * plugin as the opaque "Could not audit the project: null".
- *
- * Registered with `maxParallelUsages = 1`, this service forces Gradle to run at most one
- * audit task at a time while leaving parallel execution enabled for the rest of the build.
- */
-abstract class OssIndexAuditThrottle : BuildService<BuildServiceParameters.None>
diff --git a/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts b/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts
deleted file mode 100644
index d4d8205..0000000
--- a/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import org.apache.grails.intellij.buildlogic.OssIndexAuditThrottle
-import org.sonatype.gradle.plugins.scan.ossindex.OssIndexPluginExtension
-
-// Sonatype OSS Index / Guide vulnerability scanning. Ported from grails-core's
-// VulnerabilityScanPlugin. Credentials come from the environment:
-//   SONATYPE_GUIDE_USERNAME - Guide account email / user token username
-//   SONATYPE_GUIDE_TOKEN    - Guide Personal Access Token (PAT)
-// The `ossIndexAudit` task fails fast when SONATYPE_GUIDE_TOKEN is absent.
-
-plugins {
-    id("org.sonatype.gradle.plugins.scan")
-}
-
-// The OSS Index plugin guards its shared on-disk response cache with a file lock, so audit
-// tasks cannot run concurrently without hitting OverlappingFileLockException. Gate every
-// audit task on a single-usage build service to serialize them while leaving parallel
-// execution enabled for the rest of the build.
-val throttle = gradle.sharedServices.registerIfAbsent("ossIndexAuditThrottle", OssIndexAuditThrottle::class.java) {
-    maxParallelUsages.set(1)
-}
-
-configure<OssIndexPluginExtension> {
-    username = System.getenv("SONATYPE_GUIDE_USERNAME") ?: ""
-    password = System.getenv("SONATYPE_GUIDE_TOKEN") ?: ""
-    // Exemptions are limited to vulnerabilities with NO available upstream fix. Everything
-    // with a published fix must be bumped instead. Each entry must name the CVE and the
-    // removal condition, and be revisited whenever a fix ships. Empty until a real,
-    // unfixable CVE is encountered.
-    excludeCoordinates = emptySet()
-}
-
-tasks.named("ossIndexAudit") {
-    group = "verification"
-    description = "Scans ${project.name} runtime dependencies for known vulnerabilities via Sonatype Guide"
-    usesService(throttle)
-    doFirst {
-        if (System.getenv("SONATYPE_GUIDE_TOKEN").isNullOrEmpty()) {
-            throw GradleException(
-                "SONATYPE_GUIDE_TOKEN environment variable is not set. " +
-                    "A Sonatype Guide Personal Access Token is required to run the vulnerability scan."
-            )
-        }
-        println("##[ossIndexAudit:begin] ${project.path}")
-    }
-    doLast {
-        println("##[ossIndexAudit:end] ${project.path}")
-    }
-}
diff --git a/build.gradle.kts b/build.gradle.kts
index 8f0cd6a..b20dc23 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -26,9 +26,9 @@ plugins {
     id("org.nosphere.apache.rat") version "0.8.1"
     // build-hygiene conventions (build-logic included build)
     id("jacoco-report-aggregation")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
 }
 
 group = "org.apache.grails.intellij"
diff --git a/copyright/build.gradle.kts b/copyright/build.gradle.kts
index f6f9874..4257796 100644
--- a/copyright/build.gradle.kts
+++ b/copyright/build.gradle.kts
@@ -18,9 +18,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/coverage/build.gradle.kts b/coverage/build.gradle.kts
index d4eda8d..740371b 100644
--- a/coverage/build.gradle.kts
+++ b/coverage/build.gradle.kts
@@ -18,9 +18,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/gradle-tooling/build.gradle.kts b/gradle-tooling/build.gradle.kts
index f51f633..c829d64 100644
--- a/gradle-tooling/build.gradle.kts
+++ b/gradle-tooling/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/grails-compiler-patch/build.gradle.kts b/grails-compiler-patch/build.gradle.kts
index 896fa65..ebd59a4 100644
--- a/grails-compiler-patch/build.gradle.kts
+++ b/grails-compiler-patch/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/grails-rt/build.gradle.kts b/grails-rt/build.gradle.kts
index da5ee88..54d5472 100644
--- a/grails-rt/build.gradle.kts
+++ b/grails-rt/build.gradle.kts
@@ -15,9 +15,9 @@
  */
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
 }
 
 group = "org.apache.grails.intellij.rt"
diff --git a/hibernate/build.gradle.kts b/hibernate/build.gradle.kts
index 2e88bf9..ff36d98 100644
--- a/hibernate/build.gradle.kts
+++ b/hibernate/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts
index 99961d9..efd45f1 100644
--- a/i18n/build.gradle.kts
+++ b/i18n/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/jps-plugin/build.gradle.kts b/jps-plugin/build.gradle.kts
index cee000e..7b70d34 100644
--- a/jps-plugin/build.gradle.kts
+++ b/jps-plugin/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/langInjection/build.gradle.kts b/langInjection/build.gradle.kts
index c84fbac..fd0e4d1 100644
--- a/langInjection/build.gradle.kts
+++ b/langInjection/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/maven/build.gradle.kts b/maven/build.gradle.kts
index 2e1300a..df4f1d2 100644
--- a/maven/build.gradle.kts
+++ b/maven/build.gradle.kts
@@ -17,9 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/testFramework/build.gradle.kts b/testFramework/build.gradle.kts
index 0822f0b..a9cc5da 100644
--- a/testFramework/build.gradle.kts
+++ b/testFramework/build.gradle.kts
@@ -20,9 +20,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
-    id("grails-intellij.reproducible")
-    id("grails-intellij.jacoco")
-    id("grails-intellij.vulnerability-scan")
+    id("org.apache.grails.intellij.build.reproducible")
+    id("org.apache.grails.intellij.build.jacoco")
+    id("org.apache.grails.intellij.build.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }


(grails-intellij-plugin) 17/35: Track the dormant grails-rt code in the improvement plan

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 02f421815a4c93b6969cdc0ccb2263b8f3ff0361
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:47:24 2026 -0400

    Track the dormant grails-rt code in the improvement plan
    
    Expands the Dormant code section of grails-rt/README.md into a table of what is
    unreachable and why, and adds the matching item 0.1b to IMPROVEMENT-PLAN.md so
    revive-or-delete is a decision someone owns rather than a comment.
    
    Five entries, roughly half the jar:
    
      addAgentJar                      no callers, so -javaagent: never runs
      Agent                            reached only via addAgentJar
      AddAgentJarToClassPathTransformer  installed only by Agent.premain
      ForkListenerTransformer          installed only by Agent.premain
      GrailsIdeaTestListener           Grails 2 GrailsBuildListener API; its only
                                       reference is gated on a Gradle task that only
                                       the old Grails Gradle plugin defines
    
    Each claim is re-checked against the tree rather than asserted: addAgentJar has
    exactly one occurrence (its own definition), Agent and ForkListenerTransformer
    have zero references outside grails-rt, and GrailsIdeaTestListener is referenced
    twice but only as a string class name passed through
    -Dgrails.build.listeners, never linked.
    
    The README states plainly that these are not bugs and points at the plan item, so
    the next person to notice an unused Java agent does not go looking for the missing
    call site. Deleting them would reduce grails-rt to the two Groovy resources the
    Grails 3+ paths actually use.
    
    The plan item also picks up the leftover untracked assetPipeline/ build output from
    a module that was removed from settings.gradle.
---
 IMPROVEMENT-PLAN.md | 20 ++++++++++++++++++++
 grails-rt/README.md | 31 +++++++++++++++++++------------
 2 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/IMPROVEMENT-PLAN.md b/IMPROVEMENT-PLAN.md
index 107e4f3..fbca14d 100644
--- a/IMPROVEMENT-PLAN.md
+++ b/IMPROVEMENT-PLAN.md
@@ -47,6 +47,26 @@ anything. Concrete checklist:
 - [ ] Confirm the `gen/` GSP lexer sources regenerate from their JFlex inputs; document
       the regeneration command in `DEVELOPMENT.md`.
 
+**0.1b Dormant code inherited from the JetBrains import** — decide revive vs delete for
+each. All of these arrived already-orphaned in `3d4bc40` (the initial import); none was
+broken by later work. They are documented in `grails-rt/README.md` so they are not
+rediscovered as bugs. Deleting them would shrink `grails-rt` to the two Groovy resources
+that Grails 3+ actually uses.
+- [ ] `GrailsExecutionUtils.addAgentJar` has **no callers**, so the `-javaagent:` path
+      never executes. The jar is a valid agent again (`Premain-Class` is declared on the
+      `jar` task — it cannot be a `META-INF/MANIFEST.MF` resource, because Gradle's `Jar`
+      task silently shadows one), but the capability is unused. Decide whether forked-run
+      instrumentation is still wanted.
+- [ ] `Agent`, `AddAgentJarToClassPathTransformer`, `ForkListenerTransformer` are reachable
+      only through `addAgentJar`, so they are dead with it.
+- [ ] `GrailsIdeaTestListener` implements `grails.build.GrailsBuildListener`, a Grails 2
+      build-system API that does not exist in Grails 3+. Its only reference is
+      `GrailsTaskManagerExtension`, gated on a Gradle task named `grails-test-app` that
+      only the old Grails Gradle plugin defines — so it is unreachable on Grails 3+ too.
+      Grails 7/8 test-event reporting needs a different mechanism; scope that in 2.6.
+- [ ] Untracked `assetPipeline/` build output remains from a module deleted from
+      `settings.gradle`. Safe to remove.
+
 **0.2 Real-app walkthrough** — generate three apps and open each with the plugin:
 1. Grails **7.0.x** release (via `https://start.grails.org` — forge production
    endpoint) with the `web` application type,
diff --git a/grails-rt/README.md b/grails-rt/README.md
index 3838080..5911858 100644
--- a/grails-rt/README.md
+++ b/grails-rt/README.md
@@ -29,18 +29,25 @@ Grails 3+ only ever loads the Groovy resources and the marker, none of which tou
 
 ## Dormant code
 
-Both of these arrived already-orphaned in the initial JetBrains import (`3d4bc40`); neither was
-broken by later work.
-
-- **`GrailsExecutionUtils.addAgentJar` has no callers**, so the `-javaagent:` path never executes.
-  The jar *is* a valid agent (`Premain-Class` is declared on the `jar` task), but the capability is
-  unused. The attributes must be set on the task, not via a `META-INF/MANIFEST.MF` resource —
-  Gradle's `Jar` task writes its own manifest and silently shadows such a resource, which is why the
-  packaged jar previously carried only `Manifest-Version: 1.0`.
-- **`GrailsIdeaTestListener`** implements `grails.build.GrailsBuildListener`, the Grails 2 build
-  system API. Its only reference is `GrailsTaskManagerExtension`, gated on a Gradle task named
-  `grails-test-app` that only the old Grails Gradle plugin defines — so it is unreachable on
-  Grails 3+ as well.
+Roughly half this jar is unreachable. All of it arrived already-orphaned in the initial JetBrains
+import (`3d4bc40`) — `git log -S"addAgentJar"` returns that one commit, so none of it was wired up
+and later broken. **Do not treat these as bugs**; revive-or-delete is tracked as item 0.1b in
+[IMPROVEMENT-PLAN.md](../IMPROVEMENT-PLAN.md).
+
+| Dormant | Why it is unreachable |
+| --- | --- |
+| `GrailsExecutionUtils.addAgentJar` | no callers, so the `-javaagent:` path never executes |
+| `Agent` | reached only via `addAgentJar` |
+| `AddAgentJarToClassPathTransformer` | installed only by `Agent.premain` |
+| `ForkListenerTransformer` | installed only by `Agent.premain`, and only when `idea.grails.kind.file` is set |
+| `GrailsIdeaTestListener` | implements `grails.build.GrailsBuildListener`, a Grails 2 API absent from 3+. Its only reference is `GrailsTaskManagerExtension`, gated on a Gradle task named `grails-test-app` that only the old Grails Gradle plugin defines — so unreachable on Grails 3+ too |
+
+The jar *is* nonetheless a valid Java agent: `Premain-Class` and `Can-Redefine-Classes` are declared
+on the `jar` task. They cannot live in a `META-INF/MANIFEST.MF` resource — Gradle's `Jar` task writes
+its own manifest and silently shadows one, which is why the packaged jar previously carried only
+`Manifest-Version: 1.0` and would have failed a `-javaagent:` launch with *"Failed to find
+Premain-Class manifest attribute."* Restoring it changed no behaviour, since nothing injects the
+agent; it just means the jar no longer lies about what it is.
 
 ## Build
 


(grails-intellij-plugin) 03/35: Fix release workflow hardcoding the pre-rename plugin ZIP name

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 6be9d414b9bdf6059ec97234eebac732e7606918
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 07:15:17 2026 -0400

    Fix release workflow hardcoding the pre-rename plugin ZIP name
    
    The publish job renamed build/distributions/Grails-${VERSION}-signed.zip to the
    ASF distribution name. That filename is derived from the IntelliJ plugin name
    (rootProject.name), which is now "Grails IntelliJ Plugin", so the step would
    have failed on the next release.
    
    Glob for *-${VERSION}-signed.zip instead of hardcoding the base name, quote the
    result so the spaces in the current name are handled, and fail with a clear
    error unless exactly one file matches -- so a future rename cannot silently
    break the release again.
---
 .github/workflows/release.yml | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c49da8e..5273b52 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -67,7 +67,19 @@ jobs:
           PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }}
         run: ./gradlew signPlugin -Pversion=${VERSION}
       - name: "📦 Rename plugin ZIP to distribution name"
-        run: cp build/distributions/Grails-${VERSION}-signed.zip ${DIST_NAME}-${VERSION}-bin.zip
+        # The ZIP base name is the IntelliJ plugin name (rootProject.name, currently
+        # "Grails IntelliJ Plugin"), not the ASF distribution name, and it contains spaces.
+        # Glob for it rather than hardcoding, so renaming the plugin cannot silently break
+        # the release; fail loudly if the match is not exactly one file.
+        run: |
+          set -euo pipefail
+          shopt -s nullglob
+          signed=(build/distributions/*-"${VERSION}"-signed.zip)
+          if [[ ${#signed[@]} -ne 1 ]]; then
+            echo "::error::expected exactly 1 signed plugin ZIP for ${VERSION} in build/distributions, found ${#signed[@]}: ${signed[*]-}"
+            exit 1
+          fi
+          cp "${signed[0]}" "${DIST_NAME}-${VERSION}-bin.zip"
       - name: "🔐 Set up GPG" # use env var to not expose the key
         env:
           GPG_KEY: ${{ secrets.GRAILS_GPG_KEY }}


(grails-intellij-plugin) 04/35: Add release verification tooling and document the release process

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 78171a4b365d826de948c0a809a43e590f10d114
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 07:15:31 2026 -0400

    Add release verification tooling and document the release process
    
    Brings the release process closer to grails-core's, scaled to this repo's two
    artifacts (source distribution + convenience binary).
    
    New verification scripts under etc/bin, mirroring grails-core's:
      * download-release-artifacts.sh - fetch staged artifacts and the Grails KEYS
      * verify-distributions.sh - checksums, GPG signatures (in a throwaway GPG
        home), and required/forbidden archive contents. Asserts the source
        distribution ships gradle-bootstrap and does NOT ship the wrapper jar.
      * verify-reproducible.sh - rebuild from the staged source distribution and
        compare to the staged binary. Compares extracted contents rather than ZIP
        hashes, because marketplace signing means the ZIPs can never match.
      * verify.sh - runs all of the above plus the RAT audit.
    
    RELEASE.md gains the sections grails-core has and this repo lacked: the
    versioning scheme, how to cut the release, a pre-vote verification guide, a
    vote email template, rollback, a GPG/KEYS appendix, a release-secrets
    appendix, and an explanation of why ASF policy requires the build to be
    reproducible.
    
    test-reproducible-build.sh now writes to etc/bin/results (which is what
    .gitignore already claimed) and keeps a copy of both builds' artifacts there,
    so a failure can actually be diagnosed instead of just reported. Comparison is
    now keyed on path rather than whole lines, so an artifact missing from one
    build is reported as missing.
    
    README.md and INSTALL cover building from nothing: prerequisites with disk and
    memory requirements, installing the pinned JDK/Gradle with and without SDKMAN!,
    what gradle-bootstrap does and when it is required, and the build itself.
---
 .gitignore                            |   4 +-
 INSTALL                               |  76 +++++--
 README.md                             | 107 ++++++---
 RELEASE.md                            | 397 ++++++++++++++++++++++++++++++----
 etc/bin/download-release-artifacts.sh |  70 ++++++
 etc/bin/test-reproducible-build.sh    |  86 ++++++--
 etc/bin/verify-distributions.sh       | 163 ++++++++++++++
 etc/bin/verify-reproducible.sh        | 126 +++++++++++
 etc/bin/verify.sh                     | 122 +++++++++++
 9 files changed, 1044 insertions(+), 107 deletions(-)

diff --git a/.gitignore b/.gitignore
index f6812c3..7495e34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,5 @@ gradle-bootstrap/gradle
 /.claude
 *.hprof
 .DS_Store
-# reproducible-build test output (etc/bin/test-reproducible-build.sh)
-/first.txt
-/second.txt
+# reproducible-build and release-verification output (etc/bin/*.sh)
 etc/bin/results
diff --git a/INSTALL b/INSTALL
index bdbd43e..c7af0f9 100644
--- a/INSTALL
+++ b/INSTALL
@@ -5,29 +5,62 @@ This document covers building the Apache Grails IntelliJ Plugin from a source
 distribution. Most users should instead install the plugin from the JetBrains
 Marketplace inside IntelliJ IDEA.
 
-Requirements & Tooling Setup for Building
------------------------------------------
-Building requires a Java Development Kit (JDK) and Gradle (https://www.gradle.org).
-A configuration file, `.sdkmanrc`, for SDKMAN! (https://sdkman.io) exists in the
-source root with the recommended versions.
+Requirements
+------------
+To run the plugin:
 
-If SDKMAN! is installed, select the tooling by running the following command at
-the source root:
+        IntelliJ IDEA Ultimate 2026.2 or newer. The plugin depends on
+        Ultimate-only functionality and will not load in IDEA Community.
+
+To build the plugin:
+
+        JDK      Liberica 25 (25.0.3-librca), pinned in `.sdkmanrc`
+        Gradle   9.3.1, pinned in `.sdkmanrc`
+        Disk     ~10 GB free for the IntelliJ Platform and Gradle caches
+        Memory   8 GB or more; the build runs a 4 GB Gradle daemon
+        Network  access to Maven Central and the JetBrains repositories
+
+Building with a JDK other than the pinned one will usually work, but the
+resulting artifacts will not be bit-for-bit reproducible against the official
+release. See RELEASE.md for details.
+
+1. Install the JDK and Gradle
+-----------------------------
+The required versions are pinned in `.sdkmanrc`, a configuration file for
+SDKMAN! (https://sdkman.io). If SDKMAN! is installed, install and select both
+tools by running the following at the source root:
 
         sdk env install
 
-Otherwise, Gradle must be bootstrapped so that the correct version is used. The
-source distribution ships without the Gradle Wrapper jar (per ASF source-release
-policy), but it can be regenerated with any locally installed Gradle:
+Otherwise install the same versions by any means you prefer -- for example
+Liberica JDK 25 (https://bell-sw.com/pages/downloads/) and Gradle 9.3.1
+(https://gradle.org/releases/) -- and put them on PATH:
+
+        export JAVA_HOME=/path/to/liberica-jdk-25
+        export PATH="$JAVA_HOME/bin:/path/to/gradle-9.3.1/bin:$PATH"
+
+Confirm the versions before continuing:
+
+        java -version
+        gradle --version
+
+2. Bootstrap the Gradle Wrapper
+-------------------------------
+Per ASF source-release policy this distribution ships without the Gradle
+Wrapper jar, so it must be regenerated once using the Gradle installed above:
 
         cd gradle-bootstrap
         gradle bootstrap
+        cd ..
+
+`gradle-bootstrap` is a small standalone Gradle build whose only job is to run
+the `wrapper` task at the version named in `.sdkmanrc` and copy the results
+(`gradlew`, `gradlew.bat`, and `gradle/wrapper/`) to the source root.
 
-This pins the wrapper to the version in `.sdkmanrc` and installs `gradlew` at the
-source root. For the remaining commands: use `gradle` if tooling was installed
-with SDKMAN! or `./gradlew` if the wrapper was bootstrapped.
+Use `./gradlew` for every command below. It is preferred over invoking `gradle`
+directly because it guarantees the pinned Gradle version is used.
 
-Building
+3. Build
 --------
 Build the plugin distribution ZIP:
 
@@ -36,6 +69,16 @@ Build the plugin distribution ZIP:
 The ZIP is written to `build/distributions/` and can be installed in IntelliJ
 IDEA via Settings > Plugins > Install Plugin from Disk.
 
+Note: the first build downloads the IntelliJ Platform (IntelliJ IDEA Ultimate)
+and the bundled plugins it compiles against, which takes time and several
+gigabytes of disk space. Later builds reuse that cache.
+
+To build a specific version instead of the default in `gradle.properties`:
+
+        ./gradlew buildPlugin -Pversion=262.0.1
+
+4. Verify (optional)
+--------------------
 Other useful tasks:
 
         ./gradlew check          # compile and run tests
@@ -43,6 +86,5 @@ Other useful tasks:
         ./gradlew rat            # Apache RAT license audit
         ./gradlew runIde         # launch a sandbox IDE with the plugin
 
-Note: the build downloads the IntelliJ Platform (IntelliJ IDEA Ultimate) and
-bundled plugins it compiles against; the first build requires network access and
-several gigabytes of disk space.
+To verify a staged or released distribution -- checksums, GPG signatures, and a
+reproducible rebuild -- see RELEASE.md and the scripts under `etc/bin`.
diff --git a/README.md b/README.md
index 97d7ef9..623dc92 100644
--- a/README.md
+++ b/README.md
@@ -10,68 +10,121 @@ This codebase was originally developed by JetBrains s.r.o. and imported from
 
 ## Requirements
 
-- IntelliJ IDEA Ultimate 2026.2+ (`sinceBuild` 262)
-- Java 25 (the `grails-rt`, `grails-compiler-patch`, and `jps-plugin` modules target Java 8/11)
+To **run** the plugin:
+
+- IntelliJ IDEA **Ultimate** 2026.2+ (`sinceBuild` 262). The plugin depends on Ultimate-only
+  functionality (JavaEE, Spring, database, microservices) and will not load in Community.
+
+To **build** the plugin:
+
+| | Version | Notes |
+| --- | --- | --- |
+| JDK | Liberica 25 (`25.0.3-librca`) | Pinned in [`.sdkmanrc`](.sdkmanrc). The exact JDK matters — see [reproducible builds](RELEASE.md#verifying-a-reproducible-build). |
+| Gradle | 9.3.1 | Pinned in [`.sdkmanrc`](.sdkmanrc). Only needed to bootstrap the wrapper; otherwise use `./gradlew`. |
+| Git | any recent | To clone the repository. |
+| Disk | ~10 GB free | The IntelliJ Platform and bundled plugins are downloaded into `.intellijPlatform` and the Gradle cache. |
+| Memory | 8 GB+ | The build runs a 4 GB Gradle daemon (set in `gradle.properties`). |
+
+The build needs network access to Maven Central and the JetBrains IntelliJ Platform
+repositories. The `grails-rt`, `grails-compiler-patch`, and `jps-plugin` modules target
+older bytecode levels (Java 8/11) but still build with the JDK above.
 
 ## Building from source
 
-For building, using, and running from a **source distribution**, see the
-[INSTALL](INSTALL) document. The steps below cover a clone of this repository.
+These steps take you from an empty machine to an installable plugin ZIP. They cover a
+clone of this repository; if you are starting from a released **source distribution**
+instead, see [INSTALL](INSTALL) — the only difference is that step 3 is mandatory there.
+
+### 1. Get the source
+
+```bash
+git clone https://github.com/apache/grails-intellij-plugin.git
+cd grails-intellij-plugin
+```
+
+### 2. Install the JDK and Gradle
 
-### 1. Set up the tooling (JDK + Gradle)
+The required versions are pinned in [`.sdkmanrc`](.sdkmanrc). The simplest way to get
+exactly those versions is [SDKMAN!](https://sdkman.io):
 
-The required JDK and Gradle versions are pinned in [`.sdkmanrc`](.sdkmanrc). The
-recommended way to install them is with [SDKMAN!](https://sdkman.io); from the source
-root run:
+```bash
+curl -s "https://get.sdkman.io" | bash     # if SDKMAN! is not installed yet
+source "$HOME/.sdkman/bin/sdkman-init.sh"
 
+sdk env install    # reads .sdkmanrc, installs the pinned JDK + Gradle
+sdk env            # selects them for the current shell
 ```
-sdk env install
+
+If you would rather not use SDKMAN!, install the same versions by any means you prefer —
+for example download [Liberica JDK 25](https://bell-sw.com/pages/downloads/) and
+[Gradle 9.3.1](https://gradle.org/releases/) manually — then make sure `java -version`
+and `gradle --version` report them:
+
+```bash
+export JAVA_HOME=/path/to/liberica-jdk-25
+export PATH="$JAVA_HOME/bin:/path/to/gradle-9.3.1/bin:$PATH"
 ```
 
-This installs the pinned Java (Liberica 25) and Gradle versions and selects them for the
-current shell. You may also install those versions by any other means you prefer.
+Building with a different JDK will usually work, but the artifacts will not be
+bit-for-bit reproducible against an official release.
 
-### 2. Bootstrap the Gradle wrapper (only if `gradlew` is missing)
+### 3. Bootstrap the Gradle Wrapper
 
-A clone of this repository already contains the Gradle Wrapper, so you can skip this step
-and use `./gradlew` directly.
+**From a git clone this step is not needed** — `gradlew` and the wrapper jar are checked
+in, so skip straight to step 4.
 
-A **source distribution** ships *without* the Gradle Wrapper jar (per ASF source-release
-policy), so it must be regenerated once. With a local Gradle installed (from step 1), run:
+**From a source distribution it is required.** ASF source releases may not contain
+compiled binaries, so the wrapper jar is stripped out and must be regenerated once using
+the local Gradle from step 2:
 
-```
+```bash
 cd gradle-bootstrap
 gradle bootstrap
 cd ..
 ```
 
-This pins the wrapper to the version in `.sdkmanrc` and installs `gradlew` /
-`gradlew.bat` at the source root. Afterwards, use `./gradlew` for every command below.
+[`gradle-bootstrap`](gradle-bootstrap) is a tiny standalone Gradle build whose only job is
+to run the `wrapper` task at the version named in `.sdkmanrc` and copy the results
+(`gradlew`, `gradlew.bat`, `gradle/wrapper/`) up to the source root. After it runs, use
+`./gradlew` for everything below.
 
-### 3. Build the plugin
+### 4. Build the plugin
 
-```
+```bash
 ./gradlew buildPlugin
 ```
 
 The plugin ZIP is written to `build/distributions/` and can be installed in IntelliJ IDEA
-via **Settings > Plugins > Install Plugin from Disk**.
+via **Settings > Plugins > ⚙ > Install Plugin from Disk**.
 
 > The first build downloads the IntelliJ Platform (IntelliJ IDEA Ultimate) and the bundled
-> plugins it compiles against — expect network access and several GB of disk usage.
+> plugins it compiles against. Expect it to take a while and pull down several GB;
+> subsequent builds reuse the `.intellijPlatform` cache.
 
-### Other useful tasks
+To build a specific version rather than the default in `gradle.properties`:
 
+```bash
+./gradlew buildPlugin -Pversion=262.0.1
 ```
-./gradlew check                  # compile and run tests
+
+### 5. Verify the build (optional)
+
+```bash
+./gradlew check                  # compile and run the test suite
 ./gradlew verifyPlugin           # IntelliJ Plugin Verifier
 ./gradlew rat                    # Apache RAT license audit
-./gradlew runIde                 # launch a sandbox IDE with the plugin
+./gradlew runIde                 # launch a sandbox IDE with the plugin installed
 ./gradlew testCodeCoverageReport # aggregate JaCoCo coverage report
 ./gradlew ossIndexAudit          # Sonatype OSS Index vulnerability scan (needs SONATYPE_GUIDE_TOKEN)
 ```
 
-To verify the build is reproducible, see [RELEASE.md](RELEASE.md#verifying-a-reproducible-build).
+To confirm the build is reproducible, see
+[RELEASE.md](RELEASE.md#verifying-a-reproducible-build).
+
+## Releasing
+
+The release process, artifact verification, and reproducibility requirements are
+documented in [RELEASE.md](RELEASE.md).
 
 ## Links
 
diff --git a/RELEASE.md b/RELEASE.md
index ea0aded..406a791 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -22,34 +22,100 @@ This document describes how a version of the Apache Grails IntelliJ plugin is re
 **ASF framing.** The official release voted on by the PMC is the **signed source
 distribution** published to `dist.apache.org`. The plugin ZIP on the JetBrains
 Marketplace is a *convenience binary* built from that same tagged source. Both must be
-reproducible from the tagged commit (see [Verifying a Reproducible Build](#verifying-a-reproducible-build)).
+reproducible from the tagged commit — see
+[Verifying a Reproducible Build](#verifying-a-reproducible-build) for why that is a
+requirement and not just good practice.
 
 The pipeline is encoded in [`.github/workflows/release.yml`](.github/workflows/release.yml)
-and is triggered when a **GitHub Release is published** for a tag (`vX.Y.Z`). It runs in
-four stages.
+and is triggered when a **GitHub Release is published** for a tag. It runs in four stages,
+described in sections 2–5 below.
 
-## Prerequisites
+## Contents
 
-- The release tag (`vX.Y.Z`) points at the exact commit to be released.
+- [Versioning](#versioning)
+- [1. Prerequisites](#1-prerequisites)
+- [2. Cut the release](#2-cut-the-release)
+- [3. Build & stage the plugin ZIP (`publish` job)](#3-build--stage-the-plugin-zip-publish-job)
+- [4. Create the source distribution (`source` job)](#4-create-the-source-distribution-source-job)
+- [5. Stage to dist.apache.org (`upload` job)](#5-stage-to-distapacheorg-upload-job)
+- [6. Verify the staged artifacts](#6-verify-the-staged-artifacts)
+- [7. Vote](#7-vote)
+- [8. Publish the release (`release` job)](#8-publish-the-release-release-job)
+- [Rollback](#rollback)
+- [Verifying a Reproducible Build](#verifying-a-reproducible-build)
+- [Appendix: GPG & the KEYS file](#appendix-gpg--the-keys-file)
+- [Appendix: Verification from a container](#appendix-verification-from-a-container)
+- [Appendix: Release secrets](#appendix-release-secrets)
+- [Appendix: Why the build must be reproducible](#appendix-why-the-build-must-be-reproducible)
+
+## Versioning
+
+The plugin does **not** follow the Grails framework version scheme. It follows the
+JetBrains Marketplace convention of versioning against the IntelliJ Platform branch it
+targets:
+
+```
+<platform-branch>.<minor>.<patch>        e.g. 262.0.0
+```
+
+- **platform-branch** — the IntelliJ Platform build branch, matching `pluginSinceBuild` in
+  [`gradle.properties`](gradle.properties). `262` is the 2026.2 branch.
+- **minor** — incremented for feature work within a platform branch.
+- **patch** — incremented for bug-fix-only releases within a platform branch.
+
+A new IntelliJ Platform branch means a new `platform-branch` segment, not a major bump:
+the 2026.3 line would start at `263.0.0`. Release tags are the version prefixed with `v`
+(`v262.0.0`), and the workflow derives the version by stripping that prefix
+(`VERSION=${TAG#v}`).
+
+The version lives in `gradle.properties` and is overridden per release with
+`-Pversion=${VERSION}`, so the checked-in value does not have to be bumped before tagging.
+
+## 1. Prerequisites
+
+- The release tag points at the exact commit to be released.
 - Marketplace signing / publishing secrets are configured: `CERTIFICATE_CHAIN`,
   `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD`, `PUBLISH_TOKEN`.
 - ASF signing + dist secrets are configured: `GRAILS_GPG_KEY`, `GPG_KEY_ID`,
   `SVC_DIST_GRAILS_USERNAME`, `SVC_DIST_GRAILS_PASSWORD`.
-- The release environment reviewers are set in [`.asf.yaml`](.asf.yaml) (GitHub does not
-  allow private groups as approvers, so approvers are listed individually).
+  See [Appendix: Release secrets](#appendix-release-secrets).
+- The `release` environment reviewers are set in [`.asf.yaml`](.asf.yaml). GitHub does not
+  allow private groups as approvers, so approvers are listed individually and there is a
+  hard limit of 6. Add yourself before starting if you are not already listed.
 - The JDK is pinned by [`.sdkmanrc`](.sdkmanrc); CI reads the version from it so local and
   CI builds match. See [INSTALL](INSTALL) for local build setup.
+- No planning documents that are excluded from the license audit
+  (`MIGRATION-PLAN.md`, `IMPROVEMENT-PLAN.md`) should still be present for a final
+  release; they are listed as RAT excludes in [`build.gradle.kts`](build.gradle.kts) and
+  are meant to be removed before the first release.
+
+## 2. Cut the release
+
+1. Go to <https://github.com/apache/grails-intellij-plugin/releases> and click
+   **Draft a new release**.
+2. Create the tag as `v<version>` (for example `v262.0.0`), targeting the branch being
+   released from.
+3. Leave **Previous tag** on *auto* and click **Generate release notes**, then edit them
+   per project agreement.
+4. Check **Set as a pre-release** for milestone / release-candidate versions.
+5. Click **Publish release**. This triggers the `Release` workflow.
+
+The first three jobs (`publish`, `source`, `upload`) run without approval. The final
+`release` job is gated on the `release` GitHub environment and will wait for a reviewer.
 
-## 1. Build & stage the plugin ZIP (`publish` job)
+## 3. Build & stage the plugin ZIP (`publish` job)
 
 1. Establish `VERSION` from the tag (`${TAG#v}`).
-2. `./gradlew check buildPlugin -Pversion=${VERSION}` — full build, tests, and Plugin ZIP.
+2. `./gradlew check buildPlugin -Pversion=${VERSION}` — full build, tests, and plugin ZIP.
 3. `./gradlew signPlugin` — JetBrains Marketplace signature.
-4. Rename to `apache-grails-intellij-plugin-${VERSION}-bin.zip`.
+4. Rename the signed ZIP to `apache-grails-intellij-plugin-${VERSION}-bin.zip`.
+   The build names the ZIP after the IntelliJ plugin name (`rootProject.name`), not the
+   ASF distribution name, so the job globs for `*-${VERSION}-signed.zip` and fails if that
+   does not match exactly one file.
 5. GPG-detached-sign (`.asc`) and checksum (`.sha512`).
 6. Upload the ZIP, `.asc`, and `.sha512` to the GitHub Release.
 
-## 2. Create the source distribution (`source` job)
+## 4. Create the source distribution (`source` job)
 
 1. Check out the tag, then remove files that must not ship in a source release
    (`.git`, `.github`, `.asf.yaml`, `gradlew`/`gradlew.bat`, `gradle/wrapper`).
@@ -57,35 +123,199 @@ four stages.
 3. GPG-detached-sign (`.asc`) and checksum (`.sha512`).
 4. Upload all three to the GitHub Release.
 
-## 3. Stage to dist.apache.org & start the vote (`upload` job)
+The Gradle Wrapper jar is removed because ASF source releases may not contain compiled
+binaries. [`gradle-bootstrap`](gradle-bootstrap) regenerates it — that is why it ships in
+the source distribution, and why `verify-distributions.sh` asserts both that
+`gradle-bootstrap` is present and that no `gradle-wrapper.jar` is.
+
+## 5. Stage to dist.apache.org (`upload` job)
 
 1. Ensure `dist/dev/grails/INTELLIJ` exists in the ASF dist SVN.
-2. Upload the source + binary distributions (with signatures and checksums) under
-   `dist/dev/grails/INTELLIJ/${VERSION}/`.
-3. **MANUAL:** start a `[VOTE]` thread for the release on `dev@grails.apache.org`.
+2. Upload the source distribution under `dist/dev/grails/INTELLIJ/${VERSION}/sources/`
+   and the convenience binary under `.../${VERSION}/distribution/`, each with its
+   signature and checksum.
+
+At this point the artifacts are staged but nothing is public. **Verify them before
+starting the vote.**
+
+## 6. Verify the staged artifacts
+
+Everything in this section can be run at once:
+
+```bash
+etc/bin/verify.sh v262.0.0 /tmp/grails-ij-verify
+```
+
+That script chains the four steps below and prints the manual checks it cannot perform.
+Add `--skip-reproducible` to stop before the slow rebuild step.
+
+Because the rebuild comparison is sensitive to host-OS differences, prefer running it in
+the container described in
+[Appendix: Verification from a container](#appendix-verification-from-a-container).
+
+### 6.1 Download the staged artifacts
+
+```bash
+etc/bin/download-release-artifacts.sh v262.0.0 /tmp/grails-ij-verify
+```
+
+Fetches the source and binary distributions (each with `.asc` and `.sha512`) from
+`https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/${VERSION}`, plus the Grails
+`KEYS` file.
+
+### 6.2 Verify checksums, signatures, and archive contents
+
+```bash
+etc/bin/verify-distributions.sh v262.0.0 /tmp/grails-ij-verify
+```
+
+For both archives this verifies the `.sha512` checksum and the `.asc` detached signature
+(imported into a throwaway GPG home, so a key you already trust locally cannot mask a bad
+signature), then checks the contents:
+
+- **Source distribution** must contain `LICENSE`, `NOTICE`, `README.md`, `INSTALL`,
+  `RELEASE.md`, `.sdkmanrc`, and `gradle-bootstrap/`, and must **not** contain
+  `gradle-wrapper.jar`, `gradlew`, `.git/`, `.github/`, or `.asf.yaml`.
+- **Binary distribution** must carry `META-INF/LICENSE`, `META-INF/NOTICE`, and
+  `META-INF/plugin.xml` inside the composed plugin jar.
+
+The equivalent manual commands are:
+
+```bash
+shasum -a 512 -c apache-grails-intellij-plugin-<version>-src.zip.sha512
+gpg --verify apache-grails-intellij-plugin-<version>-src.zip.asc \
+             apache-grails-intellij-plugin-<version>-src.zip
+```
+
+### 6.3 Run the license audit
+
+The Apache RAT audit runs against the **source distribution**, since that is the artifact
+being released:
+
+```bash
+./gradlew rat
+```
+
+`verify.sh` extracts the source distribution, bootstraps its wrapper, and runs `rat` there.
+
+### 6.4 Rebuild from source and compare to the staged binary
+
+```bash
+etc/bin/verify-reproducible.sh v262.0.0 /tmp/grails-ij-verify
+```
+
+This is the check that proves the published binary was built from the voted-on source. It
+extracts the source distribution, bootstraps the Gradle Wrapper, rebuilds `buildPlugin`,
+and compares the result against the staged binary.
+
+> **The two ZIP files will never have the same SHA.** The staged binary is signed for the
+> JetBrains Marketplace after the build, and a local rebuild is unsigned. What must match
+> is everything the *build* produced, so the script compares the extracted archive
+> contents entry by entry rather than hashing the ZIPs.
 
-Reviewers should verify signatures, checksums, the RAT license audit, and a
-**reproducible build** before voting (next section).
+### 6.5 Manual checks
 
-## 4. Publish the release (`release` job — post-vote, environment-gated)
+- Install the binary distribution into IntelliJ IDEA Ultimate via
+  **Settings > Plugins > Install Plugin from Disk**, open a Grails project, and confirm
+  the plugin loads and GSP support works.
+- Confirm the signing key belongs to a Grails PMC member — see
+  [Appendix: GPG & the KEYS file](#appendix-gpg--the-keys-file).
 
-After the vote succeeds (the `release` GitHub environment requires the reviewers in
-`.asf.yaml`):
+## 7. Vote
+
+The release vote is held on the
+[Grails dev mailing list](https://lists.apache.org/list.html?dev@grails.apache.org),
+following the [Apache voting process](https://www.apache.org/foundation/voting.html).
+Only Grails PMC votes are binding; the vote runs a minimum of 72 hours and needs at least
+three +1 votes from PMC members.
+
+Template:
+
+```
+Subject: [VOTE] Release Apache Grails IntelliJ Plugin <version>
+
+Hi all,
+
+I'd like to call a vote to release Apache Grails IntelliJ Plugin <version>.
+
+The source and binary distributions are staged at:
+  https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/<version>/
+
+The release tag is:
+  https://github.com/apache/grails-intellij-plugin/releases/tag/v<version>
+
+The KEYS file used to sign the artifacts:
+  https://dist.apache.org/repos/dist/release/grails/KEYS
+
+Verification instructions are in RELEASE.md; the artifacts can be checked with:
+  etc/bin/verify.sh v<version> /tmp/grails-ij-verify
+
+The vote is open for at least 72 hours.
+
+[ ] +1 Release this package
+[ ]  0 No opinion
+[ ] -1 Do not release this package (please provide the reason)
+
+Thanks,
+<your name>
+```
+
+## 8. Publish the release (`release` job)
+
+After the vote succeeds, approve the `release` job (the `release` GitHub environment
+requires the reviewers listed in `.asf.yaml`):
 
 1. **MANUAL:** confirm the vote result on `dev@grails.apache.org`.
-2. **MANUAL:** promote the staged artifacts from `dist/dev` to `dist/release` via
-   [`.github/scripts/releaseDistributions.sh`](.github/scripts/releaseDistributions.sh).
+2. **MANUAL:** promote the staged artifacts from `dist/dev` to `dist/release`. ASF
+   infrastructure requires this be done by a human under their own credentials, so it
+   cannot be automated:
+   ```bash
+   .github/scripts/releaseDistributions.sh v<version> INTELLIJ <ASF_USER>
+   ```
+   The script also offers to remove prior release folders — ASF dist keeps only the
+   current release, with older ones served from the archive.
 3. `./gradlew publishPlugin -Pversion=${VERSION}` — publish the convenience binary to the
    JetBrains Marketplace.
-4. **MANUAL:** update the [ASF release reporter](https://reporter.apache.org/) and send
-   the `[RESULT][VOTE]` + `[ANNOUNCE]` emails.
+4. **MANUAL:** record the release at <https://reporter.apache.org/addrelease.html?grails>
+   as `INTELLIJ-<version>`, dated the day the distributions were promoted.
+5. **MANUAL:** send `[RESULT][VOTE]` to `dev@grails.apache.org`, then `[ANNOUNCE]` to
+   `announce@apache.org`, `dev@grails.apache.org`, and `users@grails.apache.org`.
+   Announcements must come from your `@apache.org` address
+   (see <https://infra.apache.org/committer-email.html>).
+6. Flag the GitHub Release as **latest**.
+
+## Rollback
+
+A release can be withdrawn cleanly **only before the vote passes**. Once artifacts are
+promoted to `dist/release` and published to the Marketplace, they are public and the
+correct response is a new version, not a rollback.
+
+To abort a staged release:
+
+1. **Remove the staged distributions** from the ASF dev area:
+   ```bash
+   svn rm -m "Abort Apache Grails IntelliJ Plugin <version>" \
+     https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/<version>
+   ```
+2. **Cancel the `Release` workflow run** if it is still in progress.
+3. **Delete the GitHub Release** (this does not delete the tag).
+4. **Delete the tag:**
+   ```bash
+   git push --delete origin v<version>
+   ```
+5. If a `[VOTE]` thread was already started, reply to it with `[CANCEL][VOTE]` and the
+   reason.
+
+Nothing is published to the JetBrains Marketplace until the post-vote `release` job runs,
+so aborting before that point leaves no public trace.
 
 ## Verifying a Reproducible Build
 
-A vote is a good time to confirm the artifacts can be rebuilt bit-for-bit from the tag.
-Reproducibility is enforced by the `grails-intellij.reproducible` convention plugin
-(`build-logic/`), which pins archive timestamps, file order, and entry permissions across
-every module and the plugin ZIP.
+Reproducibility is enforced by the `grails-intellij.reproducible` convention plugin in
+[`build-logic`](build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts),
+applied to the root project and every module. It pins archive entry timestamps
+(`preserveFileTimestamps = false`), file order (`reproducibleFileOrder = true`), and entry
+permissions, which removes the three usual sources of archive non-determinism.
 
 ### On the same machine
 
@@ -97,30 +327,115 @@ etc/bin/test-reproducible-build.sh
 
 The script pins `SOURCE_DATE_EPOCH` to the last commit time, builds `buildPlugin` twice
 from a clean tree (`--rerun-tasks --no-build-cache`), and compares the SHA-256 of the
-plugin ZIP and every module jar. It prints `((none) ✅ build is reproducible)` on success
-and lists any differing artifacts on failure.
+plugin ZIP and every module jar. On success it prints `(none) ✅ build is reproducible`.
+
+On failure it lists each differing artifact and leaves the evidence in `etc/bin/results`:
+`first.txt` / `second.txt` hold the full hash listings, and `firstArtifact/` /
+`secondArtifact/` hold each build's copy of the artifacts, so the two can be unzipped and
+diffed to find what moved.
 
 > ⚠️ The script runs `git clean -xdf` between builds, which deletes **all** untracked
-> files. Run it in a throwaway checkout or after stashing your work.
+> files — including the multi-GB `.intellijPlatform` download cache, so each build
+> re-downloads the IntelliJ Platform. Run it in a throwaway checkout or after stashing
+> your work. `etc/bin/results` is preserved across the cleans.
 
 ### In a container (build-environment parity)
 
 To rule out host-OS differences, reproduce inside a container that resembles the CI Linux
-environment ([`etc/bin/Dockerfile`](etc/bin/Dockerfile), pinned to the same Liberica JDK
-as `.sdkmanrc`):
+environment — see
+[Appendix: Verification from a container](#appendix-verification-from-a-container).
+
+### Common reproducibility gotchas
+
+- **Archive timestamps / order / permissions** — handled by the reproducible convention
+  plugin. Any new module must apply it, or its jar will not be reproducible.
+- **`SOURCE_DATE_EPOCH`** — most tools honour it for embedded dates. Archive entries do
+  not depend on it here (timestamps are pinned to a constant), but the test script sets it
+  from the last git commit so anything that *does* read it agrees between the two builds.
+- **JDK version** — a different JDK can change bytecode. Always build with the JDK pinned
+  in `.sdkmanrc`; the Docker image installs exactly that version. This is also why the
+  build deliberately sets no Gradle toolchain: the pinned JDK is the contract.
+- **Properties files** — `java.util.Properties#store` writes a timestamp comment. Do not
+  ship generated `.properties` files without stripping it.
+- **Reflection order** — methods and fields returned by reflection are not in a
+  guaranteed order across platforms. Any generator that reflects over a class must sort
+  its results.
+
+## Appendix: GPG & the KEYS file
+
+The plugin releases under the shared `grails` dist project, so it uses the project-wide
+KEYS file rather than one of its own:
+
+<https://dist.apache.org/repos/dist/release/grails/KEYS>
+
+To verify artifacts manually, import it and trust the signing key:
+
+```bash
+wget https://dist.apache.org/repos/dist/release/grails/KEYS
+gpg --import KEYS
+```
+
+```bash
+gpg --edit-key <key id>
+gpg> trust
+gpg> 4
+gpg> quit
+```
+
+The verification scripts in `etc/bin` import KEYS into a temporary GPG home instead, so
+they neither modify nor depend on your keyring.
+
+## Appendix: Verification from a container
+
+The official artifacts are built on Linux in GitHub Actions. To reproduce that
+environment locally, use [`etc/bin/Dockerfile`](etc/bin/Dockerfile), which is pinned to
+the same Liberica JDK as `.sdkmanrc`:
 
 ```bash
 docker build -t grails-ij:testing -f etc/bin/Dockerfile .
 docker run -it --rm -v "$(pwd):/home/builder/project" grails-ij:testing bash
 # inside the container:
-etc/bin/test-reproducible-build.sh
+etc/bin/verify.sh v262.0.0 /tmp/grails-ij-verify
 ```
 
-### Common reproducibility gotchas
+Anyone bumping the JDK in `.sdkmanrc` must bump the `FROM` line in the Dockerfile to
+match, or the container will no longer reproduce the released artifacts.
 
-- **Archive timestamps / order** — handled by the reproducible convention plugin
-  (`preserveFileTimestamps=false`, `reproducibleFileOrder=true`, fixed permissions).
-- **`SOURCE_DATE_EPOCH`** — most tools honor it for embedded dates; the script sets it
-  from the last git commit so successive builds agree.
-- **JDK version** — a different JDK can change bytecode. Always build with the JDK pinned
-  in `.sdkmanrc`; the Docker image installs exactly that version.
+## Appendix: Release secrets
+
+| Secret | Purpose |
+| --- | --- |
+| `GRAILS_GPG_KEY` | ASF GPG private key used to sign the source and binary distributions. |
+| `GPG_KEY_ID` | ID of that key, passed as `--default-key`. Obtain with `gpg --list-keys --keyid-format short`. |
+| `SVC_DIST_GRAILS_USERNAME` | SVN user for `dist.apache.org`. Infra grants write access to `dev` only; promotion to `release` is manual. |
+| `SVC_DIST_GRAILS_PASSWORD` | Password for the above. |
+| `CERTIFICATE_CHAIN` | JetBrains Marketplace signing certificate chain. |
+| `PRIVATE_KEY` | JetBrains Marketplace signing private key. |
+| `PRIVATE_KEY_PASSWORD` | Password for the above. |
+| `PUBLISH_TOKEN` | JetBrains Marketplace publishing token. |
+
+Multi-line secrets such as the GPG key must be entered **with a trailing newline**;
+omitting it is a common cause of signing failures that only surface during a release.
+
+## Appendix: Why the build must be reproducible
+
+GitHub Actions is [untrusted hardware](https://www.apache.org/legal/release-policy.html#owned-controlled-hardware)
+as far as ASF release policy is concerned. Projects that release from it must make the
+build **verifiable**: anyone must be able to rebuild the artifacts elsewhere and show they
+are equivalent to what CI produced. A reproducible build is how that is demonstrated, and
+it is why [section 6.4](#64-rebuild-from-source-and-compare-to-the-staged-binary) is part
+of the pre-vote checklist rather than an optional extra.
+
+The tooling that supports this:
+
+| Path | Role |
+| --- | --- |
+| `build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts` | Pins archive timestamps, file order, and permissions across every module. |
+| `etc/bin/test-reproducible-build.sh` | Builds twice on one machine and compares artifact hashes. |
+| `etc/bin/verify-reproducible.sh` | Rebuilds from the staged source distribution and compares to the staged binary. |
+| `etc/bin/Dockerfile` | Linux environment matching CI, for ruling out host-OS differences. |
+| `.sdkmanrc` | The pinned JDK and Gradle versions; the single source of truth for both CI and the Dockerfile. |
+
+This mirrors the approach taken in
+[`grails-core`](https://github.com/apache/grails-core/blob/HEAD/RELEASE.md), scaled down
+to this repository's two artifacts.
diff --git a/etc/bin/download-release-artifacts.sh b/etc/bin/download-release-artifacts.sh
new file mode 100755
index 0000000..8add08e
--- /dev/null
+++ b/etc/bin/download-release-artifacts.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+# Downloads the staged Apache Grails IntelliJ Plugin release artifacts (source
+# distribution and convenience binary, each with its .asc signature and .sha512
+# checksum) from the ASF dev distribution area, plus the Grails KEYS file used to
+# verify the signatures.
+#
+# Usage:
+#   etc/bin/download-release-artifacts.sh <release-tag> [download-location]
+#
+# Example:
+#   etc/bin/download-release-artifacts.sh v262.0.0 /tmp/grails-ij-verify
+#
+set -euo pipefail
+
+RELEASE_TAG="${1:-}"
+DOWNLOAD_LOCATION="${2:-downloads}"
+
+if [ -z "${RELEASE_TAG}" ]; then
+  echo "Usage: $0 <release-tag> [download-location]" >&2
+  exit 1
+fi
+
+VERSION="${RELEASE_TAG#v}"
+DIST_NAME="apache-grails-intellij-plugin"
+# The plugin releases under the shared `grails` dist project, in its own INTELLIJ folder.
+DEV_ROOT="https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/${VERSION}"
+KEYS_URL="https://dist.apache.org/repos/dist/release/grails/KEYS"
+
+mkdir -p "${DOWNLOAD_LOCATION}"
+cd "${DOWNLOAD_LOCATION}"
+
+echo "==> Downloading KEYS"
+curl -f -L -o KEYS "${KEYS_URL}"
+echo "✅ KEYS downloaded"
+
+# The release workflow stages the source distribution under sources/ and the
+# convenience binary under distribution/; keep that split so paths match dist.apache.org.
+echo "==> Downloading source distribution from ${DEV_ROOT}/sources"
+for ext in "" ".asc" ".sha512"; do
+  curl -f -L -O "${DEV_ROOT}/sources/${DIST_NAME}-${VERSION}-src.zip${ext}"
+done
+echo "✅ Source distribution downloaded"
+
+echo "==> Downloading binary distribution from ${DEV_ROOT}/distribution"
+for ext in "" ".asc" ".sha512"; do
+  curl -f -L -O "${DEV_ROOT}/distribution/${DIST_NAME}-${VERSION}-bin.zip${ext}"
+done
+echo "✅ Binary distribution downloaded"
+
+echo ""
+echo "Artifacts in $(pwd):"
+ls -l
diff --git a/etc/bin/test-reproducible-build.sh b/etc/bin/test-reproducible-build.sh
index 628889a..dc8513e 100755
--- a/etc/bin/test-reproducible-build.sh
+++ b/etc/bin/test-reproducible-build.sh
@@ -20,8 +20,17 @@
 # compares the SHA-256 of each produced artifact. Any artifact whose hash differs between
 # the two builds is printed and the script exits non-zero.
 #
+# Output is written to etc/bin/results (git-ignored):
+#   first.txt / second.txt  - the full "<sha256>  <path>" listing from each build
+#   firstArtifact/          - build 1's copy of every artifact
+#   secondArtifact/         - build 2's copy of every artifact
+# When a run fails, unzip the two copies of a differing artifact and diff them to find
+# what moved (a timestamp, an entry order, a permission bit, an embedded build date).
+#
 # WARNING: this runs `git clean -xdf`, which deletes ALL untracked files (build output,
-# .idea, IDE caches, etc.). Run it only in a throwaway checkout or after stashing work.
+# .idea, IDE caches, and the several GB .intellijPlatform download cache), so each build
+# re-downloads the IntelliJ Platform. Run it only in a throwaway checkout or after
+# stashing work. etc/bin/results is preserved across the cleans.
 #
 # Usage: run from anywhere; the script locates the project root relative to itself.
 #   etc/bin/test-reproducible-build.sh
@@ -40,45 +49,84 @@ else
   sha() { shasum -a 256 "$@"; }
 fi
 
-# Hash the release artifacts (plugin ZIP) and every module jar, sorted by path so the
-# output is order-stable across runs.
-hash_artifacts() {
+RESULTS="etc/bin/results"
+
+# List the release artifacts (plugin ZIP) and every module jar, one relative path per
+# line, sorted so the output is order-stable across runs.
+list_artifacts() {
   {
     find build/distributions -type f -name '*.zip' 2> /dev/null || true
     find . -type f -path '*/build/libs/*.jar' 2> /dev/null || true
-  } | sort | while IFS= read -r f; do
-    [ -f "$f" ] && sha "$f"
-  done
+  } | sed 's|^\./||' | sort
+}
+
+# Write "<sha256>  <path>" for every artifact, and stash a copy of each one under
+# $RESULTS/$1 so a failing run can be diffed after the next `git clean` wipes the tree.
+snapshot_artifacts() {
+  local dest="${RESULTS}/$1" listing="${RESULTS}/$2" f
+  rm -rf "${dest}"
+  mkdir -p "${dest}"
+  : > "${listing}"
+  while IFS= read -r f; do
+    [ -n "$f" ] || continue
+    sha "$f" >> "${listing}"
+    mkdir -p "${dest}/$(dirname -- "$f")"
+    cp -- "$f" "${dest}/$f"
+  done < <(list_artifacts)
 }
 
 build_once() {
-  git clean -xdf
+  # -e keeps the results directory: it holds the previous build's artifacts and listings,
+  # which are the only evidence left once the tree is wiped.
+  git clean -xdf -e "${RESULTS}"
   ./gradlew buildPlugin --rerun-tasks --no-build-cache --no-daemon
 }
 
+mkdir -p "${RESULTS}"
+
 echo "==> First build (SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH})"
 build_once
-FIRST="$(hash_artifacts)"
+snapshot_artifacts firstArtifact first.txt
 
 echo "==> Second build"
 build_once
-SECOND="$(hash_artifacts)"
-
-printf '%s\n' "$FIRST" > first.txt
-printf '%s\n' "$SECOND" > second.txt
+snapshot_artifacts secondArtifact second.txt
 
 echo ""
-echo "Differing artifacts:"
-# comm on the second (hash) field: lines present in only one build indicate a hash mismatch.
-DIFF="$(comm -3 <(printf '%s\n' "$FIRST" | sort) <(printf '%s\n' "$SECOND" | sort) \
-  | awk '{print $2}' | sed 's|^[[:space:]]*||' | grep -v '^$' | sort -u || true)"
+echo "Comparing $(wc -l < "${RESULTS}/first.txt" | tr -d ' ') artifacts:"
+
+# Compare by path rather than by whole line, so an artifact that exists in only one build
+# is reported as such instead of silently looking like two unrelated hash mismatches.
+# Both sha256sum and `shasum -a 256` emit a fixed "<64 hex><2 spaces><path>" layout, so
+# the hash is columns 1-64 and the path starts at column 67 even if it contains spaces.
+hash_of() { # <listing file> <path>
+  awk -v p="$2" 'substr($0, 67) == p { print substr($0, 1, 64) }' "$1"
+}
+
+DIFF=""
+while IFS= read -r path; do
+  [ -n "$path" ] || continue
+  first_hash="$(hash_of "${RESULTS}/first.txt" "$path")"
+  second_hash="$(hash_of "${RESULTS}/second.txt" "$path")"
+  if [ "${first_hash}" != "${second_hash}" ]; then
+    DIFF="${DIFF}${path} (build1=${first_hash:-<missing>} build2=${second_hash:-<missing>})"$'\n'
+  fi
+done < <(cut -c 67- "${RESULTS}/first.txt" "${RESULTS}/second.txt" | sort -u)
 
 if [ -z "$DIFF" ]; then
   echo "  (none) ✅ build is reproducible"
   exit 0
 else
-  printf '  %s\n' "$DIFF"
+  printf '  %s\n' "${DIFF%$'\n'}"
   echo ""
-  echo "❌ build is NOT reproducible — see first.txt / second.txt for the full hash lists"
+  echo "❌ build is NOT reproducible"
+  echo "   full hash lists: ${RESULTS}/first.txt, ${RESULTS}/second.txt"
+  echo "   artifact copies: ${RESULTS}/firstArtifact/, ${RESULTS}/secondArtifact/"
+  echo "   to find the cause, unzip both copies of a differing artifact and diff them:"
+  echo "     unzip -o ${RESULTS}/firstArtifact/<path> -d ${RESULTS}/x1"
+  echo "     unzip -o ${RESULTS}/secondArtifact/<path> -d ${RESULTS}/x2"
+  echo "     diff -r ${RESULTS}/x1 ${RESULTS}/x2"
+  echo "   if the contents match, compare entry metadata instead:"
+  echo "     unzip -lv ${RESULTS}/firstArtifact/<path>"
   exit 1
 fi
diff --git a/etc/bin/verify-distributions.sh b/etc/bin/verify-distributions.sh
new file mode 100755
index 0000000..a85135c
--- /dev/null
+++ b/etc/bin/verify-distributions.sh
@@ -0,0 +1,163 @@
+#!/usr/bin/env bash
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+# Verifies the downloaded Apache Grails IntelliJ Plugin release artifacts. For both the
+# source distribution and the convenience binary this checks:
+#   * the .sha512 checksum
+#   * the .asc detached GPG signature, against the Grails KEYS file
+#   * that the archive contains the files ASF policy requires (LICENSE, NOTICE, ...)
+#   * that the source distribution does NOT ship the Gradle Wrapper jar
+#
+# Signatures are verified in a throwaway GPG home so the caller's keyring is left alone
+# and so a key already trusted locally cannot mask a bad signature.
+#
+# Run etc/bin/download-release-artifacts.sh first to populate the download location.
+#
+# Usage:
+#   etc/bin/verify-distributions.sh <release-tag> [download-location]
+#
+set -euo pipefail
+
+RELEASE_TAG="${1:-}"
+DOWNLOAD_LOCATION="${2:-downloads}"
+
+if [ -z "${RELEASE_TAG}" ]; then
+  echo "Usage: $0 <release-tag> [download-location]" >&2
+  exit 1
+fi
+
+VERSION="${RELEASE_TAG#v}"
+DIST_NAME="apache-grails-intellij-plugin"
+SRC_ZIP="${DIST_NAME}-${VERSION}-src.zip"
+BIN_ZIP="${DIST_NAME}-${VERSION}-bin.zip"
+
+cd "${DOWNLOAD_LOCATION}"
+
+# Portable SHA-512 verification: GNU coreutils on Linux, `shasum` on macOS.
+if command -v sha512sum &> /dev/null; then
+  check_sha() { sha512sum -c "$1"; }
+else
+  check_sha() { shasum -a 512 -c "$1"; }
+fi
+
+if [ ! -f KEYS ]; then
+  echo "❌ KEYS not found in $(pwd) — run etc/bin/download-release-artifacts.sh first" >&2
+  exit 1
+fi
+
+GPG_HOME="$(mktemp -d)"
+cleanup() { rm -rf "${GPG_HOME}"; }
+trap cleanup EXIT
+chmod 700 "${GPG_HOME}"
+
+echo "==> Importing KEYS into a throwaway GPG home"
+gpg --homedir "${GPG_HOME}" --batch --quiet --import KEYS
+echo "✅ KEYS imported"
+
+verify_archive() { # <zip file>
+  local zip="$1"
+  echo ""
+  echo "==> Verifying ${zip}"
+
+  if [ ! -f "${zip}" ]; then
+    echo "❌ ${zip} not found in $(pwd)" >&2
+    return 1
+  fi
+
+  check_sha "${zip}.sha512"
+  echo "✅ checksum verified"
+
+  gpg --homedir "${GPG_HOME}" --batch --verify "${zip}.asc" "${zip}"
+  echo "✅ signature verified"
+}
+
+# Fails if any required path is absent from the archive listing.
+require_entries() { # <zip file> <label> <entry>...
+  local zip="$1" label="$2"
+  shift 2
+  local listing entry missing=0
+  listing="$(unzip -Z1 "${zip}")"
+  for entry in "$@"; do
+    if ! printf '%s\n' "${listing}" | grep -qE "${entry}"; then
+      echo "❌ ${label}: required entry matching '${entry}' is missing from ${zip}" >&2
+      missing=1
+    fi
+  done
+  [ "${missing}" -eq 0 ]
+}
+
+# Fails if any forbidden path is present in the archive listing.
+forbid_entries() { # <zip file> <label> <entry>...
+  local zip="$1" label="$2"
+  shift 2
+  local listing entry found=0
+  listing="$(unzip -Z1 "${zip}")"
+  for entry in "$@"; do
+    if printf '%s\n' "${listing}" | grep -qE "${entry}"; then
+      echo "❌ ${label}: forbidden entry matching '${entry}' is present in ${zip}" >&2
+      found=1
+    fi
+  done
+  [ "${found}" -eq 0 ]
+}
+
+verify_archive "${SRC_ZIP}"
+echo "==> Checking source distribution contents"
+# Build and license instructions must be present so the source release is self-contained,
+# and gradle-bootstrap must be there because the wrapper jar is deliberately absent.
+require_entries "${SRC_ZIP}" "source distribution" \
+  '/LICENSE$' \
+  '/NOTICE$' \
+  '/README\.md$' \
+  '/INSTALL$' \
+  '/RELEASE\.md$' \
+  '/\.sdkmanrc$' \
+  '/gradle-bootstrap/build\.gradle$'
+# ASF source releases must not ship compiled binaries; the Gradle Wrapper jar is one, and
+# .git / .github / .asf.yaml are infrastructure that is not part of the release.
+forbid_entries "${SRC_ZIP}" "source distribution" \
+  'gradle-wrapper\.jar$' \
+  '/gradlew$' \
+  '/gradlew\.bat$' \
+  '/\.git/' \
+  '/\.github/' \
+  '/\.asf\.yaml$'
+echo "✅ source distribution contents verified"
+
+verify_archive "${BIN_ZIP}"
+echo "==> Checking binary distribution contents"
+# ASF policy: the convenience binary must carry LICENSE and NOTICE. The build puts them in
+# META-INF of the composed plugin jar (see processResources in build.gradle.kts), so check
+# inside that jar rather than at the top level of the ZIP.
+PLUGIN_JAR="$(unzip -Z1 "${BIN_ZIP}" | grep -E '^[^/]+/lib/[^/]+\.jar$' | head -n 1)"
+if [ -z "${PLUGIN_JAR}" ]; then
+  echo "❌ binary distribution: no plugin jar found under <plugin>/lib/ in ${BIN_ZIP}" >&2
+  exit 1
+fi
+JAR_TMP="$(mktemp -d)"
+trap 'rm -rf "${GPG_HOME}" "${JAR_TMP}"' EXIT
+unzip -q -o -j "${BIN_ZIP}" "${PLUGIN_JAR}" -d "${JAR_TMP}"
+require_entries "${JAR_TMP}/$(basename "${PLUGIN_JAR}")" "binary distribution" \
+  '^META-INF/LICENSE$' \
+  '^META-INF/NOTICE$' \
+  '^META-INF/plugin\.xml$'
+echo "✅ binary distribution contents verified (${PLUGIN_JAR})"
+
+echo ""
+echo "✅ All distribution checks passed for ${RELEASE_TAG}"
diff --git a/etc/bin/verify-reproducible.sh b/etc/bin/verify-reproducible.sh
new file mode 100755
index 0000000..3c45bd4
--- /dev/null
+++ b/etc/bin/verify-reproducible.sh
@@ -0,0 +1,126 @@
+#!/usr/bin/env bash
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+# Rebuilds the plugin from the staged SOURCE distribution and compares the result with the
+# staged convenience BINARY, which is what proves the published binary really was built
+# from the voted-on source.
+#
+# Why this compares contents rather than the ZIP's SHA:
+#
+#   The staged binary is signed for the JetBrains Marketplace (`signPlugin`), and the
+#   marketplace ZIP signer injects a signature into the archive after the build. A locally
+#   rebuilt ZIP is unsigned, so the two ZIP files can never have the same hash even when
+#   the build is perfectly reproducible. What must match is everything the build produced:
+#   every entry inside the archive, byte for byte. That is what this script checks, and it
+#   reports any entry that is present in only one side or whose contents differ.
+#
+# Requires a local Gradle on PATH (the source distribution ships without the wrapper jar,
+# per ASF policy, so the wrapper has to be bootstrapped first). Install the version pinned
+# in .sdkmanrc, e.g. with `sdk env install`.
+#
+# Run etc/bin/download-release-artifacts.sh first to populate the download location.
+#
+# Usage:
+#   etc/bin/verify-reproducible.sh <release-tag> [download-location]
+#
+set -euo pipefail
+
+RELEASE_TAG="${1:-}"
+DOWNLOAD_LOCATION="${2:-downloads}"
+
+if [ -z "${RELEASE_TAG}" ]; then
+  echo "Usage: $0 <release-tag> [download-location]" >&2
+  exit 1
+fi
+
+VERSION="${RELEASE_TAG#v}"
+DIST_NAME="apache-grails-intellij-plugin"
+SRC_ZIP="${DIST_NAME}-${VERSION}-src.zip"
+BIN_ZIP="${DIST_NAME}-${VERSION}-bin.zip"
+
+cd "${DOWNLOAD_LOCATION}"
+DOWNLOAD_DIR="$(pwd)"
+
+for f in "${SRC_ZIP}" "${BIN_ZIP}"; do
+  if [ ! -f "${f}" ]; then
+    echo "❌ ${f} not found in ${DOWNLOAD_DIR} — run etc/bin/download-release-artifacts.sh first" >&2
+    exit 1
+  fi
+done
+
+if ! command -v gradle &> /dev/null; then
+  echo "❌ no 'gradle' on PATH. The source distribution ships without the Gradle Wrapper jar," >&2
+  echo "   so a local Gradle is needed to bootstrap it. Install the version pinned in" >&2
+  echo "   .sdkmanrc (e.g. 'sdk env install') and re-run." >&2
+  exit 1
+fi
+
+WORK="${DOWNLOAD_DIR}/reproducible"
+rm -rf "${WORK}"
+mkdir -p "${WORK}"
+
+echo "==> Extracting source distribution"
+unzip -q "${SRC_ZIP}" -d "${WORK}/src"
+SRC_DIR="$(find "${WORK}/src" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
+if [ -z "${SRC_DIR}" ]; then
+  echo "❌ source distribution did not contain a top-level directory" >&2
+  exit 1
+fi
+echo "✅ extracted to ${SRC_DIR}"
+
+echo "==> Bootstrapping the Gradle Wrapper"
+(cd "${SRC_DIR}/gradle-bootstrap" && gradle bootstrap)
+echo "✅ wrapper bootstrapped"
+
+echo "==> Rebuilding the plugin from source (this downloads the IntelliJ Platform; expect several GB)"
+(cd "${SRC_DIR}" && ./gradlew buildPlugin -Pversion="${VERSION}" --no-daemon)
+
+REBUILT_ZIP="$(find "${SRC_DIR}/build/distributions" -maxdepth 1 -type f -name '*.zip' | head -n 1)"
+if [ -z "${REBUILT_ZIP}" ]; then
+  echo "❌ rebuild produced no ZIP in ${SRC_DIR}/build/distributions" >&2
+  exit 1
+fi
+echo "✅ rebuilt $(basename "${REBUILT_ZIP}")"
+
+echo "==> Comparing rebuilt archive contents against the staged binary"
+unzip -q "${REBUILT_ZIP}" -d "${WORK}/rebuilt"
+unzip -q "${DOWNLOAD_DIR}/${BIN_ZIP}" -d "${WORK}/staged"
+
+# Compare the extracted trees rather than the ZIPs themselves (see the header comment on
+# marketplace signing). diff -r reports files that differ and files present on one side only.
+if diff -r "${WORK}/rebuilt" "${WORK}/staged" > "${WORK}/diff.txt" 2>&1; then
+  echo ""
+  echo "✅ REPRODUCIBLE — every entry in the staged binary matches a rebuild from the staged source"
+  exit 0
+fi
+
+echo ""
+echo "❌ NOT REPRODUCIBLE — the staged binary does not match a rebuild from the staged source"
+echo ""
+echo "Differences:"
+sed 's/^/  /' "${WORK}/diff.txt"
+echo ""
+echo "Full report: ${WORK}/diff.txt"
+echo "Rebuilt tree: ${WORK}/rebuilt"
+echo "Staged tree:  ${WORK}/staged"
+echo ""
+echo "Before concluding the release is bad, rule out environment differences: the official"
+echo "artifacts are built on Linux with the JDK pinned in .sdkmanrc. Re-run this script in"
+echo "the container built from etc/bin/Dockerfile, which matches that environment."
+exit 1
diff --git a/etc/bin/verify.sh b/etc/bin/verify.sh
new file mode 100755
index 0000000..409d658
--- /dev/null
+++ b/etc/bin/verify.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+# Runs the full pre-vote verification of a staged Apache Grails IntelliJ Plugin release:
+#
+#   1. download   - fetch the staged artifacts and the Grails KEYS file
+#   2. distributions - checksums, GPG signatures, and required/forbidden archive contents
+#   3. license    - Apache RAT audit, run from the extracted source distribution
+#   4. reproducible - rebuild from the source distribution and compare to the staged binary
+#
+# Step 4 is slow (it downloads the IntelliJ Platform and does a full build) and is the one
+# most sensitive to host-OS differences. Pass --skip-reproducible to stop after step 3, or
+# run the whole thing inside the container from etc/bin/Dockerfile for an environment that
+# matches CI. See RELEASE.md for the full verification guide.
+#
+# Usage:
+#   etc/bin/verify.sh <release-tag> [download-location] [--skip-reproducible]
+#
+# Example:
+#   etc/bin/verify.sh v262.0.0 /tmp/grails-ij-verify
+#
+set -euo pipefail
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+
+RELEASE_TAG=""
+DOWNLOAD_LOCATION="downloads"
+SKIP_REPRODUCIBLE=0
+
+for arg in "$@"; do
+  case "${arg}" in
+    --skip-reproducible) SKIP_REPRODUCIBLE=1 ;;
+    -*)
+      echo "Unknown option: ${arg}" >&2
+      exit 1
+      ;;
+    *)
+      if [ -z "${RELEASE_TAG}" ]; then
+        RELEASE_TAG="${arg}"
+      else
+        DOWNLOAD_LOCATION="${arg}"
+      fi
+      ;;
+  esac
+done
+
+if [ -z "${RELEASE_TAG}" ]; then
+  echo "Usage: $0 <release-tag> [download-location] [--skip-reproducible]" >&2
+  exit 1
+fi
+
+VERSION="${RELEASE_TAG#v}"
+
+mkdir -p "${DOWNLOAD_LOCATION}"
+DOWNLOAD_LOCATION="$(cd "${DOWNLOAD_LOCATION}" && pwd)"
+
+echo "############################################################"
+echo "# Verifying Apache Grails IntelliJ Plugin ${VERSION}"
+echo "# Artifacts: ${DOWNLOAD_LOCATION}"
+echo "############################################################"
+
+echo ""
+echo "### 1/4 Downloading staged artifacts"
+"${SCRIPT_DIR}/download-release-artifacts.sh" "${RELEASE_TAG}" "${DOWNLOAD_LOCATION}"
+
+echo ""
+echo "### 2/4 Verifying checksums, signatures, and archive contents"
+"${SCRIPT_DIR}/verify-distributions.sh" "${RELEASE_TAG}" "${DOWNLOAD_LOCATION}"
+
+echo ""
+echo "### 3/4 Running the Apache RAT license audit on the source distribution"
+# Audit the source distribution itself, not the local checkout: the release is the source
+# archive, so that is what has to pass the license audit.
+RAT_DIR="${DOWNLOAD_LOCATION}/rat"
+rm -rf "${RAT_DIR}"
+mkdir -p "${RAT_DIR}"
+unzip -q "${DOWNLOAD_LOCATION}/apache-grails-intellij-plugin-${VERSION}-src.zip" -d "${RAT_DIR}"
+RAT_SRC="$(find "${RAT_DIR}" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
+if command -v gradle &> /dev/null; then
+  (cd "${RAT_SRC}/gradle-bootstrap" && gradle bootstrap)
+  (cd "${RAT_SRC}" && ./gradlew rat --no-daemon)
+  echo "✅ RAT audit passed"
+else
+  echo "⚠️  skipped: no 'gradle' on PATH to bootstrap the wrapper in the source distribution."
+  echo "   Install the version pinned in .sdkmanrc (e.g. 'sdk env install') to run this step."
+fi
+
+echo ""
+if [ "${SKIP_REPRODUCIBLE}" -eq 1 ]; then
+  echo "### 4/4 Reproducible build check SKIPPED (--skip-reproducible)"
+else
+  echo "### 4/4 Rebuilding from source and comparing to the staged binary"
+  "${SCRIPT_DIR}/verify-reproducible.sh" "${RELEASE_TAG}" "${DOWNLOAD_LOCATION}"
+fi
+
+echo ""
+echo "############################################################"
+echo "# ✅ Verification complete for ${RELEASE_TAG}"
+echo "#"
+echo "# Still to check by hand before voting:"
+echo "#   * install ${DOWNLOAD_LOCATION}/apache-grails-intellij-plugin-${VERSION}-bin.zip"
+echo "#     into IntelliJ IDEA Ultimate via Settings > Plugins > Install Plugin from Disk,"
+echo "#     open a Grails project, and confirm the plugin loads and GSP support works"
+echo "#   * confirm the GPG key that signed the artifacts belongs to a Grails PMC member"
+echo "#     (see the KEYS appendix in RELEASE.md)"
+echo "############################################################"


(grails-intellij-plugin) 24/35: Convert the structure package from Kotlin to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 83356c517c644379c998352d3bd28f69f3d01dc2
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 13:55:54 2026 -0400

    Convert the structure package from Kotlin to Java
    
    14 Kotlin files become 15 Java files, leaving no Kotlin under structure/.
    987 tests / 0 failures, RAT clean.
    
    This package had to be converted as one unit rather than file by file, which is a
    constraint the earlier batches did not hit: Kotlin's `override val project` cannot
    override a Java getter. Converting GrailsApplication alone produced
    "'project' overrides nothing" from every Kotlin subclass, because Kotlin
    synthesises property syntax for *reading* a Java getter but requires
    `override fun getProject()` to implement one. So a class hierarchy crossing the
    language boundary is not partially convertible -- interface and implementors move
    together.
    
    Two smaller translation traps, both caught by the compiler:
    
    * Grails2Application was Kotlin `internal`, which is public in bytecode, so the
      runner package could reach it across packages. Package-private in Java could not;
      it is public with @ApiStatus.Internal, the same fix the projectView batch needed
      for its plugin.xml-instantiated classes.
    * GrailsSettings.properties is a public FIELD, not a getter. Kotlin's
      `settings.properties[key]` reads fields and getters identically, so the literal
      translation to .getProperties() does not compile.
    
    Naming: structure/util.kt held a single @JvmField COMPARATOR, so Java already
    referenced it as UtilKt.COMPARATOR; it becomes GrailsApplications.COMPARATOR, still
    a static field, with the one import updated in GrailsApplicationCombobox.
    impl/util.kt's nullable-receiver extension becomes GrailsProperties.getPropertyValue.
    
    `by lazy` / `lazyUnsafe` fields become volatile lazily-computed fields, as in the
    earlier batches. Each is derived from immutable external-system data or the VFS, so
    an idempotent recompute under a race is harmless and only publication needs care.
    
    62 files left in the conversion target set.
---
 .../runner/ui/GrailsApplicationCombobox.java       |   2 +-
 ...ails3Application.kt => Grails3Application.java} |  10 +-
 .../grails/structure/GrailsApplication.java        |  58 +++++++++
 .../plugins/grails/structure/GrailsApplication.kt  |  54 --------
 .../grails/structure/GrailsApplicationBase.java    |  86 +++++++++++++
 .../grails/structure/GrailsApplicationBase.kt      |  58 ---------
 .../grails/structure/GrailsApplications.java       |  47 +++++++
 .../grails/structure/GrailsCommonClassNames.java   | 143 +++++++++++++++++++++
 .../grails/structure/GrailsCommonClassNames.kt     |  84 ------------
 ...ilsApplication.kt => OldGrailsApplication.java} |  15 ++-
 .../grails/structure/impl/Grails2Application.java  |  73 +++++++++++
 .../grails/structure/impl/Grails2Application.kt    |  46 -------
 .../structure/impl/Grails3ApplicationBase.java     |  91 +++++++++++++
 .../structure/impl/Grails3ApplicationBase.kt       |  55 --------
 .../structure/impl/Grails3ApplicationProvider.java |  64 +++++++++
 .../structure/impl/Grails3ApplicationProvider.kt   |  54 --------
 .../impl/Grails3MultiModuleApplication.java        |  93 ++++++++++++++
 .../impl/Grails3MultiModuleApplication.kt          |  55 --------
 .../impl/Grails3SingleModuleApplication.java       |  57 ++++++++
 .../impl/Grails3SingleModuleApplication.kt         |  51 --------
 .../impl/GrailsModuleBasedApplication.java         |  77 +++++++++++
 .../structure/impl/GrailsModuleBasedApplication.kt |  60 ---------
 .../grails/structure/impl/GrailsProperties.java    |  34 +++++
 .../impl/OldGrailsModuleBasedApplication.java      |  81 ++++++++++++
 .../impl/OldGrailsModuleBasedApplication.kt        |  69 ----------
 .../plugins/grails/structure/impl/util.kt          |  21 ---
 .../org/jetbrains/plugins/grails/structure/util.kt |  27 ----
 27 files changed, 918 insertions(+), 647 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java
index 6b81b2b..e210a5a 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java
@@ -29,7 +29,7 @@ import org.jetbrains.plugins.grails.structure.GrailsApplication;
 
 import java.util.Collection;
 
-import static org.jetbrains.plugins.grails.structure.UtilKt.COMPARATOR;
+import static org.jetbrains.plugins.grails.structure.GrailsApplications.COMPARATOR;
 
 public class GrailsApplicationCombobox extends ComboBox<GrailsApplication> {
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.java
similarity index 70%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.java
index efc17f4..1f24d56 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.java
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.structure;
 
-package org.jetbrains.plugins.grails.structure
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.gradle.GrailsModuleData;
 
-import org.jetbrains.plugins.grails.gradle.GrailsModuleData
+public interface Grails3Application extends GrailsApplication {
 
-interface Grails3Application : GrailsApplication {
-
-  val gradleData: GrailsModuleData
+  @NotNull GrailsModuleData getGradleData();
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.java
new file mode 100644
index 0000000..2b3af1c
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.NlsSafe;
+import com.intellij.openapi.util.UserDataHolderEx;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.util.version.Version;
+
+import javax.swing.Icon;
+
+public interface GrailsApplication extends UserDataHolderEx {
+
+  @NlsSafe @NotNull String getName();
+
+  default @Nullable String getAppVersion() {
+    return null;
+  }
+
+  @NotNull Icon getIcon();
+
+  @NotNull Project getProject();
+
+  /**
+   * @return folder that contains grails-app, application.properties, etc.
+   */
+  @NotNull VirtualFile getRoot();
+
+  /**
+   * @return {@code grails-app} folder
+   */
+  @NotNull VirtualFile getAppRoot();
+
+  @NotNull Version getGrailsVersion();
+
+  boolean isValid();
+
+  void invalidate();
+
+  @NotNull GlobalSearchScope getScope(boolean includeDependencies, boolean testsOnly);
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.kt
deleted file mode 100644
index 82308d4..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.NlsSafe
-import com.intellij.openapi.util.UserDataHolderEx
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.search.GlobalSearchScope
-import org.jetbrains.plugins.grails.util.version.Version
-import javax.swing.Icon
-
-interface GrailsApplication : UserDataHolderEx {
-
-  val name: @NlsSafe String
-
-  val appVersion: String? get() = null
-
-  val icon: Icon
-
-  val project: Project
-
-  /**
-   * @return folder that contains grails-app, application.properties, etc.
-   */
-  val root: VirtualFile
-
-  /**
-   * @return `grails-app` folder
-   */
-  val appRoot: VirtualFile
-
-  val grailsVersion: Version
-
-  val isValid: Boolean
-
-  fun invalidate()
-
-  fun getScope(includeDependencies: Boolean, testsOnly: Boolean): GlobalSearchScope
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.java
new file mode 100644
index 0000000..c6d3456
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.config.GrailsConstants;
+
+import javax.swing.Icon;
+import java.util.Objects;
+
+public abstract class GrailsApplicationBase extends UserDataHolderBase implements GrailsApplication {
+
+  private final Project myProject;
+  private final VirtualFile myRoot;
+  private boolean myValid = true;
+
+  protected GrailsApplicationBase(@NotNull Project project, @NotNull VirtualFile root) {
+    myProject = project;
+    myRoot = root;
+  }
+
+  @Override
+  public @NotNull Project getProject() {
+    return myProject;
+  }
+
+  @Override
+  public @NotNull VirtualFile getRoot() {
+    return myRoot;
+  }
+
+  @Override
+  public @NotNull VirtualFile getAppRoot() {
+    return Objects.requireNonNull(myRoot.findChild(GrailsConstants.APP_DIRECTORY));
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return GroovyMvcIcons.Grails_app;
+  }
+
+  @Override
+  public boolean isValid() {
+    return myValid && myRoot.findChild(GrailsConstants.APP_DIRECTORY) != null && !myProject.isDisposed();
+  }
+
+  @Override
+  public void invalidate() {
+    myValid = false;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) return true;
+    if (other == null || other.getClass() != getClass()) return false;
+    GrailsApplicationBase that = (GrailsApplicationBase)other;
+    return myProject.equals(that.myProject) && myRoot.equals(that.myRoot);
+  }
+
+  @Override
+  public int hashCode() {
+    return 31 * myProject.hashCode() + myRoot.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + "{name: " + getName() + ", root: " + myRoot + ", version: " + getGrailsVersion() + "}";
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt
deleted file mode 100644
index 09cefe5..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.UserDataHolderBase
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.config.GrailsConstants
-import javax.swing.Icon
-
-abstract class GrailsApplicationBase(
-  override val project: Project,
-  override val root: VirtualFile
-) : UserDataHolderBase(), GrailsApplication {
-
-  override val appRoot: VirtualFile get() = root.findChild(GrailsConstants.APP_DIRECTORY)!!
-
-  override val icon: Icon = GroovyMvcIcons.Grails_app
-
-  private var myValid = true
-
-  override val isValid: Boolean get() = myValid && root.findChild(GrailsConstants.APP_DIRECTORY) != null && !project.isDisposed
-
-  override fun invalidate() {
-    myValid = false
-  }
-
-  override fun equals(other: Any?): Boolean {
-    if (this === other) return true
-    if (other?.javaClass != javaClass) return false
-
-    other as GrailsApplicationBase
-
-    if (project != other.project) return false
-    if (root != other.root) return false
-
-    return true
-  }
-
-  override fun hashCode(): Int = 31 * project.hashCode() + root.hashCode()
-
-  override fun toString(): String = "${javaClass.simpleName}{name: $name, root: $root, version: $grailsVersion}"
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplications.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplications.java
new file mode 100644
index 0000000..344ed61
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplications.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Comparator;
+
+public final class GrailsApplications {
+
+  /**
+   * Orders applications by name, case-insensitively, with nulls first.
+   *
+   * <p>Was a top-level {@code @JvmField val} in structure/util.kt, so Java already referenced it
+   * as {@code UtilKt.COMPARATOR}; it keeps the same shape as a static field here.
+   */
+  public static final Comparator<GrailsApplication> COMPARATOR = new Comparator<>() {
+    @Override
+    public int compare(@Nullable GrailsApplication app1, @Nullable GrailsApplication app2) {
+      if (app1 == null && app2 == null) return 0;
+      if (app1 == null) return -1;
+      if (app2 == null) return 1;
+      return app1.getName().compareToIgnoreCase(app2.getName());
+    }
+  };
+
+  private GrailsApplications() {
+  }
+
+  static @NotNull Comparator<GrailsApplication> comparator() {
+    return COMPARATOR;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.java
new file mode 100644
index 0000000..ee384b1
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure;
+
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.util.version.Version;
+
+/**
+ * Fully qualified names of Grails/GORM classes whose packages moved between major versions.
+ * The prefix and the constraint sub-package differ per era, so callers resolve an instance for
+ * the context's application rather than hardcoding names.
+ */
+public final class GrailsCommonClassNames {
+
+  private static final String HIBERNATE_MAPPING_BUILDER = "orm.hibernate.cfg.HibernateMappingBuilder";
+  private static final String CREDIT_CARD_CONSTRAINT = "CreditCardConstraint";
+  private static final String MAX_SIZE_CONSTRAINT = "MaxSizeConstraint";
+  private static final String EMAIL_CONSTRAINT = "EmailConstraint";
+  private static final String BLANK_CONSTRAINT = "BlankConstraint";
+  private static final String RANGE_CONSTRAINT = "RangeConstraint";
+  private static final String URL_CONSTRAINT = "UrlConstraint";
+  private static final String SIZE_CONSTRAINT = "SizeConstraint";
+  private static final String IN_LIST_CONSTRAINT = "InListConstraint";
+  private static final String MATCHES_CONSTRAINT = "MatchesConstraint";
+  private static final String MIN_CONSTRAINT = "MinConstraint";
+  private static final String MAX_CONSTRAINT = "MaxConstraint";
+  private static final String MIN_SIZE_CONSTRAINT = "MinSizeConstraint";
+  private static final String SCALE_CONSTRAINT = "ScaleConstraint";
+  private static final String NOT_EQUAL_CONSTRAINT = "NotEqualConstraint";
+  private static final String NULLABLE_CONSTRAINT = "NullableConstraint";
+  private static final String VALIDATOR_CONSTRAINT = "ValidatorConstraint";
+  private static final String UNIQUE_CONSTRAINT = "UniqueConstraint";
+  private static final String URL_MAPPING_BUILDER = "web.mapping.DefaultUrlMappingEvaluator.UrlMappingBuilder";
+
+  private static final GrailsCommonClassNames AFTER_40 =
+    new GrailsCommonClassNames("org.grails.", "datastore.gorm.validation.constraints.");
+  private static final GrailsCommonClassNames AFTER_30 =
+    new GrailsCommonClassNames("org.grails.", "validation.");
+  private static final GrailsCommonClassNames BEFORE_30 =
+    new GrailsCommonClassNames("org.codehaus.groovy.grails.", "validation.");
+
+  private final String hibernateMappingBuilder;
+  private final String creditCardConstraint;
+  private final String emailConstraint;
+  private final String blankConstraint;
+  private final String rangeConstraint;
+  private final String inListConstraint;
+  private final String urlConstraint;
+  private final String sizeConstraint;
+  private final String matchesConstraint;
+  private final String minConstraint;
+  private final String maxConstraint;
+  private final String minSizeConstraint;
+  private final String maxSizeConstraint;
+  private final String scaleConstraint;
+  private final String notEqualConstraint;
+  private final String nullableConstraint;
+  private final String validatorConstraint;
+  private final String uniqueConstraint;
+  private final String urlMappingBuilder;
+
+  private GrailsCommonClassNames(@NotNull String globalPrefix, @NotNull String constraintPath) {
+    hibernateMappingBuilder = globalPrefix + HIBERNATE_MAPPING_BUILDER;
+    creditCardConstraint = globalPrefix + constraintPath + CREDIT_CARD_CONSTRAINT;
+    emailConstraint = globalPrefix + constraintPath + EMAIL_CONSTRAINT;
+    blankConstraint = globalPrefix + constraintPath + BLANK_CONSTRAINT;
+    rangeConstraint = globalPrefix + constraintPath + RANGE_CONSTRAINT;
+    inListConstraint = globalPrefix + constraintPath + IN_LIST_CONSTRAINT;
+    urlConstraint = globalPrefix + constraintPath + URL_CONSTRAINT;
+    sizeConstraint = globalPrefix + constraintPath + SIZE_CONSTRAINT;
+    matchesConstraint = globalPrefix + constraintPath + MATCHES_CONSTRAINT;
+    minConstraint = globalPrefix + constraintPath + MIN_CONSTRAINT;
+    maxConstraint = globalPrefix + constraintPath + MAX_CONSTRAINT;
+    minSizeConstraint = globalPrefix + constraintPath + MIN_SIZE_CONSTRAINT;
+    maxSizeConstraint = globalPrefix + constraintPath + MAX_SIZE_CONSTRAINT;
+    scaleConstraint = globalPrefix + constraintPath + SCALE_CONSTRAINT;
+    notEqualConstraint = globalPrefix + constraintPath + NOT_EQUAL_CONSTRAINT;
+    nullableConstraint = globalPrefix + constraintPath + NULLABLE_CONSTRAINT;
+    validatorConstraint = globalPrefix + constraintPath + VALIDATOR_CONSTRAINT;
+    uniqueConstraint = globalPrefix + constraintPath + UNIQUE_CONSTRAINT;
+    urlMappingBuilder = globalPrefix + URL_MAPPING_BUILDER;
+  }
+
+  public static @NotNull GrailsCommonClassNames getInstance(@NotNull PsiElement context) {
+    GrailsApplication app = GrailsApplicationManager.findApplication(context);
+    if (app == null) return BEFORE_30;
+    if (Version.AT_LEAST_4.contains(app.getGrailsVersion())) return AFTER_40;
+    if (Version.AT_LEAST_3.contains(app.getGrailsVersion())) return AFTER_30;
+    return BEFORE_30;
+  }
+
+  public @NotNull String getHibernateMappingBuilder() { return hibernateMappingBuilder; }
+
+  public @NotNull String getCreditCardConstraint() { return creditCardConstraint; }
+
+  public @NotNull String getEmailConstraint() { return emailConstraint; }
+
+  public @NotNull String getBlankConstraint() { return blankConstraint; }
+
+  public @NotNull String getRangeConstraint() { return rangeConstraint; }
+
+  public @NotNull String getInListConstraint() { return inListConstraint; }
+
+  public @NotNull String getUrlConstraint() { return urlConstraint; }
+
+  public @NotNull String getSizeConstraint() { return sizeConstraint; }
+
+  public @NotNull String getMatchesConstraint() { return matchesConstraint; }
+
+  public @NotNull String getMinConstraint() { return minConstraint; }
+
+  public @NotNull String getMaxConstraint() { return maxConstraint; }
+
+  public @NotNull String getMinSizeConstraint() { return minSizeConstraint; }
+
+  public @NotNull String getMaxSizeConstraint() { return maxSizeConstraint; }
+
+  public @NotNull String getScaleConstraint() { return scaleConstraint; }
+
+  public @NotNull String getNotEqualConstraint() { return notEqualConstraint; }
+
+  public @NotNull String getNullableConstraint() { return nullableConstraint; }
+
+  public @NotNull String getValidatorConstraint() { return validatorConstraint; }
+
+  public @NotNull String getUniqueConstraint() { return uniqueConstraint; }
+
+  public @NotNull String getUrlMappingBuilder() { return urlMappingBuilder; }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt
deleted file mode 100644
index 9bcfda7..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure
-
-import com.intellij.psi.PsiElement
-import org.jetbrains.plugins.grails.util.version.Version
-
-class GrailsCommonClassNames private constructor(globalPrefix: String, constraintPath: String) {
-
-  companion object {
-    private val after40: GrailsCommonClassNames = GrailsCommonClassNames("org.grails.", "datastore.gorm.validation.constraints.")
-    private val after30: GrailsCommonClassNames = GrailsCommonClassNames("org.grails.", "validation.")
-    private val before30: GrailsCommonClassNames = GrailsCommonClassNames("org.codehaus.groovy.grails.", "validation.")
-
-    @JvmStatic
-    fun getInstance(context: PsiElement): GrailsCommonClassNames {
-      val app: GrailsApplication = GrailsApplicationManager.findApplication(context) ?: return before30
-      if (Version.AT_LEAST_4.contains(app.grailsVersion)) {
-        return after40
-      }
-      else if (Version.AT_LEAST_3.contains(app.grailsVersion)) {
-        return after30
-      }
-      else {
-        return before30
-      }
-    }
-
-    private const val HIBERNATE_MAPPING_BUILDER = "orm.hibernate.cfg.HibernateMappingBuilder"
-    private const val CREDIT_CARD_CONSTRAINT    = "CreditCardConstraint"
-    private const val MAX_SIZE_CONSTRAINT       = "MaxSizeConstraint"
-    private const val EMAIL_CONSTRAINT          = "EmailConstraint"
-    private const val BLANK_CONSTRAINT          = "BlankConstraint"
-    private const val RANGE_CONSTRAINT          = "RangeConstraint"
-    private const val URL_CONSTRAINT            = "UrlConstraint"
-    private const val SIZE_CONSTRAINT           = "SizeConstraint"
-    private const val IN_LIST_CONSTRAINT        = "InListConstraint"
-    private const val MATCHES_CONSTRAINT        = "MatchesConstraint"
-    private const val MIN_CONSTRAINT            = "MinConstraint"
-    private const val MAX_CONSTRAINT            = "MaxConstraint"
-    private const val MIN_SIZE_CONSTRAINT       = "MinSizeConstraint"
-    private const val SCALE_CONSTRAINT          = "ScaleConstraint"
-    private const val NOT_EQUAL_CONSTRAINT      = "NotEqualConstraint"
-    private const val NULLABLE_CONSTRAINT       = "NullableConstraint"
-    private const val VALIDATOR_CONSTRAINT      = "ValidatorConstraint"
-    private const val UNIQUE_CONSTRAINT         = "UniqueConstraint"
-    private const val URL_MAPPING_BUILDER       = "web.mapping.DefaultUrlMappingEvaluator.UrlMappingBuilder"
-  }
-
-  val hibernateMappingBuilder: String = globalPrefix + HIBERNATE_MAPPING_BUILDER
-  val creditCardConstraint: String    = globalPrefix + constraintPath + CREDIT_CARD_CONSTRAINT
-  val emailConstraint: String         = globalPrefix + constraintPath + EMAIL_CONSTRAINT
-  val blankConstraint: String         = globalPrefix + constraintPath + BLANK_CONSTRAINT
-  val rangeConstraint: String         = globalPrefix + constraintPath + RANGE_CONSTRAINT
-  val inListConstraint: String        = globalPrefix + constraintPath + IN_LIST_CONSTRAINT
-  val urlConstraint: String           = globalPrefix + constraintPath + URL_CONSTRAINT
-  val sizeConstraint: String          = globalPrefix + constraintPath + SIZE_CONSTRAINT
-  val matchesConstraint: String       = globalPrefix + constraintPath + MATCHES_CONSTRAINT
-  val minConstraint: String           = globalPrefix + constraintPath + MIN_CONSTRAINT
-  val maxConstraint: String           = globalPrefix + constraintPath + MAX_CONSTRAINT
-  val minSizeConstraint: String       = globalPrefix + constraintPath + MIN_SIZE_CONSTRAINT
-  val maxSizeConstraint: String       = globalPrefix + constraintPath + MAX_SIZE_CONSTRAINT
-  val scaleConstraint: String         = globalPrefix + constraintPath + SCALE_CONSTRAINT
-  val notEqualConstraint: String      = globalPrefix + constraintPath + NOT_EQUAL_CONSTRAINT
-  val nullableConstraint: String      = globalPrefix + constraintPath + NULLABLE_CONSTRAINT
-  val validatorConstraint: String     = globalPrefix + constraintPath + VALIDATOR_CONSTRAINT
-  val uniqueConstraint: String        = globalPrefix + constraintPath + UNIQUE_CONSTRAINT
-  val urlMappingBuilder: String       = globalPrefix + URL_MAPPING_BUILDER
-
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.java
similarity index 62%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.java
index 11fced1..170857b 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.java
@@ -13,15 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.structure;
 
-package org.jetbrains.plugins.grails.structure
+import com.intellij.lang.properties.psi.PropertiesFile;
+import com.intellij.openapi.module.Module;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-import com.intellij.lang.properties.psi.PropertiesFile
-import com.intellij.openapi.module.Module
+public interface OldGrailsApplication extends GrailsApplication {
 
-interface OldGrailsApplication : GrailsApplication {
+  @NotNull Module getModule();
 
-  val module: Module
-
-  val applicationProperties: PropertiesFile?
+  @Nullable PropertiesFile getApplicationProperties();
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.java
new file mode 100644
index 0000000..1e70256
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.config.GrailsModuleStructureUtil;
+import org.jetbrains.plugins.grails.config.GrailsSettingsService;
+import org.jetbrains.plugins.grails.config.PrintGrailsSettingsConstants;
+import org.jetbrains.plugins.grails.util.version.Version;
+import org.jetbrains.plugins.grails.util.version.VersionImpl;
+
+// Public because it is referenced from the runner package. It was Kotlin `internal`, which is
+// public in bytecode, so cross-package use compiled; package-private here would not.
+@ApiStatus.Internal
+public final class Grails2Application extends OldGrailsModuleBasedApplication {
+
+  public Grails2Application(@NotNull VirtualFile root, @NotNull Module module) {
+    super(module, root);
+  }
+
+  @Override
+  public @NotNull String getName() {
+    String name = getApplicationPropertiesValue("app.name");
+    return name != null ? name : getModule().getName();
+  }
+
+  @Override
+  public @Nullable String getAppVersion() {
+    return getApplicationPropertiesValue("app.version");
+  }
+
+  public @Nullable Version getApplicationPropertiesVersion() {
+    String version = getApplicationPropertiesValue(GrailsModuleStructureUtil.GRAILS_VERSION_KEY);
+    return version == null || version.isBlank() ? null : new VersionImpl(version);
+  }
+
+  public boolean isRunForked() {
+    return getBooleanSetting(PrintGrailsSettingsConstants.DEBUG_RUN_FORK);
+  }
+
+  public boolean isTestForked() {
+    return getBooleanSetting(PrintGrailsSettingsConstants.DEBUG_TEST_FORK);
+  }
+
+  private @Nullable String getApplicationPropertiesValue(@NotNull String key) {
+    return ReadAction.compute(() -> GrailsProperties.getPropertyValue(getApplicationProperties(), key));
+  }
+
+  private boolean getBooleanSetting(@NotNull String key) {
+    return ReadAction.compute(() -> {
+      String value = GrailsSettingsService.getGrailsSettings(getModule()).properties.get(key);
+      return value != null && Boolean.parseBoolean(value);
+    });
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt
deleted file mode 100644
index 3aec612..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.config.GrailsModuleStructureUtil
-import org.jetbrains.plugins.grails.config.GrailsSettingsService.getGrailsSettings
-import org.jetbrains.plugins.grails.config.PrintGrailsSettingsConstants
-import org.jetbrains.plugins.grails.util.version.Version
-import org.jetbrains.plugins.grails.util.version.VersionImpl
-
-internal class Grails2Application(root: VirtualFile, module: Module) : OldGrailsModuleBasedApplication(module, root) {
-
-  override val name: String get() = getApplicationPropertiesValue("app.name") ?: module.name
-
-  override val appVersion: String? get() = getApplicationPropertiesValue("app.version")
-
-  val applicationPropertiesVersion: Version? get() {
-    val version = getApplicationPropertiesValue(GrailsModuleStructureUtil.GRAILS_VERSION_KEY)
-    return if (version.isNullOrBlank()) null else VersionImpl(version)
-  }
-
-  val isRunForked: Boolean get() = getBooleanSetting(PrintGrailsSettingsConstants.DEBUG_RUN_FORK)
-
-  val isTestForked: Boolean get() = getBooleanSetting(PrintGrailsSettingsConstants.DEBUG_TEST_FORK)
-
-  private fun getApplicationPropertiesValue(key: String) = runReadAction { applicationProperties?.getPropertyValue(key) }
-
-  private fun getBooleanSetting(key: String) = runReadAction { getGrailsSettings(module).properties[key]?.toBoolean() ?: false }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.java
new file mode 100644
index 0000000..5f8ec44
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.openapi.externalSystem.model.DataNode;
+import com.intellij.openapi.externalSystem.model.project.ModuleData;
+import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.gradle.GrailsModuleData;
+import org.jetbrains.plugins.grails.structure.Grails3Application;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationBase;
+import org.jetbrains.plugins.grails.util.version.Version;
+import org.jetbrains.plugins.grails.util.version.VersionImpl;
+
+import java.util.Objects;
+
+abstract class Grails3ApplicationBase extends GrailsApplicationBase implements Grails3Application {
+
+  private final DataNode<ModuleData> myModuleDataNode;
+
+  // Both derived from the external-system model, which does not change for a given node, so they
+  // are computed once. Volatile publication only; the computation is idempotent.
+  private volatile GrailsModuleData myGradleData;
+  private volatile Version myGrailsVersion;
+
+  protected Grails3ApplicationBase(@NotNull Project project,
+                                   @NotNull VirtualFile root,
+                                   @NotNull DataNode<ModuleData> moduleDataNode) {
+    super(project, root);
+    myModuleDataNode = moduleDataNode;
+  }
+
+  @Override
+  public @NotNull String getName() {
+    return myModuleDataNode.getData().getExternalName();
+  }
+
+  @Override
+  public @Nullable String getAppVersion() {
+    return myModuleDataNode.getData().getVersion();
+  }
+
+  @Override
+  public @NotNull GrailsModuleData getGradleData() {
+    GrailsModuleData result = myGradleData;
+    if (result == null) {
+      result = Objects.requireNonNull(ExternalSystemApiUtil.find(myModuleDataNode, GrailsModuleData.KEY)).getData();
+      myGradleData = result;
+    }
+    return result;
+  }
+
+  @Override
+  public @NotNull Version getGrailsVersion() {
+    Version result = myGrailsVersion;
+    if (result == null) {
+      result = new VersionImpl(getGradleData().getGrailsVersion());
+      myGrailsVersion = result;
+    }
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) return true;
+    if (other == null || other.getClass() != getClass()) return false;
+    if (!super.equals(other)) return false;
+    return myModuleDataNode.equals(((Grails3ApplicationBase)other).myModuleDataNode);
+  }
+
+  @Override
+  public int hashCode() {
+    return 31 * super.hashCode() + myModuleDataNode.hashCode();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt
deleted file mode 100644
index 2507f13..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.openapi.externalSystem.model.DataNode
-import com.intellij.openapi.externalSystem.model.project.ModuleData
-import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.util.lazyUnsafe
-import org.jetbrains.plugins.grails.gradle.GrailsModuleData
-import org.jetbrains.plugins.grails.structure.Grails3Application
-import org.jetbrains.plugins.grails.structure.GrailsApplicationBase
-import org.jetbrains.plugins.grails.util.version.VersionImpl
-
-internal abstract class Grails3ApplicationBase(
-    project: Project,
-    root: VirtualFile,
-    private val moduleDataNode: DataNode<ModuleData>
-) : GrailsApplicationBase(project, root), Grails3Application {
-
-  override val name: String get() = moduleDataNode.data.externalName
-
-  override val appVersion: String? get() = moduleDataNode.data.version
-
-  override val grailsVersion by lazyUnsafe { VersionImpl(gradleData.grailsVersion) }
-
-  override val gradleData by lazyUnsafe { ExternalSystemApiUtil.find(moduleDataNode, GrailsModuleData.KEY)!!.data }
-
-  override fun equals(other: Any?): Boolean {
-    if (this === other) return true
-    if (other?.javaClass != javaClass) return false
-    if (!super.equals(other)) return false
-
-    other as Grails3ApplicationBase
-
-    return moduleDataNode == other.moduleDataNode
-  }
-
-  override fun hashCode() = 31 * super.hashCode() + moduleDataNode.hashCode()
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.java
new file mode 100644
index 0000000..5264479
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.openapi.externalSystem.model.DataNode;
+import com.intellij.openapi.externalSystem.model.project.ModuleData;
+import com.intellij.openapi.externalSystem.model.project.ProjectData;
+import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
+import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings;
+import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
+import com.intellij.openapi.externalSystem.model.ExternalProjectInfo;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil;
+import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
+import org.jetbrains.plugins.gradle.util.GradleConstants;
+import org.jetbrains.plugins.grails.gradle.GrailsModuleData;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationProvider;
+
+public final class Grails3ApplicationProvider extends GrailsApplicationProvider {
+
+  @Override
+  public @Nullable GrailsApplication createApplication(@NotNull Project project, @NotNull VirtualFile root) {
+    String path = root.getPath();
+
+    ExternalProjectSettings projectSettings =
+      ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).getLinkedProjectSettings(path);
+    if (!(projectSettings instanceof GradleProjectSettings linkedProjectSettings)) return null;
+
+    ExternalProjectInfo gradleProjectInfo = ProjectDataManager.getInstance()
+      .getExternalProjectData(project, GradleConstants.SYSTEM_ID, linkedProjectSettings.getExternalProjectPath());
+    if (gradleProjectInfo == null) return null;
+
+    DataNode<ProjectData> structure = gradleProjectInfo.getExternalProjectStructure();
+    DataNode<ModuleData> moduleData = GradleProjectResolverUtil.findModule(structure, path);
+    if (moduleData == null) return null;
+    if (ExternalSystemApiUtil.find(moduleData, GrailsModuleData.KEY) == null) return null;
+
+    if (linkedProjectSettings.isResolveModulePerSourceSet()) {
+      return new Grails3MultiModuleApplication(project, root, moduleData);
+    }
+
+    Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(root);
+    return module != null ? new Grails3SingleModuleApplication(module, root, moduleData) : null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt
deleted file mode 100644
index 7a490d5..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.openapi.externalSystem.service.project.ProjectDataManager
-import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.findModule
-import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
-import org.jetbrains.plugins.gradle.util.GradleConstants.SYSTEM_ID
-import org.jetbrains.plugins.grails.gradle.GrailsModuleData
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.GrailsApplicationProvider
-
-class Grails3ApplicationProvider : GrailsApplicationProvider() {
-
-  override fun createApplication(project: Project, root: VirtualFile): GrailsApplication? {
-    val path = root.path
-
-    val settings = ExternalSystemApiUtil.getSettings(project, SYSTEM_ID)
-    val linkedProjectSettings = settings.getLinkedProjectSettings(path) as? GradleProjectSettings ?: return null
-    val gradleProjectInfo = ProjectDataManager.getInstance().getExternalProjectData(
-        project, SYSTEM_ID, linkedProjectSettings.externalProjectPath
-    ) ?: return null
-
-    val moduleData = findModule(gradleProjectInfo.externalProjectStructure, path) ?: return null
-    if (ExternalSystemApiUtil.find(moduleData, GrailsModuleData.KEY) == null) return null
-
-    return if (linkedProjectSettings.isResolveModulePerSourceSet) {
-      Grails3MultiModuleApplication(project, root, moduleData)
-    }
-    else {
-      ProjectRootManager.getInstance(project).fileIndex.getModuleForFile(root)?.let { module ->
-        Grails3SingleModuleApplication(module, root, moduleData)
-      }
-    }
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.java
new file mode 100644
index 0000000..0cd1d63
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.openapi.externalSystem.model.DataNode;
+import com.intellij.openapi.externalSystem.model.project.ModuleData;
+import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class Grails3MultiModuleApplication extends Grails3ApplicationBase {
+
+  private final DataNode<ModuleData> myModuleDataNode;
+
+  // The source sets come from the external-system model and do not change for a given node.
+  private volatile List<GradleSourceSetData> mySourceSets;
+
+  Grails3MultiModuleApplication(@NotNull Project project,
+                                @NotNull VirtualFile root,
+                                @NotNull DataNode<ModuleData> moduleDataNode) {
+    super(project, root, moduleDataNode);
+    myModuleDataNode = moduleDataNode;
+  }
+
+  @Override
+  public @NotNull GlobalSearchScope getScope(boolean includeDependencies, boolean testsOnly) {
+    if (testsOnly) {
+      return sourceSetScope("test", includeDependencies, true)
+        .uniteWith(sourceSetScope("integrationTest", includeDependencies, true));
+    }
+    return sourceSetScope("main", includeDependencies, false);
+  }
+
+  private @NotNull GlobalSearchScope sourceSetScope(@NotNull String sourceSetName,
+                                                    boolean includeDependencies,
+                                                    boolean testsOnly) {
+    Module module = findModule(sourceSetName);
+    if (module == null) return GlobalSearchScope.EMPTY_SCOPE;
+    return includeDependencies ? module.getModuleRuntimeScope(testsOnly) : module.getModuleScope(testsOnly);
+  }
+
+  private @NotNull List<GradleSourceSetData> getSourceSets() {
+    List<GradleSourceSetData> result = mySourceSets;
+    if (result == null) {
+      result = new ArrayList<>();
+      for (DataNode<GradleSourceSetData> node : ExternalSystemApiUtil.findAll(myModuleDataNode, GradleSourceSetData.KEY)) {
+        result.add(node.getData());
+      }
+      mySourceSets = result;
+    }
+    return result;
+  }
+
+  private @Nullable Module findModule(@NotNull String sourceSetName) {
+    GradleSourceSetData sourceSet = null;
+    for (GradleSourceSetData candidate : getSourceSets()) {
+      if (candidate.getId().endsWith(":" + sourceSetName)) {
+        sourceSet = candidate;
+        break;
+      }
+    }
+    if (sourceSet == null) return null;
+
+    for (Module module : ModuleManager.getInstance(getProject()).getModules()) {
+      if (sourceSet.getId().equals(ExternalSystemApiUtil.getExternalProjectId(module))) {
+        return module;
+      }
+    }
+    return null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt
deleted file mode 100644
index e41654b..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.openapi.externalSystem.model.DataNode
-import com.intellij.openapi.externalSystem.model.project.ModuleData
-import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.module.ModuleManager
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.search.GlobalSearchScope.EMPTY_SCOPE
-import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
-
-internal class Grails3MultiModuleApplication(
-    project: Project,
-    root: VirtualFile,
-    moduleDataNode: DataNode<ModuleData>
-) : Grails3ApplicationBase(project, root, moduleDataNode) {
-
-  override fun getScope(includeDependencies: Boolean, testsOnly: Boolean): GlobalSearchScope {
-    fun getScope(sourceSetName: String) = findModule(sourceSetName)?.let {
-      if (includeDependencies) it.getModuleRuntimeScope(testsOnly) else it.getModuleScope(testsOnly)
-    } ?: EMPTY_SCOPE
-
-    return when {
-      testsOnly -> getScope("test").uniteWith(getScope("integrationTest"))
-      else -> getScope("main")
-    }
-  }
-
-  private val mySourceSets by lazy {
-    ExternalSystemApiUtil.findAll(moduleDataNode, GradleSourceSetData.KEY).map { it.data }
-  }
-
-  private fun findModule(sourceSetName: String): Module? {
-    val sourceSet = mySourceSets.find { it.id.endsWith(":$sourceSetName") } ?: return null
-    return ModuleManager.getInstance(project).modules.find { ExternalSystemApiUtil.getExternalProjectId(it) == sourceSet.id }
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.java
new file mode 100644
index 0000000..fea9fe3
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.openapi.externalSystem.model.DataNode;
+import com.intellij.openapi.externalSystem.model.project.ModuleData;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import org.jetbrains.annotations.NotNull;
+
+final class Grails3SingleModuleApplication extends Grails3ApplicationBase {
+
+  private final Module myModule;
+
+  Grails3SingleModuleApplication(@NotNull Module module,
+                                 @NotNull VirtualFile root,
+                                 @NotNull DataNode<ModuleData> moduleDataNode) {
+    super(module.getProject(), root, moduleDataNode);
+    myModule = module;
+  }
+
+  public @NotNull Module getModule() {
+    return myModule;
+  }
+
+  @Override
+  public @NotNull GlobalSearchScope getScope(boolean includeDependencies, boolean testsOnly) {
+    return includeDependencies ? myModule.getModuleRuntimeScope(testsOnly) : myModule.getModuleScope(testsOnly);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) return true;
+    if (other == null || other.getClass() != getClass()) return false;
+    if (!super.equals(other)) return false;
+    return myModule.equals(((Grails3SingleModuleApplication)other).myModule);
+  }
+
+  @Override
+  public int hashCode() {
+    return 31 * super.hashCode() + myModule.hashCode();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt
deleted file mode 100644
index cac4fc9..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.openapi.externalSystem.model.DataNode
-import com.intellij.openapi.externalSystem.model.project.ModuleData
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.search.GlobalSearchScope
-
-internal class Grails3SingleModuleApplication(
-    val module: Module,
-    root: VirtualFile,
-    moduleDataNode: DataNode<ModuleData>
-) : Grails3ApplicationBase(module.project, root, moduleDataNode) {
-
-  override fun getScope(includeDependencies: Boolean, testsOnly: Boolean): GlobalSearchScope {
-    return if (includeDependencies) {
-      module.getModuleRuntimeScope(testsOnly)
-    }
-    else {
-      module.getModuleScope(testsOnly)
-    }
-  }
-
-  override fun equals(other: Any?): Boolean {
-    if (this === other) return true
-    if (other?.javaClass != javaClass) return false
-    if (!super.equals(other)) return false
-
-    other as Grails3SingleModuleApplication
-
-    return module == other.module
-  }
-
-  override fun hashCode() = 31 * super.hashCode() + module.hashCode()
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.java
new file mode 100644
index 0000000..a39f0f4
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationBase;
+
+public abstract class GrailsModuleBasedApplication extends GrailsApplicationBase {
+
+  private final Module myModule;
+
+  protected GrailsModuleBasedApplication(@NotNull Module module, @NotNull VirtualFile root) {
+    super(module.getProject(), root);
+    myModule = module;
+  }
+
+  public @NotNull Module getModule() {
+    return myModule;
+  }
+
+  @Override
+  public boolean isValid() {
+    return super.isValid() && !myModule.isDisposed();
+  }
+
+  @Override
+  public @NotNull GlobalSearchScope getScope(boolean includeDependencies, boolean testsOnly) {
+    if (testsOnly && includeDependencies) {
+      return myModule.getModuleTestsWithDependentsScope();
+    }
+    if (testsOnly) {
+      // tests only, without dependencies: the test sources minus the production sources
+      return CachedValuesManager.getManager(getProject()).getCachedValue(this, () -> {
+        GlobalSearchScope moduleScope = myModule.getModuleScope(true);
+        GlobalSearchScope moduleWithoutTestsScope = myModule.getModuleScope(false);
+        GlobalSearchScope result = moduleScope.intersectWith(GlobalSearchScope.notScope(moduleWithoutTestsScope));
+        return Result.create(result, ProjectRootManager.getInstance(getProject()));
+      });
+    }
+    if (includeDependencies) {
+      return myModule.getModuleRuntimeScope(false);
+    }
+    return myModule.getModuleScope(false);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) return true;
+    if (other == null || other.getClass() != getClass()) return false;
+    if (!super.equals(other)) return false;
+    return myModule.equals(((GrailsModuleBasedApplication)other).myModule);
+  }
+
+  @Override
+  public int hashCode() {
+    return 31 * super.hashCode() + myModule.hashCode();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt
deleted file mode 100644
index 4913bde..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import org.jetbrains.plugins.grails.structure.GrailsApplicationBase
-
-abstract class GrailsModuleBasedApplication(
-  val module: Module,
-  root: VirtualFile
-) : GrailsApplicationBase(module.project, root) {
-
-  override val isValid: Boolean get() = super.isValid && !module.isDisposed
-
-  override fun getScope(includeDependencies: Boolean, testsOnly: Boolean): GlobalSearchScope {
-    return when {
-      testsOnly && includeDependencies -> module.moduleTestsWithDependentsScope
-      testsOnly && !includeDependencies -> CachedValuesManager.getManager(project).getCachedValue(this) {
-        val moduleScope = module.getModuleScope(true)
-        val moduleWithoutTestsScope = module.getModuleScope(false)
-        val result = moduleScope.intersectWith(GlobalSearchScope.notScope(moduleWithoutTestsScope))
-        Result.create(result, ProjectRootManager.getInstance(project))
-      }
-      !testsOnly && includeDependencies -> module.getModuleRuntimeScope(false)
-      !testsOnly && !includeDependencies -> module.getModuleScope(false)
-      else -> throw RuntimeException("never happens")
-    }
-  }
-
-  override fun equals(other: Any?): Boolean {
-    if (this === other) return true
-    if (other?.javaClass != javaClass) return false
-    if (!super.equals(other)) return false
-
-    other as GrailsModuleBasedApplication
-
-    return module == other.module
-  }
-
-  override fun hashCode(): Int = 31 * super.hashCode() + module.hashCode()
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsProperties.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsProperties.java
new file mode 100644
index 0000000..2cfe544
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsProperties.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.lang.properties.psi.PropertiesFile;
+import com.intellij.lang.properties.IProperty;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class GrailsProperties {
+
+  private GrailsProperties() {
+  }
+
+  /** Null-tolerant property lookup; was a nullable-receiver extension in impl/util.kt. */
+  static @Nullable String getPropertyValue(@Nullable PropertiesFile file, @NotNull String key) {
+    if (file == null) return null;
+    IProperty property = file.findPropertyByKey(key);
+    return property != null ? property.getValue() : null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.java
new file mode 100644
index 0000000..72c0800
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.structure.impl;
+
+import com.intellij.lang.properties.psi.PropertiesFile;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.GlobalSearchScopesCore;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.config.GrailsConfigUtils;
+import org.jetbrains.plugins.grails.structure.OldGrailsApplication;
+import org.jetbrains.plugins.grails.util.version.Version;
+import org.jetbrains.plugins.grails.util.version.VersionImpl;
+
+public abstract class OldGrailsModuleBasedApplication extends GrailsModuleBasedApplication implements OldGrailsApplication {
+
+  private static final String[] TEST_FOLDERS = {"test/unit", "test/integration", "test/functional"};
+
+  // Deferred and cached: computing it walks the VFS, and it is only needed for tests-only scopes.
+  private volatile GlobalSearchScope myTestFoldersScope;
+
+  protected OldGrailsModuleBasedApplication(@NotNull Module module, @NotNull VirtualFile root) {
+    super(module, root);
+  }
+
+  private @NotNull GlobalSearchScope getTestFoldersScope() {
+    GlobalSearchScope result = myTestFoldersScope;
+    if (result == null) {
+      result = GlobalSearchScope.EMPTY_SCOPE;
+      for (String path : TEST_FOLDERS) {
+        VirtualFile directory = getRoot().findFileByRelativePath(path);
+        if (directory != null && directory.isDirectory()) {
+          result = result.union(GlobalSearchScopesCore.directoryScope(getProject(), directory, true));
+        }
+      }
+      myTestFoldersScope = result;
+    }
+    return result;
+  }
+
+  @Override
+  public @NotNull Version getGrailsVersion() {
+    String version = GrailsConfigUtils.getGrailsVersion(getModule());
+    return version != null ? new VersionImpl(version) : Version.LATEST_2x;
+  }
+
+  @Override
+  public @NotNull GlobalSearchScope getScope(boolean includeDependencies, boolean testsOnly) {
+    GlobalSearchScope scope = super.getScope(includeDependencies, testsOnly);
+    return testsOnly ? scope.union(getTestFoldersScope()) : scope;
+  }
+
+  @Override
+  public @Nullable PropertiesFile getApplicationProperties() {
+    VirtualFile root = getRoot();
+    if (!root.isValid()) return null;
+
+    VirtualFile applicationProperties = root.findChild("application.properties");
+    if (applicationProperties == null) return null;
+
+    PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(applicationProperties);
+    return psiFile instanceof PropertiesFile propertiesFile ? propertiesFile : null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt
deleted file mode 100644
index 6ad9602..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.lang.properties.psi.PropertiesFile
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiManager
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.search.GlobalSearchScopesCore
-import org.jetbrains.plugins.grails.config.GrailsConfigUtils
-import org.jetbrains.plugins.grails.structure.OldGrailsApplication
-import org.jetbrains.plugins.grails.util.version.Version
-import org.jetbrains.plugins.grails.util.version.Version.LATEST_2x
-import org.jetbrains.plugins.grails.util.version.VersionImpl
-
-abstract class OldGrailsModuleBasedApplication(
-    module: Module,
-    root: VirtualFile
-) : GrailsModuleBasedApplication(module, root), OldGrailsApplication {
-
-  private val myTestFoldersScope by lazy {
-    arrayOf("test/unit", "test/integration", "test/functional").fold(GlobalSearchScope.EMPTY_SCOPE) { scope, path ->
-      val directory = root.findFileByRelativePath(path)
-      if (directory != null && directory.isDirectory) {
-        scope.union(GlobalSearchScopesCore.directoryScope(project, directory, true))
-      }
-      else {
-        scope
-      }
-    }
-  }
-
-  override val grailsVersion: Version get() = GrailsConfigUtils.getGrailsVersion(module)?.let { VersionImpl(it) } ?: LATEST_2x
-
-  override fun getScope(includeDependencies: Boolean, testsOnly: Boolean): GlobalSearchScope {
-    val scope = super.getScope(includeDependencies, testsOnly)
-    if (testsOnly) {
-      return scope.union(myTestFoldersScope)
-    }
-    else {
-      return scope
-    }
-  }
-
-  override val applicationProperties: PropertiesFile? get() {
-    val root = root
-    if (!root.isValid) return null
-
-    val applicationProperties = root.findChild("application.properties") ?: return null
-
-    val psiFile = PsiManager.getInstance(project).findFile(applicationProperties)
-    return if (psiFile is PropertiesFile) psiFile else null
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/util.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/util.kt
deleted file mode 100644
index 186f511..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/util.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure.impl
-
-import com.intellij.lang.properties.psi.PropertiesFile
-
-internal fun PropertiesFile?.getPropertyValue(key: String) = this?.findPropertyByKey(key)?.value
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/util.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/util.kt
deleted file mode 100644
index fe5de44..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/structure/util.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.structure
-
-@JvmField
-val COMPARATOR: Comparator<GrailsApplication?> = Comparator { app1, app2 ->
-  when {
-    app1 == null && app2 == null -> 0
-    app1 == null -> -1
-    app2 == null -> 1
-    else -> app1.name.compareTo(app2.name, ignoreCase = true)
-  }
-}
\ No newline at end of file


(grails-intellij-plugin) 01/35: Port build-hygiene tooling from grails-core

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 8adc98ab9b14b1fb2bbaa4e2572cac990eb809ba
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sat Jul 18 22:05:27 2026 -0400

    Port build-hygiene tooling from grails-core
    
    Add a build-logic included build with Kotlin convention plugins for
    build reproducibility, JaCoCo code coverage, and Sonatype OSS Index
    vulnerability scanning, applied to the root project and every module.
    
    - reproducible archives (preserveFileTimestamps=false, reproducibleFileOrder,
      fixed permissions) so the plugin ZIP and module jars are byte-stable
    - JaCoCo per-module + root jacoco-report-aggregation (testCodeCoverageReport),
      CC-safe under this repo's org.gradle.configuration-cache=true
    - ossIndexAudit with an OssIndexAuditThrottle build service and fail-fast when
      SONATYPE_GUIDE_TOKEN is absent
    - CI: coverage.yml (Codecov), vulnerability-scan.yml (OSS Index), codeql.yml
    - codecov.yml and .github/dependabot.yml (gradle, /build-logic, github-actions)
    - RELEASE.md documenting the release pipeline + reproducibility verification
    - etc/bin/test-reproducible-build.sh and etc/bin/Dockerfile for repro testing
    - .gitattributes pinning LF; enable GitHub issues in .asf.yaml
---
 .asf.yaml                                          |   3 +
 .gitattributes                                     |   8 ++
 .github/dependabot.yml                             |  43 +++++++
 .github/workflows/codeql.yml                       |  55 +++++++++
 .github/workflows/coverage.yml                     |  60 ++++++++++
 .github/workflows/vulnerability-scan.yml           |  85 ++++++++++++++
 .gitignore                                         |   4 +
 RELEASE.md                                         | 126 +++++++++++++++++++++
 build-logic/build.gradle.kts                       |  29 +++++
 build-logic/settings.gradle.kts                    |  26 +++++
 .../src/main/kotlin/OssIndexAuditThrottle.kt       |  35 ++++++
 .../main/kotlin/grails-intellij.jacoco.gradle.kts  |  46 ++++++++
 .../kotlin/grails-intellij.reproducible.gradle.kts |  34 ++++++
 .../grails-intellij.vulnerability-scan.gradle.kts  |  66 +++++++++++
 build.gradle.kts                                   |  35 ++++++
 codecov.yml                                        |  37 ++++++
 copyright/build.gradle.kts                         |   3 +
 coverage/build.gradle.kts                          |   3 +
 etc/bin/Dockerfile                                 |  48 ++++++++
 etc/bin/test-reproducible-build.sh                 |  84 ++++++++++++++
 gradle-tooling/build.gradle.kts                    |   3 +
 gradle.properties                                  |   3 +
 grails-compiler-patch/build.gradle.kts             |   3 +
 grails-rt/build.gradle.kts                         |   3 +
 hibernate/build.gradle.kts                         |   3 +
 i18n/build.gradle.kts                              |   3 +
 jps-plugin/build.gradle.kts                        |   3 +
 langInjection/build.gradle.kts                     |   3 +
 maven/build.gradle.kts                             |   3 +
 settings.gradle.kts                                |   4 +
 testFramework/build.gradle.kts                     |   3 +
 31 files changed, 864 insertions(+)

diff --git a/.asf.yaml b/.asf.yaml
index 9772437..9563653 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -32,6 +32,9 @@ github:
       prevent_self_review: false
   description: "Grails plugin for IntelliJ IDEA"
   homepage: https://grails.apache.org/
+  # enable the GitHub Issues tracker for this repository
+  features:
+    issues: true
   rulesets:
     - name: "Default Branch Protection"
       type: branch
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..45d9ad4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+# Files that must keep LF line endings on every platform. The reproducible-build
+# Docker image (etc/bin/Dockerfile) refuses shell scripts with CRLF shebangs, and
+# Gradle wrapper/property parsing expects LF. Pin them so a checkout under
+# core.autocrlf=true cannot corrupt the build.
+*.sh            text eol=lf
+gradlew         text eol=lf
+Dockerfile      text eol=lf
+*.properties    text eol=lf
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..24cdef9
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,43 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+version: 2
+updates:
+  # Main build (root Gradle project + all modules)
+  - package-ecosystem: gradle
+    directory: "/"
+    schedule:
+      interval: daily
+    open-pull-requests-limit: 10
+    target-branch: main
+    labels:
+      - "type: dependency upgrade"
+  # build-logic included build (convention plugins + their tool dependencies)
+  - package-ecosystem: gradle
+    directory: "/build-logic"
+    schedule:
+      interval: daily
+    open-pull-requests-limit: 10
+    target-branch: main
+    labels:
+      - "type: dependency upgrade"
+  # GitHub Actions used by the workflows
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    target-branch: main
+    labels:
+      - "type: dependency upgrade"
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..84b35b1
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "CodeQL"
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+    strategy:
+      fail-fast: false
+      matrix:
+        # The plugin is Kotlin + Java; the CodeQL 'java' pack covers both JVM languages.
+        language: [ 'java' ]
+    steps:
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+        with:
+          distribution: liberica # .sdkmanrc pins librca = Liberica
+          java-version: 25
+      - name: "🔎 Initialize CodeQL"
+        uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
+        with:
+          languages: ${{ matrix.language }}
+          build-mode: none
+      - name: "🔬 Perform CodeQL Analysis"
+        uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000..5b68590
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,60 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "Coverage"
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+  workflow_dispatch:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+  coverage:
+    name: "Coverage"
+    runs-on: ubuntu-latest
+    steps:
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+      - name: "📝 Read JDK version from .sdkmanrc"
+        id: sdkmanrc
+        run: echo "java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2 | sed 's/-librca$//')" >> "$GITHUB_OUTPUT"
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+        with:
+          distribution: liberica # .sdkmanrc pins librca = Liberica
+          java-version: ${{ steps.sdkmanrc.outputs.java }}
+      - name: "🐘 Setup Gradle"
+        uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+      - name: "🌡️ Run tests with coverage"
+        run: ./gradlew testCodeCoverageReport --continue --stacktrace
+      - name: "📤 Upload coverage artifact"
+        if: always()
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+        with:
+          name: coverage-report
+          path: build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml
+          if-no-files-found: warn
+      - name: "📊 Upload coverage to Codecov"
+        continue-on-error: true
+        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml
+          verbose: true
diff --git a/.github/workflows/vulnerability-scan.yml b/.github/workflows/vulnerability-scan.yml
new file mode 100644
index 0000000..5a47483
--- /dev/null
+++ b/.github/workflows/vulnerability-scan.yml
@@ -0,0 +1,85 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "Vulnerability Scan"
+on:
+  push:
+    branches:
+      - main
+  schedule:
+    # Run every Monday at 03:00 UTC
+    - cron: '0 3 * * 1'
+  workflow_dispatch:
+# Do not scan concurrently; OSS Index has per-account rate limits
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: false
+jobs:
+  scan:
+    name: "OSS Index Scan"
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    steps:
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+      - name: "📝 Read JDK version from .sdkmanrc"
+        id: sdkmanrc
+        run: echo "java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2 | sed 's/-librca$//')" >> "$GITHUB_OUTPUT"
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+        with:
+          distribution: liberica # .sdkmanrc pins librca = Liberica
+          java-version: ${{ steps.sdkmanrc.outputs.java }}
+      - name: "🐘 Setup Gradle"
+        uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+      - name: "🔍 Run OSS Index Vulnerability Scan"
+        env:
+          SONATYPE_GUIDE_USERNAME: ${{ secrets.SONATYPE_GUIDE_USERNAME }}
+          SONATYPE_GUIDE_TOKEN: ${{ secrets.SONATYPE_GUIDE_TOKEN }}
+        continue-on-error: true
+        id: scan
+        run: ./gradlew ossIndexAudit --continue --info 2>&1 | tee /tmp/ossindex-scan.log; exit ${PIPESTATUS[0]}
+      - name: "📋 Publish Vulnerability Summary"
+        if: always()
+        run: |
+          echo "## 🔍 OSS Index Vulnerability Scan" >> $GITHUB_STEP_SUMMARY
+          if [ "${{ steps.scan.outcome }}" = "success" ]; then
+            echo "✅ No vulnerabilities found." >> $GITHUB_STEP_SUMMARY
+          else
+            echo "❌ Vulnerabilities detected." >> $GITHUB_STEP_SUMMARY
+            echo "" >> $GITHUB_STEP_SUMMARY
+            echo '```' >> $GITHUB_STEP_SUMMARY
+            awk '
+              BEGIN { in_section=0; in_vuln=0 }
+              { gsub(/\033\[[0-9;]*m/, "") }
+              /^##\[ossIndexAudit:begin\]/ { in_section=1; next }
+              /^##\[ossIndexAudit:end\]/ { in_section=0; in_vuln=0; next }
+              !in_section { next }
+              /^\[[0-9]+\/[0-9]+\] - pkg:maven\// {
+                sub(/^\[[0-9]+\/[0-9]+\] - /, "")
+                if (!seen_coord[$0]++) { print ""; print }
+                next
+              }
+              /^   Vulnerability Title:/ { in_vuln=1; block=$0 "\n"; cve_id=""; next }
+              in_vuln && /^   CVE:/ { match($0,/CVE-[0-9-]+/); if (RSTART) cve_id=substr($0,RSTART,RLENGTH); block=block $0 "\n"; next }
+              in_vuln && /^   Reference:/ { block=block $0 "\n"; if (cve_id && !seen_cve[cve_id]++) printf "%s",block; in_vuln=0; next }
+              in_vuln { block=block $0 "\n" }
+            ' /tmp/ossindex-scan.log >> $GITHUB_STEP_SUMMARY \
+              || echo "(no scan output captured — check the full log)" >> $GITHUB_STEP_SUMMARY
+            echo '```' >> $GITHUB_STEP_SUMMARY
+          fi
diff --git a/.gitignore b/.gitignore
index f328f1a..f6812c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,7 @@ gradle-bootstrap/gradle
 /.claude
 *.hprof
 .DS_Store
+# reproducible-build test output (etc/bin/test-reproducible-build.sh)
+/first.txt
+/second.txt
+etc/bin/results
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000..ea0aded
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,126 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Apache Grails IntelliJ Plugin — Release Process
+
+This document describes how a version of the Apache Grails IntelliJ plugin is released.
+
+**ASF framing.** The official release voted on by the PMC is the **signed source
+distribution** published to `dist.apache.org`. The plugin ZIP on the JetBrains
+Marketplace is a *convenience binary* built from that same tagged source. Both must be
+reproducible from the tagged commit (see [Verifying a Reproducible Build](#verifying-a-reproducible-build)).
+
+The pipeline is encoded in [`.github/workflows/release.yml`](.github/workflows/release.yml)
+and is triggered when a **GitHub Release is published** for a tag (`vX.Y.Z`). It runs in
+four stages.
+
+## Prerequisites
+
+- The release tag (`vX.Y.Z`) points at the exact commit to be released.
+- Marketplace signing / publishing secrets are configured: `CERTIFICATE_CHAIN`,
+  `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD`, `PUBLISH_TOKEN`.
+- ASF signing + dist secrets are configured: `GRAILS_GPG_KEY`, `GPG_KEY_ID`,
+  `SVC_DIST_GRAILS_USERNAME`, `SVC_DIST_GRAILS_PASSWORD`.
+- The release environment reviewers are set in [`.asf.yaml`](.asf.yaml) (GitHub does not
+  allow private groups as approvers, so approvers are listed individually).
+- The JDK is pinned by [`.sdkmanrc`](.sdkmanrc); CI reads the version from it so local and
+  CI builds match. See [INSTALL](INSTALL) for local build setup.
+
+## 1. Build & stage the plugin ZIP (`publish` job)
+
+1. Establish `VERSION` from the tag (`${TAG#v}`).
+2. `./gradlew check buildPlugin -Pversion=${VERSION}` — full build, tests, and Plugin ZIP.
+3. `./gradlew signPlugin` — JetBrains Marketplace signature.
+4. Rename to `apache-grails-intellij-plugin-${VERSION}-bin.zip`.
+5. GPG-detached-sign (`.asc`) and checksum (`.sha512`).
+6. Upload the ZIP, `.asc`, and `.sha512` to the GitHub Release.
+
+## 2. Create the source distribution (`source` job)
+
+1. Check out the tag, then remove files that must not ship in a source release
+   (`.git`, `.github`, `.asf.yaml`, `gradlew`/`gradlew.bat`, `gradle/wrapper`).
+2. Zip to `apache-grails-intellij-plugin-${VERSION}-src.zip`.
+3. GPG-detached-sign (`.asc`) and checksum (`.sha512`).
+4. Upload all three to the GitHub Release.
+
+## 3. Stage to dist.apache.org & start the vote (`upload` job)
+
+1. Ensure `dist/dev/grails/INTELLIJ` exists in the ASF dist SVN.
+2. Upload the source + binary distributions (with signatures and checksums) under
+   `dist/dev/grails/INTELLIJ/${VERSION}/`.
+3. **MANUAL:** start a `[VOTE]` thread for the release on `dev@grails.apache.org`.
+
+Reviewers should verify signatures, checksums, the RAT license audit, and a
+**reproducible build** before voting (next section).
+
+## 4. Publish the release (`release` job — post-vote, environment-gated)
+
+After the vote succeeds (the `release` GitHub environment requires the reviewers in
+`.asf.yaml`):
+
+1. **MANUAL:** confirm the vote result on `dev@grails.apache.org`.
+2. **MANUAL:** promote the staged artifacts from `dist/dev` to `dist/release` via
+   [`.github/scripts/releaseDistributions.sh`](.github/scripts/releaseDistributions.sh).
+3. `./gradlew publishPlugin -Pversion=${VERSION}` — publish the convenience binary to the
+   JetBrains Marketplace.
+4. **MANUAL:** update the [ASF release reporter](https://reporter.apache.org/) and send
+   the `[RESULT][VOTE]` + `[ANNOUNCE]` emails.
+
+## Verifying a Reproducible Build
+
+A vote is a good time to confirm the artifacts can be rebuilt bit-for-bit from the tag.
+Reproducibility is enforced by the `grails-intellij.reproducible` convention plugin
+(`build-logic/`), which pins archive timestamps, file order, and entry permissions across
+every module and the plugin ZIP.
+
+### On the same machine
+
+From a clean checkout of the tagged commit:
+
+```bash
+etc/bin/test-reproducible-build.sh
+```
+
+The script pins `SOURCE_DATE_EPOCH` to the last commit time, builds `buildPlugin` twice
+from a clean tree (`--rerun-tasks --no-build-cache`), and compares the SHA-256 of the
+plugin ZIP and every module jar. It prints `((none) ✅ build is reproducible)` on success
+and lists any differing artifacts on failure.
+
+> ⚠️ The script runs `git clean -xdf` between builds, which deletes **all** untracked
+> files. Run it in a throwaway checkout or after stashing your work.
+
+### In a container (build-environment parity)
+
+To rule out host-OS differences, reproduce inside a container that resembles the CI Linux
+environment ([`etc/bin/Dockerfile`](etc/bin/Dockerfile), pinned to the same Liberica JDK
+as `.sdkmanrc`):
+
+```bash
+docker build -t grails-ij:testing -f etc/bin/Dockerfile .
+docker run -it --rm -v "$(pwd):/home/builder/project" grails-ij:testing bash
+# inside the container:
+etc/bin/test-reproducible-build.sh
+```
+
+### Common reproducibility gotchas
+
+- **Archive timestamps / order** — handled by the reproducible convention plugin
+  (`preserveFileTimestamps=false`, `reproducibleFileOrder=true`, fixed permissions).
+- **`SOURCE_DATE_EPOCH`** — most tools honor it for embedded dates; the script sets it
+  from the last git commit so successive builds agree.
+- **JDK version** — a different JDK can change bytecode. Always build with the JDK pinned
+  in `.sdkmanrc`; the Docker image installs exactly that version.
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
new file mode 100644
index 0000000..2ef3348
--- /dev/null
+++ b/build-logic/build.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+plugins {
+    `kotlin-dsl`
+}
+
+// The convention plugins here are consumed by the root build and every module. The
+// vulnerability-scan plugin applies the Sonatype OSS Index plugin and references its
+// extension type, so that plugin must be on the build-logic compile classpath.
+dependencies {
+    // Dependabot (gradle ecosystem, /build-logic) keeps this version current.
+    implementation("org.sonatype.gradle.plugins:scan-gradle-plugin:3.1.6")
+}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 0000000..8334e44
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+rootProject.name = "build-logic"
diff --git a/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt b/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt
new file mode 100644
index 0000000..4f8619f
--- /dev/null
+++ b/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.intellij.buildlogic
+
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
+/**
+ * Shared build service used solely as a concurrency gate for `ossIndexAudit` tasks.
+ *
+ * The Sonatype OSS Index Gradle plugin backs every audit task with a single shared
+ * on-disk response cache guarded by a file lock. When Gradle runs audit tasks in parallel,
+ * they contend for that lock and fail with `OverlappingFileLockException`, surfaced by the
+ * plugin as the opaque "Could not audit the project: null".
+ *
+ * Registered with `maxParallelUsages = 1`, this service forces Gradle to run at most one
+ * audit task at a time while leaving parallel execution enabled for the rest of the build.
+ */
+abstract class OssIndexAuditThrottle : BuildService<BuildServiceParameters.None>
diff --git a/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts b/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts
new file mode 100644
index 0000000..9011919
--- /dev/null
+++ b/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import org.gradle.testing.jacoco.tasks.JacocoReport
+
+// Per-project JaCoCo conventions. Ported from grails-core's GrailsJacocoPlugin (per-module
+// portion only). The cross-project aggregate is produced by the root build via Gradle's
+// built-in `jacoco-report-aggregation` plugin, which is safe under the configuration cache
+// (this repo has org.gradle.configuration-cache=true, unlike grails-core).
+
+plugins {
+    jacoco
+}
+
+jacoco {
+    // keep in sync with `jacocoVersion` in gradle.properties
+    toolVersion = (project.findProperty("jacocoVersion") ?: "0.8.14").toString()
+}
+
+tasks.withType<Test>().configureEach {
+    finalizedBy(tasks.withType<JacocoReport>())
+}
+
+tasks.withType<JacocoReport>().configureEach {
+    dependsOn(tasks.withType<Test>())
+    reports {
+        xml.required.set(true)
+        html.required.set(true)
+        csv.required.set(false)
+    }
+}
diff --git a/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts b/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts
new file mode 100644
index 0000000..2644224
--- /dev/null
+++ b/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Reproducible-build conventions. Ported from grails-core's CompilePlugin.configureReproducible.
+// Applied to the root project and every module so that the plugin ZIP
+// (build/distributions/*.zip) and all composed/module jars are byte-for-byte stable.
+//
+// preserveFileTimestamps = false pins every archive entry to a constant timestamp, so
+// SOURCE_DATE_EPOCH is not required for archive reproducibility; reproducibleFileOrder
+// removes the only other common source of non-determinism (filesystem iteration order).
+// Fixed permissions guarantee the same entry metadata regardless of the host umask.
+
+tasks.withType<AbstractArchiveTask>().configureEach {
+    isPreserveFileTimestamps = false
+    isReproducibleFileOrder = true
+    filePermissions { unix("0644") }
+    dirPermissions { unix("0755") }
+}
diff --git a/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts b/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts
new file mode 100644
index 0000000..d4d8205
--- /dev/null
+++ b/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import org.apache.grails.intellij.buildlogic.OssIndexAuditThrottle
+import org.sonatype.gradle.plugins.scan.ossindex.OssIndexPluginExtension
+
+// Sonatype OSS Index / Guide vulnerability scanning. Ported from grails-core's
+// VulnerabilityScanPlugin. Credentials come from the environment:
+//   SONATYPE_GUIDE_USERNAME - Guide account email / user token username
+//   SONATYPE_GUIDE_TOKEN    - Guide Personal Access Token (PAT)
+// The `ossIndexAudit` task fails fast when SONATYPE_GUIDE_TOKEN is absent.
+
+plugins {
+    id("org.sonatype.gradle.plugins.scan")
+}
+
+// The OSS Index plugin guards its shared on-disk response cache with a file lock, so audit
+// tasks cannot run concurrently without hitting OverlappingFileLockException. Gate every
+// audit task on a single-usage build service to serialize them while leaving parallel
+// execution enabled for the rest of the build.
+val throttle = gradle.sharedServices.registerIfAbsent("ossIndexAuditThrottle", OssIndexAuditThrottle::class.java) {
+    maxParallelUsages.set(1)
+}
+
+configure<OssIndexPluginExtension> {
+    username = System.getenv("SONATYPE_GUIDE_USERNAME") ?: ""
+    password = System.getenv("SONATYPE_GUIDE_TOKEN") ?: ""
+    // Exemptions are limited to vulnerabilities with NO available upstream fix. Everything
+    // with a published fix must be bumped instead. Each entry must name the CVE and the
+    // removal condition, and be revisited whenever a fix ships. Empty until a real,
+    // unfixable CVE is encountered.
+    excludeCoordinates = emptySet()
+}
+
+tasks.named("ossIndexAudit") {
+    group = "verification"
+    description = "Scans ${project.name} runtime dependencies for known vulnerabilities via Sonatype Guide"
+    usesService(throttle)
+    doFirst {
+        if (System.getenv("SONATYPE_GUIDE_TOKEN").isNullOrEmpty()) {
+            throw GradleException(
+                "SONATYPE_GUIDE_TOKEN environment variable is not set. " +
+                    "A Sonatype Guide Personal Access Token is required to run the vulnerability scan."
+            )
+        }
+        println("##[ossIndexAudit:begin] ${project.path}")
+    }
+    doLast {
+        println("##[ossIndexAudit:end] ${project.path}")
+    }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index b65649b..8f0cd6a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import org.gradle.testing.jacoco.tasks.JacocoReport
 import org.jetbrains.intellij.platform.gradle.TestFrameworkType
 import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask
 
@@ -23,6 +24,11 @@ plugins {
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform") version "2.18.1"
     id("org.nosphere.apache.rat") version "0.8.1"
+    // build-hygiene conventions (build-logic included build)
+    id("jacoco-report-aggregation")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
 }
 
 group = "org.apache.grails.intellij"
@@ -89,6 +95,30 @@ dependencies {
     // JPS/build
     runtimeOnly(project(":grails-compiler-patch"))
     runtimeOnly(project(":jps-plugin"))
+
+    // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
+    jacocoAggregation(project(":copyright"))
+    jacocoAggregation(project(":coverage"))
+    jacocoAggregation(project(":gradle-tooling"))
+    jacocoAggregation(project(":grails-compiler-patch"))
+    jacocoAggregation(project(":grails-rt"))
+    jacocoAggregation(project(":hibernate"))
+    jacocoAggregation(project(":i18n"))
+    jacocoAggregation(project(":jps-plugin"))
+    jacocoAggregation(project(":langInjection"))
+    jacocoAggregation(project(":maven"))
+    jacocoAggregation(project(":testFramework"))
+}
+
+// jacoco-report-aggregation auto-registers testCodeCoverageReport from the default `test`
+// suite, producing build/reports/jacoco/testCodeCoverageReport/... ; ensure the XML variant
+// is emitted for Codecov upload.
+tasks.named<JacocoReport>("testCodeCoverageReport") {
+    reports {
+        xml.required.set(true)
+        html.required.set(true)
+        csv.required.set(false)
+    }
 }
 
 kotlin {
@@ -212,6 +242,11 @@ tasks.rat {
             "out/**",
             // IntelliJ Platform Gradle Plugin local cache (generated Ivy descriptors, git-ignored)
             ".intellijPlatform/**",
+            // build-logic included build: generated output and local caches (sources are headered)
+            "build-logic/build/**",
+            "build-logic/.gradle/**",
+            // reproducible-build test output (git-ignored, regenerated by etc/bin/test-reproducible-build.sh)
+            "etc/bin/results/**",
             // generated code (JFlex lexers; headers are injected by the generator config)
             "gen/**",
             // test fixtures: content *is* the test input (mock Grails projects/JARs);
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..44ee4d7
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,37 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+codecov:
+  require_ci_to_pass: yes
+
+comment:
+  layout: "reach, diff, flags, files"
+  behavior: default
+  require_changes: false
+  require_base: no
+  require_head: yes
+
+coverage:
+  precision: 4
+  round: nearest
+  status:
+    patch:
+      default:
+        target: auto
+        informational: true
+    project:
+      default:
+        target: auto
+        informational: true
diff --git a/copyright/build.gradle.kts b/copyright/build.gradle.kts
index 1c117a6..f6f9874 100644
--- a/copyright/build.gradle.kts
+++ b/copyright/build.gradle.kts
@@ -18,6 +18,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/coverage/build.gradle.kts b/coverage/build.gradle.kts
index 572a710..d4eda8d 100644
--- a/coverage/build.gradle.kts
+++ b/coverage/build.gradle.kts
@@ -18,6 +18,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/etc/bin/Dockerfile b/etc/bin/Dockerfile
new file mode 100644
index 0000000..8b8cd13
--- /dev/null
+++ b/etc/bin/Dockerfile
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A container that resembles the GitHub Actions Linux build environment, used to test
+# reproducible builds independently of the host OS. Mount the project directory into the
+# container so builds run in the container but artifacts can be compared on the host.
+#
+# Build & run from the project root:
+#   docker build -t grails-ij:testing -f etc/bin/Dockerfile .
+#   docker run -it --rm -v "$(pwd):/home/builder/project" grails-ij:testing bash
+# Then, inside the container:
+#   cd project && etc/bin/test-reproducible-build.sh
+#
+# Keep the JDK version synced with .sdkmanrc (java=<version>-librca).
+FROM bellsoft/liberica-openjdk-debian:25.0.3
+
+USER root
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends \
+        ca-certificates curl unzip coreutils git gpg sudo psmisc locales \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
+    && dpkg-reconfigure --frontend=noninteractive locales \
+    && update-locale LANG=en_US.UTF-8
+
+RUN useradd --create-home --home-dir /home/builder builder \
+    && echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+USER builder
+WORKDIR /home/builder/project
+
+ENV CI=true
+ENV LANG=en_US.UTF-8
+ENV LC_ALL=en_US.UTF-8
+
+CMD ["/bin/bash"]
diff --git a/etc/bin/test-reproducible-build.sh b/etc/bin/test-reproducible-build.sh
new file mode 100755
index 0000000..628889a
--- /dev/null
+++ b/etc/bin/test-reproducible-build.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+# Verifies that the Grails IntelliJ plugin builds reproducibly: it builds the plugin
+# ZIP (and every module jar) twice from a clean tree with SOURCE_DATE_EPOCH pinned, then
+# compares the SHA-256 of each produced artifact. Any artifact whose hash differs between
+# the two builds is printed and the script exits non-zero.
+#
+# WARNING: this runs `git clean -xdf`, which deletes ALL untracked files (build output,
+# .idea, IDE caches, etc.). Run it only in a throwaway checkout or after stashing work.
+#
+# Usage: run from anywhere; the script locates the project root relative to itself.
+#   etc/bin/test-reproducible-build.sh
+set -euo pipefail
+
+export SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)"
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/../.." &> /dev/null && pwd)"
+cd "${PROJECT_ROOT}"
+
+# Portable SHA-256: prefer GNU coreutils, fall back to macOS `shasum`.
+if command -v sha256sum &> /dev/null; then
+  sha() { sha256sum "$@"; }
+else
+  sha() { shasum -a 256 "$@"; }
+fi
+
+# Hash the release artifacts (plugin ZIP) and every module jar, sorted by path so the
+# output is order-stable across runs.
+hash_artifacts() {
+  {
+    find build/distributions -type f -name '*.zip' 2> /dev/null || true
+    find . -type f -path '*/build/libs/*.jar' 2> /dev/null || true
+  } | sort | while IFS= read -r f; do
+    [ -f "$f" ] && sha "$f"
+  done
+}
+
+build_once() {
+  git clean -xdf
+  ./gradlew buildPlugin --rerun-tasks --no-build-cache --no-daemon
+}
+
+echo "==> First build (SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH})"
+build_once
+FIRST="$(hash_artifacts)"
+
+echo "==> Second build"
+build_once
+SECOND="$(hash_artifacts)"
+
+printf '%s\n' "$FIRST" > first.txt
+printf '%s\n' "$SECOND" > second.txt
+
+echo ""
+echo "Differing artifacts:"
+# comm on the second (hash) field: lines present in only one build indicate a hash mismatch.
+DIFF="$(comm -3 <(printf '%s\n' "$FIRST" | sort) <(printf '%s\n' "$SECOND" | sort) \
+  | awk '{print $2}' | sed 's|^[[:space:]]*||' | grep -v '^$' | sort -u || true)"
+
+if [ -z "$DIFF" ]; then
+  echo "  (none) ✅ build is reproducible"
+  exit 0
+else
+  printf '  %s\n' "$DIFF"
+  echo ""
+  echo "❌ build is NOT reproducible — see first.txt / second.txt for the full hash lists"
+  exit 1
+fi
diff --git a/gradle-tooling/build.gradle.kts b/gradle-tooling/build.gradle.kts
index 05786b0..f51f633 100644
--- a/gradle-tooling/build.gradle.kts
+++ b/gradle-tooling/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/gradle.properties b/gradle.properties
index 4b6cf1a..473db41 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,6 +18,9 @@ version=262.0.0
 
 kotlin.stdlib.default.dependency=false
 
+# Build-hygiene tool versions
+jacocoVersion=0.8.14
+
 # https://plugins.jetbrains.com/docs/intellij/testing-faq.html#how-to-test-a-jvm-language
 # set path to local intellij-community project
 test.idea.home.path=/Users/yann/idea-ultimate/ultimate/community
diff --git a/grails-compiler-patch/build.gradle.kts b/grails-compiler-patch/build.gradle.kts
index dc323c3..896fa65 100644
--- a/grails-compiler-patch/build.gradle.kts
+++ b/grails-compiler-patch/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/grails-rt/build.gradle.kts b/grails-rt/build.gradle.kts
index 925a933..da5ee88 100644
--- a/grails-rt/build.gradle.kts
+++ b/grails-rt/build.gradle.kts
@@ -15,6 +15,9 @@
  */
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
 }
 
 group = "org.apache.grails.intellij.rt"
diff --git a/hibernate/build.gradle.kts b/hibernate/build.gradle.kts
index 6faff4a..2e88bf9 100644
--- a/hibernate/build.gradle.kts
+++ b/hibernate/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts
index d134dac..99961d9 100644
--- a/i18n/build.gradle.kts
+++ b/i18n/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/jps-plugin/build.gradle.kts b/jps-plugin/build.gradle.kts
index 0ae7944..cee000e 100644
--- a/jps-plugin/build.gradle.kts
+++ b/jps-plugin/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/langInjection/build.gradle.kts b/langInjection/build.gradle.kts
index e74c2d1..c84fbac 100644
--- a/langInjection/build.gradle.kts
+++ b/langInjection/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/maven/build.gradle.kts b/maven/build.gradle.kts
index 4b47727..2e1300a 100644
--- a/maven/build.gradle.kts
+++ b/maven/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f5a8f20..53b8d2d 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -13,6 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+pluginManagement {
+    includeBuild("build-logic")
+}
+
 rootProject.name = "Grails IntelliJ Plugin"
 
 include("copyright")
diff --git a/testFramework/build.gradle.kts b/testFramework/build.gradle.kts
index f965f64..0822f0b 100644
--- a/testFramework/build.gradle.kts
+++ b/testFramework/build.gradle.kts
@@ -20,6 +20,9 @@ fun properties(key: String) = project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }


(grails-intellij-plugin) 13/35: Split compiler plugins into their own tier, distinct from libraries

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit e71acf34cc290499b800457200cf59dbce6002a4
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:24:40 2026 -0400

    Split compiler plugins into their own tier, distinct from libraries
    
    The tier directories exist to make it obvious what each subproject is, and
    lumping jps-plugin and grails-compiler-patch in with the libraries lost that:
    they are not dependencies the plugin links against, they are the jars named by
    <compileServer.plugin classpath="..."/> and they run inside the end user's JPS
    build process.
    
    Adds an org.apache.grails.intellij.build.compiler-plugin convention plugin with
    group org.apache.grails.intellij.compiler, and moves jps-plugin and
    grails-compiler-patch onto it. Packaged names become
    org.apache.grails.intellij.compiler.{jps-plugin,grails-compiler-patch}.jar with
    the descriptor updated to match; verified that every classpath entry resolves to
    a real jar in lib/.
    
    The distinction is not cosmetic: JPS resolves that attribute by file name and
    silently skips anything missing, so these two jar names are part of the plugin's
    contract and must stay pinned and in sync with the descriptor. Nothing depends
    on the name of a plain library. Giving them their own directory makes that
    obligation visible instead of leaving it as a comment.
    
    That leaves the lib tier as genuine dependencies: gradle-tooling (Gradle tooling
    API model builders), grails-rt (runtime jar pushed into user JVMs, located by
    class rather than by name) and testFramework (test-only support).
---
 ...he.grails.intellij.build.compiler-plugin.gradle | 43 ++++++++++++++++++++++
 grails-compiler-patch/build.gradle                 |  2 +-
 jps-plugin/build.gradle                            |  2 +-
 resources/META-INF/plugin.xml                      |  2 +-
 4 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler-plugin.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler-plugin.gradle
new file mode 100644
index 0000000..7f81405
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler-plugin.gradle
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// A compiler plugin: code that runs inside the end user's JPS build process rather than in the
+// IDE. These are the jars named by <compileServer.plugin classpath="..."/> in plugin.xml.
+//
+// This is a distinct tier from lib/ on purpose. JPS resolves the classpath attribute by file
+// name and silently skips entries it cannot find, so these jar names are part of the plugin's
+// contract and must stay pinned and in sync with the descriptor -- unlike a plain library, whose
+// name nothing depends on. Keeping them in their own directory makes that obligation visible.
+
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-platform'
+    id 'org.jetbrains.intellij.platform.module'
+}
+
+def shortName = projectDir.name
+
+group = 'org.apache.grails.intellij.compiler'
+base.archivesName = shortName
+
+// Pinned, not derived. IPGP's default is "${rootProject.name}.${project.path with ':' -> '.'}",
+// which tracks both the root project name and the project's depth in the tree; that is exactly
+// how the descriptor's classpath attribute went stale before.
+tasks.named('composedJar') { task ->
+    task.archiveBaseName = "org.apache.grails.intellij.compiler.$shortName"
+}
diff --git a/grails-compiler-patch/build.gradle b/grails-compiler-patch/build.gradle
index c045dc1..dad67cd 100644
--- a/grails-compiler-patch/build.gradle
+++ b/grails-compiler-patch/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.intellij-lib'
+    id 'org.apache.grails.intellij.build.compiler-plugin'
     id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
diff --git a/jps-plugin/build.gradle b/jps-plugin/build.gradle
index 34f3260..261d571 100644
--- a/jps-plugin/build.gradle
+++ b/jps-plugin/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.intellij-lib'
+    id 'org.apache.grails.intellij.build.compiler-plugin'
     id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index 3f062bf..ca6f492 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -323,7 +323,7 @@
          builders with nothing more than a line in idea.log. These names are pinned by the
          org.apache.grails.intellij.build.intellij-lib convention plugin rather than derived from
          rootProject.name, which is what let this attribute go stale previously. -->
-    <compileServer.plugin classpath="org.apache.grails.intellij.lib.jps-plugin.jar;org.apache.grails.intellij.lib.grails-compiler-patch.jar"/>
+    <compileServer.plugin classpath="org.apache.grails.intellij.compiler.jps-plugin.jar;org.apache.grails.intellij.compiler.grails-compiler-patch.jar"/>
 
     <multiHostInjector implementation="org.jetbrains.plugins.grails.lang.gsp.GspCssInjector"/>
     <multiHostInjector implementation="org.jetbrains.plugins.grails.references.constraints.GormRegexInjector"/>


(grails-intellij-plugin) 35/35: Merge main into gradle-restructure

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 696da9fe00d2e834f081f71d04944f0305b7e0ed
Merge: 2f37ce3 623a757
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 16:32:21 2026 -0400

    Merge main into gradle-restructure
    
    Brings in the GORM 4+ HQL injection and criteria-resolution fixes from
    main (PR #6 plus the criteria/property-navigation commit), relocated onto
    the restructured layout:
    
      hibernate/resources/intellij.groovy.grails.hibernate.xml
        -> pluginModules/hibernate/src/main/resources/org.apache.grails.intellij.module.hibernate.xml
      hibernate/src/.../Grails3HibernateFacetActivity.java
        -> pluginModules/hibernate/src/main/java/...
      langInjection/resources/META-INF/languageInjections.xml
        -> pluginModules/langInjection/src/main/resources/META-INF/...
      resources/META-INF/plugin.xml
        -> plugin/src/main/resources/META-INF/plugin.xml
      src/... -> plugin/src/main/java/...
      test/... -> plugin/src/test/java/...
    
    The restructuring is kept in full; the merge delta against the
    pre-merge branch tip is exactly main's 8-file, +431/-14 change.

 .../grails/references/domain/GormHqlInjector.java  | 122 ++++++++++++++++++++
 .../domain/criteria/CriteriaBuilderUtil.java       |  52 ++++++++-
 .../CriteriaPropertyReferenceProvider.java         |  13 ++-
 plugin/src/main/resources/META-INF/plugin.xml      |   1 +
 .../groovy/grails/domain/GormHqlInjectionTest.java | 128 +++++++++++++++++++++
 .../hibernate/Grails3HibernateFacetActivity.java   | 117 +++++++++++++++++++
 ...org.apache.grails.intellij.module.hibernate.xml |   1 +
 .../main/resources/META-INF/languageInjections.xml |  11 +-
 8 files changed, 431 insertions(+), 14 deletions(-)

diff --cc plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormHqlInjector.java
index 0000000,505549d..505549d
mode 000000,100644..100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormHqlInjector.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormHqlInjector.java
diff --cc plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormHqlInjectionTest.java
index 0000000,c7c3f16..c7c3f16
mode 000000,100644..100644
--- a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormHqlInjectionTest.java
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormHqlInjectionTest.java
diff --cc pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/Grails3HibernateFacetActivity.java
index 0000000,6ea9ff8..6ea9ff8
mode 000000,100644..100644
--- a/pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/Grails3HibernateFacetActivity.java
+++ b/pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/Grails3HibernateFacetActivity.java


(grails-intellij-plugin) 09/35: Convert the projectView package from Kotlin to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit ba98364204d81656f1e328b88ada5f87038572f0
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 10:18:17 2026 -0400

    Convert the projectView package from Kotlin to Java
    
    Second batch: 28 Kotlin files become 30 Java files, leaving no Kotlin under
    projectView. Verified against the pre-conversion baseline -- 987 tests, 0
    failures, no test classes lost -- and RAT still passes.
    
    Two constructs had no direct Java equivalent:
    
    * typealias Classes / TreeNodes in nodes/util.kt are expanded to
      Collection<PsiClass> and Collection<AbstractTreeNode<?>>. The ripple stayed
      inside nodes/ -- the earlier grep hits in EnhancesTraitsCollector.kt and
      GormTraitContributor.kt were substring matches on unrelated identifiers.
    * The reified findValueOfType/findNotNullValueOfType extensions become
      TreeNodeUtil.findValueOfType(node, Class<T>), so the six call sites now pass
      the class explicitly.
    
    Top-level declarations again became named holders: nodes/util.kt -> TreeNodeUtil,
    nodes/impl.kt -> GrailsNodes, projectView/impl/impl.kt -> GrailsViewItems,
    showHide.kt -> GrailsProjectViewPanes, and ep.kt's EP_NAME moved onto
    GrailsViewNodeProvider as a constant, matching how platform extension points are
    declared. Kotlin's Triple for special folders became a GrailsViewItems.SpecialFolder
    record, and `by lazyUnsafe` node fields became volatile lazily-computed fields.
    
    Visibility needed care: GrailsProjectViewPane, GrailsTreeStructureProvider,
    GrailsAppNodeProvider, Grails3NodeProvider and OldGrailsNodeProvider are
    instantiated reflectively from plugin.xml. Kotlin `internal` is public in
    bytecode, so these are explicitly public in Java -- package-private would have
    compiled and then failed at runtime. GrailsNodeComparator is public for the same
    cross-package reason.
    
    Behaviour deliberately preserved rather than fixed: getPackagesRegular's trailing
    `filter { !it.isEmpty() }` dropped empty lists, which split never produces, so a
    class in the default package still contributes an empty-named package node. That
    quirk predates this conversion and is documented in place.
---
 .../grails/projectView/GrailsPluginsNode.java      |  69 ++++++++
 .../grails/projectView/GrailsPluginsNode.kt        |  48 ------
 .../grails/projectView/GrailsProjectViewPane.java  | 132 +++++++++++++++
 .../grails/projectView/GrailsProjectViewPane.kt    |  64 -------
 .../grails/projectView/GrailsProjectViewPanes.java |  54 ++++++
 .../projectView/GrailsTreeStructureProvider.java   |  70 ++++++++
 .../projectView/GrailsTreeStructureProvider.kt     |  58 -------
 .../projectView/api/GrailsSingleNodeProvider.java  |  38 +++++
 .../projectView/api/GrailsSingleNodeProvider.kt    |  30 ----
 .../projectView/api/GrailsViewNodeProvider.java    |  35 ++++
 .../projectView/api/GrailsViewNodeProvider.kt      |  26 ---
 .../projectView/impl/Grails3NodeProvider.java      |  77 +++++++++
 .../grails/projectView/impl/Grails3NodeProvider.kt |  59 -------
 .../projectView/impl/GrailsAppNodeProvider.java    |  67 ++++++++
 .../projectView/impl/GrailsAppNodeProvider.kt      |  51 ------
 .../projectView/impl/GrailsNodeComparator.java     |  75 +++++++++
 .../projectView/impl/GrailsNodeComparator.kt       |  60 -------
 .../grails/projectView/impl/GrailsViewItems.java   |  81 +++++++++
 .../projectView/impl/OldGrailsNodeProvider.java    | 135 +++++++++++++++
 .../projectView/impl/OldGrailsNodeProvider.kt      | 101 -----------
 .../plugins/grails/projectView/impl/impl.kt        |  52 ------
 .../grails/projectView/nodes/CompactedFqn.java     | 119 +++++++++++++
 .../grails/projectView/nodes/CompactedFqn.kt       |  65 -------
 .../grails/projectView/nodes/GrailsActionNode.java |  60 +++++++
 .../grails/projectView/nodes/GrailsActionNode.kt   |  44 -----
 .../projectView/nodes/GrailsApplicationNode.java   |  75 +++++++++
 .../projectView/nodes/GrailsApplicationNode.kt     |  59 -------
 .../nodes/GrailsArtefactHandlerNode.java           |  58 +++++++
 .../projectView/nodes/GrailsArtefactHandlerNode.kt |  41 -----
 .../nodes/GrailsArtefactHandlerNodeBase.java       |  50 ++++++
 .../nodes/GrailsArtefactHandlerNodeBase.kt         |  37 ----
 .../nodes/GrailsArtefactPackageNode.java           |  72 ++++++++
 .../projectView/nodes/GrailsArtefactPackageNode.kt |  48 ------
 .../projectView/nodes/GrailsControllerNode.java    |  62 +++++++
 .../projectView/nodes/GrailsControllerNode.kt      |  39 -----
 .../projectView/nodes/GrailsFlatPackageNode.java   |  77 +++++++++
 .../projectView/nodes/GrailsFlatPackageNode.kt     |  53 ------
 .../grails/projectView/nodes/GrailsNodes.java      | 186 +++++++++++++++++++++
 .../{api/ep.kt => nodes/GrailsPackageValue.java}   |   8 +-
 .../grails/projectView/nodes/GrailsPluginNode.java |  78 +++++++++
 .../grails/projectView/nodes/GrailsPluginNode.kt   |  56 -------
 .../projectView/nodes/GrailsPsiDirectoryNode.java  |  85 ++++++++++
 .../projectView/nodes/GrailsPsiDirectoryNode.kt    |  42 -----
 .../nodes/GrailsRegularPackageNode.java            | 104 ++++++++++++
 .../projectView/nodes/GrailsRegularPackageNode.kt  |  80 ---------
 .../grails/projectView/nodes/GrailsRootNode.java   |  54 ++++++
 .../grails/projectView/nodes/GrailsRootNode.kt     |  35 ----
 .../projectView/nodes/OldGrailsPluginsNode.java    |  74 ++++++++
 .../projectView/nodes/OldGrailsPluginsNode.kt      |  52 ------
 .../nodes/OtherGrailsAppSourcesNode.java           | 114 +++++++++++++
 .../projectView/nodes/OtherGrailsAppSourcesNode.kt |  87 ----------
 .../grails/projectView/nodes/TreeNodeUtil.java     |  60 +++++++
 .../plugins/grails/projectView/nodes/impl.kt       | 112 -------------
 .../plugins/grails/projectView/nodes/util.kt       |  46 -----
 .../plugins/grails/projectView/showHide.kt         |  41 -----
 .../structure/sync/GrailsStartupActivity.java      |   4 +-
 56 files changed, 2168 insertions(+), 1491 deletions(-)

diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java b/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
new file mode 100644
index 0000000..e6aca5c
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.plugins.GrailsPluginDescriptor;
+import org.jetbrains.plugins.grails.plugins.ImplKt;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsPluginNode;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class GrailsPluginsNode extends ProjectViewNode<String> {
+
+  public GrailsPluginsNode(@NotNull Project project, @NotNull ViewSettings settings) {
+    super(project, "plugins", settings);
+  }
+
+  public @NotNull GrailsApplication getGrailsApplication() {
+    return (GrailsApplication)getParentValue();
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildren() {
+    GrailsApplication application = getGrailsApplication();
+    Project project = application.getProject();
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (GrailsPluginDescriptor descriptor : ImplKt.computePlugins(application)) {
+      result.add(new GrailsPluginNode(project, descriptor, getSettings()));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    for (AbstractTreeNode<?> child : getChildren()) {
+      if (child instanceof ProjectViewNode<?> node && node.contains(file)) return true;
+    }
+    return false;
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData presentation) {
+    presentation.setIcon(GroovyMvcIcons.Groovy_mvc_plugin);
+    presentation.setPresentableText("Plugins");
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.kt b/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.kt
deleted file mode 100644
index 7161957..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ProjectViewNode
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.plugins.computePlugins
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsPluginNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-class GrailsPluginsNode(project: Project, settings: ViewSettings) : ProjectViewNode<String>(project, "plugins", settings) {
-
-  val grailsApplication: GrailsApplication get() = parentValue as GrailsApplication
-
-  override fun getChildren(): Collection<AbstractTreeNode<*>> {
-    val application = grailsApplication
-    val project = application.project
-    return application.computePlugins().map { GrailsPluginNode(project, it, settings) }
-  }
-
-  override fun contains(file: VirtualFile): Boolean = children.any { it is ProjectViewNode && it.contains(file) }
-
-  override fun update(presentation: PresentationData) {
-    presentation.apply {
-      setIcon(GroovyMvcIcons.Groovy_mvc_plugin)
-      presentableText = "Plugins"
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java b/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java
new file mode 100644
index 0000000..d0e3c52
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView;
+
+import com.intellij.ide.impl.ProjectViewSelectInTarget;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.AbstractProjectViewPaneWithAsyncSupport;
+import com.intellij.ide.projectView.impl.ProjectTreeStructure;
+import com.intellij.ide.projectView.impl.ProjectViewTree;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.config.GrailsConstants;
+import org.jetbrains.plugins.grails.projectView.impl.GrailsNodeComparator;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsRootNode;
+
+import javax.swing.Icon;
+import javax.swing.tree.DefaultTreeModel;
+import java.util.Comparator;
+
+public final class GrailsProjectViewPane extends AbstractProjectViewPaneWithAsyncSupport {
+
+  private final Project project;
+
+  public GrailsProjectViewPane(@NotNull Project project) {
+    super(project);
+    this.project = project;
+  }
+
+  public @NotNull Project getProject() {
+    return project;
+  }
+
+  @Override
+  public @NotNull String getId() {
+    return GrailsConstants.GRAILS;
+  }
+
+  @Override
+  public @NotNull Icon getIcon() {
+    return GroovyMvcIcons.Grails;
+  }
+
+  @Override
+  public @NotNull String getTitle() {
+    return getId();
+  }
+
+  @Override
+  public int getWeight() {
+    return 13;
+  }
+
+  @Override
+  public @NotNull ProjectViewSelectInTarget createSelectInTarget() {
+    return new GrailsSelectInTarget(project, getId(), getTitle());
+  }
+
+  @Override
+  protected @NotNull ProjectTreeStructure createStructure() {
+    return new ProjectTreeStructure(project, getId()) {
+      @Override
+      protected @NotNull AbstractTreeNode<?> createRoot(@NotNull Project project, @NotNull ViewSettings settings) {
+        return new GrailsRootNode(project, settings);
+      }
+
+      @Override
+      public boolean isToBuildChildrenInBackground(@NotNull Object element) {
+        return true;
+      }
+    };
+  }
+
+  @Override
+  protected @NotNull ProjectViewTree createTree(@NotNull DefaultTreeModel treeModel) {
+    return new ProjectViewTree(treeModel);
+  }
+
+  @Override
+  protected @NotNull Comparator<NodeDescriptor<?>> createComparator() {
+    return new GrailsNodeComparator(project, getId());
+  }
+
+  @Override
+  public boolean isInitiallyVisible() {
+    return false;
+  }
+
+  private static final class GrailsSelectInTarget extends ProjectViewSelectInTarget implements DumbAware {
+
+    private final String minorViewId;
+    private final String title;
+
+    private GrailsSelectInTarget(@NotNull Project project, @NotNull String minorViewId, @NotNull String title) {
+      super(project);
+      this.minorViewId = minorViewId;
+      this.title = title;
+    }
+
+    @Override
+    public String toString() {
+      return title;
+    }
+
+    @Override
+    public String getMinorViewId() {
+      return minorViewId;
+    }
+
+    @Override
+    public float getWeight() {
+      return 5.239f;
+    }
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.kt b/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.kt
deleted file mode 100644
index 9e608c9..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView
-
-import com.intellij.ide.impl.ProjectViewSelectInTarget
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.AbstractProjectViewPaneWithAsyncSupport
-import com.intellij.ide.projectView.impl.ProjectTreeStructure
-import com.intellij.ide.projectView.impl.ProjectViewTree
-import com.intellij.ide.util.treeView.NodeDescriptor
-import com.intellij.openapi.project.DumbAware
-import com.intellij.openapi.project.Project
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.config.GrailsConstants
-import org.jetbrains.plugins.grails.projectView.impl.GrailsNodeComparator
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsRootNode
-import javax.swing.Icon
-import javax.swing.tree.DefaultTreeModel
-
-internal class GrailsProjectViewPane(val project: Project) : AbstractProjectViewPaneWithAsyncSupport(project) {
-  override fun getId(): String = GrailsConstants.GRAILS
-
-  override fun getIcon(): Icon = GroovyMvcIcons.Grails
-
-  override fun getTitle(): String = id
-
-  override fun getWeight(): Int = 13
-
-  override fun createSelectInTarget(): ProjectViewSelectInTarget = object : ProjectViewSelectInTarget(project), DumbAware {
-
-    override fun toString() = title
-
-    override fun getMinorViewId() = id
-
-    override fun getWeight() = 5.239f
-  }
-
-  override fun createStructure(): ProjectTreeStructure = object : ProjectTreeStructure(project, id) {
-
-    override fun createRoot(project: Project, settings: ViewSettings) = GrailsRootNode(project, settings)
-
-    override fun isToBuildChildrenInBackground(element: Any) = true
-  }
-
-  override fun createTree(treeModel: DefaultTreeModel): ProjectViewTree = ProjectViewTree(treeModel)
-
-  override fun createComparator(): Comparator<NodeDescriptor<*>> = GrailsNodeComparator(project, id)
-
-  override fun isInitiallyVisible(): Boolean = false
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java b/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java
new file mode 100644
index 0000000..21b87ac
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.config.GrailsConstants;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/** Was showHide.kt; adds or removes the Grails project view pane as applications appear or go away. */
+public final class GrailsProjectViewPanes {
+
+  static final String ID = GrailsConstants.GRAILS;
+
+  private GrailsProjectViewPanes() {
+  }
+
+  public static void showHide(@NotNull Project project) {
+    GrailsApplicationManager grailsApplicationManager = GrailsApplicationManager.getInstance(project);
+    ProjectView projectView = ProjectView.getInstance(project);
+    boolean hasPane = Arrays.asList(projectView.getPaneIds()).contains(ID);
+    if (grailsApplicationManager.hasApplications()) {
+      if (!hasPane) {
+        projectView.addProjectPane(getPane(project));
+      }
+    }
+    else if (hasPane) {
+      projectView.removeProjectPane(getPane(project));
+    }
+  }
+
+  private static @NotNull AbstractProjectViewPane getPane(@NotNull Project project) {
+    return Objects.requireNonNull(AbstractProjectViewPane.EP.findFirstSafe(project, pane -> ID.equals(pane.getId())));
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java b/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
new file mode 100644
index 0000000..1a97ecf
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView;
+
+import com.intellij.ide.projectView.TreeStructureProvider;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataSink;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtilKt;
+import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsApplicationNode;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactPackageNode;
+import org.jetbrains.plugins.grails.projectView.nodes.TreeNodeUtil;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public final class GrailsTreeStructureProvider implements TreeStructureProvider {
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> modify(@NotNull AbstractTreeNode<?> parent,
+                                                         @NotNull Collection<AbstractTreeNode<?>> children,
+                                                         ViewSettings settings) {
+    return children;
+  }
+
+  @Override
+  public void uiDataSnapshot(@NotNull DataSink sink, @NotNull Collection<? extends AbstractTreeNode<?>> selection) {
+    AbstractTreeNode<?> single = singleOrNull(selection);
+    if (single == null) return;
+
+    sink.lazy(CommonDataKeys.VIRTUAL_FILE, () -> {
+      if (single instanceof GrailsApplicationNode applicationNode) {
+        return applicationNode.getValue().getRoot();
+      }
+      if (single instanceof GrailsArtefactHandlerNode handlerNode) {
+        return handlerNode.getValue().getDirectory(handlerNode.getGrailsApplication());
+      }
+      return null;
+    });
+    sink.lazy(GrailsActionUtilKt.getGRAILS_APPLICATION(), () -> TreeNodeUtil.findValueOfType(single, GrailsApplication.class));
+    sink.lazy(GrailsActionUtilKt.getGRAILS_ARTEFACT_HANDLER(), () -> TreeNodeUtil.findValueOfType(single, GrailsArtefactHandler.class));
+    sink.lazy(GrailsActionUtilKt.getGRAILS_ARTEFACT_PACKAGE(),
+              () -> single instanceof GrailsArtefactPackageNode<?> packageNode ? packageNode.getPackageFqn() : null);
+  }
+
+  private static AbstractTreeNode<?> singleOrNull(@NotNull Collection<? extends AbstractTreeNode<?>> selection) {
+    if (selection.size() != 1) return null;
+    Iterator<? extends AbstractTreeNode<?>> iterator = selection.iterator();
+    return iterator.hasNext() ? iterator.next() : null;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.kt b/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.kt
deleted file mode 100644
index 378bfd8..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView
-
-import com.intellij.ide.projectView.TreeStructureProvider
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.intellij.openapi.actionSystem.DataSink
-import org.jetbrains.plugins.grails.actions.GRAILS_APPLICATION
-import org.jetbrains.plugins.grails.actions.GRAILS_ARTEFACT_HANDLER
-import org.jetbrains.plugins.grails.actions.GRAILS_ARTEFACT_PACKAGE
-import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsApplicationNode
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactPackageNode
-import org.jetbrains.plugins.grails.projectView.nodes.findValueOfType
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-internal class GrailsTreeStructureProvider : TreeStructureProvider {
-  override fun modify(parent: AbstractTreeNode<*>,
-                      children: Collection<AbstractTreeNode<*>>,
-                      settings: ViewSettings): Collection<AbstractTreeNode<*>> = children
-
-  override fun uiDataSnapshot(sink: DataSink, selection: Collection<AbstractTreeNode<*>?>) {
-    val single = selection.singleOrNull() ?: return
-    sink.lazy(CommonDataKeys.VIRTUAL_FILE) {
-      when (single) {
-        is GrailsApplicationNode -> single.value.root
-        is GrailsArtefactHandlerNode -> single.value.getDirectory(single.grailsApplication)
-        else -> null
-      }
-    }
-    sink.lazy(GRAILS_APPLICATION) {
-      single.findValueOfType<GrailsApplication>()
-    }
-    sink.lazy(GRAILS_ARTEFACT_HANDLER) {
-      single.findValueOfType<GrailsArtefactHandler>()
-    }
-    sink.lazy(GRAILS_ARTEFACT_PACKAGE) {
-      (single as? GrailsArtefactPackageNode)?.packageFqn
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java b/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java
new file mode 100644
index 0000000..12ffaad
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.api;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Collection;
+import java.util.List;
+
+public abstract class GrailsSingleNodeProvider implements GrailsViewNodeProvider {
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> createNodes(@NotNull GrailsApplication application,
+                                                              @NotNull ViewSettings settings) {
+    AbstractTreeNode<?> node = createNode(application, settings);
+    return node != null ? List.of(node) : List.of();
+  }
+
+  public abstract @Nullable AbstractTreeNode<?> createNode(@NotNull GrailsApplication application, @NotNull ViewSettings settings);
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.kt b/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.kt
deleted file mode 100644
index c0237b7..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.api
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-abstract class GrailsSingleNodeProvider : GrailsViewNodeProvider {
-
-  override fun createNodes(application: GrailsApplication, settings: ViewSettings): Collection<AbstractTreeNode<*>> {
-    return createNode(application, settings)?.let { listOf(it) } ?: emptyList()
-  }
-
-  abstract fun createNode(application: GrailsApplication, settings: ViewSettings): AbstractTreeNode<*>?
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java b/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java
new file mode 100644
index 0000000..56f205c
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.api;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Collection;
+
+public interface GrailsViewNodeProvider {
+
+  // Was a top-level val in ep.kt; as a constant on the interface it stays reachable as
+  // GrailsViewNodeProvider.EP_NAME, which matches how platform extension points are declared.
+  ExtensionPointName<GrailsViewNodeProvider> EP_NAME =
+    ExtensionPointName.create("org.intellij.grails.viewNodeProvider");
+
+  @NotNull Collection<AbstractTreeNode<?>> createNodes(@NotNull GrailsApplication application, @NotNull ViewSettings settings);
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.kt b/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.kt
deleted file mode 100644
index e43f0f8..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.api
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-interface GrailsViewNodeProvider {
-
-  fun createNodes(application: GrailsApplication, settings: ViewSettings): Collection<AbstractTreeNode<*>>
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java b/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java
new file mode 100644
index 0000000..046ca76
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.impl;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.projectView.impl.nodes.PsiFileSystemItemFilter;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileSystemItem;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.projectView.GrailsPluginsNode;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.util.version.Version;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class Grails3NodeProvider implements GrailsViewNodeProvider {
+
+  private static final List<String> SPECIAL_FILES = List.of("build.gradle", "settings.gradle", "gradle.properties");
+  private static final List<String> SPECIAL_DIRS = List.of("src/main/scripts", "src/main/webapp");
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> createNodes(@NotNull GrailsApplication application,
+                                                              @NotNull ViewSettings settings) {
+    if (application.getGrailsVersion().compareTo(Version.GRAILS_3_0) < 0) {
+      return List.of();
+    }
+
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+
+    List<PsiDirectory> specialDirs = new ArrayList<>();
+    for (String path : SPECIAL_DIRS) {
+      PsiDirectory directory = GrailsViewItems.findPsiDirectory(application, path);
+      if (directory != null) specialDirs.add(directory);
+    }
+    for (PsiDirectory directory : specialDirs) {
+      result.add(new GrailsPsiDirectoryNode(directory, settings, NodeWeights.SRC_FOLDERS));
+    }
+
+    PsiDirectory src = GrailsViewItems.findPsiDirectory(application, "src");
+    if (src != null) {
+      PsiFileSystemItemFilter filter = item -> !specialDirs.contains(item) && GrailsViewItems.shouldShowItem(item);
+      result.add(new GrailsPsiDirectoryNode(src, settings, NodeWeights.SRC_FOLDERS, filter));
+    }
+
+    for (String path : SPECIAL_FILES) {
+      PsiFile file = GrailsViewItems.findPsiFile(application, path);
+      if (file != null) {
+        result.add(new PsiFileNode(application.getProject(), file, settings));
+      }
+    }
+
+    result.add(new GrailsPluginsNode(application.getProject(), settings));
+    return result;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.kt b/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.kt
deleted file mode 100644
index 0924eaa..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.impl
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.psi.PsiFileSystemItem
-import org.jetbrains.plugins.grails.projectView.GrailsPluginsNode
-import org.jetbrains.plugins.grails.projectView.NodeWeights.SRC_FOLDERS
-import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.util.version.Version.GRAILS_3_0
-
-class Grails3NodeProvider : GrailsViewNodeProvider {
-
-  private val specialFiles = listOf("build.gradle", "settings.gradle", "gradle.properties")
-  private val specialDirs = listOf("src/main/scripts", "src/main/webapp")
-
-  override fun createNodes(application: GrailsApplication, settings: ViewSettings): Collection<AbstractTreeNode<*>> {
-    if (application.grailsVersion >= GRAILS_3_0) {
-      val result = mutableListOf<AbstractTreeNode<*>>()
-
-      val specialDirs = specialDirs.mapNotNull { application.findPsiDirectory(it) }
-
-      result += specialDirs.map {
-        GrailsPsiDirectoryNode(it, settings, nodeWeight = SRC_FOLDERS)
-      }
-
-      application.findPsiDirectory("src")?.let {
-        val filter: (PsiFileSystemItem) -> Boolean = { it !in specialDirs && shouldShowItem(it) }
-        result += GrailsPsiDirectoryNode(it, settings, nodeWeight = SRC_FOLDERS, filter = filter)
-      }
-
-      result += specialFiles.mapNotNull { application.findPsiFile(it) }.map {
-        PsiFileNode(application.project, it, settings)
-      }
-
-      result += GrailsPluginsNode(application.project, settings)
-      return result
-    }
-    return emptyList()
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java
new file mode 100644
index 0000000..a5159ac
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.impl;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode;
+import org.jetbrains.plugins.grails.projectView.nodes.OtherGrailsAppSourcesNode;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+public final class GrailsAppNodeProvider implements GrailsViewNodeProvider {
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> createNodes(@NotNull GrailsApplication application,
+                                                              @NotNull ViewSettings settings) {
+    Project project = application.getProject();
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+
+    for (GrailsDisplayableArtefactHandler handler : ArtefactHandlers.displayableArtefactHandlers()) {
+      if (handler.isVisible(application)) {
+        result.add(new GrailsArtefactHandlerNode(project, handler, settings));
+      }
+    }
+
+    for (Map.Entry<String, GrailsViewItems.SpecialFolder> entry : GrailsViewItems.SPECIAL_GRAILS_APP_FOLDERS.entrySet()) {
+      PsiDirectory directory = GrailsViewItems.findAppPsiDirectory(application, entry.getKey());
+      if (directory != null) {
+        GrailsViewItems.SpecialFolder data = entry.getValue();
+        result.add(new GrailsPsiDirectoryNode(directory, settings, data.icon(), data.weight(), data.title(),
+                                              GrailsViewItems::shouldShowItem));
+      }
+    }
+
+    PsiDirectory appRoot = PsiManager.getInstance(project).findDirectory(application.getAppRoot());
+    if (appRoot != null) {
+      result.add(new OtherGrailsAppSourcesNode(appRoot, settings));
+    }
+
+    return result;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt
deleted file mode 100644
index 130b6f6..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.impl
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.psi.PsiManager
-import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers
-import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode
-import org.jetbrains.plugins.grails.projectView.nodes.OtherGrailsAppSourcesNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-internal class GrailsAppNodeProvider : GrailsViewNodeProvider {
-  override fun createNodes(application: GrailsApplication, settings: ViewSettings): Collection<AbstractTreeNode<*>> {
-    val project = application.project
-
-    val result = mutableListOf<AbstractTreeNode<*>>()
-
-    ArtefactHandlers.displayableArtefactHandlers().filter { it.isVisible(application) }.mapTo(result) {
-      GrailsArtefactHandlerNode(project, it, settings)
-    }
-
-    for ((name, data) in specialGrailsAppFolders) {
-      application.findAppPsiDirectory(name)?.let {
-        result += GrailsPsiDirectoryNode(it, settings, data.first, data.second, data.third, ::shouldShowItem)
-      }
-    }
-
-    PsiManager.getInstance(project).findDirectory(application.appRoot)?.let {
-      result += OtherGrailsAppSourcesNode(it, settings)
-    }
-
-    return result
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java
new file mode 100644
index 0000000..2e739d8
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.impl;
+
+import com.intellij.ide.projectView.impl.GroupByTypeComparator;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.projectView.GrailsPluginsNode;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsApplicationNode;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode;
+import org.jetbrains.plugins.grails.projectView.nodes.OldGrailsPluginsNode;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Comparator;
+
+public final class GrailsNodeComparator implements Comparator<NodeDescriptor<?>> {
+
+  private final GroupByTypeComparator delegate;
+
+  public GrailsNodeComparator(@NotNull Project project, @NotNull String id) {
+    this.delegate = new GroupByTypeComparator(project, id);
+  }
+
+  @Override
+  public int compare(NodeDescriptor<?> left, NodeDescriptor<?> right) {
+    if (right instanceof OldGrailsPluginsNode || right instanceof GrailsPluginsNode) return -1;
+    if (left instanceof OldGrailsPluginsNode || left instanceof GrailsPluginsNode) return 1;
+
+    if (left instanceof GrailsApplicationNode leftApp && right instanceof GrailsApplicationNode rightApp) {
+      GrailsApplication leftValue = leftApp.getValue();
+      GrailsApplication rightValue = rightApp.getValue();
+      return StringUtil.naturalCompare(leftValue != null ? leftValue.getName() : null,
+                                       rightValue != null ? rightValue.getName() : null);
+    }
+
+    if (left instanceof GrailsArtefactHandlerNode leftHandler && right instanceof GrailsArtefactHandlerNode rightHandler) {
+      return leftHandler.getValue().getWeight() - rightHandler.getValue().getWeight();
+    }
+    if (left instanceof GrailsArtefactHandlerNode) return -1;
+    if (right instanceof GrailsArtefactHandlerNode) return 1;
+
+    if (left instanceof GrailsPsiDirectoryNode leftDir && right instanceof GrailsPsiDirectoryNode rightDir) {
+      return leftDir.getNodeWeight() - rightDir.getNodeWeight();
+    }
+
+    if (right instanceof PsiFileNode rightFile && !(left instanceof PsiFileNode)
+        && rightFile.getParent() instanceof GrailsApplicationNode) {
+      return -1;
+    }
+    if (left instanceof PsiFileNode leftFile && !(right instanceof PsiFileNode)
+        && leftFile.getParent() instanceof GrailsApplicationNode) {
+      return 1;
+    }
+
+    return delegate.compare(left, right);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.kt b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.kt
deleted file mode 100644
index 501d0c8..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.impl
-
-import com.intellij.ide.projectView.impl.GroupByTypeComparator
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode
-import com.intellij.ide.util.treeView.NodeDescriptor
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtil
-import org.jetbrains.plugins.grails.projectView.GrailsPluginsNode
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsApplicationNode
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode
-import org.jetbrains.plugins.grails.projectView.nodes.OldGrailsPluginsNode
-
-internal class GrailsNodeComparator(project: Project, id: String) : Comparator<NodeDescriptor<*>> {
-  private val delegate = GroupByTypeComparator(project, id)
-
-  override fun compare(left: NodeDescriptor<*>, right: NodeDescriptor<*>): Int {
-    if (right is OldGrailsPluginsNode || right is GrailsPluginsNode) return -1
-    if (left is OldGrailsPluginsNode || left is GrailsPluginsNode) return 1
-
-    if (left is GrailsApplicationNode && right is GrailsApplicationNode) {
-      return StringUtil.naturalCompare(left.value?.name, right.value?.name)
-    }
-
-    if (left is GrailsArtefactHandlerNode && right is GrailsArtefactHandlerNode) {
-      return left.value.weight - right.value.weight
-    }
-    if (left is GrailsArtefactHandlerNode) return -1
-    if (right is GrailsArtefactHandlerNode) return 1
-
-    if (left is GrailsPsiDirectoryNode && right is GrailsPsiDirectoryNode) {
-      return left.nodeWeight - right.nodeWeight
-    }
-
-    if (right is PsiFileNode && left !is PsiFileNode && right.parent is GrailsApplicationNode) {
-      return -1
-    }
-    if (left is PsiFileNode && right !is PsiFileNode && left.parent is GrailsApplicationNode) {
-      return 1
-    }
-
-    return delegate.compare(left, right)
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java
new file mode 100644
index 0000000..4923a48
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.impl;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiFileSystemItem;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+
+import javax.swing.Icon;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class GrailsViewItems {
+
+  /** Replaces the Kotlin Triple that carried the icon, weight and title of a special folder. */
+  public record SpecialFolder(@NotNull Icon icon, int weight, @NotNull String title) {
+  }
+
+  public static final Map<String, SpecialFolder> SPECIAL_GRAILS_APP_FOLDERS = specialFolders();
+
+  private GrailsViewItems() {
+  }
+
+  private static Map<String, SpecialFolder> specialFolders() {
+    // LinkedHashMap because the project view renders these in declaration order.
+    Map<String, SpecialFolder> result = new LinkedHashMap<>();
+    result.put("conf", new SpecialFolder(AllIcons.Nodes.ConfigFolder, NodeWeights.CONFIG_FOLDER, "Configuration"));
+    result.put("views", new SpecialFolder(GroovyMvcIcons.Gsp_logo, NodeWeights.VIEWS_FOLDER, "Views"));
+    result.put("init", new SpecialFolder(AllIcons.Nodes.ConfigFolder, NodeWeights.CONFIG_FOLDER - 1, "Initialization"));
+    return Map.copyOf(result);
+  }
+
+  public static boolean shouldShowItem(@NotNull PsiFileSystemItem item) {
+    if (!(item instanceof GroovyFile groovyFile)) return true;
+    PsiClass[] classes = groovyFile.getClasses();
+    if (classes.length != 1) return true;
+    return !(GrailsArtefacts.getArtefactHandler(classes[0]) instanceof GrailsDisplayableArtefactHandler);
+  }
+
+  public static @Nullable PsiFile findPsiFile(@NotNull GrailsApplication application, @NotNull String name) {
+    VirtualFile file = application.getRoot().findFileByRelativePath(name);
+    return file != null ? PsiManager.getInstance(application.getProject()).findFile(file) : null;
+  }
+
+  public static @Nullable PsiDirectory findPsiDirectory(@NotNull GrailsApplication application, @NotNull String name) {
+    VirtualFile file = application.getRoot().findFileByRelativePath(name);
+    return file != null ? PsiManager.getInstance(application.getProject()).findDirectory(file) : null;
+  }
+
+  /** Resolves relative to {@code grails-app} rather than the application root. */
+  public static @Nullable PsiDirectory findAppPsiDirectory(@NotNull GrailsApplication application, @NotNull String name) {
+    VirtualFile file = application.getAppRoot().findFileByRelativePath(name);
+    return file != null ? PsiManager.getInstance(application.getProject()).findDirectory(file) : null;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java b/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java
new file mode 100644
index 0000000..4aacbb7
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.impl;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.javaee.JavaeeIcons;
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.PlatformIcons;
+import icons.JetgroovyIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider;
+import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode;
+import org.jetbrains.plugins.grails.projectView.nodes.OldGrailsPluginsNode;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.OldGrailsApplication;
+
+import javax.swing.Icon;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+public class OldGrailsNodeProvider implements GrailsViewNodeProvider {
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> createNodes(@NotNull GrailsApplication application,
+                                                              @NotNull ViewSettings settings) {
+    if (!(application instanceof OldGrailsApplication oldApplication)) {
+      return List.of();
+    }
+
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    Project project = oldApplication.getProject();
+    PsiManager manager = PsiManager.getInstance(project);
+
+    PsiFile applicationProperties = GrailsViewItems.findPsiFile(oldApplication, "application.properties");
+    if (applicationProperties != null) {
+      result.add(new PsiFileNode(project, applicationProperties, settings));
+    }
+
+    PsiDirectory webApp = GrailsViewItems.findPsiDirectory(oldApplication, "web-app");
+    if (webApp != null) {
+      result.add(new GrailsPsiDirectoryNode(webApp, settings, JavaeeIcons.WEB_FOLDER_CLOSED,
+                                            NodeWeights.WEB_APP_FOLDER, "web-app"));
+    }
+
+    PsiDirectory scripts = GrailsViewItems.findPsiDirectory(oldApplication, "scripts");
+    if (scripts != null) {
+      result.add(new GrailsPsiDirectoryNode(scripts, settings, JetgroovyIcons.Groovy.Gant_16x16,
+                                            NodeWeights.SRC_FOLDERS, "Scripts"));
+    }
+
+    for (VirtualFile child : oldApplication.getRoot().getChildren()) {
+      if (child.getName().endsWith("GrailsPlugin.groovy")) {
+        PsiFile pluginFile = manager.findFile(child);
+        if (pluginFile != null) {
+          result.add(new PsiFileNode(project, pluginFile, settings));
+        }
+        break;
+      }
+    }
+
+    VirtualFile srcDir = oldApplication.getRoot().findChild("src");
+    if (srcDir != null) {
+      for (VirtualFile child : srcDir.getChildren()) {
+        PsiDirectory directory = manager.findDirectory(child);
+        if (directory == null) continue;
+        String name = child.getName();
+        result.add(new GrailsPsiDirectoryNode(directory, settings, sourceIcon(name), NodeWeights.SRC_FOLDERS,
+                                              "Sources:" + name, GrailsViewItems::shouldShowItem));
+      }
+    }
+
+    VirtualFile testDir = oldApplication.getRoot().findChild("test");
+    if (testDir != null) {
+      for (VirtualFile child : testDir.getChildren()) {
+        PsiDirectory directory = manager.findDirectory(child);
+        if (directory == null) continue;
+        String name = child.getName();
+        Icon icon = switch (name) {
+          case "functional", "integration" -> GroovyMvcIcons.Grails_test;
+          default -> PlatformIcons.TEST_SOURCE_FOLDER;
+        };
+        result.add(new GrailsPsiDirectoryNode(directory, settings, icon, NodeWeights.TESTS_FOLDER, "Tests:" + name));
+      }
+    }
+
+    result.add(new OldGrailsPluginsNode(project, settings));
+    return result;
+  }
+
+  /** Uses the language whose ID matches the directory name to pick an icon, as the Kotlin did. */
+  private static @NotNull Icon sourceIcon(@NotNull String name) {
+    Language language = Language.findLanguageByID(capitalize(name));
+    if (language == null) {
+      language = Language.findLanguageByID(name.toUpperCase(Locale.getDefault()));
+    }
+    if (language != null) {
+      FileType fileType = language.getAssociatedFileType();
+      if (fileType != null && fileType.getIcon() != null) {
+        return fileType.getIcon();
+      }
+    }
+    return PlatformIcons.SOURCE_FOLDERS_ICON;
+  }
+
+  private static @NotNull String capitalize(@NotNull String value) {
+    if (value.isEmpty()) return value;
+    return Character.toUpperCase(value.charAt(0)) + value.substring(1);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.kt b/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.kt
deleted file mode 100644
index c7d4721..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.impl
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.javaee.JavaeeIcons
-import com.intellij.lang.Language
-import com.intellij.psi.PsiManager
-import com.intellij.util.PlatformIcons
-import icons.JetgroovyIcons
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.projectView.NodeWeights.SRC_FOLDERS
-import org.jetbrains.plugins.grails.projectView.NodeWeights.TESTS_FOLDER
-import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider
-import org.jetbrains.plugins.grails.projectView.nodes.GrailsPsiDirectoryNode
-import org.jetbrains.plugins.grails.projectView.nodes.OldGrailsPluginsNode
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.OldGrailsApplication
-import java.util.Locale
-
-class OldGrailsNodeProvider : GrailsViewNodeProvider {
-
-  override fun createNodes(application: GrailsApplication, settings: ViewSettings): Collection<AbstractTreeNode<*>> {
-    if (application is OldGrailsApplication) {
-      val result = mutableListOf<AbstractTreeNode<*>>()
-      val project = application.project
-      val manager = PsiManager.getInstance(project)
-
-      application.findPsiFile("application.properties")?.let {
-        result += PsiFileNode(project, it, settings)
-      }
-
-      application.findPsiDirectory("web-app")?.let {
-        result += GrailsPsiDirectoryNode(
-          it, settings, JavaeeIcons.WEB_FOLDER_CLOSED, NodeWeights.WEB_APP_FOLDER, "web-app"
-        )
-      }
-
-      application.findPsiDirectory("scripts")?.let {
-        result += GrailsPsiDirectoryNode(
-            it, settings, JetgroovyIcons.Groovy.Gant_16x16, SRC_FOLDERS, "Scripts"
-        )
-      }
-
-      application.root.children.find { it.name.endsWith("GrailsPlugin.groovy") }?.let {
-        manager.findFile(it)?.let {
-          result += PsiFileNode(project, it, settings)
-        }
-      }
-
-      application.root.findChild("src")?.let { srcDir ->
-        for (child in srcDir.children) {
-          manager.findDirectory(child)?.let {
-            val name = child.name
-            val icon = (Language.findLanguageByID(name.replaceFirstChar { it.uppercaseChar() }) ?: Language.findLanguageByID(name.uppercase(Locale.getDefault())))
-                ?.associatedFileType?.icon ?: PlatformIcons.SOURCE_FOLDERS_ICON
-            result += GrailsPsiDirectoryNode(it, settings, icon, SRC_FOLDERS, "Sources:$name", ::shouldShowItem)
-          }
-        }
-      }
-
-      application.root.findChild("test")?.let { testDir ->
-        for (child in testDir.children) {
-          manager.findDirectory(child)?.let {
-            val name = child.name
-            val icon = when (name) {
-              "unit" -> PlatformIcons.TEST_SOURCE_FOLDER
-              "functional" -> GroovyMvcIcons.Grails_test
-              "integration" -> GroovyMvcIcons.Grails_test
-              else -> PlatformIcons.TEST_SOURCE_FOLDER
-            }
-            result += GrailsPsiDirectoryNode(it, settings, icon, TESTS_FOLDER, "Tests:$name")
-          }
-        }
-      }
-
-      result += OldGrailsPluginsNode(project, settings)
-
-      return result
-    }
-
-    return emptyList()
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt b/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt
deleted file mode 100644
index 283cbbc..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/impl/impl.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.impl
-
-import com.intellij.icons.AllIcons
-import com.intellij.psi.PsiFileSystemItem
-import com.intellij.psi.PsiManager
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler as ArtefactHandler
-
-internal fun shouldShowItem(item: PsiFileSystemItem): Boolean {
-  if (item !is GroovyFile) return true
-  val clazz = item.classes.singleOrNull() ?: return true
-  val handler = GrailsArtefacts.getArtefactHandler(clazz)
-  return handler !is ArtefactHandler
-}
-
-internal val specialGrailsAppFolders = mapOf(
-  "conf" to Triple(AllIcons.Nodes.ConfigFolder, NodeWeights.CONFIG_FOLDER, "Configuration"),
-  "views" to Triple(GroovyMvcIcons.Gsp_logo, NodeWeights.VIEWS_FOLDER, "Views"),
-  "init" to Triple(AllIcons.Nodes.ConfigFolder, NodeWeights.CONFIG_FOLDER - 1, "Initialization")
-)
-
-internal fun GrailsApplication.findPsiFile(name: String) = root.findFileByRelativePath(name)?.let {
-  PsiManager.getInstance(project).findFile(it)
-}
-
-internal fun GrailsApplication.findPsiDirectory(name: String) = root.findFileByRelativePath(name)?.let {
-  PsiManager.getInstance(project).findDirectory(it)
-}
-
-internal fun GrailsApplication.findAppPsiDirectory(name: String) = appRoot.findFileByRelativePath(name)?.let {
-  PsiManager.getInstance(project).findDirectory(it)
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java b/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java
new file mode 100644
index 0000000..5487e50
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a fully qualified name with compacted part.
+ *
+ * <p>Consider {@code com.foo.bar.baz.bax} fqn in the view under {@code com.foo} node.
+ * <pre>
+ * - com
+ *   - foo
+ *     - bar.baz.bax // &lt;- this node
+ *   + bad
+ *   + goo
+ *   ...
+ * </pre>
+ */
+public final class CompactedFqn {
+
+  private final List<String> baseFqn;
+  private final List<String> relativeParts;
+  private final boolean hasCompactedFqns;
+  private final List<String> allParts;
+
+  // Deferred because it is only needed when the node is actually rendered or validated.
+  private volatile List<String> expandedFqns;
+
+  /**
+   * @param baseFqn       possibly empty list of parts of the base fqn, e.g. {@code ["com", "foo"]}
+   * @param relativeParts non-empty list of parts of the fqn relative to the baseFqn, e.g. {@code ["bar", "baz", "bax"]}
+   */
+  public CompactedFqn(@NotNull List<String> baseFqn, @NotNull List<String> relativeParts) {
+    if (relativeParts.isEmpty()) {
+      throw new IllegalArgumentException("relativeParts must not be empty");
+    }
+    this.baseFqn = List.copyOf(baseFqn);
+    this.relativeParts = List.copyOf(relativeParts);
+    this.hasCompactedFqns = this.relativeParts.size() > 1;
+
+    List<String> all = new ArrayList<>(this.baseFqn);
+    all.addAll(this.relativeParts);
+    this.allParts = List.copyOf(all);
+  }
+
+  public @NotNull List<String> getBaseFqn() {
+    return baseFqn;
+  }
+
+  public @NotNull List<String> getRelativeParts() {
+    return relativeParts;
+  }
+
+  public boolean getHasCompactedFqns() {
+    return hasCompactedFqns;
+  }
+
+  /** All parts of this fqn, e.g.: {@code ["com", "foo", "bar", "bar", "bax"]}. */
+  public @NotNull List<String> getAllParts() {
+    return allParts;
+  }
+
+  /**
+   * All expanded fqns, e.g.: {@code ["com.foo.bar", "com.foo.bar.baz", "com.foo.bar.baz.bax"]}.
+   * The list starts from the parent fqn.
+   */
+  public @NotNull List<String> getExpandedFqns() {
+    List<String> result = expandedFqns;
+    if (result == null) {
+      List<String> computed = new ArrayList<>(relativeParts.size());
+      List<String> current = new ArrayList<>(baseFqn);
+      for (String relativePart : relativeParts) {
+        current.add(relativePart);
+        computed.add(GrailsNodes.fqnString(current));
+      }
+      result = List.copyOf(computed);
+      expandedFqns = result;
+    }
+    return result;
+  }
+
+  // Kotlin's data class equality covered only the primary constructor properties; the
+  // derived members are a function of these two, so this stays equivalent.
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof CompactedFqn other)) return false;
+    return baseFqn.equals(other.baseFqn) && relativeParts.equals(other.relativeParts);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(baseFqn, relativeParts);
+  }
+
+  @Override
+  public String toString() {
+    return GrailsNodes.fqnString(allParts);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.kt
deleted file mode 100644
index 650a920..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.util.lazyUnsafe
-
-/**
- * Represents a fully qualified name with compacted part.
- *
- * Consider `com.foo.bar.baz.bax` fqn in the view under `com.foo` node.
- * ```
- * - com
- *   - foo
- *     - bar.baz.bax // <- this node
- *   + bad
- *   + goo
- *   ...
- * ```
- * @param baseFqn possibly empty list of parts of the base fqn, e.g. `["com", "foo"]`
- * @param relativeParts non-empty list of parts of the fqn relative to the [baseFqn], e.g. `["bar", "baz", "bax"]`
- *
- */
-data class CompactedFqn(val baseFqn: List<String>, val relativeParts: List<String>) {
-
-  init {
-    require(relativeParts.isNotEmpty())
-  }
-
-  val hasCompactedFqns: Boolean = relativeParts.size > 1
-
-  /**
-   * All parts of this fqn, e.g.: `["com", "foo", "bar", "bar", "bax"]`.
-   */
-  val allParts: List<String> = baseFqn + relativeParts
-
-  /**
-   * All expanded fqns, e.g.: `["com.foo.bar", "com.foo.bar.baz", "com.foo.bar.baz.bax"]`.
-   * The list starts from the parent fqn.
-   */
-  val expandedFqns: List<String> by lazyUnsafe {
-    val result = ArrayList<String>(relativeParts.size)
-    val current = baseFqn.toMutableList()
-    for (relativePart in relativeParts) {
-      current.add(relativePart)
-      result.add(current.fqnString())
-    }
-    result
-  }
-
-  override fun toString(): String = allParts.fqnString()
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java
new file mode 100644
index 0000000..d8d64d4
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.BasePsiMemberNode;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiMember;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class GrailsActionNode extends BasePsiMemberNode<PsiMember> {
+
+  private final String actionName;
+
+  public GrailsActionNode(@NotNull String actionName, @NotNull PsiMember action, @NotNull ViewSettings settings) {
+    super(action.getProject(), action, settings);
+    this.actionName = actionName;
+  }
+
+  public @NotNull String getActionName() {
+    return actionName;
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildrenImpl() {
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (PsiFile view : GrailsUtils.getViewPsiByAction(getValue())) {
+      result.add(new PsiFileNode(getProject(), view, getSettings()));
+    }
+    return result;
+  }
+
+  @Override
+  protected void updateImpl(@NotNull PresentationData data) {
+    data.setPresentableText(actionName);
+    data.setIcon(GroovyMvcIcons.Action_method);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.kt
deleted file mode 100644
index 309167d..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.BasePsiMemberNode
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.psi.PsiMember
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.util.GrailsUtils
-
-class GrailsActionNode(
-  val actionName: String,
-  action: PsiMember,
-  settings: ViewSettings
-) : BasePsiMemberNode<PsiMember>(action.project, action, settings) {
-
-  override fun getChildrenImpl(): Collection<AbstractTreeNode<*>> {
-    return GrailsUtils.getViewPsiByAction(value).map { PsiFileNode(project, it, settings) }
-  }
-
-  override fun updateImpl(data: PresentationData) {
-    data.apply {
-      presentableText = actionName
-      setIcon(GroovyMvcIcons.Action_method)
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java
new file mode 100644
index 0000000..8a5aa2f
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GrailsBundle;
+import org.jetbrains.plugins.grails.projectView.api.GrailsViewNodeProvider;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class GrailsApplicationNode extends ProjectViewNode<GrailsApplication> {
+
+  public GrailsApplicationNode(@NotNull GrailsApplication application, @NotNull ViewSettings viewSettings) {
+    super(application.getProject(), application, viewSettings);
+  }
+
+  @Override
+  public boolean shouldUpdateData() {
+    return getValue().isValid() && super.shouldUpdateData();
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildren() {
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (GrailsViewNodeProvider provider : GrailsViewNodeProvider.EP_NAME.getExtensionList()) {
+      result.addAll(provider.createNodes(getValue(), getSettings()));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    Project project = getProject();
+    return project != null && GrailsApplicationManager.getInstance(project).findApplication(file) == getValue();
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData presentation) {
+    GrailsApplication application = getValue();
+    presentation.setIcon(application.getIcon());
+    presentation.addText(application.getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
+    String appVersion = application.getAppVersion();
+    if (appVersion != null) {
+      presentation.addText(" " + appVersion, SimpleTextAttributes.REGULAR_ATTRIBUTES); // NON-NLS
+    }
+    presentation.addText(" " + GrailsBundle.message("project.view.application.node.version.label",
+                                                    application.getGrailsVersion()),
+                         SimpleTextAttributes.REGULAR_ATTRIBUTES);
+    presentation.setTooltip(application.getRoot().getPath());
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.kt
deleted file mode 100644
index ca5cba6..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ProjectViewNode
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.ui.SimpleTextAttributes
-import org.jetbrains.plugins.grails.GrailsBundle
-import org.jetbrains.plugins.grails.projectView.api.EP_NAME
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-
-class GrailsApplicationNode(
-  application: GrailsApplication,
-  viewSettings: ViewSettings
-) : ProjectViewNode<GrailsApplication>(application.project, application, viewSettings) {
-
-  override fun shouldUpdateData(): Boolean {
-    return value.isValid && super.shouldUpdateData()
-  }
-
-  override fun getChildren(): Collection<AbstractTreeNode<*>> = EP_NAME.extensions.flatMap {
-    it.createNodes(value, settings)
-  }
-
-  override fun contains(file: VirtualFile): Boolean {
-    return project?.let { GrailsApplicationManager.getInstance(it).findApplication(file) == value } ?: false
-  }
-
-  override fun update(presentation: PresentationData) {
-    presentation.apply {
-      val application = value
-      setIcon(application.icon)
-      addText(application.name, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)
-      application.appVersion?.let { version ->
-        addText(" ${version}", SimpleTextAttributes.REGULAR_ATTRIBUTES) // NON-NLS
-      }
-      addText(" " + GrailsBundle.message("project.view.application.node.version.label", application.grailsVersion), SimpleTextAttributes.REGULAR_ATTRIBUTES)
-      tooltip = application.root.path
-    }
-  }
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java
new file mode 100644
index 0000000..2da767b
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+
+import java.util.Collection;
+import java.util.Objects;
+
+public class GrailsArtefactHandlerNode extends GrailsArtefactHandlerNodeBase<GrailsDisplayableArtefactHandler> {
+
+  public GrailsArtefactHandlerNode(Project project,
+                                   @NotNull GrailsDisplayableArtefactHandler artefactHandler,
+                                   @NotNull ViewSettings viewSettings) {
+    super(project, viewSettings, artefactHandler);
+  }
+
+  @Override
+  protected @NotNull GrailsDisplayableArtefactHandler getArtefactHandler() {
+    return getValue();
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData presentation) {
+    presentation.setIcon(getValue().getGroupIcon());
+    presentation.setPresentableText(getValue().getTitle());
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildren() {
+    return GrailsNodes.getArtefactNodes(Objects.requireNonNull(getProject()), getSettings(), getValue(), getArtefacts());
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    return file.isDirectory() || TreeNodeUtil.mayContain(getGrailsApplication(), file);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.kt
deleted file mode 100644
index 48e453e..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-
-class GrailsArtefactHandlerNode(
-  project: Project,
-  artefactHandler: GrailsDisplayableArtefactHandler,
-  viewSettings: ViewSettings
-) : GrailsArtefactHandlerNodeBase<GrailsDisplayableArtefactHandler>(project, viewSettings, artefactHandler) {
-
-  override val artefactHandler: GrailsDisplayableArtefactHandler get() = value
-
-  override fun update(presentation: PresentationData) {
-    presentation.setIcon(value.groupIcon)
-    presentation.presentableText = value.title
-  }
-
-  override fun getChildren(): TreeNodes = getArtefactNodes(project!!, settings, value, artefacts)
-
-  override fun contains(file: VirtualFile): Boolean = file.isDirectory || mayContain(grailsApplication, file)
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java
new file mode 100644
index 0000000..c386e1b
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.search.GlobalSearchScope;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Collection;
+
+public abstract class GrailsArtefactHandlerNodeBase<T> extends ProjectViewNode<T> {
+
+  protected GrailsArtefactHandlerNodeBase(Project project, @NotNull ViewSettings viewSettings, @NotNull T value) {
+    super(project, value, viewSettings);
+  }
+
+  public @NotNull GrailsApplication getGrailsApplication() {
+    return TreeNodeUtil.findNotNullValueOfType(this, GrailsApplication.class);
+  }
+
+  protected @NotNull GlobalSearchScope getScope() {
+    return getGrailsApplication().getScope(false, false);
+  }
+
+  protected abstract @NotNull GrailsDisplayableArtefactHandler getArtefactHandler();
+
+  protected @NotNull Collection<PsiClass> getArtefacts() {
+    return GrailsArtefacts.getArtefacts(getArtefactHandler(), getGrailsApplication(), getScope());
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt
deleted file mode 100644
index 3931dfe..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.ProjectViewNode
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.openapi.project.Project
-import com.intellij.psi.search.GlobalSearchScope
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-import org.jetbrains.plugins.grails.artefact.impl.GrailsArtefacts
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-abstract class GrailsArtefactHandlerNodeBase<T : Any>(
-  project: Project,
-  viewSettings: ViewSettings,
-  value: T
-) : ProjectViewNode<T>(project, value, viewSettings) {
-
-  val grailsApplication: GrailsApplication get() = findNotNullValueOfType()
-  protected val scope: GlobalSearchScope get() = grailsApplication.getScope(includeDependencies = false, testsOnly = false)
-  protected abstract val artefactHandler: GrailsDisplayableArtefactHandler
-  protected val artefacts: Classes get() = GrailsArtefacts.getArtefacts(artefactHandler, grailsApplication, scope)
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java
new file mode 100644
index 0000000..2fd821a
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiPackage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+public abstract class GrailsArtefactPackageNode<T> extends GrailsArtefactHandlerNodeBase<T> {
+
+  protected GrailsArtefactPackageNode(Project project, @NotNull ViewSettings settings, @NotNull T value) {
+    super(project, settings, value);
+  }
+
+  @Override
+  protected final @NotNull GrailsDisplayableArtefactHandler getArtefactHandler() {
+    return TreeNodeUtil.findNotNullValueOfType(this, GrailsDisplayableArtefactHandler.class);
+  }
+
+  @Override
+  public final int getTypeSortWeight(boolean sortByType) {
+    return 3;
+  }
+
+  public abstract @NotNull String getPackageFqn();
+
+  @Override
+  public final boolean canRepresent(@Nullable Object element) {
+    return canRepresentInner(element) || super.canRepresent(element);
+  }
+
+  private boolean canRepresentInner(@Nullable Object element) {
+    return element instanceof VirtualFile file && file.isDirectory() && getNodeDirectories().contains(file);
+  }
+
+  protected abstract @NotNull Collection<VirtualFile> getNodeDirectories();
+
+  protected @NotNull Collection<VirtualFile> packageDirectories(@NotNull String fqn) {
+    PsiPackage psiPackage = JavaPsiFacade.getInstance(Objects.requireNonNull(getProject())).findPackage(fqn);
+    if (psiPackage == null) return List.of();
+    List<VirtualFile> result = new ArrayList<>();
+    for (PsiDirectory directory : psiPackage.getDirectories(getScope())) {
+      result.add(directory.getVirtualFile());
+    }
+    return result;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.kt
deleted file mode 100644
index a21c589..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiDirectory
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler
-
-abstract class GrailsArtefactPackageNode<T : Any>(
-  project: Project,
-  settings: ViewSettings,
-  value: T
-) : GrailsArtefactHandlerNodeBase<T>(project, settings, value) {
-  final override val artefactHandler: GrailsDisplayableArtefactHandler
-    get() = findNotNullValueOfType()
-
-  final override fun getTypeSortWeight(sortByType: Boolean): Int = 3
-
-  abstract val packageFqn: String
-
-  final override fun canRepresent(element: Any?): Boolean = canRepresentInner(element) || super.canRepresent(element)
-
-  private fun canRepresentInner(element: Any?): Boolean = element is VirtualFile && element.isDirectory && element in nodeDirectories
-
-  protected abstract val nodeDirectories: Collection<VirtualFile>
-
-  protected fun packageDirectories(fqn: String): Collection<VirtualFile> {
-    val package_ = JavaPsiFacade.getInstance(project!!).findPackage(fqn) ?: return emptyList()
-    return package_.getDirectories(scope).map(PsiDirectory::getVirtualFile)
-  }
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
new file mode 100644
index 0000000..60c507f
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.ClassTreeNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiMember;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.impl.controllers.FunctionsKt;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+public class GrailsControllerNode extends ClassTreeNode {
+
+  private volatile GrailsApplication grailsApplication;
+
+  public GrailsControllerNode(@NotNull PsiClass clazz, @NotNull ViewSettings settings) {
+    super(clazz.getProject(), clazz, settings);
+  }
+
+  public @NotNull GrailsApplication getGrailsApplication() {
+    GrailsApplication result = grailsApplication;
+    if (result == null) {
+      result = TreeNodeUtil.findNotNullValueOfType(this, GrailsApplication.class);
+      grailsApplication = result;
+    }
+    return result;
+  }
+
+  @Override
+  public @Nullable Collection<AbstractTreeNode<?>> getChildrenImpl() {
+    if (!getSettings().isShowMembers()) return null;
+    if (!(getValue() instanceof GrTypeDefinition clazz)) return null;
+
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (Map.Entry<String, PsiMember> action : FunctionsKt.getActions(clazz, getGrailsApplication()).entrySet()) {
+      result.add(new GrailsActionNode(action.getKey(), action.getValue(), getSettings()));
+    }
+    return result;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.kt
deleted file mode 100644
index ac66691..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.ClassTreeNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.psi.PsiClass
-import com.intellij.util.lazyUnsafe
-import org.jetbrains.plugins.grails.artefact.impl.controllers.getActions
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
-
-class GrailsControllerNode(clazz: PsiClass, settings: ViewSettings) : ClassTreeNode(clazz.project, clazz, settings) {
-
-  val grailsApplication: GrailsApplication by lazyUnsafe { findNotNullValueOfType<GrailsApplication>() }
-
-  override fun getChildrenImpl(): Collection<AbstractTreeNode<*>>? {
-    if (!settings.isShowMembers) return null
-    val clazz = value as? GrTypeDefinition ?: return null
-    return getActions(clazz, grailsApplication).map {
-      GrailsActionNode(it.key, it.value, settings)
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java
new file mode 100644
index 0000000..36f81e8
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.TreeViewUtil;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiPackage;
+import com.intellij.ui.IconManager;
+import com.intellij.ui.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Objects;
+
+class GrailsFlatPackageNode extends GrailsArtefactPackageNode<GrailsPackageValue> {
+
+  private final String packageFqn;
+
+  GrailsFlatPackageNode(Project project, @NotNull ViewSettings settings, @NotNull String packageFqn) {
+    super(project, settings, new GrailsPackageValue(packageFqn));
+    this.packageFqn = packageFqn;
+  }
+
+  @Override
+  public @NotNull String getPackageFqn() {
+    return packageFqn;
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData presentation) {
+    presentation.setIcon(IconManager.getInstance().getPlatformIcon(PlatformIcons.Package));
+    String presentableText = packageFqn;
+    if (getSettings().isAbbreviatePackageNames()) {
+      PsiPackage psiPackage = JavaPsiFacade.getInstance(Objects.requireNonNull(getProject())).findPackage(packageFqn);
+      if (psiPackage != null) {
+        String abbreviated = TreeViewUtil.calcAbbreviatedPackageFQName(psiPackage);
+        if (abbreviated != null) presentableText = abbreviated;
+      }
+    }
+    presentation.setPresentableText(presentableText);
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildren() {
+    return GrailsNodes.getClassNodes(getArtefacts(), Objects.requireNonNull(getProject()), getSettings(),
+                                     getArtefactHandler(), packageFqn);
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    return !file.isDirectory() && getNodeDirectories().contains(file.getParent());
+  }
+
+  @Override
+  protected @NotNull Collection<VirtualFile> getNodeDirectories() {
+    return packageDirectories(packageFqn);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.kt
deleted file mode 100644
index 48f7427..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.TreeViewUtil
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.ui.IconManager
-import com.intellij.ui.PlatformIcons
-
-internal class GrailsFlatPackageNode(
-  project: Project,
-  settings: ViewSettings,
-  override val packageFqn: String
-) : GrailsArtefactPackageNode<GrailsPackageValue>(project, settings, GrailsPackageValue(packageFqn)) {
-  override fun update(presentation: PresentationData) {
-    presentation.setIcon(IconManager.getInstance().getPlatformIcon(PlatformIcons.Package))
-    presentation.presentableText = if (settings.isAbbreviatePackageNames) {
-      JavaPsiFacade.getInstance(project!!).findPackage(packageFqn)
-        ?.let(TreeViewUtil::calcAbbreviatedPackageFQName)
-      ?: packageFqn
-    }
-    else {
-      packageFqn
-    }
-  }
-
-  override fun getChildren(): TreeNodes = artefacts.getClassNodes(project!!, settings, artefactHandler, packageFqn)
-
-  override fun contains(file: VirtualFile): Boolean = !file.isDirectory && file.parent in nodeDirectories
-
-  override val nodeDirectories: Collection<VirtualFile>
-    get() = packageDirectories(packageFqn)
-}
-
-internal data class GrailsPackageValue(val packageFqn: String)
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java
new file mode 100644
index 0000000..5d24e0a
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.ClassTreeNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+final class GrailsNodes {
+
+  private GrailsNodes() {
+  }
+
+  static @NotNull String fqnString(@NotNull List<String> parts) {
+    return String.join(".", parts);
+  }
+
+  static @NotNull Collection<AbstractTreeNode<?>> getArtefactNodes(@NotNull Project project,
+                                                                   @NotNull ViewSettings settings,
+                                                                   @NotNull GrailsDisplayableArtefactHandler artefactHandler,
+                                                                   @NotNull Collection<PsiClass> artefacts) {
+    if (settings.isFlattenPackages()) {
+      Collection<String> packages = getPackagesFlattened(artefacts, settings.isHideEmptyMiddlePackages());
+      List<AbstractTreeNode<?>> result = new ArrayList<>();
+      for (String packageFqn : packages) {
+        result.add(new GrailsFlatPackageNode(project, settings, packageFqn));
+      }
+      result.addAll(getClassNodes(artefacts, project, settings, artefactHandler, ""));
+      return result;
+    }
+    return getNodesRegular(artefacts, project, settings, artefactHandler, List.of());
+  }
+
+  private static @NotNull Collection<String> getPackagesFlattened(@NotNull Collection<PsiClass> classes,
+                                                                  boolean hideEmptyMiddlePackages) {
+    Collection<String> classPackageFqns = new LinkedHashSet<>();
+    for (PsiClass clazz : classes) {
+      String packageName = getPackageName(clazz);
+      if (packageName != null) classPackageFqns.add(packageName);
+    }
+
+    Set<String> packageFqns = new LinkedHashSet<>(classPackageFqns);
+    if (!hideEmptyMiddlePackages) {
+      for (String fqn : classPackageFqns) {
+        String current = fqn;
+        while (!current.isEmpty()) {
+          current = StringUtil.getPackageName(current);
+          packageFqns.add(current);
+        }
+      }
+    }
+    packageFqns.remove("");
+    return packageFqns;
+  }
+
+  static @NotNull Collection<AbstractTreeNode<?>> getClassNodes(@NotNull Collection<PsiClass> classes,
+                                                                @NotNull Project project,
+                                                                @NotNull ViewSettings settings,
+                                                                @NotNull GrailsDisplayableArtefactHandler artefactHandler,
+                                                                @NotNull List<String> packageFqn) {
+    return getClassNodes(classes, project, settings, artefactHandler, fqnString(packageFqn));
+  }
+
+  static @NotNull Collection<AbstractTreeNode<?>> getClassNodes(@NotNull Collection<PsiClass> classes,
+                                                                @NotNull Project project,
+                                                                @NotNull ViewSettings settings,
+                                                                @NotNull GrailsDisplayableArtefactHandler artefactHandler,
+                                                                @NotNull String packageFqn) {
+    List<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (PsiClass clazz : classes) {
+      if (!packageFqn.equals(getPackageName(clazz))) continue;
+      AbstractTreeNode<?> node = artefactHandler.createNode(clazz, settings);
+      result.add(node != null ? node : new ClassTreeNode(project, clazz, settings));
+    }
+    return result;
+  }
+
+  static @NotNull Collection<AbstractTreeNode<?>> getNodesRegular(@NotNull Collection<PsiClass> classes,
+                                                                  @NotNull Project project,
+                                                                  @NotNull ViewSettings settings,
+                                                                  @NotNull GrailsDisplayableArtefactHandler artefactHandler,
+                                                                  @NotNull List<String> packageFqn) {
+    Collection<List<String>> packageFqns = getPackagesRegular(classes, packageFqn, settings.isHideEmptyMiddlePackages());
+    List<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (List<String> relativeParts : packageFqns) {
+      result.add(new GrailsRegularPackageNode(project, settings, new CompactedFqn(packageFqn, relativeParts)));
+    }
+    result.addAll(getClassNodes(classes, project, settings, artefactHandler, packageFqn));
+    return result;
+  }
+
+  static @NotNull Collection<List<String>> getPackagesRegular(@NotNull Collection<PsiClass> classes,
+                                                              @NotNull List<String> basePackage,
+                                                              boolean hideEmptyMiddlePackages) {
+    StringBuilder prefixBuilder = new StringBuilder();
+    for (String part : basePackage) {
+      prefixBuilder.append(part).append('.');
+    }
+    String packagePrefix = prefixBuilder.toString();
+
+    Collection<String> fullPackageNames = new LinkedHashSet<>();
+    for (PsiClass clazz : classes) {
+      String packageName = getPackageName(clazz);
+      if (packageName != null && packageName.startsWith(packagePrefix)) {
+        fullPackageNames.add(packageName);
+      }
+    }
+
+    // Faithful to the Kotlin: substringAfter(prefix, "") strips everything up to and including
+    // the first occurrence of the prefix, and split(".") is a literal split. The original's
+    // trailing `filter { !it.isEmpty() }` dropped empty *lists*, which split never produces --
+    // so a class in the default package (relative == "") contributed [""] and was kept. That
+    // surfaces as a package node with an empty name; preserved here rather than quietly changed,
+    // because it is a pre-existing behaviour and not part of this conversion.
+    List<List<String>> relativePackageParts = new ArrayList<>();
+    for (String fullPackageName : fullPackageNames) {
+      int idx = fullPackageName.indexOf(packagePrefix);
+      String relative = idx < 0 ? "" : fullPackageName.substring(idx + packagePrefix.length());
+      relativePackageParts.add(List.of(relative.split("\\.", -1)));
+    }
+
+    if (hideEmptyMiddlePackages) {
+      List<List<String>> nonEmptyPackageParts = new ArrayList<>();
+      List<List<String>> emptyPackageParts = new ArrayList<>();
+      for (List<String> parts : relativePackageParts) {
+        (parts.size() == 1 ? nonEmptyPackageParts : emptyPackageParts).add(parts);
+      }
+
+      Set<String> nonEmptyPackageNames = new HashSet<>();
+      for (List<String> parts : nonEmptyPackageParts) {
+        nonEmptyPackageNames.add(parts.get(0));
+      }
+
+      List<List<String>> result = new ArrayList<>(nonEmptyPackageParts);
+      for (List<String> parts : emptyPackageParts) {
+        if (!nonEmptyPackageNames.contains(parts.get(0))) {
+          result.add(parts);
+        }
+      }
+      return result;
+    }
+
+    // Only the first segment matters here, de-duplicated, each wrapped as a single-element list.
+    Set<String> firstParts = new HashSet<>();
+    for (List<String> parts : relativePackageParts) {
+      firstParts.add(parts.get(0));
+    }
+    List<List<String>> result = new ArrayList<>(firstParts.size());
+    for (String part : firstParts) {
+      result.add(List.of(part));
+    }
+    return result;
+  }
+
+  private static @Nullable String getPackageName(@NotNull PsiClass clazz) {
+    String qualifiedName = clazz.getQualifiedName();
+    return qualifiedName != null ? StringUtil.getPackageName(qualifiedName) : null;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/ep.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java
similarity index 71%
rename from src/org/jetbrains/plugins/grails/projectView/api/ep.kt
rename to src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java
index a8a6d0a..fbcedc2 100644
--- a/src/org/jetbrains/plugins/grails/projectView/api/ep.kt
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package org.jetbrains.plugins.grails.projectView.api
+package org.jetbrains.plugins.grails.projectView.nodes;
 
-import com.intellij.openapi.extensions.ExtensionPointName
+import org.jetbrains.annotations.NotNull;
 
-val EP_NAME: ExtensionPointName<GrailsViewNodeProvider> = ExtensionPointName.create("org.intellij.grails.viewNodeProvider")
\ No newline at end of file
+/** Node value for a flattened package; identity is the fqn, as with the Kotlin data class. */
+record GrailsPackageValue(@NotNull String packageFqn) {
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java
new file mode 100644
index 0000000..690b40f
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.AbstractPsiBasedNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiClass;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.plugins.GrailsPluginDescriptor;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.Collection;
+import java.util.List;
+
+public class GrailsPluginNode extends AbstractPsiBasedNode<GrailsPluginDescriptor> {
+
+  private volatile GrailsApplication grailsApplication;
+
+  public GrailsPluginNode(Project project, @NotNull GrailsPluginDescriptor value, @NotNull ViewSettings settings) {
+    super(project, value, settings);
+    myName = value.getPluginName();
+  }
+
+  public @NotNull GrailsApplication getGrailsApplication() {
+    GrailsApplication result = grailsApplication;
+    if (result == null) {
+      result = TreeNodeUtil.findNotNullValueOfType(this, GrailsApplication.class);
+      grailsApplication = result;
+    }
+    return result;
+  }
+
+  @Override
+  protected @Nullable PsiClass extractPsiFromValue() {
+    GrailsPluginDescriptor value = getValue();
+    return value != null ? value.getPluginClass() : null;
+  }
+
+  @Override
+  protected @NotNull Collection<AbstractTreeNode<?>> getChildrenImpl() {
+    return List.of();
+  }
+
+  @Override
+  public boolean isAlwaysLeaf() {
+    return true;
+  }
+
+  @Override
+  protected void updateImpl(@NotNull PresentationData data) {
+    GrailsPluginDescriptor value = getValue();
+    data.setIcon(GroovyMvcIcons.Groovy_mvc_plugin);
+    data.addText(value.getPluginName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
+    String version = value.getPluginVersion();
+    data.addText(" " + (version != null ? version : getGrailsApplication().getGrailsVersion()),
+                 SimpleTextAttributes.REGULAR_ATTRIBUTES);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.kt
deleted file mode 100644
index e42749d..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.AbstractPsiBasedNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.project.Project
-import com.intellij.psi.PsiClass
-import com.intellij.ui.SimpleTextAttributes
-import com.intellij.util.lazyUnsafe
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.plugins.GrailsPluginDescriptor
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-class GrailsPluginNode(
-    project: Project,
-    value: GrailsPluginDescriptor,
-    settings: ViewSettings
-) : AbstractPsiBasedNode<GrailsPluginDescriptor>(project, value, settings) {
-
-  init {
-    myName = value.pluginName
-  }
-
-  val grailsApplication: GrailsApplication by lazyUnsafe { findNotNullValueOfType<GrailsApplication>() }
-
-  override fun extractPsiFromValue(): PsiClass? = value?.pluginClass
-
-  override fun getChildrenImpl(): Collection<AbstractTreeNode<*>> = emptyList()
-
-  override fun isAlwaysLeaf(): Boolean = true
-
-  override fun updateImpl(data: PresentationData) {
-    data.apply {
-      setIcon(GroovyMvcIcons.Groovy_mvc_plugin)
-      addText(value.pluginName, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)
-      addText(" ${value.pluginVersion ?: grailsApplication.grailsVersion}", SimpleTextAttributes.REGULAR_ATTRIBUTES)
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java
new file mode 100644
index 0000000..f000b51
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.ide.projectView.impl.nodes.PsiFileSystemItemFilter;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFileSystemItem;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.Icon;
+
+public class GrailsPsiDirectoryNode extends PsiDirectoryNode {
+
+  private final Icon nodeIcon;
+  private final int nodeWeight;
+  private final String nodeTitle;
+
+  public GrailsPsiDirectoryNode(@NotNull PsiDirectory directory, @NotNull ViewSettings settings) {
+    this(directory, settings, null, 3, null, null);
+  }
+
+  public GrailsPsiDirectoryNode(@NotNull PsiDirectory directory, @NotNull ViewSettings settings, int nodeWeight) {
+    this(directory, settings, null, nodeWeight, null, null);
+  }
+
+  public GrailsPsiDirectoryNode(@NotNull PsiDirectory directory,
+                                @NotNull ViewSettings settings,
+                                int nodeWeight,
+                                @Nullable PsiFileSystemItemFilter filter) {
+    this(directory, settings, null, nodeWeight, null, filter);
+  }
+
+  public GrailsPsiDirectoryNode(@NotNull PsiDirectory directory,
+                                @NotNull ViewSettings settings,
+                                @Nullable Icon nodeIcon,
+                                int nodeWeight,
+                                @Nullable String nodeTitle) {
+    this(directory, settings, nodeIcon, nodeWeight, nodeTitle, null);
+  }
+
+  public GrailsPsiDirectoryNode(@NotNull PsiDirectory directory,
+                                @NotNull ViewSettings settings,
+                                @Nullable Icon nodeIcon,
+                                int nodeWeight,
+                                @Nullable String nodeTitle,
+                                @Nullable PsiFileSystemItemFilter filter) {
+    super(directory.getProject(), directory, settings, filter);
+    this.nodeIcon = nodeIcon;
+    this.nodeWeight = nodeWeight;
+    this.nodeTitle = nodeTitle;
+  }
+
+  public @Nullable Icon getNodeIcon() {
+    return nodeIcon;
+  }
+
+  public int getNodeWeight() {
+    return nodeWeight;
+  }
+
+  @Override
+  protected void updateImpl(@NotNull PresentationData data) {
+    super.updateImpl(data);
+    if (nodeIcon != null) data.setIcon(nodeIcon);
+    if (nodeTitle != null) data.setPresentableText(nodeTitle);
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.kt
deleted file mode 100644
index 1e6a170..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode
-import com.intellij.psi.PsiDirectory
-import com.intellij.psi.PsiFileSystemItem
-import javax.swing.Icon
-
-open class GrailsPsiDirectoryNode(
-  directory: PsiDirectory,
-  settings: ViewSettings,
-  val nodeIcon: Icon? = null,
-  val nodeWeight: Int = 3,
-  private val nodeTitle: String? = null,
-  filter: ((PsiFileSystemItem) -> Boolean)? = null
-) : PsiDirectoryNode(directory.project, directory, settings, filter) {
-
-  override fun updateImpl(data: PresentationData) {
-    super.updateImpl(data)
-    data.apply {
-      nodeIcon?.let { setIcon(it) }
-      nodeTitle?.let { presentableText = it }
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java
new file mode 100644
index 0000000..83bea7f
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.ide.util.treeView.ValidateableNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.IconManager;
+import com.intellij.ui.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+
+public class GrailsRegularPackageNode extends GrailsArtefactPackageNode<CompactedFqn> implements ValidateableNode {
+
+  public GrailsRegularPackageNode(Project project, @NotNull ViewSettings settings, @NotNull CompactedFqn fqns) {
+    super(project, settings, fqns);
+  }
+
+  @Override
+  public @NotNull String getPackageFqn() {
+    return getValue().toString();
+  }
+
+  /**
+   * This method checks if this node contains compacted fqns and that all compacted fqns are still empty.
+   *
+   * <p>Consider the structure:
+   * <pre>
+   * - com
+   *   - foo.bar // &lt;- this node
+   *     - Something
+   * </pre>
+   * In the above case we need to ensure that {@code com.foo} package is still empty.
+   * If there is something to show under {@code com.foo}, then we consider this node invalid,
+   * so {@code com} children would be recomputed, resulting in the new structure:
+   * <pre>
+   * - com
+   *     - foo
+   *       - SomethingNew
+   *       - bar
+   *         - Something
+   * </pre>
+   */
+  @Override
+  public boolean isValid() {
+    CompactedFqn value = getValue();
+    if (value.getHasCompactedFqns() && getSettings().isHideEmptyMiddlePackages()) {
+      Collection<List<String>> newPackages = GrailsNodes.getPackagesRegular(getArtefacts(), value.getBaseFqn(), true);
+      return newPackages.contains(value.getRelativeParts());
+    }
+    return true;
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData presentation) {
+    presentation.setIcon(IconManager.getInstance().getPlatformIcon(PlatformIcons.Package));
+    presentation.setPresentableText(GrailsNodes.fqnString(getValue().getRelativeParts()));
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildren() {
+    return GrailsNodes.getNodesRegular(getArtefacts(), Objects.requireNonNull(getProject()), getSettings(),
+                                       getArtefactHandler(), getValue().getAllParts());
+  }
+
+  @Override
+  protected @NotNull Collection<VirtualFile> getNodeDirectories() {
+    Collection<VirtualFile> result = new HashSet<>();
+    for (String fqn : getValue().getExpandedFqns()) {
+      result.addAll(packageDirectories(fqn));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    for (VirtualFile directory : packageDirectories(getValue().getExpandedFqns().get(0))) {
+      if (VfsUtil.isAncestor(directory, file, true)) return true;
+    }
+    return false;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.kt
deleted file mode 100644
index 4fca3e4..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.ValidateableNode
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VfsUtil
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.ui.IconManager
-import com.intellij.ui.PlatformIcons
-
-class GrailsRegularPackageNode(
-  project: Project,
-  settings: ViewSettings,
-  fqns: CompactedFqn
-) : GrailsArtefactPackageNode<CompactedFqn>(project, settings, fqns), ValidateableNode {
-
-  override val packageFqn get() = value.toString()
-
-  /**
-   * This method checks if this node contains compacted fqns and that all compacted fqns are still empty.
-   *
-   * Consider the structure:
-   * ```
-   * - com
-   *   - foo.bar // <- this node
-   *     - Something
-   * ```
-   * In the above case we need to ensure that `com.foo` package is still empty.
-   * If there is something to show under `com.foo`, then we consider this node invalid,
-   * so `com` children would be recomputed, resulting in the new structure:
-   * ```
-   * - com
-   *     - foo
-   *       - SomethingNew
-   *       - bar
-   *         - Something
-   * ```
-   */
-  override fun isValid(): Boolean {
-    val value = value
-    if (value.hasCompactedFqns && settings.isHideEmptyMiddlePackages) {
-      val (baseFqn, relativeParts) = value
-      val newPackages = getPackagesRegular(artefacts, baseFqn, true)
-      return relativeParts in newPackages
-    }
-    return true
-  }
-
-  override fun update(presentation: PresentationData) {
-    presentation.setIcon(IconManager.getInstance().getPlatformIcon(PlatformIcons.Package))
-    presentation.presentableText = value.relativeParts.fqnString()
-  }
-
-  override fun getChildren(): TreeNodes = getNodesRegular(artefacts, project!!, settings, artefactHandler, value.allParts)
-
-  override val nodeDirectories: Collection<VirtualFile> get() = value.expandedFqns.flatMapTo(HashSet(), ::packageDirectories)
-
-  override fun contains(file: VirtualFile): Boolean {
-    return packageDirectories(value.expandedFqns.first()).any {
-      VfsUtil.isAncestor(it, file, true)
-    }
-  }
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java
new file mode 100644
index 0000000..86cb4dc
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GrailsRootNode extends ProjectViewNode<Project> {
+
+  public GrailsRootNode(@NotNull Project project, @NotNull ViewSettings viewSettings) {
+    super(project, project, viewSettings);
+  }
+
+  @Override
+  public @NotNull List<GrailsApplicationNode> getChildren() {
+    List<GrailsApplicationNode> result = new ArrayList<>();
+    for (GrailsApplication application : GrailsApplicationManager.getInstance(getValue()).getApplications()) {
+      result.add(new GrailsApplicationNode(application, getSettings()));
+    }
+    return result;
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData presentation) {
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    return GrailsApplicationManager.getInstance(getValue()).findApplication(file) != null;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.kt
deleted file mode 100644
index b23d42d..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ProjectViewNode
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-
-class GrailsRootNode(project: Project, viewSettings: ViewSettings) : ProjectViewNode<Project>(project, project, viewSettings) {
-
-  override fun getChildren(): List<GrailsApplicationNode> = GrailsApplicationManager.getInstance(value).applications.map {
-    GrailsApplicationNode(it, settings)
-  }
-
-  override fun update(presentation: PresentationData): Unit = Unit
-
-  override fun contains(file: VirtualFile): Boolean = GrailsApplicationManager.getInstance(value).findApplication(file) != null
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java
new file mode 100644
index 0000000..43b48b5
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.config.GrailsFramework;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.structure.OldGrailsApplication;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class OldGrailsPluginsNode extends ProjectViewNode<String> {
+
+  public OldGrailsPluginsNode(@NotNull Project project, @NotNull ViewSettings settings) {
+    super(project, "plugins", settings);
+  }
+
+  public @NotNull OldGrailsApplication getGrailsApplication() {
+    return (OldGrailsApplication)getParentValue();
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildren() {
+    OldGrailsApplication application = getGrailsApplication();
+    PsiManager manager = PsiManager.getInstance(application.getProject());
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    for (VirtualFile root : GrailsFramework.getInstance().getCommonPluginRoots(application.getModule(), false)) {
+      PsiDirectory directory = manager.findDirectory(root);
+      if (directory != null) {
+        result.add(new GrailsPsiDirectoryNode(directory, getSettings(), GroovyMvcIcons.Groovy_mvc_plugin,
+                                              NodeWeights.FOLDER, directory.getName()));
+      }
+    }
+    return result;
+  }
+
+  @Override
+  protected void update(@NotNull PresentationData data) {
+    data.setIcon(GroovyMvcIcons.Groovy_mvc_plugin);
+    data.setPresentableText("Plugins");
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    for (AbstractTreeNode<?> child : getChildren()) {
+      if (child instanceof GrailsPsiDirectoryNode node && node.contains(file)) return true;
+    }
+    return false;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.kt
deleted file mode 100644
index 82041e5..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ProjectViewNode
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiManager
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.config.GrailsFramework
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.structure.OldGrailsApplication
-
-class OldGrailsPluginsNode(project: Project, settings: ViewSettings) : ProjectViewNode<String>(project, "plugins", settings) {
-
-  val grailsApplication: OldGrailsApplication get() = parentValue as OldGrailsApplication
-
-  override fun getChildren(): Collection<AbstractTreeNode<*>> {
-    val manager = PsiManager.getInstance(grailsApplication.project)
-    return GrailsFramework.getInstance().getCommonPluginRoots(grailsApplication.module, false).mapNotNull {
-      manager.findDirectory(it)?.let {
-        GrailsPsiDirectoryNode(it, settings, GroovyMvcIcons.Groovy_mvc_plugin, NodeWeights.FOLDER, it.name)
-      }
-    }
-  }
-
-  override fun update(data: PresentationData) {
-    data.apply {
-      setIcon(GroovyMvcIcons.Groovy_mvc_plugin)
-      presentableText = "Plugins"
-    }
-  }
-
-  override fun contains(file: VirtualFile): Boolean = children.any { it is GrailsPsiDirectoryNode && it.contains(file) }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java b/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java
new file mode 100644
index 0000000..284b373
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers;
+import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler;
+import org.jetbrains.plugins.grails.projectView.NodeWeights;
+import org.jetbrains.plugins.grails.projectView.impl.GrailsViewItems;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+public class OtherGrailsAppSourcesNode extends GrailsPsiDirectoryNode {
+
+  private volatile GrailsApplication grailsApplication;
+
+  public OtherGrailsAppSourcesNode(@NotNull PsiDirectory directory, @NotNull ViewSettings settings) {
+    super(directory, settings, PlatformIcons.SOURCE_FOLDERS_ICON, NodeWeights.OTHER_GRAILS_APP_FOLDER, null);
+  }
+
+  public @NotNull GrailsApplication getGrailsApplication() {
+    GrailsApplication result = grailsApplication;
+    if (result == null) {
+      result = TreeNodeUtil.findNotNullValueOfType(this, GrailsApplication.class);
+      grailsApplication = result;
+    }
+    return result;
+  }
+
+  @Override
+  public @NotNull Collection<AbstractTreeNode<?>> getChildrenImpl() {
+    GrailsApplication application = getGrailsApplication();
+    Project project = application.getProject();
+    PsiManager manager = PsiManager.getInstance(project);
+    Set<String> specialNames = GrailsViewItems.SPECIAL_GRAILS_APP_FOLDERS.keySet();
+
+    // directories from nodes that are showed separately
+    Set<VirtualFile> visibleArtefactDirs = new LinkedHashSet<>();
+    for (GrailsDisplayableArtefactHandler handler : ArtefactHandlers.displayableArtefactHandlers()) {
+      if (!handler.isVisible(application)) continue;
+      VirtualFile directory = handler.getDirectory(application);
+      if (directory != null) visibleArtefactDirs.add(directory);
+    }
+    List<VirtualFile> artefactDirs = new ArrayList<>();
+    for (VirtualFile dir : visibleArtefactDirs) {
+      if (!specialNames.contains(dir.getName())) artefactDirs.add(dir);
+    }
+
+    // other grails-app directories excluding special, special are showed separately
+    List<VirtualFile> otherDirs = new ArrayList<>();
+    for (VirtualFile child : application.getAppRoot().getChildren()) {
+      if (!artefactDirs.contains(child) && !specialNames.contains(child.getName())) otherDirs.add(child);
+    }
+
+    Collection<AbstractTreeNode<?>> result = new ArrayList<>();
+    // do not show artefact nodes under these directories
+    for (VirtualFile dir : artefactDirs) {
+      PsiDirectory directory = manager.findDirectory(dir);
+      if (directory != null) {
+        result.add(new PsiDirectoryNode(project, directory, getSettings(), GrailsViewItems::shouldShowItem));
+      }
+    }
+    for (VirtualFile dir : otherDirs) {
+      PsiDirectory directory = manager.findDirectory(dir);
+      if (directory != null) {
+        result.add(new PsiDirectoryNode(project, directory, getSettings()));
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    if (!super.contains(file)) return false;
+    PsiFile psiFile = PsiManager.getInstance(Objects.requireNonNull(getProject())).findFile(file);
+    return psiFile != null && GrailsViewItems.shouldShowItem(psiFile);
+  }
+
+  @Override
+  protected void updateImpl(@NotNull PresentationData data) {
+    super.updateImpl(data);
+    data.setLocationString("Other sources");
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt
deleted file mode 100644
index 0c99d65..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.PresentationData
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiDirectory
-import com.intellij.psi.PsiManager
-import com.intellij.util.PlatformIcons
-import com.intellij.util.lazyUnsafe
-import org.jetbrains.plugins.grails.artefact.api.ArtefactHandlers
-import org.jetbrains.plugins.grails.projectView.NodeWeights
-import org.jetbrains.plugins.grails.projectView.impl.shouldShowItem
-import org.jetbrains.plugins.grails.projectView.impl.specialGrailsAppFolders
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-class OtherGrailsAppSourcesNode(
-  directory: PsiDirectory,
-  settings: ViewSettings
-) : GrailsPsiDirectoryNode(
-  directory,
-  settings,
-  nodeWeight = NodeWeights.OTHER_GRAILS_APP_FOLDER,
-  nodeIcon = PlatformIcons.SOURCE_FOLDERS_ICON
-) {
-
-  val grailsApplication: GrailsApplication by lazyUnsafe { findNotNullValueOfType<GrailsApplication>() }
-
-  override fun getChildrenImpl(): Collection<AbstractTreeNode<*>> {
-    val application = grailsApplication
-    val project = application.project
-    val manager = PsiManager.getInstance(project)
-
-    // directories from nodes that are showed separately
-    val artefactDirs = ArtefactHandlers.displayableArtefactHandlers().filter {
-      it.isVisible(application)
-    }.mapNotNullTo(HashSet()) {
-      it.getDirectory(application)
-    }.filter {
-      it.name !in specialGrailsAppFolders.keys
-    }
-
-    // other grails-app directories excluding special, special are showed separately
-    val otherDirs = application.appRoot.children.filter {
-      it !in artefactDirs && it.name !in specialGrailsAppFolders.keys
-    }
-
-    // do not show artefact nodes under these directories
-    val artefactDirNodes = artefactDirs.mapNotNull {
-      manager.findDirectory(it)
-    }.map { PsiDirectoryNode(project, it, settings, ::shouldShowItem) }
-
-    val otherDirNodes = otherDirs.mapNotNull {
-      manager.findDirectory(it)
-    }.map { PsiDirectoryNode(project, it, settings) }
-
-    return artefactDirNodes + otherDirNodes
-  }
-
-  override fun contains(file: VirtualFile): Boolean = super.contains(file) && PsiManager.getInstance(project!!).findFile(file)?.let {
-    shouldShowItem(it)
-  } ?: false
-
-  override fun updateImpl(data: PresentationData) {
-    super.updateImpl(data)
-    data.apply {
-      locationString = "Other sources"
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java b/src/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java
new file mode 100644
index 0000000..1f97e03
--- /dev/null
+++ b/src/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.grails.projectView.nodes;
+
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+
+import java.util.Objects;
+
+public final class TreeNodeUtil {
+
+  private TreeNodeUtil() {
+  }
+
+  /**
+   * Walks up from the node's parent looking for the first ancestor value of the given type.
+   * These were reified inline extensions in Kotlin; Java needs the type passed explicitly.
+   */
+  public static <T> @Nullable T findValueOfType(@NotNull AbstractTreeNode<?> node, @NotNull Class<T> type) {
+    AbstractTreeNode<?> current = node.getParent();
+    while (current != null) {
+      Object value = current.getValue();
+      if (type.isInstance(value)) {
+        return type.cast(value);
+      }
+      current = current.getParent();
+    }
+    return null;
+  }
+
+  public static <T> @NotNull T findNotNullValueOfType(@NotNull AbstractTreeNode<?> node, @NotNull Class<T> type) {
+    return Objects.requireNonNull(findValueOfType(node, type),
+                                  () -> "no ancestor value of type " + type.getName() + " above " + node);
+  }
+
+  public static boolean mayContain(@NotNull GrailsApplication application, @NotNull VirtualFile file) {
+    PsiFile psiFile = PsiManager.getInstance(application.getProject()).findFile(file);
+    return psiFile instanceof GroovyFile groovyFile && groovyFile.getClasses().length == 1;
+  }
+}
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/impl.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/impl.kt
deleted file mode 100644
index 1043818..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/impl.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.projectView.ViewSettings
-import com.intellij.ide.projectView.impl.nodes.ClassTreeNode
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.psi.PsiClass
-import org.jetbrains.plugins.grails.artefact.api.GrailsDisplayableArtefactHandler as ArtefactHandler
-
-internal fun getArtefactNodes(project: Project,
-                              settings: ViewSettings,
-                              artefactHandler: ArtefactHandler,
-                              artefacts: Classes): TreeNodes {
-  if (settings.isFlattenPackages) {
-    val packages = getPackagesFlattened(artefacts, settings.isHideEmptyMiddlePackages)
-    val packageNodes = packages.map { GrailsFlatPackageNode(project, settings, it) }
-    val classNodes = artefacts.getClassNodes(project, settings, artefactHandler, emptyList())
-    return packageNodes + classNodes
-  }
-  else {
-    return getNodesRegular(artefacts, project, settings, artefactHandler, emptyList())
-  }
-}
-
-private fun getPackagesFlattened(classes: Classes, hideEmptyMiddlePackages: Boolean): Collection<String> {
-  val classPackageFqns = classes.mapNotNullTo(LinkedHashSet(), ::getPackageName)
-  val packageFqns = classPackageFqns.toMutableSet()
-  if (!hideEmptyMiddlePackages) {
-    for (fqn in classPackageFqns) {
-      var current = fqn
-      while (current != "") {
-        current = StringUtil.getPackageName(current)
-        packageFqns += current
-      }
-    }
-  }
-  packageFqns.remove("")
-  return packageFqns
-}
-
-internal fun Classes.getClassNodes(project: Project,
-                                   settings: ViewSettings,
-                                   artefactHandler: ArtefactHandler,
-                                   packageFqn: List<String>): TreeNodes {
-  return getClassNodes(project, settings, artefactHandler, packageFqn.fqnString())
-}
-
-internal fun Classes.getClassNodes(project: Project,
-                                   settings: ViewSettings,
-                                   artefactHandler: ArtefactHandler,
-                                   packageFqn: String): TreeNodes {
-  val classes = getClassesInPackage(packageFqn)
-  return classes.map { artefactHandler.createNode(it, settings) ?: ClassTreeNode(project, it, settings) }
-}
-
-internal fun getNodesRegular(classes: Classes,
-                             project: Project,
-                             settings: ViewSettings,
-                             artefactHandler: ArtefactHandler,
-                             packageFqn: List<String>): TreeNodes {
-  val packageFqns = getPackagesRegular(classes, packageFqn, settings.isHideEmptyMiddlePackages)
-  val packageNodes = packageFqns.map { GrailsRegularPackageNode(project, settings, CompactedFqn(packageFqn, it)) }
-  val classNodes = classes.getClassNodes(project, settings, artefactHandler, packageFqn)
-  return packageNodes + classNodes
-}
-
-internal fun getPackagesRegular(classes: Classes, basePackage: List<String>, hideEmptyMiddlePackages: Boolean): Collection<List<String>> {
-  val packagePrefix = basePackage.joinToString("") { "$it." }
-  val fullPackageNames: Collection<String> = classes.mapNotNullTo(LinkedHashSet(), ::getPackageName).filter {
-    it.startsWith(packagePrefix)
-  }
-  val relativePackageParts: Collection<List<String>> = fullPackageNames.asSequence().map {
-    it.substringAfter(packagePrefix, "")
-  }.map {
-    it.split(".")
-  }.filter {
-    !it.isEmpty()
-  }.toList()
-  if (hideEmptyMiddlePackages) {
-    val (nonEmptyPackageParts, emptyPackageParts) = relativePackageParts.partition { it.size == 1 }
-    val nonEmptyPackageNames: Set<String> = nonEmptyPackageParts.mapTo(HashSet()) { it[0] }
-    val otherPackageParts = emptyPackageParts.filter { it[0] !in nonEmptyPackageNames }
-    return nonEmptyPackageParts + otherPackageParts
-  }
-  else {
-    return relativePackageParts.mapTo(HashSet()) { it[0] }.map(::listOf)
-  }
-}
-
-private fun getPackageName(clazz: PsiClass): String? = clazz.qualifiedName?.let(StringUtil::getPackageName)
-
-private fun Classes.getClassesInPackage(basePackage: String) = filter { clazz ->
-  getPackageName(clazz) == basePackage
-}
-
-fun List<String>.fqnString(): String = joinToString(".")
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/util.kt b/src/org/jetbrains/plugins/grails/projectView/nodes/util.kt
deleted file mode 100644
index fb538ae..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/nodes/util.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView.nodes
-
-import com.intellij.ide.util.treeView.AbstractTreeNode
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiManager
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-
-typealias TreeNodes = Collection<AbstractTreeNode<*>>
-typealias Classes = Collection<PsiClass>
-
-internal inline fun <reified T : Any> AbstractTreeNode<*>.findNotNullValueOfType(): T {
-  return findValueOfType()!!
-}
-
-internal inline fun <reified T : Any> AbstractTreeNode<*>.findValueOfType(): T? {
-  var current: AbstractTreeNode<*>? = this.parent
-  while (current != null) {
-    val value = current.value
-    if (value is T) return value
-    current = current.parent
-  }
-  return null
-}
-
-fun mayContain(application: GrailsApplication, file: VirtualFile): Boolean {
-  val psiFile = PsiManager.getInstance(application.project).findFile(file) ?: return false
-  return psiFile is GroovyFile && psiFile.classes.size == 1
-}
diff --git a/src/org/jetbrains/plugins/grails/projectView/showHide.kt b/src/org/jetbrains/plugins/grails/projectView/showHide.kt
deleted file mode 100644
index bc85726..0000000
--- a/src/org/jetbrains/plugins/grails/projectView/showHide.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.projectView
-
-import com.intellij.ide.projectView.ProjectView
-import com.intellij.ide.projectView.impl.AbstractProjectViewPane
-import com.intellij.openapi.project.Project
-import org.jetbrains.plugins.grails.config.GrailsConstants
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-import java.util.function.Predicate
-
-internal const val ID = GrailsConstants.GRAILS
-
-internal fun showHide(project: Project) {
-  val grailsApplicationManager = GrailsApplicationManager.getInstance(project)
-  val projectView = ProjectView.getInstance(project)
-  if (grailsApplicationManager.hasApplications()) {
-    if (ID !in projectView.paneIds) {
-      projectView.addProjectPane(getPane(project))
-    }
-  }
-  else if (ID in projectView.paneIds) {
-    projectView.removeProjectPane(getPane(project))
-  }
-}
-
-private fun getPane(project: Project) = AbstractProjectViewPane.EP.findFirstSafe(project, Predicate { it.id == ID })!!
\ No newline at end of file
diff --git a/src/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java b/src/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java
index 336120b..a8f5129 100644
--- a/src/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java
+++ b/src/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java
@@ -29,7 +29,7 @@ import kotlin.Unit;
 import kotlin.coroutines.Continuation;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.grails.projectView.ShowHideKt;
+import org.jetbrains.plugins.grails.projectView.GrailsProjectViewPanes;
 import org.jetbrains.plugins.grails.references.TraitInjectorService;
 import org.jetbrains.plugins.grails.runner.GrailsCommandExecutor;
 import org.jetbrains.plugins.grails.service.GrailsBackgroundService;
@@ -60,7 +60,7 @@ public final class GrailsStartupActivity implements ProjectActivity, DumbAware {
         backgroundService.run(new GrailsRunConfigurationTask(project));
       }
       backgroundService.run(new GrailsSdkCheckTask(project));
-      ApplicationManager.getApplication().invokeLater(() -> ShowHideKt.showHide(project), project.getDisposed());
+      ApplicationManager.getApplication().invokeLater(() -> GrailsProjectViewPanes.showHide(project), project.getDisposed());
     });
 
     connection.subscribe(ModuleRootListener.TOPIC, new ModuleRootListener() {


(grails-intellij-plugin) 22/35: Centralize repository declarations in a settings plugin

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit b40eecea31e6dd9494d41ec8b888940f32df60f1
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 13:01:14 2026 -0400

    Centralize repository declarations in a settings plugin
    
    Adds org.apache.grails.intellij.build.repositories, a precompiled *settings*
    script plugin (the .settings.gradle suffix makes it target Settings rather than
    Project) applied from settings.gradle. It is the single source of truth for where
    dependencies come from, and it sets FAIL_ON_PROJECT_REPOS so the centralization is
    enforced rather than merely conventional.
    
    Verified enforcement is real, not just declared: adding repositories { } to a
    module fails with "Build was configured to prefer settings repositories over
    project repositories but repository 'MavenRepo' was added by build file
    pluginModules/hibernate/build.gradle".
    
    The IntelliJ Platform repositories come from org.jetbrains.intellij.platform.settings,
    which registers the intellijPlatform repositories extension onto
    dependencyResolutionManagement -- so even those no longer need a per-project block.
    
    This removes duplicate repositories { } blocks from two convention plugins that had
    to be kept in sync, and closes the failure mode that surfaced during the plugin
    move: the root project silently lost its repositories along with the java plugin,
    and RAT -- which resolves its own tool jar at execution time -- failed the
    configuration cache with "Cannot resolve external dependency
    org.apache.rat:apache-rat because no repositories are defined". Repositories are
    now independent of which convention plugins a project happens to apply.
    
    build-logic/settings.gradle keeps its own declarations: it is a separate build and
    cannot consume a plugin it has not yet compiled.
    
    Packaging is byte-identical, 987 tests / 0 failures, RAT clean, configuration cache
    still reused.
---
 build-logic/README.md                              | 25 ++++++++++-
 .../org.apache.grails.intellij.build.base.gradle   |  8 ++--
 ....grails.intellij.build.intellij-platform.gradle |  5 ---
 ...ils.intellij.build.repositories.settings.gradle | 48 ++++++++++++++++++++++
 settings.gradle                                    |  4 ++
 5 files changed, 80 insertions(+), 10 deletions(-)

diff --git a/build-logic/README.md b/build-logic/README.md
index 2de40f6..8883d85 100644
--- a/build-logic/README.md
+++ b/build-logic/README.md
@@ -7,6 +7,10 @@ Written as **precompiled Groovy script plugins** in `src/main/groovy`, named aft
 so a subproject build file is one `plugins { }` line plus its own dependencies. Deliberately not
 imperative `Plugin<Project>` classes.
 
+One of them targets `Settings` rather than `Project`: the `.settings.gradle` suffix on
+`org.apache.grails.intellij.build.repositories.settings.gradle` makes it a settings plugin, applied
+from `settings.gradle`. That is what keeps repository declarations out of the projects entirely.
+
 ## Why plugin versions live here
 
 `build-logic/build.gradle` declares the IntelliJ Platform, Kotlin, RAT and Sonatype scan plugins as
@@ -37,7 +41,7 @@ build still come from the root `gradle.properties` via `providers.gradleProperty
 | `rat` | root only — the licence audit |
 | `coverage-aggregation` | root only — cross-project JaCoCo |
 | `reproducible`, `jacoco`, `vulnerability-scan` | applied transitively |
-| `legacy-layout` | **transitional** — delete once all tiers use standard source layout |
+| `repositories` | **settings** plugin — applied from `settings.gradle`, not by a project |
 
 ## Groovy DSL gotchas
 
@@ -49,3 +53,22 @@ build still come from the root `gradle.properties` via `providers.gradleProperty
   see how `testFramework(...)` is called in `testFramework/build.gradle`.
 - There are no type-safe accessors. Run a real build after each change; a typo is a
   configuration-time `MissingMethodException`, not a compile error.
+
+## Repositories
+
+All dependency repositories are declared once, by the `repositories` settings plugin, and
+`dependencyResolutionManagement` is set to `FAIL_ON_PROJECT_REPOS`. A project that declares its own
+`repositories { }` block now fails the build:
+
+> Build was configured to prefer settings repositories over project repositories but repository
+> 'MavenRepo' was added by build file 'pluginModules/hibernate/build.gradle'
+
+The IntelliJ Platform repositories come from `org.jetbrains.intellij.platform.settings`, which
+registers the `intellijPlatform` repositories extension onto `dependencyResolutionManagement`.
+
+This replaced `repositories { }` blocks in two separate convention plugins that had to be kept in
+sync — and whose absence on the root project broke the licence audit the moment the root stopped
+applying the java conventions, because RAT resolves its own tool jar at execution time.
+
+`build-logic/settings.gradle` keeps its own repository declarations: it is a separate build and
+cannot consume a plugin it has not yet compiled.
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle
index 51fc71c..e2de742 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle
@@ -18,11 +18,11 @@
  */
 
 // Conventions shared by every project, including ones that compile nothing.
+//
+// Repositories are NOT declared here: they come from the repositories settings plugin, and
+// dependencyResolutionManagement is set to FAIL_ON_PROJECT_REPOS, so declaring any here would
+// fail the build.
 
 plugins {
     id 'org.apache.grails.intellij.build.reproducible'
 }
-
-repositories {
-    mavenCentral()
-}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
index 5f63a28..7803dd5 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
@@ -31,11 +31,6 @@ plugins {
     id 'org.jetbrains.intellij.platform.base'
 }
 
-repositories {
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
 
 dependencies {
     intellijPlatform {
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.repositories.settings.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.repositories.settings.gradle
new file mode 100644
index 0000000..0a09bf9
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.repositories.settings.gradle
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.gradle.api.initialization.resolve.RepositoriesMode
+
+// The single source of truth for where dependencies come from.
+//
+// This is a precompiled *settings* script plugin (the .settings.gradle suffix), applied from
+// settings.gradle rather than by a project. It replaces per-project `repositories { }` blocks,
+// which previously appeared in two convention plugins and had to be kept in sync -- and whose
+// absence on the root project broke the licence audit the moment the root stopped applying the
+// java conventions.
+//
+// FAIL_ON_PROJECT_REPOS makes the centralisation real: a project that declares its own
+// repositories now fails the build instead of quietly diverging.
+
+plugins {
+    // Registers the intellijPlatform repositories extension onto dependencyResolutionManagement,
+    // so the IntelliJ Platform artifact repositories can be declared here rather than per project.
+    id 'org.jetbrains.intellij.platform.settings'
+}
+
+dependencyResolutionManagement {
+    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
+
+    repositories {
+        mavenCentral()
+        intellijPlatform {
+            defaultRepositories()
+        }
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index e8bb747..a3eae40 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -17,6 +17,10 @@ pluginManagement {
     includeBuild('build-logic')
 }
 
+plugins {
+    id 'org.apache.grails.intellij.build.repositories'
+}
+
 rootProject.name = 'grails-intellij-plugin'
 
 include 'plugin'


(grails-intellij-plugin) 30/35: Convert the five Kotlin test classes to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 8af0a4805872f46f58c6e3e31a12401692c77080
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 15:22:49 2026 -0400

    Convert the five Kotlin test classes to Java
    
    These lean on Kotlin raw strings for the Groovy and GSP fixtures, where the
    exact bytes matter: the fixtures embed <caret>, <selection> and inline
    <error descr="..."> markers that the test framework parses by offset.
    
    A Kotlin raw string keeps the newline immediately after the opening quotes,
    while a Java text block strips it, so every block gains a leading blank line to
    compensate, and content plus closing delimiter sit flush-left so incidental-
    indentation stripping is a no-op. DomainFieldIntentionsTest holds 38 of these,
    so it was transformed by script and then checked by extracting every literal
    from both files and comparing: all 38 are byte-identical. The shorter GSP
    fixtures use explicit escapes instead of text blocks, being single-line.
    
    Two platform test-framework methods are Kotlin with default arguments, which
    Java cannot omit. Their values come from the $default bridges:
    createStdProjectFolders takes "" and importProject takes false. WebRoot is in
    com.intellij.javaee.web, not the .facet subpackage Kotlin's import implied.
    
    Kotlin default parameters in the two i18n intention tests become overloads.
    
    Verified: 186 classes / 987 tests / 0 failures / 0 errors, unchanged; each
    converted class ran its full count (23, 2, 8, 14, 14); rat clean.
---
 ...mporterTest.kt => GrailsMavenImporterTest.java} |  69 +++---
 ...tionsTest.kt => DomainFieldIntentionsTest.java} | 275 ++++++++++++---------
 .../grails/i18n/GrailsGroovyI18nIntentionTest.java | 115 +++++++++
 .../grails/i18n/GrailsGroovyI18nIntentionTest.kt   | 105 --------
 ...ectionTest.kt => GrailsI18nInspectionTest.java} | 131 +++++-----
 .../groovy/grails/i18n/GspI18nIntentionTest.java   | 116 +++++++++
 .../groovy/grails/i18n/GspI18nIntentionTest.kt     | 118 ---------
 7 files changed, 497 insertions(+), 432 deletions(-)

diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.java
similarity index 68%
rename from plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.java
index aa60e20..f6f606c 100644
--- a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.java
@@ -13,20 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.groovy.grails;
 
-package org.jetbrains.plugins.groovy.grails
+import com.intellij.facet.FacetManager;
+import com.intellij.javaee.web.facet.WebFacet;
+import com.intellij.javaee.web.WebRoot;
+import com.intellij.maven.testFramework.MavenMultiVersionImportingTestCase;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.plugins.grails.config.GrailsFramework;
+import org.junit.Test;
 
-import com.intellij.facet.FacetManager
-import com.intellij.javaee.web.facet.WebFacet
-import com.intellij.maven.testFramework.MavenMultiVersionImportingTestCase
-import com.intellij.openapi.vfs.VfsUtil
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.config.GrailsFramework
-import org.junit.Test
-import java.util.Arrays
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
-class GrailsMavenImporterTest : MavenMultiVersionImportingTestCase() {
-  private fun createGrailsStdFolders() {
+public class GrailsMavenImporterTest extends MavenMultiVersionImportingTestCase {
+
+  private void createGrailsStdFolders() {
     createProjectSubDirs("grails-app/conf/hibernate",
                          "grails-app/controllers",
                          "grails-app/domain",
@@ -40,13 +45,13 @@ class GrailsMavenImporterTest : MavenMultiVersionImportingTestCase() {
                          "src/java",
                          "test/unit",
                          "test/integration",
-                         "web-app/WEB-INF")
+                         "web-app/WEB-INF");
   }
 
   @Test
-  fun testImportGrailsProject() {
-    createStdProjectFolders()
-    createGrailsStdFolders()
+  public void testImportGrailsProject() {
+    createStdProjectFolders("");
+    createGrailsStdFolders();
 
     importProject("  <groupId>test</groupId>" +
                   "  <artifactId>project</artifactId>" +
@@ -73,9 +78,9 @@ class GrailsMavenImporterTest : MavenMultiVersionImportingTestCase() {
                   "        <version>1.0</version>" +
                   "      </plugin>" +
                   "    </plugins>" +
-                  "  </build>")
+                  "  </build>", false);
 
-    assertModules("project")
+    assertModules("project");
 
     assertSources("project",
                   "grails-app/controllers",
@@ -90,34 +95,34 @@ class GrailsMavenImporterTest : MavenMultiVersionImportingTestCase() {
                   "src/java",
                   "src/main/java",
                   "src/gwt",
-                  "src/scala")
-    assertDefaultResources("project", "grails-app/resources")
+                  "src/scala");
+    assertDefaultResources("project", "grails-app/resources");
 
     assertTestSources("project",
                       "src/test/java",
                       "test/integration",
                       "test/functional",
-                      "test/unit")
-    assertDefaultTestResources("project")
+                      "test/unit");
+    assertDefaultTestResources("project");
 
-    val module = getModule("project")
+    Module module = getModule("project");
 
-    val webFacet = FacetManager.getInstance(module).findFacet(WebFacet.ID, "GrailsWeb")
-    assertNotNull(webFacet)
+    WebFacet webFacet = FacetManager.getInstance(module).findFacet(WebFacet.ID, "GrailsWeb");
+    assertNotNull(webFacet);
 
-    val appRoot = GrailsFramework.getInstance().findAppRoot(module)
-    assertNotNull(appRoot)
+    VirtualFile appRoot = GrailsFramework.getInstance().findAppRoot(module);
+    assertNotNull(appRoot);
 
-    val shouldBeRoot: MutableList<VirtualFile?> = ArrayList(Arrays.asList(
+    List<VirtualFile> shouldBeRoot = new ArrayList<>(Arrays.asList(
       VfsUtil.findRelativeFile(appRoot, "web-app"),
       VfsUtil.findRelativeFile(appRoot, "grails-app", "views")
-    ))
+    ));
 
-    for (webRoot in webFacet!!.getWebRoots()) {
-      shouldBeRoot.remove(webRoot.file)
+    for (WebRoot webRoot : webFacet.getWebRoots()) {
+      shouldBeRoot.remove(webRoot.getFile());
     }
 
-    assertTrue("Following web folder was not added to web roots: " + shouldBeRoot.toString() + "; " + webFacet.getWebRoots(),
-               shouldBeRoot.isEmpty())
+    assertTrue("Following web folder was not added to web roots: " + shouldBeRoot + "; " + webFacet.getWebRoots(),
+               shouldBeRoot.isEmpty());
   }
 }
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.java
similarity index 54%
rename from plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.java
index 2aa76ed..c559a21 100644
--- a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.java
@@ -13,17 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.groovy.grails.action;
 
-package org.jetbrains.plugins.groovy.grails.action
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.impl.source.PostprocessReformattingAspect;
+import org.jetbrains.plugins.groovy.grails.GrailsTestCase;
 
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.psi.impl.source.PostprocessReformattingAspect
-import org.jetbrains.plugins.groovy.grails.GrailsTestCase
+public class DomainFieldIntentionsTest extends GrailsTestCase {
 
-class DomainFieldIntentionsTest : GrailsTestCase() {
 
-  fun testInnerClass() {
-    val file = addDomain("""
+  public void testInnerClass() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String name;
@@ -32,35 +34,38 @@ class City {
     String nam<caret>e;
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", false)
+    runIntention(file, "Make property nullable", false);
   }
 
-  fun testTransientsField() {
-    val file = addDomain("""
+  public void testTransientsField() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
 
   static transients = ["name"]
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", false)
+    runIntention(file, "Make property nullable", false);
   }
 
-  fun testConstraintsNotExists1() {
-    val file = addDomain("""
+  public void testConstraintsNotExists1() {
+    PsiFile file = addDomain("""
+
 class City {
 
     String nam<caret>e;
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
     String name;
@@ -69,21 +74,23 @@ class City {
         name(nullable: true)
     }
 }
-""")
+""");
   }
 
-  fun testConstraintsNotExists2() {
-    val file = addDomain("""
+  public void testConstraintsNotExists2() {
+    PsiFile file = addDomain("""
+
 class City {
 
     String nam<caret>e;
 
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
     String name;
@@ -92,22 +99,24 @@ class City {
         name(nullable: true)
     }
 }
-""")
+""");
   }
 
-  fun testConstraintsWithoutInitializer() {
-    val file = addDomain("""
+  public void testConstraintsWithoutInitializer() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
 
   static constraints
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -116,22 +125,24 @@ class City {
       name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testConstraintsNull() {
-    val file = addDomain("""
+  public void testConstraintsNull() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
 
   static constraints = null
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -140,35 +151,38 @@ class City {
       name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testConstraintsWithInvalidInitializer() {
-    val file = addDomain("""
+  public void testConstraintsWithInvalidInitializer() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
 
   static constraints = "!!!!!!"
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", false)
+    runIntention(file, "Make property nullable", false);
   }
 
-  fun testConstraintsEmpty() {
-    val file = addDomain("""
+  public void testConstraintsEmpty() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
 
   static constraints = { }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -177,19 +191,20 @@ class City {
       name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  private fun checkResult(text: String) {
-    ApplicationManager.getApplication().runWriteAction {
-      PostprocessReformattingAspect.getInstance(project).doPostponedFormatting()
-    }
+  private void checkResult(String text) {
+    ApplicationManager.getApplication().runWriteAction(() -> {
+      PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting();
+    });
 
-    myFixture.checkResult(text)
+    myFixture.checkResult(text);
   }
 
-  fun testConstraintsHasFieldDescription() {
-    val file = addDomain("""
+  public void testConstraintsHasFieldDescription() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -198,11 +213,12 @@ class City {
     name()
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -211,11 +227,12 @@ class City {
     name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testNullableAlreadyExists() {
-    val file = addDomain("""
+  public void testNullableAlreadyExists() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -224,13 +241,14 @@ class City {
     name(nullable: true)
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", false)
+    runIntention(file, "Make property nullable", false);
   }
 
-  fun testNonNullable() {
-    val file = addDomain("""
+  public void testNonNullable() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -239,11 +257,12 @@ class City {
     name(nullable: false)
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -252,11 +271,12 @@ class City {
     name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testInvalidValue() {
-    val file = addDomain("""
+  public void testInvalidValue() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -265,25 +285,27 @@ class City {
     name(nullable: 1 + 2)
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", false)
+    runIntention(file, "Make property nullable", false);
   }
 
-  fun testMakeNullableOnPrimitiveField() {
-    val file = addDomain("""
+  public void testMakeNullableOnPrimitiveField() {
+    PsiFile file = addDomain("""
+
 class City {
 
   int siz<caret>e;
 
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", false)
+    runIntention(file, "Make property nullable", false);
   }
 
-  fun testAppStatement1() {
-    val file = addDomain("""
+  public void testAppStatement1() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -292,11 +314,12 @@ class City {
     name nullable: false
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -305,11 +328,12 @@ class City {
     name nullable: true
   }
 }
-""")
+""");
   }
 
-  fun testAppStatement2() {
-    val file = addDomain("""
+  public void testAppStatement2() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -318,11 +342,12 @@ class City {
     name asdasdasd: 4
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -331,11 +356,12 @@ class City {
     name nullable: true, asdasdasd: 4
   }
 }
-""")
+""");
   }
 
-  fun testCreation1() {
-    val file = addDomain("""
+  public void testCreation1() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -344,11 +370,12 @@ class City {
     name(nullable:, size: 4)
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -357,11 +384,12 @@ class City {
     name(nullable: true, size: 4)
   }
 }
-""")
+""");
   }
 
-  fun testCreation2() {
-    val file = addDomain("""
+  public void testCreation2() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -370,11 +398,12 @@ class City {
     name(nullable: false, size: 4)
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -383,11 +412,12 @@ class City {
     name(nullable: true, size: 4)
   }
 }
-""")
+""");
   }
 
-  fun testCreation3() {
-    val file = addDomain("""
+  public void testCreation3() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -396,11 +426,12 @@ class City {
     name(size: 4, nullable: )
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -409,11 +440,12 @@ class City {
     name(size: 4, nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testCreation4() {
-    val file = addDomain("""
+  public void testCreation4() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -423,11 +455,12 @@ class City {
       description(nullable: false)
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -438,11 +471,12 @@ class City {
       name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testMakeUnique() {
-    val file = addDomain("""
+  public void testMakeUnique() {
+    PsiFile file = addDomain("""
+
 class City {
 
   String nam<caret>e;
@@ -451,11 +485,12 @@ class City {
     name()
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property unique", true)
+    runIntention(file, "Make property unique", true);
 
     checkResult("""
+
 class City {
 
   String name;
@@ -464,11 +499,12 @@ class City {
     name(unique: true)
   }
 }
-""")
+""");
   }
 
-  fun testMakeUniqueInCommand() {
-    val file = addController("""
+  public void testMakeUniqueInCommand() {
+    PsiFile file = addController("""
+
 class CccController {
   def index = { ZzzCommand com ->
   }
@@ -482,13 +518,14 @@ class ZzzCommand {
     name()
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property unique", false)
+    runIntention(file, "Make property unique", false);
   }
 
-  fun testMakeNullableInCommand() {
-    val file = addController("""
+  public void testMakeNullableInCommand() {
+    PsiFile file = addController("""
+
 class CccController {
   def index = { ZzzCommand com ->
   }
@@ -502,11 +539,12 @@ class ZzzCommand {
     name()
   }
 }
-""")
+""");
 
-    runIntention(file, "Make property nullable", true)
+    runIntention(file, "Make property nullable", true);
 
     checkResult("""
+
 class CccController {
   def index = { ZzzCommand com ->
   }
@@ -520,19 +558,20 @@ class ZzzCommand {
     name(nullable: true)
   }
 }
-""")
+""");
   }
 
-  fun testUniqueIsNotApplicable() {
-    val file = addDomain("""
+  public void testUniqueIsNotApplicable() {
+    PsiFile file = addDomain("""
+
 class City {
 
   Set stree<caret>t;
 
 }
-""")
+""");
 
-    runIntention(file, "Make property unique", false)
+    runIntention(file, "Make property unique", false);
   }
 
-}
\ No newline at end of file
+}
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.java
new file mode 100644
index 0000000..0de03b5
--- /dev/null
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.grails.i18n;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.groovy.grails.i18n.GrailsI18nGroovyQuickFixHandler;
+import com.intellij.openapi.util.Trinity;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.plugins.grails.fileType.GspFileType;
+
+import java.util.List;
+
+public class GrailsGroovyI18nIntentionTest extends LightJavaCodeInsightFixtureTestCase {
+
+  private void doTest(String text, String defaultPropertyValue) {
+    doTest(text, defaultPropertyValue, "");
+  }
+
+  private void doTest(String text, String defaultPropertyValue, String args) {
+    PsiFile file = myFixture.configureByText(GspFileType.GSP_FILE_TYPE, text);
+
+    List<IntentionAction> intentions = myFixture.filterAvailableIntentions("Extract");
+    if (intentions.isEmpty()) {
+      assertNull(defaultPropertyValue);
+      return;
+    }
+
+    try {
+      GrailsI18nGroovyQuickFixHandler.INSTANCE.checkApplicability(file, myFixture.getEditor());
+    }
+    catch (IncorrectOperationException e) {
+      assertNull(defaultPropertyValue);
+      return;
+    }
+
+    Trinity<String, String, PsiElement> pair =
+      GrailsI18nGroovyQuickFixHandler.calculatePropertyValue(myFixture.getEditor(), file);
+    assertNotNull(pair);
+
+    assertEquals(defaultPropertyValue, pair.first);
+    assertEquals(args, pair.second);
+  }
+
+  public void testIntention1() {
+    doTest("${\"<caret>aaa\"}", "aaa");
+  }
+
+  public void testIntention2() {
+    doTest("${\"<selection>aaa 12</selection>\"}", "aaa 12");
+  }
+
+  public void testIntention3() {
+    doTest("${<selection>\"aaa\"</selection>}", "aaa");
+  }
+
+  public void testIntention4() {
+    doTest("${\"\"\"<selection>aaa</selection>\"\"\"}", "aaa");
+  }
+
+  public void testIntention5() {
+    doTest("${<selection>\"\"\"aaa\"\"\"</selection>}", "aaa");
+  }
+
+  public void testIntention6() {
+    doTest("${'<selection>aaa</selection>'}", "aaa");
+  }
+
+  public void testIntention7() {
+    doTest("${<selection>'aaa'</selection>}", "aaa");
+  }
+
+  public void testIntentionFalse() {
+    doTest("${'a<selection>a</selection>a'}", null);
+  }
+
+  public void testIntentionGString1() {
+    doTest("${<selection>'a${777}aa'</selection>}", "a${777}aa");
+  }
+
+  public void testIntentionGString2() {
+    doTest("${<selection>\"\"\"a${777}a${888}a\"\"\"</selection>}", "a{0}a{1}a", "777, 888");
+  }
+
+  public void testIntentionGString3() {
+    doTest("${<selection>\"\"\"a${777}a${888}a\"\"\"</selection>}", "a{0}a{1}a", "777, 888");
+  }
+
+  public void testIntentionGString4() {
+    doTest("${\"<caret>a${777}a${888}a\"}", "a{0}a{1}a", "777, 888");
+  }
+
+  public void testIntentionGStringExpression() {
+    doTest("${\"<caret>a$a a\"}", "a{0} a", "a");
+  }
+
+  public void testIntentionGStringSum() {
+    doTest("${\"<caret>a${a + 1} a\"}", "a{0} a", "a + 1");
+  }
+}
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt
deleted file mode 100644
index 16cb6b5..0000000
--- a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.groovy.grails.i18n
-
-import com.intellij.groovy.grails.i18n.GrailsI18nGroovyQuickFixHandler
-import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
-import com.intellij.util.IncorrectOperationException
-import org.jetbrains.plugins.grails.fileType.GspFileType
-
-class GrailsGroovyI18nIntentionTest : LightJavaCodeInsightFixtureTestCase() {
-
-  private fun doTest(text: String, defaultPropertyValue: String? = null, args: String = "") {
-    val file = myFixture.configureByText(GspFileType.GSP_FILE_TYPE, text)
-
-    val intentions = myFixture.filterAvailableIntentions("Extract")
-    if (intentions.isEmpty()) {
-      assertNull(defaultPropertyValue)
-      return
-    }
-
-    try {
-      GrailsI18nGroovyQuickFixHandler.INSTANCE.checkApplicability(file, myFixture.editor)
-    }
-    catch (e: IncorrectOperationException) {
-      assertNull(defaultPropertyValue)
-      return
-    }
-
-    val pair = GrailsI18nGroovyQuickFixHandler.calculatePropertyValue(myFixture.editor, file)
-    assertNotNull(pair)
-
-    assertEquals(defaultPropertyValue, pair!!.first)
-    assertEquals(args, pair.second)
-  }
-
-  fun testIntention1() {
-    doTest("""${'$'}{"<caret>aaa"}""", "aaa")
-  }
-
-  fun testIntention2() {
-    doTest("""${'$'}{"<selection>aaa 12</selection>"}""", "aaa 12")
-  }
-
-  fun testIntention3() {
-    doTest("""${'$'}{<selection>"aaa"</selection>}""", "aaa")
-  }
-
-  fun testIntention4() {
-    doTest("\${\"\"\"<selection>aaa</selection>\"\"\"}", "aaa")
-  }
-
-  fun testIntention5() {
-    doTest("\${<selection>\"\"\"aaa\"\"\"</selection>}", "aaa")
-  }
-
-  fun testIntention6() {
-    doTest("""${'$'}{'<selection>aaa</selection>'}""", "aaa")
-  }
-
-  fun testIntention7() {
-    doTest("""${'$'}{<selection>'aaa'</selection>}""", "aaa")
-  }
-
-  fun testIntentionFalse() {
-    doTest("""${'$'}{'a<selection>a</selection>a'}""", null)
-  }
-
-  fun testIntentionGString1() {
-    doTest("""${'$'}{<selection>'a${'$'}{777}aa'</selection>}""", "a${'$'}{777}aa")
-  }
-
-  fun testIntentionGString2() {
-    doTest("\${<selection>\"\"\"a${'$'}{777}a\${888}a\"\"\"</selection>}", "a{0}a{1}a", "777, 888")
-  }
-
-  fun testIntentionGString3() {
-    doTest("\${<selection>\"\"\"a\${777}a\${888}a\"\"\"</selection>}", "a{0}a{1}a", "777, 888")
-  }
-
-  fun testIntentionGString4() {
-    doTest("""${'$'}{"<caret>a${'$'}{777}a${'$'}{888}a"}""", "a{0}a{1}a", "777, 888")
-  }
-
-  fun testIntentionGStringExpression() {
-    doTest("""${'$'}{"<caret>a${'$'}a a"}""", "a{0} a", "a")
-  }
-
-  fun testIntentionGStringSum() {
-    doTest("""${'$'}{"<caret>a${'$'}{a + 1} a"}""", "a{0} a", "a + 1")
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.java
similarity index 55%
rename from plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.java
index 020124c..aca731c 100644
--- a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.java
@@ -13,52 +13,58 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.groovy.grails.i18n;
 
-package org.jetbrains.plugins.groovy.grails.i18n
+import com.intellij.codeInspection.InspectionProfileEntry;
+import com.intellij.codeInspection.ex.InspectionToolWrapper;
+import com.intellij.groovy.grails.i18n.GrailsI18nInspection;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.plugins.groovy.grails.GrailsTestCase;
 
-import com.intellij.codeInspection.ex.InspectionToolWrapper
-import com.intellij.groovy.grails.i18n.GrailsI18nInspection
-import com.intellij.profile.codeInspection.InspectionProjectProfileManager
-import org.jetbrains.plugins.groovy.grails.GrailsTestCase
+import java.util.List;
 
-class GrailsI18nInspectionTest : GrailsTestCase() {
+public class GrailsI18nInspectionTest extends GrailsTestCase {
 
-  override fun setUp() {
-    super.setUp()
-    myFixture.enableInspections(GrailsI18nInspection::class.java)
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    myFixture.enableInspections(GrailsI18nInspection.class);
   }
 
-  private fun setIgnoreIfDefault(value: Boolean) {
-    val tool = InspectionProjectProfileManager.getInstance(project).currentProfile.getInspectionTool(
-      GrailsI18nInspection().shortName, project)
-    val inspection = (tool as InspectionToolWrapper).tool as GrailsI18nInspection
-
-    inspection.ignoreTagsWithDefault = value
+  private void setIgnoreIfDefault(boolean value) {
+    InspectionToolWrapper<?, ?> tool = InspectionProjectProfileManager.getInstance(getProject()).getCurrentProfile()
+      .getInspectionTool(new GrailsI18nInspection().getShortName(), getProject());
+    InspectionProfileEntry inspection = tool.getTool();
+    ((GrailsI18nInspection)inspection).ignoreTagsWithDefault = value;
   }
 
-  fun testCodeReference() {
+  public void testCodeReference() {
     myFixture.addFileToProject("grails-app/i18n/messages.properties", """
+
 aaa1=Aaaa1
 aaa2=Aaaa2
 bbb=Bbbb
-""")
+""");
 
-    myFixture.addFileToProject("grails-app/views/a.gsp", "<g:message code='aa<caret>' />")
+    myFixture.addFileToProject("grails-app/views/a.gsp", "<g:message code='aa<caret>' />");
 
-    assertEquals(listOf("aaa1", "aaa2"), myFixture.getCompletionVariants("grails-app/views/a.gsp"))
+    assertEquals(List.of("aaa1", "aaa2"), myFixture.getCompletionVariants("grails-app/views/a.gsp"));
   }
 
-  fun testHighlightingGsp() {
-    setIgnoreIfDefault(true)
+  public void testHighlightingGsp() {
+    setIgnoreIfDefault(true);
 
     myFixture.addFileToProject("grails-app/i18n/messages.properties", """
+
 aaa.bbb=Aaa bbb
-""")
+""");
+
+    PsiFile gsp = addView("aaa.gsp", """
 
-    val gsp = addView("aaa.gsp", """
 <g:message code="aaa.bbb" />
 <g:message code="<error descr="Cannot resolve property key">aaa.bbb.sss</error>" />
-<g:message code="aaa.${"$"}{xxx}" />
+<g:message code="aaa.${xxx}" />
 <g:message code="asdaskdjaskdaj" default="Default Text" />
 
 <<error descr="Element tooltip:tip is not allowed here">tooltip:tip</error> code="sdfsdfsdf" />
@@ -68,46 +74,50 @@ aaa.bbb=Aaa bbb
 
 <%--suppress InvalidI18nProperty --%>
 <g:sortableColumn titleKey="sdfsdfsdf" />
-""")
+""");
 
-    myFixture.testHighlighting(true, false, true, gsp.virtualFile)
+    myFixture.testHighlighting(true, false, true, gsp.getVirtualFile());
   }
 
-  fun testSuppressionWarningForFile() {
-    val gsp = addView("aaa.gsp", """
+  public void testSuppressionWarningForFile() {
+    PsiFile gsp = addView("aaa.gsp", """
+
 <%--suppress InvalidI18nProperty --%>
 
 <div />
 <g:sortableColumn titleKey="sdfsdfsdf" />
-""")
+""");
 
-    myFixture.testHighlighting(true, false, true, gsp.virtualFile)
+    myFixture.testHighlighting(true, false, true, gsp.getVirtualFile());
   }
 
-  fun testSuppressionAllWarningInFile() {
-    val gsp = addView("aaa.gsp", """
+  public void testSuppressionAllWarningInFile() {
+    PsiFile gsp = addView("aaa.gsp", """
+
 <%--suppress ALL --%>
 
 <div />
 <g:sortableColumn titleKey="sdfsdfsdf" />
-""")
+""");
 
-    myFixture.testHighlighting(true, false, true, gsp.virtualFile)
+    myFixture.testHighlighting(true, false, true, gsp.getVirtualFile());
   }
 
-  fun testHighlightingGroovy() {
-    setIgnoreIfDefault(true)
+  public void testHighlightingGroovy() {
+    setIgnoreIfDefault(true);
 
     myFixture.addFileToProject("grails-app/i18n/messages.properties", """
+
 aaa.bbb=Aaa bbb
-""")
+""");
+
+    PsiFile file = addController("""
 
-    val file = addController("""
 class CccController {
   def index = {
     g.message(code:"aaa.bbb")
     g.message(code:"<error descr="Cannot resolve property key">aaa.bbb.sss</error>")
-    g.message(code:"aaa.${'$'}{xxx}")
+    g.message(code:"aaa.${xxx}")
     g.message(code:"asdaskdjaskdaj", default:"Default Text")
 
     g.sortableColumn(titleKey:"<error descr="Cannot resolve property key">sdfsdfsdf</error>")
@@ -122,50 +132,53 @@ class CccController {
     g.sortableColumn(titleKey:"sdfsdfsdf")
   }
 }
-""")
+""");
 
-    myFixture.testHighlighting(true, false, true, file.virtualFile)
+    myFixture.testHighlighting(true, false, true, file.getVirtualFile());
   }
 
-  fun testHighlighting2() {
-    setIgnoreIfDefault(false)
+  public void testHighlighting2() {
+    setIgnoreIfDefault(false);
+
+    PsiFile file = addController("""
 
-    val file = addController("""
 class CccController {
   def index = {
     g.sortableColumn(titleKey:"<error>sdfsdfsdf</error>", title:"Default Text")
   }
 }
-""")
+""");
 
-    myFixture.testHighlighting(true, false, true, file.virtualFile)
+    myFixture.testHighlighting(true, false, true, file.getVirtualFile());
   }
 
-  fun testCreatePropertyGsp() {
-    setIgnoreIfDefault(false)
+  public void testCreatePropertyGsp() {
+    setIgnoreIfDefault(false);
 
     myFixture.addFileToProject("grails-app/i18n/messages.properties", """
+
 aaa.bbb=Aaa bbb
-""")
+""");
 
-    configureByView("aaa.gsp", "<g:message code='aaa.bbb.ccc<caret>' />")
+    configureByView("aaa.gsp", "<g:message code='aaa.bbb.ccc<caret>' />");
 
-    val intentions = myFixture.filterAvailableIntentions("Create property")
-    assertSize(1, intentions)
+    var intentions = myFixture.filterAvailableIntentions("Create property");
+    assertSize(1, intentions);
 
-    assertEmpty(myFixture.filterAvailableIntentions("Don't check"))
+    assertEmpty(myFixture.filterAvailableIntentions("Don't check"));
   }
 
-  fun testCreatePropertyGsp2() {
-    setIgnoreIfDefault(false)
+  public void testCreatePropertyGsp2() {
+    setIgnoreIfDefault(false);
 
     myFixture.addFileToProject("grails-app/i18n/messages.properties", """
+
 aaa.bbb=Aaa bbb
-""")
+""");
 
-    configureByView("aaa.gsp", "<g:message code='aaa.bbb.ccc<caret>' default='aaa'/>")
+    configureByView("aaa.gsp", "<g:message code='aaa.bbb.ccc<caret>' default='aaa'/>");
 
-    assertSize(1, myFixture.filterAvailableIntentions("Create property"))
-    assertSize(1, myFixture.filterAvailableIntentions("Don't check"))
+    assertSize(1, myFixture.filterAvailableIntentions("Create property"));
+    assertSize(1, myFixture.filterAvailableIntentions("Don't check"));
   }
 }
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.java
new file mode 100644
index 0000000..9e3a814
--- /dev/null
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.grails.i18n;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.groovy.grails.i18n.GrailsI18nQuickFixHandler;
+import com.intellij.openapi.util.Couple;
+import com.intellij.psi.PsiFile;
+import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.plugins.grails.fileType.GspFileType;
+
+import java.util.List;
+
+public class GspI18nIntentionTest extends LightJavaCodeInsightFixtureTestCase {
+
+  private void doTest(String text) {
+    doTest(text, null, null);
+  }
+
+  private void doTest(String text, String defaultPropertyValue, String args) {
+    PsiFile file = myFixture.configureByText(GspFileType.GSP_FILE_TYPE, text);
+
+    List<IntentionAction> intentions = myFixture.filterAvailableIntentions("Extract");
+    if (intentions.isEmpty()) {
+      assertNull(defaultPropertyValue);
+      return;
+    }
+
+    try {
+      GrailsI18nQuickFixHandler.INSTANCE.checkApplicability(file, myFixture.getEditor());
+    }
+    catch (IncorrectOperationException e) {
+      assertNull(defaultPropertyValue);
+      return;
+    }
+
+    Couple<String> pair = GrailsI18nQuickFixHandler.calculatePropertyValue(myFixture.getEditor(), file);
+    assertNotNull(pair);
+
+    assertEquals(defaultPropertyValue, pair.first);
+    assertEquals(args, pair.second);
+  }
+
+  public void testIntention1() {
+    doTest("\na<selection>aa\nsda</selection>\n", "aa\nsda", "");
+  }
+
+  public void testIntention2() {
+    doTest("\na<selection>aa<% out << 1 %>sda</selection>\n");
+  }
+
+  public void testIntention21() {
+    doTest("\na<selection>aasda${</selection>2}\n");
+  }
+
+  public void testIntention22() {
+    doTest("\na${1<selection>}aasda</selection>\n");
+  }
+
+  public void testIntention3() {
+    doTest("a<selection><div>aasda</div></selection>", "<div>aasda</div>", "");
+  }
+
+  public void testIntention4() {
+    doTest("a<selection><g:link/></selection>");
+  }
+
+  public void testIntention5() {
+    doTest("a<caret>aa");
+  }
+
+  public void testIntention6() {
+    doTest("a<% 1 %><selection>${aaa}</selection><% out << \"aaa\" %>", "{0}", "aaa");
+  }
+
+  public void testIntention7() {
+    doTest("a<selection>a ${aaa} s <%= 777 * 5   %> </selection> sdsa", "a {0} s {1} ", "aaa, 777 * 5");
+  }
+
+  public void testIntention8() {
+    doTest("<selection>aaa${777}bbb${'!' + actionName + 'aaa'}</selection>", "aaa{0}bbb{1}",
+           "777, '!' + actionName + 'aaa'");
+  }
+
+  public void testIntention9() {
+    doTest("<selection>aaa${777}${777}</selection>", "aaa{0}{1}", "777, 777");
+  }
+
+  public void testIntention10() {
+    doTest("<input class=\"button green medium\" type=\"button\" value=\"<selection>Subscribe</selection>\"/>",
+           "Subscribe", "");
+  }
+
+  public void testIntention11() {
+    doTest("<input class=\"button green medium\" type=\"button\" value=\"<selection>Subscribe ${aaa}</selection>\"/>",
+           "Subscribe {0}", "aaa");
+  }
+
+  public void testIntention12() {
+    doTest("<input class=\"button green medium\" type=\"button\" <selection>value=\"Subscribe</selection>\"/>");
+  }
+}
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt
deleted file mode 100644
index 7750cdd..0000000
--- a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.groovy.grails.i18n
-
-import com.intellij.groovy.grails.i18n.GrailsI18nQuickFixHandler
-import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
-import com.intellij.util.IncorrectOperationException
-import org.jetbrains.plugins.grails.fileType.GspFileType
-
-class GspI18nIntentionTest : LightJavaCodeInsightFixtureTestCase() {
-
-  private fun doTest(text: String, defaultPropertyValue: String? = null, args: String? = null) {
-    val file = myFixture.configureByText(GspFileType.GSP_FILE_TYPE, text)
-
-    val intentions = myFixture.filterAvailableIntentions("Extract")
-    if (intentions.isEmpty()) {
-      assertNull(defaultPropertyValue)
-      return
-    }
-
-    try {
-      GrailsI18nQuickFixHandler.INSTANCE.checkApplicability(file, myFixture.editor)
-    }
-    catch (e: IncorrectOperationException) {
-      assertNull(defaultPropertyValue)
-      return
-    }
-
-    val pair = GrailsI18nQuickFixHandler.calculatePropertyValue(myFixture.editor, file)
-    assertNotNull(pair)
-
-    assertEquals(defaultPropertyValue, pair!!.first)
-    assertEquals(args, pair.second)
-  }
-
-  fun testIntention1() {
-    doTest(
-      """
-a<selection>aa
-sda</selection>
-""", "aa\nsda", ""
-    )
-  }
-
-  fun testIntention2() {
-    doTest(
-      """
-a<selection>aa<% out << 1 %>sda</selection>
-"""
-    )
-  }
-
-  fun testIntention21() {
-    doTest("""
-a<selection>aasda${'$'}{</selection>2}
-""")
-  }
-
-  fun testIntention22() {
-    doTest("""
-a${'$'}{1<selection>}aasda</selection>
-""")
-  }
-
-  fun testIntention3() {
-    doTest("a<selection><div>aasda</div></selection>", "<div>aasda</div>", "")
-  }
-
-  fun testIntention4() {
-    doTest("a<selection><g:link/></selection>")
-  }
-
-  fun testIntention5() {
-    doTest("a<caret>aa")
-  }
-
-  fun testIntention6() {
-    doTest("a<% 1 %><selection>${'$'}{aaa}</selection><% out << \"aaa\" %>", "{0}", "aaa")
-  }
-
-  fun testIntention7() {
-    doTest("a<selection>a ${'$'}{aaa} s <%= 777 * 5   %> </selection> sdsa", "a {0} s {1} ", "aaa, 777 * 5")
-  }
-
-  fun testIntention8() {
-    doTest("<selection>aaa${'$'}{777}bbb${'$'}{'!' + actionName + 'aaa'}</selection>", "aaa{0}bbb{1}", "777, '!' + actionName + 'aaa'")
-  }
-
-  fun testIntention9() {
-    doTest("<selection>aaa${'$'}{777}${'$'}{777}</selection>", "aaa{0}{1}", "777, 777")
-  }
-
-  fun testIntention10() {
-    doTest("<input class=\"button green medium\" type=\"button\" value=\"<selection>Subscribe</selection>\"/>", "Subscribe", "")
-  }
-
-  fun testIntention11() {
-    doTest("<input class=\"button green medium\" type=\"button\" value=\"<selection>Subscribe ${'$'}{aaa}</selection>\"/>", "Subscribe {0}", "aaa")
-  }
-
-  fun testIntention12() {
-    doTest("<input class=\"button green medium\" type=\"button\" <selection>value=\"Subscribe</selection>\"/>")
-  }
-}


(grails-intellij-plugin) 25/35: Convert the gson package from Kotlin to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit abc719b15a2c8d05639b10056452a60cf66909e6
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 14:09:33 2026 -0400

    Convert the gson package from Kotlin to Java
    
    11 Kotlin files become 17 Java files, leaving no Kotlin under gson/.
    987 tests / 0 failures, RAT clean.
    
    The package turned out to be self-contained -- nothing outside gson/ references it,
    all eleven extension classes are instantiated from plugin.xml, and the top-level
    declarations were only used in-package -- so unlike the structure batch it needed no
    call-site changes anywhere else.
    
    The file count grows because Kotlin allowed several public classes per file. The
    seven multi-class files split one class per file, and the PSI patterns that were
    top-level vals in GsonControllerReferences.kt and GsonRenderReferences.kt collect
    into a package-private GsonPatterns holder rather than becoming two *Kt classes.
    
    Dropped one piece of dead code: COMPLETION_PLACE in GsonControllerReferences.kt was
    a private top-level val with exactly one occurrence, its own declaration. Private
    top-level means nothing outside the file could reach it, so this is not a judgement
    call. Everything else is preserved, including the commented-out "bean" named
    argument, which documents a deliberate omission.
    
    Three translation details worth recording:
    
    * GroovyFile.getTopStatements() returns GrTopStatement, not GrStatement. Kotlin's
      inferred loop variable hid the distinction; Java needs the exact type, and it
      lives in ...lang.psi.api.toplevel, not ...lang.psi.
    * Kotlin's replaceAfterLast(delimiter, replacement) has no Java equivalent, so the
      template-path rewriting in GsonTemplateReference is written out with lastIndexOf.
      Its fallback matters: when the delimiter is absent the Kotlin returns the original
      string, which is why the rename path checks startsWith("/") before splitting.
    * TraitInjectorService's two-argument getInjectedTraits lacked @JvmStatic, so Java
      would have had to reach it through .Companion. That file stays Kotlin because it
      is built on coroutines, so the annotation was added there instead -- a one-line
      change that keeps a clean surface for the converted callers.
    
    GsonImportContributor's `by lazy` over a fixed list became a plain static constant;
    the laziness bought nothing.
    
    51 files left in the conversion target set.
---
 .../grails/gson/GsonControllerReference.java       |  47 ++++++
 .../grails/gson/GsonControllerReferences.kt        |  87 -----------
 .../grails/gson/GsonExpectedPackageProvider.java   |  33 +++++
 .../grails/gson/GsonExpectedPackageProvider.kt     |  27 ----
 ...rtContributor.kt => GsonImportContributor.java} |  33 +++--
 ...Provider.kt => GsonModelArgumentsProvider.java} |  37 +++--
 .../gson/GsonModelFieldArgumentDescriptor.java     |  54 +++++++
 .../gson/GsonModelFieldArgumentDescriptor.kt       |  35 -----
 .../plugins/grails/gson/GsonPatterns.java          | 100 +++++++++++++
 .../grails/gson/GsonPositionManagerDelegate.java   |  97 ++++++++++++
 .../grails/gson/GsonPositionManagerDelegate.kt     |  70 ---------
 .../plugins/grails/gson/GsonRWAccessDetector.java  |  49 ++++++
 ...ontributor.kt => GsonReferenceContributor.java} |  17 ++-
 .../gson/GsonRenderNamedArgumentsProvider.java     |  54 +++++++
 .../plugins/grails/gson/GsonRenderReferences.kt    | 164 ---------------------
 .../plugins/grails/gson/GsonTemplateReference.java |  74 ++++++++++
 .../grails/gson/GsonTemplateReferenceProvider.java |  35 +++++
 .../gson/GsonTemplateReferenceQueryExecutor.java   |  54 +++++++
 .../gson/GsonTemplateRendererContributor.java      |  60 ++++++++
 .../grails/gson/GsonTemplateRendererContributor.kt |  51 -------
 .../grails/gson/GsonTransformationSupport.java     |  64 ++++++++
 .../grails/gson/GsonTransformationSupport.kt       |  51 -------
 .../jetbrains/plugins/grails/gson/GsonUtils.java   | 117 +++++++++++++++
 .../org/jetbrains/plugins/grails/gson/GsonUtils.kt |  79 ----------
 .../grails/references/TraitInjectorService.kt      |   4 +
 25 files changed, 892 insertions(+), 601 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReference.java
new file mode 100644
index 0000000..731c38b
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReference.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementResolveResult;
+import com.intellij.psi.PsiPolyVariantReferenceBase;
+import com.intellij.psi.ResolveResult;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField;
+
+public final class GsonControllerReference extends PsiPolyVariantReferenceBase<GrArgumentLabel> {
+
+  public GsonControllerReference(@NotNull GrArgumentLabel element) {
+    super(element, true);
+  }
+
+  @Override
+  public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
+    PsiElement resolved = resolve();
+    return resolved == null ? ResolveResult.EMPTY_ARRAY : new ResolveResult[]{new PsiElementResolveResult(resolved)};
+  }
+
+  @Override
+  public @Nullable PsiElement resolve() {
+    GrArgumentLabel element = getElement();
+    for (GrScriptField field : GsonUtils.getModelFields(element)) {
+      if (field.getName().equals(element.getName())) return field;
+    }
+    return null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt
deleted file mode 100644
index 3c8daef..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import com.intellij.patterns.PatternCondition
-import com.intellij.patterns.PlatformPatterns
-import com.intellij.patterns.PsiJavaPatterns
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiElementResolveResult
-import com.intellij.psi.PsiPolyVariantReferenceBase
-import com.intellij.psi.PsiReference
-import com.intellij.psi.ResolveResult
-import com.intellij.psi.util.PsiTreeUtil
-import com.intellij.util.ProcessingContext
-import org.jetbrains.plugins.groovy.findUsages.GroovyReadWriteAccessDetector
-import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
-import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ControlFlowBuilderUtil
-import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyElementPattern
-import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns
-
-class GsonControllerReference(element: GrArgumentLabel) : PsiPolyVariantReferenceBase<GrArgumentLabel>(element, true) {
-
-  override fun multiResolve(incompleteCode: Boolean): Array<out ResolveResult> {
-    val resolved = resolve() ?: return emptyArray()
-    return arrayOf(PsiElementResolveResult(resolved))
-  }
-
-  override fun resolve(): PsiElement? = getModelFields(element).find { it.name == element.name }
-}
-
-class GsonRWAccessDetector : GroovyReadWriteAccessDetector() {
-
-  override fun isReadWriteAccessible(element: PsiElement): Boolean = isModelVariable(element)
-
-  override fun isDeclarationWriteAccess(element: PsiElement): Boolean {
-    return if (isModelVariable(element)) false else super.isDeclarationWriteAccess(element)
-  }
-
-  override fun getReferenceAccess(referencedElement: PsiElement, reference: PsiReference): Access {
-    return if (reference is GsonControllerReference) Access.Write else super.getReferenceAccess(referencedElement, reference)
-  }
-
-  override fun getExpressionAccess(expression: PsiElement): Access {
-    return if (CONTROLLER_REFERENCE_PLACE.accepts(expression)) Access.Write else super.getExpressionAccess(expression)
-  }
-}
-
-private val RETURN_STATEMENT = object : PatternCondition<PsiElement>("is in return statement") {
-
-  override fun accepts(element: PsiElement, context: ProcessingContext?): Boolean {
-    val statement = PsiTreeUtil.getParentOfType(element, GrListOrMap::class.java) ?: return false
-    return ControlFlowBuilderUtil.isCertainlyReturnStatement(statement)
-  }
-}
-
-/*
- * def index() {
- *   return [<place>]
- * }
- *
- * def index() {
- *   [<place>]
- * }
- */
-val CONTROLLER_REFERENCE_PLACE: GroovyElementPattern.Capture<GrArgumentLabel> = GroovyPatterns.namedArgumentLabel(null).with(RETURN_STATEMENT)
-
-private val COMPLETION_PLACE = PsiJavaPatterns.psiElement().withParent(
-    PlatformPatterns.psiElement(GrReferenceExpression::class.java).withParent(
-        PlatformPatterns.psiElement(GrListOrMap::class.java)
-    )
-).with(RETURN_STATEMENT)
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.java
new file mode 100644
index 0000000..501ffc3
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.resolve.ExpectedPackageNameProvider;
+
+// Public: instantiated reflectively from plugin.xml (expectedPackageNameProvider). It was Kotlin
+// internal, which is public in bytecode, so package-private here would fail at runtime.
+public final class GsonExpectedPackageProvider implements ExpectedPackageNameProvider {
+
+  @Override
+  public @Nullable String inferPackageName(@NotNull GroovyFile file) {
+    // GSON views are scripts at the views root, so they belong to the default package.
+    return file.getVirtualFile().getNameSequence().toString().endsWith(GsonConstants.FILE_SUFFIX) ? "" : null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt
deleted file mode 100644
index 9a04273..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-import org.jetbrains.plugins.groovy.lang.resolve.ExpectedPackageNameProvider
-
-internal class GsonExpectedPackageProvider : ExpectedPackageNameProvider {
-  override fun inferPackageName(file: GroovyFile): String? {
-    return if (file.virtualFile.nameSequence.endsWith(GsonConstants.FILE_SUFFIX)) "" else null
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.java
similarity index 50%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.java
index ac10e6a..f19663b 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.java
@@ -13,23 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.gson;
 
-package org.jetbrains.plugins.grails.gson
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.resolve.imports.GrImportContributor;
+import org.jetbrains.plugins.groovy.lang.resolve.imports.GroovyImport;
+import org.jetbrains.plugins.groovy.lang.resolve.imports.StaticStarImport;
 
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-import org.jetbrains.plugins.groovy.lang.resolve.imports.GrImportContributor
-import org.jetbrains.plugins.groovy.lang.resolve.imports.GroovyImport
-import org.jetbrains.plugins.groovy.lang.resolve.imports.StaticStarImport
+import java.util.List;
 
-class GsonImportContributor : GrImportContributor {
+public final class GsonImportContributor implements GrImportContributor {
 
-  private val imports: List<GroovyImport> by lazy {
-    mutableListOf<GroovyImport>().apply {
-      add(StaticStarImport("org.springframework.http.HttpStatus"))
-      add(StaticStarImport("org.springframework.http.HttpMethod"))
-      add(StaticStarImport("grails.web.http.HttpHeaders"))
-    }
-  }
+  // A constant, so no lazy initialisation is needed: the Kotlin wrapped this fixed list in a
+  // lazy delegate, which bought nothing.
+  private static final List<GroovyImport> IMPORTS = List.of(
+    new StaticStarImport("org.springframework.http.HttpStatus"),
+    new StaticStarImport("org.springframework.http.HttpMethod"),
+    new StaticStarImport("grails.web.http.HttpHeaders")
+  );
 
-  override fun getFileImports(file: GroovyFile): List<GroovyImport> = if (isGsonFile(file)) imports else emptyList()
+  @Override
+  public @NotNull List<GroovyImport> getFileImports(@NotNull GroovyFile file) {
+    return GsonUtils.isGsonFile(file) ? IMPORTS : List.of();
+  }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.java
similarity index 51%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.java
index 6d440ca..35dc38c 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.java
@@ -13,26 +13,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.gson;
 
-package org.jetbrains.plugins.grails.gson
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider;
+import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
+import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ControlFlowBuilderUtil;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField;
 
-import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider
-import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor
-import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap
-import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ControlFlowBuilderUtil
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
-class GsonModelArgumentsProvider : GroovyNamedArgumentProvider() {
+public final class GsonModelArgumentsProvider extends GroovyNamedArgumentProvider {
 
-  override fun getNamedArguments(literal: GrListOrMap): Map<String, NamedArgumentDescriptor> {
-    if (!ControlFlowBuilderUtil.isCertainlyReturnStatement(literal)) return emptyMap()
+  @Override
+  public @NotNull Map<String, NamedArgumentDescriptor> getNamedArguments(@NotNull GrListOrMap literal) {
+    // Only the map a controller action returns describes the GSON model.
+    if (!ControlFlowBuilderUtil.isCertainlyReturnStatement(literal)) return Map.of();
 
-    val modelFields = getModelFields(literal)
-    if (modelFields.isEmpty()) return emptyMap()
-    val result = mutableMapOf<String, NamedArgumentDescriptor>()
+    List<GrScriptField> modelFields = GsonUtils.getModelFields(literal);
+    if (modelFields.isEmpty()) return Map.of();
 
-    for (field in modelFields) {
-      result[field.name] = GsonModelFieldArgumentDescriptor(field)
+    Map<String, NamedArgumentDescriptor> result = new LinkedHashMap<>();
+    for (GrScriptField field : modelFields) {
+      result.put(field.getName(), new GsonModelFieldArgumentDescriptor(field));
     }
-    return result
+    return result;
   }
-}
\ No newline at end of file
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.java
new file mode 100644
index 0000000..2c8d190
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.PsiType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.extensions.impl.NamedArgumentDescriptorBase;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
+import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
+
+public final class GsonModelFieldArgumentDescriptor extends NamedArgumentDescriptorBase {
+
+  private final GrField myField;
+
+  public GsonModelFieldArgumentDescriptor(@NotNull GrField field) {
+    myField = field;
+  }
+
+  public @NotNull GrField getField() {
+    return myField;
+  }
+
+  @Override
+  public @NotNull GsonControllerReference createReference(@NotNull GrArgumentLabel label) {
+    return new GsonControllerReference(label);
+  }
+
+  @Override
+  public boolean checkType(@NotNull PsiType type, @NotNull GroovyPsiElement context) {
+    return TypesUtil.isAssignable(myField.getType(), type, context);
+  }
+
+  @Override
+  public @NotNull LookupElementBuilder customizeLookupElement(@NotNull LookupElementBuilder lookupElement) {
+    return lookupElement.appendTailText(" Gson Model Field", true)
+      .withTypeText(myField.getType().getPresentableText());
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt
deleted file mode 100644
index a7eb5b5..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import com.intellij.codeInsight.lookup.LookupElementBuilder
-import com.intellij.psi.PsiType
-import org.jetbrains.plugins.groovy.extensions.impl.NamedArgumentDescriptorBase
-import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel
-import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
-
-class GsonModelFieldArgumentDescriptor(val field: GrField) : NamedArgumentDescriptorBase() {
-
-  override fun createReference(label: GrArgumentLabel): GsonControllerReference = GsonControllerReference(label)
-
-  override fun checkType(type: PsiType, context: GroovyPsiElement): Boolean = TypesUtil.isAssignable(field.type, type, context)
-
-  override fun customizeLookupElement(lookupElement: LookupElementBuilder): LookupElementBuilder =
-      lookupElement.appendTailText(" Gson Model Field", true).withTypeText(field.type.presentableText)
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPatterns.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPatterns.java
new file mode 100644
index 0000000..7fdcaae
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPatterns.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.patterns.PatternCondition;
+import com.intellij.patterns.PsiMethodPattern;
+import com.intellij.patterns.StandardPatterns;
+import com.intellij.psi.CommonClassNames;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ProcessingContext;
+import com.intellij.psi.util.PsiTreeUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
+import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ControlFlowBuilderUtil;
+import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl;
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyElementPattern;
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyExpressionPattern;
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns;
+import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
+
+/**
+ * PSI patterns shared by the GSON reference and named-argument providers. These were top-level
+ * declarations spread across GsonControllerReferences.kt and GsonRenderReferences.kt.
+ */
+final class GsonPatterns {
+
+  private GsonPatterns() {
+  }
+
+  private static final PatternCondition<PsiElement> RETURN_STATEMENT =
+    new PatternCondition<>("is in return statement") {
+      @Override
+      public boolean accepts(@NotNull PsiElement element, @Nullable ProcessingContext context) {
+        GrListOrMap statement = PsiTreeUtil.getParentOfType(element, GrListOrMap.class);
+        return statement != null && ControlFlowBuilderUtil.isCertainlyReturnStatement(statement);
+      }
+    };
+
+  /**
+   * <pre>
+   * def index() {
+   *   return [&lt;place&gt;]
+   * }
+   *
+   * def index() {
+   *   [&lt;place&gt;]
+   * }
+   * </pre>
+   */
+  static final GroovyElementPattern.Capture<GrArgumentLabel> CONTROLLER_REFERENCE_PLACE =
+    GroovyPatterns.namedArgumentLabel(null).with(RETURN_STATEMENT);
+
+  private static final PsiMethodPattern RENDER_METHOD_BASE_PATTERN =
+    new PsiMethodPattern().withName("render").definedInClass("grails.plugin.json.view.api.GrailsJsonViewHelper");
+
+  /** GrailsJsonViewHelper#render(java.util.Map) */
+  static final PsiMethodPattern RENDER_METHOD_PATTERN_1 =
+    RENDER_METHOD_BASE_PATTERN.withParameters(CommonClassNames.JAVA_UTIL_MAP);
+
+  /** GrailsJsonViewHelper#render(java.lang.Object, java.util.Map) */
+  private static final PsiMethodPattern RENDER_METHOD_PATTERN_2 =
+    RENDER_METHOD_BASE_PATTERN.withParameters(CommonClassNames.JAVA_LANG_OBJECT, CommonClassNames.JAVA_UTIL_MAP);
+
+  /** GrailsJsonViewHelper#render(java.lang.Object, java.util.Map, groovy.lang.Closure) */
+  private static final PsiMethodPattern RENDER_METHOD_PATTERN_3 =
+    RENDER_METHOD_BASE_PATTERN.withParameters(CommonClassNames.JAVA_LANG_OBJECT,
+                                              CommonClassNames.JAVA_UTIL_MAP,
+                                              GroovyCommonClassNames.GROOVY_LANG_CLOSURE);
+
+  /**
+   * Second (java.util.Map) parameter of
+   * GrailsJsonViewHelper#render(java.lang.Object, java.util.Map) and
+   * GrailsJsonViewHelper#render(java.lang.Object, java.util.Map, groovy.lang.Closure).
+   */
+  static final GroovyExpressionPattern<GrListOrMap, ?> RENDER_MAP_PARAMETER =
+    new GroovyExpressionPattern.Capture<>(GrListOrMap.class)
+      .methodCallParameter(1, StandardPatterns.or(RENDER_METHOD_PATTERN_2, RENDER_METHOD_PATTERN_3));
+
+  /** g.render(template: "&lt;place&gt;") */
+  static final GroovyElementPattern.Capture<GrLiteralImpl> TEMPLATE_REFERENCE_PLACE =
+    GroovyPatterns.stringLiteral().withParent(
+      GroovyPatterns.namedArgument().withLabel("template").withParent(
+        GroovyPatterns.groovyElement().withParent(
+          GroovyPatterns.methodCall().withMethod(RENDER_METHOD_PATTERN_1))));
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.java
new file mode 100644
index 0000000..03c7ac6
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.sun.jdi.ReferenceType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+import org.jetbrains.plugins.groovy.extensions.debugger.ScriptPositionManagerHelper;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+
+public final class GsonPositionManagerDelegate extends ScriptPositionManagerHelper {
+
+  private static final String SUFFIX = "_" + GsonConstants.EXTENSION;
+
+  /** Mirrors the runtime template-class naming: every non-word character becomes an underscore. */
+  private static @NotNull String clean(@NotNull String value) {
+    return value.replaceAll("[\\W\\s]", "_");
+  }
+
+  @Override
+  public boolean isAppropriateScriptFile(@NotNull GroovyFile scriptFile) {
+    return GsonUtils.isGsonFile(scriptFile) && GrailsApplicationManager.findApplication(scriptFile) != null;
+  }
+
+  /**
+   * grails.views.resolve.GenericGroovyTemplateResolver#resolveTemplateName
+   */
+  @Override
+  public @Nullable String getRuntimeScriptName(@NotNull GroovyFile groovyFile) {
+    GrailsApplication application = GrailsApplicationManager.findApplication(groovyFile);
+    if (application == null) return null;
+    VirtualFile file = groovyFile.getVirtualFile();
+    if (file == null) return null;
+    VirtualFile viewsRoot = ProjectFileIndex.getInstance(groovyFile.getProject()).getSourceRootForFile(file);
+    if (viewsRoot == null) return null;
+    String path = VfsUtilCore.getRelativePath(file, viewsRoot);
+    if (path == null) return null;
+    return clean(application.getName()) + "_" + path.replace("/", "_").replace(".", "_");
+  }
+
+  @Override
+  public boolean isAppropriateRuntimeName(@NotNull String runtimeName) {
+    return runtimeName.endsWith(SUFFIX);
+  }
+
+  @Override
+  public @Nullable String customizeClassName(@NotNull PsiClass psiClass) {
+    return psiClass.getContainingFile() instanceof GroovyFile file ? getRuntimeScriptName(file) : null;
+  }
+
+  @Override
+  public @Nullable PsiFile getExtraScriptIfNotFound(@NotNull ReferenceType refType,
+                                                    @NotNull String runtimeName,
+                                                    @NotNull Project project,
+                                                    @NotNull GlobalSearchScope scope) {
+    String appNameAndViewPath = runtimeName.endsWith(SUFFIX)
+                                ? runtimeName.substring(0, runtimeName.length() - SUFFIX.length())
+                                : runtimeName;
+    for (GrailsApplication app : GrailsApplicationManager.getInstance(project).getApplications()) {
+      String appPrefix = clean(app.getName());
+      String viewPath = (appNameAndViewPath.startsWith(appPrefix)
+                         ? appNameAndViewPath.substring(appPrefix.length())
+                         : appNameAndViewPath).replace("_", "/");
+      VirtualFile view = app.getAppRoot().findFileByRelativePath("views" + viewPath + GsonConstants.FILE_SUFFIX);
+      if (view == null) continue;
+      if (scope.contains(view)) {
+        PsiFile psiFile = PsiManager.getInstance(project).findFile(view);
+        if (psiFile != null) return psiFile;
+      }
+    }
+    return null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt
deleted file mode 100644
index 1e3bd97..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.roots.ProjectFileIndex
-import com.intellij.openapi.vfs.VfsUtilCore
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiFile
-import com.intellij.psi.PsiManager
-import com.intellij.psi.search.GlobalSearchScope
-import com.sun.jdi.ReferenceType
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-import org.jetbrains.plugins.groovy.extensions.debugger.ScriptPositionManagerHelper
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-
-class GsonPositionManagerDelegate : ScriptPositionManagerHelper() {
-
-  companion object {
-    private const val suffix = "_${GsonConstants.EXTENSION}"
-    private fun String.clean(): String = replace("[\\W\\s]".toRegex(), "_")
-  }
-
-  override fun isAppropriateScriptFile(scriptFile: GroovyFile): Boolean {
-    return isGsonFile(scriptFile) && GrailsApplicationManager.findApplication(scriptFile) != null
-  }
-
-  /**
-   * grails.views.resolve.GenericGroovyTemplateResolver#resolveTemplateName
-   */
-  override fun getRuntimeScriptName(groovyFile: GroovyFile): String? {
-    val application = GrailsApplicationManager.findApplication(groovyFile) ?: return null
-    val file = groovyFile.virtualFile ?: return null
-    val viewsRoot = ProjectFileIndex.getInstance(groovyFile.project).getSourceRootForFile(file) ?: return null
-    val path = VfsUtilCore.getRelativePath(file, viewsRoot) ?: return null
-    return application.name.clean() + "_" + path.replace("/", "_").replace(".", "_")
-  }
-
-  override fun isAppropriateRuntimeName(runtimeName: String): Boolean = runtimeName.endsWith(suffix)
-
-  override fun customizeClassName(psiClass: PsiClass): String? = (psiClass.containingFile as? GroovyFile)?.let {
-    getRuntimeScriptName(it)
-  }
-
-  override fun getExtraScriptIfNotFound(refType: ReferenceType, runtimeName: String, project: Project, scope: GlobalSearchScope): PsiFile? {
-    val appNameAndViewPath = runtimeName.removeSuffix(suffix)
-    for (app in GrailsApplicationManager.getInstance(project).applications) {
-      val viewPath = appNameAndViewPath.removePrefix(app.name.clean()).replace("_", "/")
-      val viewFilePath = "views" + viewPath + GsonConstants.FILE_SUFFIX
-      val view = app.appRoot.findFileByRelativePath(viewFilePath) ?: continue
-      if (scope.contains(view)) PsiManager.getInstance(project).findFile(view)?.let { return it }
-    }
-    return null
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRWAccessDetector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRWAccessDetector.java
new file mode 100644
index 0000000..3e63c3b
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRWAccessDetector.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.findUsages.GroovyReadWriteAccessDetector;
+
+public final class GsonRWAccessDetector extends GroovyReadWriteAccessDetector {
+
+  @Override
+  public boolean isReadWriteAccessible(@NotNull PsiElement element) {
+    return GsonUtils.isModelVariable(element);
+  }
+
+  @Override
+  public boolean isDeclarationWriteAccess(@NotNull PsiElement element) {
+    // A model-closure variable is populated by the controller, not by its own declaration.
+    return !GsonUtils.isModelVariable(element) && super.isDeclarationWriteAccess(element);
+  }
+
+  @Override
+  public @NotNull Access getReferenceAccess(@NotNull PsiElement referencedElement, @NotNull PsiReference reference) {
+    return reference instanceof GsonControllerReference
+           ? Access.Write
+           : super.getReferenceAccess(referencedElement, reference);
+  }
+
+  @Override
+  public @NotNull Access getExpressionAccess(@NotNull PsiElement expression) {
+    return GsonPatterns.CONTROLLER_REFERENCE_PLACE.accepts(expression)
+           ? Access.Write
+           : super.getExpressionAccess(expression);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.java
similarity index 57%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.java
index bc264b0..ed3116a 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.java
@@ -13,15 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.gson;
 
-package org.jetbrains.plugins.grails.gson
+import com.intellij.psi.PsiReferenceContributor;
+import com.intellij.psi.PsiReferenceRegistrar;
+import org.jetbrains.annotations.NotNull;
 
-import com.intellij.psi.PsiReferenceContributor
-import com.intellij.psi.PsiReferenceRegistrar
+public final class GsonReferenceContributor extends PsiReferenceContributor {
 
-class GsonReferenceContributor : PsiReferenceContributor() {
-
-  override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
-    registrar.registerReferenceProvider(TEMPLATE_REFERENCE_PLACE, GsonTemplateReferenceProvider())
+  @Override
+  public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
+    registrar.registerReferenceProvider(GsonPatterns.TEMPLATE_REFERENCE_PLACE, new GsonTemplateReferenceProvider());
   }
-}
\ No newline at end of file
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderNamedArgumentsProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderNamedArgumentsProvider.java
new file mode 100644
index 0000000..8dc1ab8
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderNamedArgumentsProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider;
+import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor;
+import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
+
+import java.util.Map;
+
+public final class GsonRenderNamedArgumentsProvider extends GroovyNamedArgumentProvider {
+
+  @Override
+  public void getNamedArguments(@NotNull GrCall call,
+                                @NotNull GroovyResolveResult resolveResult,
+                                @Nullable String argumentName,
+                                boolean forCompletion,
+                                @NotNull Map<String, NamedArgumentDescriptor> result) {
+    if (GsonPatterns.RENDER_METHOD_PATTERN_1.accepts(resolveResult.getElement())) {
+      result.put("template", NamedArgumentDescriptor.TYPE_STRING);
+      result.put("collection", NamedArgumentDescriptor.SIMPLE_ON_TOP);
+      result.put("model", NamedArgumentDescriptor.TYPE_MAP);
+      result.put("var", NamedArgumentDescriptor.TYPE_STRING);
+      // result.put("bean", NamedArgumentDescriptor.SIMPLE_NORMAL);
+    }
+  }
+
+  @Override
+  public @NotNull Map<String, NamedArgumentDescriptor> getNamedArguments(@NotNull GrListOrMap literal) {
+    if (GsonPatterns.RENDER_MAP_PARAMETER.accepts(literal)) {
+      return Map.of("includes", NamedArgumentDescriptor.TYPE_LIST,
+                    "excludes", NamedArgumentDescriptor.TYPE_LIST,
+                    "deep", NamedArgumentDescriptor.TYPE_BOOL);
+    }
+    return Map.of();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt
deleted file mode 100644
index 59e339a..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import com.intellij.openapi.application.QueryExecutorBase
-import com.intellij.openapi.roots.ProjectFileIndex
-import com.intellij.openapi.vfs.VfsUtilCore
-import com.intellij.patterns.PsiMethodPattern
-import com.intellij.patterns.StandardPatterns
-import com.intellij.psi.CommonClassNames
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiManager
-import com.intellij.psi.PsiReference
-import com.intellij.psi.PsiReferenceBase
-import com.intellij.psi.PsiReferenceProvider
-import com.intellij.psi.search.UsageSearchContext
-import com.intellij.psi.search.searches.ReferencesSearch
-import com.intellij.util.ProcessingContext
-import com.intellij.util.Processor
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider
-import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult
-import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
-import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl
-import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyElementPattern
-import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyExpressionPattern
-import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns
-import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames
-
-class GsonRenderNamedArgumentsProvider : GroovyNamedArgumentProvider() {
-
-  override fun getNamedArguments(call: GrCall,
-                                 resolveResult: GroovyResolveResult,
-                                 argumentName: String?,
-                                 forCompletion: Boolean,
-                                 result: MutableMap<String, NamedArgumentDescriptor>) {
-    if (RENDER_METHOD_PATTERN_1.accepts(resolveResult.element)) {
-      result["template"] = NamedArgumentDescriptor.TYPE_STRING
-      result["collection"] = NamedArgumentDescriptor.SIMPLE_ON_TOP
-      result["model"] = NamedArgumentDescriptor.TYPE_MAP
-      result["var"] = NamedArgumentDescriptor.TYPE_STRING
-//    result["bean"] = NamedArgumentDescriptor.SIMPLE_NORMAL
-    }
-  }
-
-  override fun getNamedArguments(literal: GrListOrMap): Map<String, NamedArgumentDescriptor> {
-    if (RENDER_MAP_PARAMETER.accepts(literal)) {
-      return mapOf(
-          "includes" to NamedArgumentDescriptor.TYPE_LIST,
-          "excludes" to NamedArgumentDescriptor.TYPE_LIST,
-          "deep" to NamedArgumentDescriptor.TYPE_BOOL
-      )
-    }
-    return emptyMap()
-  }
-}
-
-class GsonTemplateReferenceProvider : PsiReferenceProvider() {
-  override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<out PsiReference> {
-    val parent = element.parent
-    if (parent !is GrNamedArgument) return emptyArray()
-    if (parent.expression != element) return emptyArray()
-    return arrayOf(GsonTemplateReference(element as GrLiteral))
-  }
-}
-
-class GsonTemplateReference(literal: GrLiteral) : PsiReferenceBase<GrLiteral>(literal) {
-
-  override fun resolve(): PsiElement? {
-    val value = element.value as? String ?: return null
-    val virtualFile = element.containingFile?.virtualFile ?: return null
-    val template = if (value.startsWith("/")) {
-      val templateName = value.split('/').lastOrNull()
-      val templatePath = value.replaceAfterLast('/', "_$templateName${GsonConstants.FILE_SUFFIX}")
-      val viewsRoot = ProjectFileIndex.getInstance(element.project).getSourceRootForFile(virtualFile) ?: return null
-      VfsUtilCore.findRelativeFile(templatePath, viewsRoot)
-    }
-    else {
-      virtualFile.parent?.findChild("_$value${GsonConstants.FILE_SUFFIX}")
-    }
-    return PsiManager.getInstance(element.project).findFile(template ?: return null)
-  }
-
-  override fun handleElementRename(newElementName: String): PsiElement? {
-    val current = element.value as? String ?: throw IllegalStateException()
-    val templateName = getGsonTemplateName(newElementName)
-    val newContent = if (current.startsWith("/")) {
-      current.replaceAfterLast('/', templateName)
-    }
-    else {
-      templateName
-    }
-    return super.handleElementRename(newContent)
-  }
-}
-
-class GsonTemplateReferenceQueryExecutor : QueryExecutorBase<PsiReference, ReferencesSearch.SearchParameters>(true) {
-
-  override fun processQuery(p: ReferencesSearch.SearchParameters, consumer: Processor<in PsiReference>) {
-    val element = p.elementToSearch as? GroovyFile ?: return
-    val templateName = getGsonTemplateName(element) ?: return
-    p.optimizer.searchWord(templateName, p.effectiveSearchScope, UsageSearchContext.IN_STRINGS, true, element)
-
-    val virtualFile = element.virtualFile
-    val viewsRoot = ProjectFileIndex.getInstance(element.project).getSourceRootForFile(virtualFile) ?: return
-    val path = VfsUtilCore.getRelativePath(virtualFile.parent, viewsRoot) ?: return
-    val templateFqn = "/$path/$templateName"
-    p.optimizer.searchWord(templateFqn, p.effectiveSearchScope, UsageSearchContext.IN_STRINGS, true, element)
-  }
-}
-
-private val RENDER_METHOD_BASE_PATTERN = PsiMethodPattern().withName("render").definedInClass(
-    "grails.plugin.json.view.api.GrailsJsonViewHelper"
-)
-
-// GrailsJsonViewHelper#render(java.util.Map)
-private val RENDER_METHOD_PATTERN_1 = RENDER_METHOD_BASE_PATTERN.withParameters(
-    CommonClassNames.JAVA_UTIL_MAP
-)
-
-// GrailsJsonViewHelper#render(java.lang.Object, java.util.Map)
-private val RENDER_METHOD_PATTERN_2 = RENDER_METHOD_BASE_PATTERN.withParameters(
-    CommonClassNames.JAVA_LANG_OBJECT, CommonClassNames.JAVA_UTIL_MAP
-)
-
-// GrailsJsonViewHelper#render(java.lang.Object, java.util.Map, groovy.lang.Closure)
-private val RENDER_METHOD_PATTERN_3 = RENDER_METHOD_BASE_PATTERN.withParameters(
-    CommonClassNames.JAVA_LANG_OBJECT, CommonClassNames.JAVA_UTIL_MAP, GroovyCommonClassNames.GROOVY_LANG_CLOSURE
-)
-
-// second (java.util.Map) parameter of
-// GrailsJsonViewHelper#render(java.lang.Object, java.util.Map)
-// GrailsJsonViewHelper#render(java.lang.Object, java.util.Map, groovy.lang.Closure)
-private val RENDER_MAP_PARAMETER = GroovyExpressionPattern.Capture(GrListOrMap::class.java).methodCallParameter(
-    1, StandardPatterns.or(RENDER_METHOD_PATTERN_2, RENDER_METHOD_PATTERN_3)
-)
-
-// g.render(template: "<place>")
-val TEMPLATE_REFERENCE_PLACE: GroovyElementPattern.Capture<GrLiteralImpl> = GroovyPatterns.stringLiteral().withParent(
-    GroovyPatterns.namedArgument().withLabel("template").withParent(
-        GroovyPatterns.groovyElement().withParent(
-            GroovyPatterns.methodCall().withMethod(RENDER_METHOD_PATTERN_1)
-        )
-    )
-)
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReference.java
new file mode 100644
index 0000000..34b7ffb
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReference.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
+
+public final class GsonTemplateReference extends PsiReferenceBase<GrLiteral> {
+
+  public GsonTemplateReference(@NotNull GrLiteral literal) {
+    super(literal);
+  }
+
+  @Override
+  public @Nullable PsiElement resolve() {
+    GrLiteral element = getElement();
+    if (!(element.getValue() instanceof String value)) return null;
+    if (element.getContainingFile() == null) return null;
+    VirtualFile virtualFile = element.getContainingFile().getVirtualFile();
+    if (virtualFile == null) return null;
+
+    VirtualFile template;
+    if (value.startsWith("/")) {
+      // An absolute template path is resolved against the views source root, with the file name
+      // rewritten to the underscore-prefixed template form.
+      int slash = value.lastIndexOf('/');
+      String templateName = value.substring(slash + 1);
+      String templatePath = value.substring(0, slash + 1) + "_" + templateName + GsonConstants.FILE_SUFFIX;
+      VirtualFile viewsRoot = ProjectFileIndex.getInstance(element.getProject()).getSourceRootForFile(virtualFile);
+      if (viewsRoot == null) return null;
+      template = VfsUtilCore.findRelativeFile(templatePath, viewsRoot);
+    }
+    else {
+      VirtualFile parent = virtualFile.getParent();
+      template = parent == null ? null : parent.findChild("_" + value + GsonConstants.FILE_SUFFIX);
+    }
+
+    return template == null ? null : PsiManager.getInstance(element.getProject()).findFile(template);
+  }
+
+  @Override
+  public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
+    if (!(getElement().getValue() instanceof String current)) throw new IllegalStateException();
+    String templateName = GsonUtils.getGsonTemplateName(newElementName);
+    // Keep an absolute path's directory part; a bare name is replaced outright.
+    int slash = current.lastIndexOf('/');
+    String newContent = current.startsWith("/") && slash >= 0
+                        ? current.substring(0, slash + 1) + templateName
+                        : templateName;
+    return super.handleElementRename(newContent);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceProvider.java
new file mode 100644
index 0000000..e0a1774
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.PsiReferenceProvider;
+import com.intellij.util.ProcessingContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
+
+public final class GsonTemplateReferenceProvider extends PsiReferenceProvider {
+
+  @Override
+  public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element,
+                                                        @NotNull ProcessingContext context) {
+    if (!(element.getParent() instanceof GrNamedArgument parent)) return PsiReference.EMPTY_ARRAY;
+    if (parent.getExpression() != element) return PsiReference.EMPTY_ARRAY;
+    return new PsiReference[]{new GsonTemplateReference((GrLiteral)element)};
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceQueryExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceQueryExecutor.java
new file mode 100644
index 0000000..70c9621
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateReferenceQueryExecutor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.openapi.application.QueryExecutorBase;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.UsageSearchContext;
+import com.intellij.psi.search.searches.ReferencesSearch;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+
+public final class GsonTemplateReferenceQueryExecutor
+  extends QueryExecutorBase<PsiReference, ReferencesSearch.SearchParameters> {
+
+  public GsonTemplateReferenceQueryExecutor() {
+    super(true);
+  }
+
+  @Override
+  public void processQuery(@NotNull ReferencesSearch.SearchParameters p,
+                           @NotNull Processor<? super PsiReference> consumer) {
+    if (!(p.getElementToSearch() instanceof GroovyFile element)) return;
+    String templateName = GsonUtils.getGsonTemplateName(element);
+    if (templateName == null) return;
+
+    // Templates are referenced by bare name and by absolute path, so search for both spellings.
+    p.getOptimizer().searchWord(templateName, p.getEffectiveSearchScope(), UsageSearchContext.IN_STRINGS, true, element);
+
+    VirtualFile virtualFile = element.getVirtualFile();
+    VirtualFile viewsRoot = ProjectFileIndex.getInstance(element.getProject()).getSourceRootForFile(virtualFile);
+    if (viewsRoot == null) return;
+    String path = VfsUtilCore.getRelativePath(virtualFile.getParent(), viewsRoot);
+    if (path == null) return;
+    String templateFqn = "/" + path + "/" + templateName;
+    p.getOptimizer().searchWord(templateFqn, p.getEffectiveSearchScope(), UsageSearchContext.IN_STRINGS, true, element);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.java
new file mode 100644
index 0000000..7998eb2
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.psi.CommonClassNames;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiType;
+import com.intellij.psi.ResolveState;
+import com.intellij.psi.scope.PsiScopeProcessor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
+import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor;
+import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
+import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtilKt;
+
+public final class GsonTemplateRendererContributor extends NonCodeMembersContributor {
+
+  private static final String JSON_UNESCAPED = "grails.plugin.json.builder.JsonOutput.JsonUnescaped";
+
+  @Override
+  protected String getParentClassName() {
+    return "grails.plugin.json.view.api.internal.TemplateRenderer";
+  }
+
+  @Override
+  public void processDynamicElements(@NotNull PsiType qualifierType,
+                                     @NotNull PsiScopeProcessor processor,
+                                     @NotNull PsiElement place,
+                                     @NotNull ResolveState state) {
+    if (!ResolveUtilKt.shouldProcessMethods(processor)) return;
+    String name = ResolveUtil.getNameHint(processor);
+    if (name == null) return;
+
+    // render(value) and render(var, collection); both are synthesised for whatever name the
+    // resolver is asking about.
+    GrLightMethodBuilder single = new GrLightMethodBuilder(place.getManager(), name);
+    single.addParameter("value", CommonClassNames.JAVA_LANG_OBJECT);
+    single.setReturnType(JSON_UNESCAPED, place.getResolveScope());
+    if (!processor.execute(single, state)) return;
+
+    GrLightMethodBuilder collection = new GrLightMethodBuilder(place.getManager(), name);
+    collection.addParameter("var", CommonClassNames.JAVA_LANG_OBJECT);
+    collection.addParameter("collection", CommonClassNames.JAVA_LANG_ITERABLE);
+    collection.setReturnType(JSON_UNESCAPED, place.getResolveScope());
+    processor.execute(collection, state);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt
deleted file mode 100644
index ece7747..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import com.intellij.psi.CommonClassNames
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiType
-import com.intellij.psi.ResolveState
-import com.intellij.psi.scope.PsiScopeProcessor
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder
-import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor
-import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil
-import org.jetbrains.plugins.groovy.lang.resolve.shouldProcessMethods
-
-class GsonTemplateRendererContributor : NonCodeMembersContributor() {
-
-  override fun getParentClassName(): String = "grails.plugin.json.view.api.internal.TemplateRenderer"
-
-  override fun processDynamicElements(qualifierType: PsiType, processor: PsiScopeProcessor, place: PsiElement, state: ResolveState) {
-    if (!processor.shouldProcessMethods()) return
-    val name = ResolveUtil.getNameHint(processor) ?: return
-
-    GrLightMethodBuilder(place.manager, name).apply {
-      addParameter("value", CommonClassNames.JAVA_LANG_OBJECT)
-      setReturnType("grails.plugin.json.builder.JsonOutput.JsonUnescaped", place.resolveScope)
-      if (!processor.execute(this, state)) return
-    }
-
-    GrLightMethodBuilder(place.manager, name).apply {
-      addParameter("var", CommonClassNames.JAVA_LANG_OBJECT)
-      addParameter("collection", CommonClassNames.JAVA_LANG_ITERABLE)
-      setReturnType("grails.plugin.json.builder.JsonOutput.JsonUnescaped", place.resolveScope)
-      if (!processor.execute(this, state)) return
-    }
-  }
-}
-
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.java
new file mode 100644
index 0000000..92367fc
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.grails.references.TraitInjectorService;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
+import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport;
+import org.jetbrains.plugins.groovy.transformations.TransformationContext;
+
+public final class GsonTransformationSupport implements AstTransformationSupport {
+
+  private static final String VIEW_TYPE = "views";
+  private static final String GSON_VIEW_TYPE = "view.gson";
+
+  @Override
+  public void applyTransformation(@NotNull TransformationContext context) {
+    if (!(context.getCodeClass() instanceof GroovyScriptClass scriptClass)) return;
+    GroovyFile file = scriptClass.getContainingFile();
+    if (!file.getName().endsWith(GsonConstants.FILE_SUFFIX)) return;
+
+    GrClosableBlock modelClosure = GsonUtils.findModelClosure(file);
+    if (modelClosure != null) {
+      // Each variable declared in the model closure becomes a script field, so references to it
+      // elsewhere in the view resolve.
+      modelClosure.acceptChildren(new GroovyElementVisitor() {
+        @Override
+        public void visitVariableDeclaration(@NotNull GrVariableDeclaration variableDeclaration) {
+          for (GrVariable variable : variableDeclaration.getVariables()) {
+            context.addField(new GrScriptField(variable, scriptClass));
+          }
+        }
+      });
+    }
+
+    context.setSuperType("grails.plugin.json.view.JsonViewTemplate");
+    for (String fqn : TraitInjectorService.getInjectedTraits(context.getCodeClass(), VIEW_TYPE)) {
+      context.addInterface(fqn);
+    }
+    for (String fqn : TraitInjectorService.getInjectedTraits(context.getCodeClass(), GSON_VIEW_TYPE)) {
+      context.addInterface(fqn);
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt
deleted file mode 100644
index ed97d80..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.grails.references.TraitInjectorService
-import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass
-import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport
-import org.jetbrains.plugins.groovy.transformations.TransformationContext
-
-class GsonTransformationSupport : AstTransformationSupport {
-
-  private val VIEW_TYPE = "views"
-  private val GSON_VIEW_TYPE = "view.gson"
-
-  override fun applyTransformation(context: TransformationContext) {
-    if (context.codeClass !is GroovyScriptClass) return
-    val scriptClass = context.codeClass as GroovyScriptClass
-    val file = scriptClass.containingFile
-    if (!file.name.endsWith(GsonConstants.FILE_SUFFIX)) return
-
-    findModelClosure(file)?.acceptChildren(object : GroovyElementVisitor() {
-      override fun visitVariableDeclaration(variableDeclaration: GrVariableDeclaration) {
-        for (variable in variableDeclaration.variables) {
-          context.addField(GrScriptField(variable, scriptClass))
-        }
-      }
-    })
-    context.setSuperType("grails.plugin.json.view.JsonViewTemplate")
-    val s = { fqn: String -> context.addInterface(fqn) }
-    TraitInjectorService.getInjectedTraits(context.codeClass, VIEW_TYPE).forEach(s)
-    TraitInjectorService.getInjectedTraits(context.codeClass, GSON_VIEW_TYPE).forEach(s)
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.java
new file mode 100644
index 0000000..d6bdea8
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.gson;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
+import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class GsonUtils {
+
+  private GsonUtils() {
+  }
+
+  public static boolean isGsonFile(@NotNull PsiFile file) {
+    return file.getName().endsWith(GsonConstants.FILE_SUFFIX);
+  }
+
+  static @NotNull List<GrScriptField> getModelFields(@Nullable PsiElement element) {
+    return getModelFields(getScriptClass(element));
+  }
+
+  public static @Nullable GroovyScriptClass getScriptClass(@Nullable PsiElement element) {
+    PsiElement parent = PsiTreeUtil.getParentOfType(element, GrField.class, GrMethod.class);
+    if (!(parent instanceof GrMember action)) return null;
+
+    for (PsiFile view : GrailsUtils.getViewPsiByAction(action)) {
+      if (!isGsonFile(view)) continue;
+      if (view instanceof GroovyFile groovyFile && groovyFile.getScriptClass() instanceof GroovyScriptClass scriptClass) {
+        return scriptClass;
+      }
+      return null;
+    }
+    return null;
+  }
+
+  public static @NotNull List<GrScriptField> getModelFields(@Nullable GroovyScriptClass scriptClass) {
+    if (scriptClass == null) return List.of();
+    List<GrScriptField> result = new ArrayList<>();
+    for (var field : scriptClass.getFields()) {
+      if (field instanceof GrScriptField scriptField) result.add(scriptField);
+    }
+    return result;
+  }
+
+  public static boolean isModelVariable(@NotNull PsiElement variable) {
+    return variable instanceof GrVariable grVariable && isModelVariable(grVariable);
+  }
+
+  public static boolean isModelVariable(@NotNull GrVariable variable) {
+    PsiElement parent = variable.getParent();
+    if (parent == null || !(parent.getParent() instanceof GrClosableBlock closure)) return false;
+    if (!(variable.getContainingFile() instanceof GroovyFile file)) return false;
+    return closure.equals(findModelClosure(file));
+  }
+
+  public static @Nullable GrClosableBlock findModelClosure(@NotNull GroovyFile file) {
+    for (GrTopStatement statement : file.getTopStatements()) {
+      if (!(statement instanceof GrMethodCallExpression call)) continue;
+      if (!(call.getInvokedExpression() instanceof GrReferenceExpression ref)) continue;
+      if (!"model".equals(ref.getReferenceName())) continue;
+      GrClosableBlock[] arguments = call.getClosureArguments();
+      return arguments.length == 1 ? arguments[0] : null;
+    }
+    return null;
+  }
+
+  public static boolean isGsonTemplate(@Nullable PsiElement element) {
+    if (!(element instanceof GroovyFile file) || !file.isScript()) return false;
+    VirtualFile virtualFile = file.getVirtualFile();
+    if (virtualFile == null) return false;
+    CharSequence name = virtualFile.getNameSequence();
+    return name.length() > 0 && name.charAt(0) == '_' && name.toString().endsWith(GsonConstants.FILE_SUFFIX);
+  }
+
+  public static @Nullable String getGsonTemplateName(@NotNull GroovyFile file) {
+    return isGsonTemplate(file) ? getGsonTemplateName(file.getName()) : null;
+  }
+
+  public static @NotNull String getGsonTemplateName(@NotNull String fileName) {
+    String name = fileName.startsWith("_") ? fileName.substring(1) : fileName;
+    return name.endsWith(GsonConstants.FILE_SUFFIX)
+           ? name.substring(0, name.length() - GsonConstants.FILE_SUFFIX.length())
+           : name;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.kt
deleted file mode 100644
index 1b75e7f..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.gson
-
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiFile
-import com.intellij.psi.util.PsiTreeUtil
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.grails.util.GrailsUtils
-import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField
-import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass
-
-fun isGsonFile(file: PsiFile): Boolean = file.name.endsWith(GsonConstants.FILE_SUFFIX)
-
-internal fun getModelFields(element: PsiElement?): List<GrScriptField> {
-  return getModelFields(getScriptClass(element))
-}
-
-fun getScriptClass(element: PsiElement?): GroovyScriptClass? {
-  val action = PsiTreeUtil.getParentOfType(element, GrField::class.java, GrMethod::class.java) as? GrMember ?: return null
-  val gsonView = GrailsUtils.getViewPsiByAction(action).find { isGsonFile(it) }
-  return (gsonView as? GroovyFile)?.scriptClass as? GroovyScriptClass
-}
-
-fun getModelFields(scriptClass: GroovyScriptClass?): List<GrScriptField> {
-  if (scriptClass == null) return emptyList()
-  return scriptClass.fields.filterIsInstance(GrScriptField::class.java)
-}
-
-fun isModelVariable(variable: PsiElement): Boolean {
-  return variable is GrVariable && isModelVariable(variable)
-}
-
-fun isModelVariable(variable: GrVariable): Boolean {
-  val closure = variable.parent?.parent as? GrClosableBlock ?: return false
-  val file = variable.containingFile as? GroovyFile ?: return false
-  return closure == findModelClosure(file)
-}
-
-fun findModelClosure(file: GroovyFile): GrClosableBlock? {
-  for (statement in file.topStatements) {
-    if (statement !is GrMethodCallExpression) continue
-    if ("model" != (statement.invokedExpression as? GrReferenceExpression)?.referenceName) continue
-    val arguments = statement.closureArguments
-    return if (arguments.size == 1) arguments[0] else null
-  }
-  return null
-}
-
-fun isGsonTemplate(element: PsiElement?): Boolean = element is GroovyFile && element.isScript && element.virtualFile?.let {
-  it.nameSequence.startsWith("_") && it.nameSequence.endsWith(GsonConstants.FILE_SUFFIX)
-} ?: false
-
-fun getGsonTemplateName(file: GroovyFile): String? = if (isGsonTemplate(file)) getGsonTemplateName(file.name) else null
-
-fun getGsonTemplateName(fileName: String): String = fileName.removePrefix("_").removeSuffix(
-  GsonConstants.FILE_SUFFIX)
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
index 0b600fb..f77e28f 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
@@ -312,6 +312,10 @@ class TraitInjectorService(
       return getInjectedTraits(clazz, artefactType)
     }
 
+    // @JvmStatic so Java callers reach this as TraitInjectorService.getInjectedTraits(...) rather
+    // than through .Companion. This class stays Kotlin (it is built on coroutines), so the
+    // annotation is how it keeps a usable surface for the converted Java callers.
+    @JvmStatic
     fun getInjectedTraits(context: PsiElement, artefactType: String): Collection<String> {
       val result: MutableSet<String> = LinkedHashSet()
       result.addAll(doGetInjectedTraits(context, artefactType))


(grails-intellij-plugin) 28/35: Convert the gsp parsing, plugin discovery and test-runner setup to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit f4b26662f0c12344855230117f2f7e0e124dffe6
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 14:57:08 2026 -0400

    Convert the gsp parsing, plugin discovery and test-runner setup to Java
    
    Twenty more files, in four groups.
    
    lang/gsp: the four chameleon element types and the highlighter provider are
    mechanical. gspImportUtil.kt already carried @file:JvmName("GspImportUtil"),
    so its one Java caller is unaffected by the file becoming a real class.
    
    plugins/: descriptors.kt splits into the interface plus its two
    implementations, and impl.kt becomes GrailsPlugins. GrailsPluginDescriptor
    computes the plugin name with an explicit Character.toLowerCase rather than
    StringUtil.decapitalize -- decapitalize leaves an all-caps prefix alone, so
    URLMappingsGrailsPlugin would have stopped matching what Kotlin's
    replaceFirstChar produced.
    
    tests/runner: byClass.kt, byDirectory.kt and oldUtils.kt used file names as
    namespaces, which has no Java equivalent; they collapse into
    GrailsTestConfigurationSetup, the unit they always were. versions.kt held
    three constants, two of which (1.1, 3.0) already existed on Version as
    GRAILS_1_1 and GRAILS_3_0; the third is added there as GRAILS_1_3 and the
    separate holder goes away.
    
    assetPipeline: references.kt splits by class, with the private asset-folder
    computation becoming static helpers on AssetsFileReferenceSet, its only user.
    
    TraitInjectorService.kt stays Kotlin (coroutines) but had called
    doGetEnhancesTraits as a same-package top-level function, so it now names the
    holder class.
---
 .../impl/controllers/ControllerActions.java        |  86 ++++++++
 .../grails/artefact/impl/controllers/functions.kt  |  61 ------
 .../grails/config/GrailsIgnoredProvider.java       |  55 +++++
 .../plugins/grails/config/GrailsIgnoredProvider.kt |  43 ----
 .../highlighter/GspEditorHighlighterProvider.java  |  36 ++++
 .../highlighter/GspEditorHighlighterProvider.kt    |  33 ---
 ...tType.kt => GroovyDeclarationsElementType.java} |  18 +-
 ...entType.kt => GroovyExpressionElementType.java} |  18 +-
 ...lueType.kt => GroovyMapAttributeValueType.java} |  18 +-
 .../chameleons/GspGroovyLazyElementType.java       |  46 +++++
 .../groovy/chameleons/GspGroovyLazyElementType.kt  |  36 ----
 .../grails/lang/gsp/psi/impl/GspImportUtil.java    |  89 ++++++++
 .../grails/lang/gsp/psi/impl/gspImportUtil.kt      |  80 --------
 .../assetPipeline/AssetReferenceContributor.java   |  51 +++++
 .../assetPipeline/AssetReferenceProvider.java}     |  17 +-
 .../assetPipeline/AssetScriptInjector.java         |  62 ++++++
 .../assetPipeline/AssetScriptInjector.kt           |  56 -----
 .../assetPipeline/AssetsFileReferenceSet.java      | 124 +++++++++++
 .../pluginSupport/assetPipeline/references.kt      | 113 ----------
 .../plugins/Grails3CompiledPluginDescriptor.java   |  47 +++++
 .../plugins/Grails3SourcePluginDescriptor.java     |  47 +++++
 .../grails/plugins/GrailsPluginDescriptor.java     |  39 ++++
 .../plugins/grails/plugins/GrailsPlugins.java      | 109 ++++++++++
 .../plugins/grails/plugins/descriptors.kt          |  46 -----
 .../org/jetbrains/plugins/grails/plugins/impl.kt   |  67 ------
 .../grails/projectView/GrailsPluginsNode.java      |   4 +-
 .../projectView/nodes/GrailsControllerNode.java    |   4 +-
 .../grails/references/EnhancesTraitsCollector.java |  90 ++++++++
 .../grails/references/EnhancesTraitsCollector.kt   |  63 ------
 .../grails/references/TraitInjectorService.kt      |   2 +-
 ...solver.kt => GspTagMethodOverloadResolver.java} |  17 +-
 .../DetachedCriteriaDelegatesToProvider.java       |  58 ++++++
 .../DetachedCriteriaDelegatesToProvider.kt         |  53 -----
 .../grails/spring/GrailsSpringBeanDiscoverer.java  |   4 +-
 .../grails/spring/GrailsSpringSemContributor.java  |  43 ++++
 .../grails/spring/GrailsSpringSemContributor.kt    |  37 ----
 .../runner/GrailsTestConfigurationProducer.java    |   4 +-
 .../tests/runner/GrailsTestConfigurationSetup.java | 228 +++++++++++++++++++++
 .../plugins/grails/tests/runner/byClass.kt         |  95 ---------
 .../plugins/grails/tests/runner/byDirectory.kt     | 106 ----------
 .../plugins/grails/tests/runner/oldUtils.kt        |  36 ----
 .../plugins/grails/util/version/Version.java       |   1 +
 .../plugins/grails/util/version/versions.kt        |  21 --
 43 files changed, 1279 insertions(+), 984 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/ControllerActions.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/ControllerActions.java
new file mode 100644
index 0000000..ab854e7
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/ControllerActions.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.artefact.impl.controllers;
+
+import com.intellij.openapi.util.Key;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.ResolveState;
+import com.intellij.psi.SyntheticElement;
+import com.intellij.psi.scope.ElementClassHint;
+import com.intellij.psi.scope.PsiScopeProcessor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+import org.jetbrains.plugins.grails.util.version.Version;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class ControllerActions {
+
+  private ControllerActions() {
+  }
+
+  /** Action name to the member declaring it, in declaration order. */
+  public static @NotNull Map<String, PsiMember> getActions(@NotNull GrTypeDefinition controller,
+                                                           @NotNull GrailsApplication grailsApplication) {
+    ActionProcessor processor = new ActionProcessor(grailsApplication.getGrailsVersion().isAtLeast(Version.GRAILS_2_0));
+    controller.processDeclarations(processor, ResolveState.initial(), null, controller);
+    return processor.getActions();
+  }
+
+  private static final class ActionProcessor implements PsiScopeProcessor, ElementClassHint {
+
+    private final boolean myAtLeast14;
+    private final Map<String, PsiMember> myActions = new LinkedHashMap<>();
+
+    private ActionProcessor(boolean atLeast14) {
+      myAtLeast14 = atLeast14;
+    }
+
+    private @NotNull Map<String, PsiMember> getActions() {
+      return myActions;
+    }
+
+    @Override
+    public boolean shouldProcess(@NotNull DeclarationKind kind) {
+      return kind == DeclarationKind.FIELD || kind == DeclarationKind.METHOD;
+    }
+
+    @Override
+    public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
+      if (element instanceof SyntheticElement) return true;
+      String name = GrailsUtils.getActionName0(element, false, myAtLeast14);
+      // First declaration wins, so a subclass override does not displace the base action.
+      if (name != null && !myActions.containsKey(name)) {
+        myActions.put(name, (PsiMember)element);
+      }
+      return true;
+    }
+
+    @Override
+    public <T> @Nullable T getHint(@NotNull Key<T> hintKey) {
+      if (hintKey == ElementClassHint.KEY) {
+        //noinspection unchecked
+        return (T)this;
+      }
+      return null;
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt
deleted file mode 100644
index 6e3441f..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.artefact.impl.controllers
-
-import com.intellij.openapi.util.Key
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiMember
-import com.intellij.psi.ResolveState
-import com.intellij.psi.SyntheticElement
-import com.intellij.psi.scope.ElementClassHint
-import com.intellij.psi.scope.ElementClassHint.DeclarationKind.FIELD
-import com.intellij.psi.scope.ElementClassHint.DeclarationKind.METHOD
-import com.intellij.psi.scope.PsiScopeProcessor
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.util.GrailsUtils
-import org.jetbrains.plugins.grails.util.version.Version.GRAILS_2_0
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
-
-fun getActions(controller: GrTypeDefinition, grailsApplication: GrailsApplication): Map<String, PsiMember> {
-  val processor = ActionProcessor(grailsApplication.grailsVersion >= GRAILS_2_0)
-  controller.processDeclarations(processor, ResolveState.initial(), null, controller)
-  return processor.actions
-}
-
-class ActionProcessor(private val atLeast14: Boolean) : PsiScopeProcessor, ElementClassHint {
-
-  val actions: MutableMap<String, PsiMember> = mutableMapOf()
-
-  override fun shouldProcess(kind: ElementClassHint.DeclarationKind): Boolean {
-    return kind == FIELD || kind == METHOD
-  }
-
-  override fun execute(element: PsiElement, state: ResolveState): Boolean {
-    if (element is SyntheticElement) return true
-    val name = GrailsUtils.getActionName0(element, false, atLeast14)
-    if (name != null && !actions.containsKey(name)) {
-      actions.put(name, element as PsiMember)
-    }
-    return true
-  }
-
-  @Suppress("UNCHECKED_CAST")
-  override fun <T : Any?> getHint(hintKey: Key<T>): T? {
-    if (hintKey == ElementClassHint.KEY) return this as T
-    return null
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.java
new file mode 100644
index 0000000..82c26a3
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.config;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.changes.IgnoredBeanFactory;
+import com.intellij.openapi.vcs.changes.IgnoredFileDescriptor;
+import com.intellij.openapi.vcs.changes.IgnoredFileProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GrailsBundle;
+
+import java.util.Collections;
+import java.util.Set;
+
+/** Keeps the shared {@code ~/.grails} cache out of VCS when it happens to sit inside the project. */
+public final class GrailsIgnoredProvider implements IgnoredFileProvider {
+
+  @Override
+  public boolean isIgnoredFile(@NotNull Project project, @NotNull FilePath filePath) {
+    return FileUtil.isAncestor(GrailsFramework.getUserHomeGrails(), filePath.getPath(), false);
+  }
+
+  @Override
+  public @NotNull Set<IgnoredFileDescriptor> getIgnoredFiles(@NotNull Project project) {
+    String grailsDir = GrailsFramework.getUserHomeGrails();
+    String projectBasePath = project.getBasePath();
+    if (projectBasePath == null) return Collections.emptySet();
+
+    if (FileUtil.isAncestor(projectBasePath, grailsDir, true)) {
+      return Set.of(IgnoredBeanFactory.ignoreUnderDirectory(grailsDir, project));
+    }
+
+    return Collections.emptySet();
+  }
+
+  @Override
+  public @NotNull String getIgnoredGroupDescription() {
+    return GrailsBundle.message("ignored.files.description.framework.dir");
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt
deleted file mode 100644
index 6f78a5c..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.config
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.io.FileUtil
-import com.intellij.openapi.vcs.FilePath
-import com.intellij.openapi.vcs.changes.IgnoredBeanFactory
-import com.intellij.openapi.vcs.changes.IgnoredFileDescriptor
-import com.intellij.openapi.vcs.changes.IgnoredFileProvider
-import org.jetbrains.plugins.grails.GrailsBundle
-
-internal class GrailsIgnoredProvider : IgnoredFileProvider {
-  override fun isIgnoredFile(project: Project, filePath: FilePath) =
-    FileUtil.isAncestor(GrailsFramework.getUserHomeGrails(), filePath.path, false)
-
-  override fun getIgnoredFiles(project: Project): Set<IgnoredFileDescriptor> {
-    val grailsDir = GrailsFramework.getUserHomeGrails()
-    val projectBasePath = project.basePath ?: return emptySet()
-
-    if (FileUtil.isAncestor(projectBasePath, grailsDir, true)) {
-      return setOf(IgnoredBeanFactory.ignoreUnderDirectory(grailsDir, project))
-    }
-
-    return emptySet()
-  }
-
-  override fun getIgnoredGroupDescription() = GrailsBundle.message("ignored.files.description.framework.dir")
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.java
new file mode 100644
index 0000000..3e2811c
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.lang.gsp.highlighter;
+
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.highlighter.EditorHighlighter;
+import com.intellij.openapi.fileTypes.EditorHighlighterProvider;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class GspEditorHighlighterProvider implements EditorHighlighterProvider {
+
+  @Override
+  public @NotNull EditorHighlighter getEditorHighlighter(@Nullable Project project,
+                                                         @NotNull FileType fileType,
+                                                         @Nullable VirtualFile virtualFile,
+                                                         @NotNull EditorColorsScheme colors) {
+    return new GspEditorHighlighter(colors, project, virtualFile);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt
deleted file mode 100644
index 6816b3d..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.lang.gsp.highlighter
-
-import com.intellij.openapi.editor.colors.EditorColorsScheme
-import com.intellij.openapi.editor.highlighter.EditorHighlighter
-import com.intellij.openapi.fileTypes.EditorHighlighterProvider
-import com.intellij.openapi.fileTypes.FileType
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VirtualFile
-
-internal class GspEditorHighlighterProvider : EditorHighlighterProvider {
-  override fun getEditorHighlighter(project: Project?,
-                                    fileType: FileType,
-                                    virtualFile: VirtualFile?,
-                                    colors: EditorColorsScheme): EditorHighlighter {
-    return GspEditorHighlighter(colors, project, virtualFile)
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.java
similarity index 62%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.java
index f02d351..e04a7fd 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.java
@@ -13,13 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons;
 
-package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons
+import com.intellij.lang.ASTNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.impl.GrGspDeclarationHolderImpl;
 
-import com.intellij.lang.ASTNode
-import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.impl.GrGspDeclarationHolderImpl
+public class GroovyDeclarationsElementType extends GspGroovyLazyElementType {
 
-class GroovyDeclarationsElementType(debugName: String) : GspGroovyLazyElementType(debugName) {
+  public GroovyDeclarationsElementType(@NotNull String debugName) {
+    super(debugName);
+  }
 
-  override fun createNode(text: CharSequence?): ASTNode = GrGspDeclarationHolderImpl(this, text)
+  @Override
+  public ASTNode createNode(@Nullable CharSequence text) {
+    return new GrGspDeclarationHolderImpl(this, text);
+  }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.java
similarity index 63%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.java
index 8bd2c0c..fa7fe7f 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.java
@@ -13,13 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons;
 
-package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons
+import com.intellij.lang.ASTNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.impl.GrGspExprInjectionImpl;
 
-import com.intellij.lang.ASTNode
-import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.impl.GrGspExprInjectionImpl
+public class GroovyExpressionElementType extends GspGroovyLazyElementType {
 
-class GroovyExpressionElementType(debugName: String) : GspGroovyLazyElementType(debugName) {
+  public GroovyExpressionElementType(@NotNull String debugName) {
+    super(debugName);
+  }
 
-  override fun createNode(text: CharSequence?): ASTNode = GrGspExprInjectionImpl(this, text)
+  @Override
+  public ASTNode createNode(@Nullable CharSequence text) {
+    return new GrGspExprInjectionImpl(this, text);
+  }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.java
similarity index 63%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.java
index ffab1c6..69dfbc6 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.java
@@ -13,13 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons;
 
-package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons
+import com.intellij.lang.ASTNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.impl.GrMapAttrValueImpl;
 
-import com.intellij.lang.ASTNode
-import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.impl.GrMapAttrValueImpl
+public class GroovyMapAttributeValueType extends GspGroovyLazyElementType {
 
-class GroovyMapAttributeValueType(debugName: String) : GspGroovyLazyElementType(debugName) {
+  public GroovyMapAttributeValueType(@NotNull String debugName) {
+    super(debugName);
+  }
 
-  override fun createNode(text: CharSequence?): ASTNode = GrMapAttrValueImpl(this, text)
+  @Override
+  public ASTNode createNode(@Nullable CharSequence text) {
+    return new GrMapAttrValueImpl(this, text);
+  }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.java
new file mode 100644
index 0000000..1a9977e
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.PsiBuilder;
+import com.intellij.lang.PsiBuilderFactory;
+import com.intellij.psi.ParsingDiagnostics;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.ILazyParseableElementType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.GspAwareGroovyParser;
+import org.jetbrains.plugins.groovy.GroovyLanguage;
+
+/**
+ * Base for the Groovy islands inside a GSP: the contents are reparsed lazily by the GSP-aware
+ * Groovy parser rather than by the plain Groovy one.
+ */
+public abstract class GspGroovyLazyElementType extends ILazyParseableElementType {
+
+  protected GspGroovyLazyElementType(@NotNull String debugName) {
+    super(debugName, GroovyLanguage.INSTANCE);
+  }
+
+  @Override
+  protected ASTNode doParseContents(@NotNull ASTNode chameleon, @NotNull PsiElement psi) {
+    PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(psi.getProject(), chameleon);
+    long startTime = System.nanoTime();
+    ASTNode result = new GspAwareGroovyParser().parse(this, builder).getFirstChildNode();
+    ParsingDiagnostics.registerParse(builder, getLanguage(), System.nanoTime() - startTime);
+    return result;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt
deleted file mode 100644
index b5d1f31..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.chameleons
-
-import com.intellij.lang.ASTNode
-import com.intellij.lang.PsiBuilderFactory
-import com.intellij.psi.ParsingDiagnostics
-import com.intellij.psi.PsiElement
-import com.intellij.psi.tree.ILazyParseableElementType
-import org.jetbrains.plugins.grails.lang.gsp.parsing.groovy.GspAwareGroovyParser
-import org.jetbrains.plugins.groovy.GroovyLanguage
-
-abstract class GspGroovyLazyElementType(debugName: String) : ILazyParseableElementType(debugName, GroovyLanguage) {
-
-  override fun doParseContents(chameleon: ASTNode, psi: PsiElement): ASTNode {
-    val builder = PsiBuilderFactory.getInstance().createBuilder(psi.project, chameleon)
-    val startTime = System.nanoTime()
-    val result = GspAwareGroovyParser().parse(this, builder).firstChildNode
-    ParsingDiagnostics.registerParse(builder, language, System.nanoTime() - startTime)
-    return result
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/GspImportUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/GspImportUtil.java
new file mode 100644
index 0000000..821c5bd
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/GspImportUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.lang.gsp.psi.impl;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.xml.XmlAttribute;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.lang.gsp.GspDirectiveKind;
+import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.api.GspGroovyFile;
+import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.directive.GspDirective;
+import org.jetbrains.plugins.groovy.lang.resolve.imports.GroovyFileImports;
+import org.jetbrains.plugins.groovy.lang.resolve.imports.impl.GroovyImportCollector;
+
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Imports a GSP declares through {@code <%@ page import="..." %>} directives, in the form the
+ * Groovy resolver consumes.
+ */
+public final class GspImportUtil {
+
+  private GspImportUtil() {
+  }
+
+  private static final Pattern GSP_IMPORT_PATTERN = Pattern.compile(
+    "(static\\s+)?(((?:\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+\\.)*)(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+))(?:(?:\\s+as\\s+(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+))|(\\.\\*))?"
+  );
+
+  public static @NotNull GroovyFileImports getFileImports(@NotNull GspGroovyFile file) {
+    return CachedValuesManager.getCachedValue(file, () -> CachedValueProvider.Result.create(doGetFileImports(file), file));
+  }
+
+  private static @NotNull GroovyFileImports doGetFileImports(@NotNull GspGroovyFile file) {
+    GroovyImportCollector collector = new GroovyImportCollector(file);
+    for (GspDirective directive : file.getGspLanguageRoot().getDirectiveTags(GspDirectiveKind.PAGE, true)) {
+      XmlAttribute attribute = directive.getAttribute("import");
+      if (attribute == null) continue;
+      String value = attribute.getValue();
+      if (value == null) continue;
+      StringTokenizer st = new StringTokenizer(value, ";");
+      while (st.hasMoreTokens()) {
+        addImport(collector, st.nextToken().trim());
+      }
+    }
+    return collector.build();
+  }
+
+  private static void addImport(@NotNull GroovyImportCollector collector, @NotNull String str) {
+    Matcher matcher = GSP_IMPORT_PATTERN.matcher(str);
+    if (!matcher.matches()) return;
+
+    boolean isStatic = matcher.group(1) != null;
+    boolean isStar = matcher.group(6) != null;
+    if (isStatic && isStar) {
+      collector.addStaticStarImport(matcher.group(2));
+    }
+    else if (isStatic) {
+      String className = matcher.group(3);
+      if (className.isEmpty()) return;
+      String memberName = matcher.group(4);
+      String alias = matcher.group(5);
+      collector.addStaticImport(StringUtil.trimEnd(className, "."), memberName, alias != null ? alias : memberName);
+    }
+    else if (isStar) {
+      collector.addStarImport(matcher.group(2));
+    }
+    else {
+      String alias = matcher.group(5);
+      collector.addRegularImport(matcher.group(2), alias != null ? alias : matcher.group(4));
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt
deleted file mode 100644
index 5240380..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:JvmName("GspImportUtil")
-
-package org.jetbrains.plugins.grails.lang.gsp.psi.impl
-
-import com.intellij.psi.util.CachedValueProvider
-import com.intellij.psi.util.CachedValuesManager
-import org.jetbrains.plugins.grails.lang.gsp.GspDirectiveKind
-import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.api.GspGroovyFile
-import org.jetbrains.plugins.groovy.lang.resolve.imports.GroovyFileImports
-import org.jetbrains.plugins.groovy.lang.resolve.imports.impl.GroovyImportCollector
-import java.util.StringTokenizer
-import java.util.regex.Pattern
-
-private val gspImportPattern = Pattern.compile(
-  "(static\\s+)?(((?:\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+\\.)*)(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+))(?:(?:\\s+as\\s+(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+))|(\\.\\*))?"
-)
-
-fun GspGroovyFile.getFileImports(): GroovyFileImports {
-  return CachedValuesManager.getCachedValue(this) {
-    CachedValueProvider.Result.create(doGetFileImports(), this)
-  }
-}
-
-private fun GspGroovyFile.doGetFileImports(): GroovyFileImports {
-  val collector = GroovyImportCollector(this)
-  for (directive in gspLanguageRoot.getDirectiveTags(GspDirectiveKind.PAGE, true)) {
-    val attribute = directive.getAttribute("import") ?: continue
-    val value = attribute.value ?: continue
-    val st = StringTokenizer(value, ";")
-    while (st.hasMoreTokens()) {
-      collector.addImport(st.nextToken().trim())
-    }
-  }
-  return collector.build()
-}
-
-private fun GroovyImportCollector.addImport(str: String) {
-  val matcher = gspImportPattern.matcher(str)
-  if (!matcher.matches()) return
-
-  val isStatic = matcher.group(1) != null
-  val isStar = matcher.group(6) != null
-  if (isStatic && isStar) {
-    addStaticStarImport(classFqn = matcher.group(2))
-  }
-  else if (isStatic) {
-    val className = matcher.group(3)
-    if (className.isEmpty()) return
-    addStaticImport(
-      classFqn = className.removeSuffix("."),
-      memberName = matcher.group(4),
-      name = matcher.group(5) ?: matcher.group(4)
-    )
-  }
-  else if (isStar) {
-    addStarImport(packageFqn = matcher.group(2))
-  }
-  else {
-    addRegularImport(
-      classFqn = matcher.group(2),
-      name = matcher.group(5) ?: matcher.group(4)
-    )
-  }
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceContributor.java
new file mode 100644
index 0000000..af50f54
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceContributor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.pluginSupport.assetPipeline;
+
+import com.intellij.patterns.XmlAttributeValuePattern;
+import com.intellij.patterns.XmlNamedElementPattern;
+import com.intellij.patterns.XmlPatterns;
+import com.intellij.patterns.XmlTagPattern;
+import com.intellij.psi.PsiReferenceContributor;
+import com.intellij.psi.PsiReferenceRegistrar;
+import org.jetbrains.annotations.NotNull;
+
+public final class AssetReferenceContributor extends PsiReferenceContributor {
+
+  // #CHECK# asset.pipeline.grails.AssetsTagLib
+  private static final String[][] ASSET_PLACES = {
+    {"asset:javascript", "src"},
+    {"asset:stylesheet", "href"},
+    {"asset:stylesheet", "src"},
+    {"asset:image", "src"},
+    {"asset:link", "href"},
+    {"asset:assetPathExists", "src"},
+  };
+
+  @Override
+  public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
+    AssetReferenceProvider provider = new AssetReferenceProvider();
+    for (String[] place : ASSET_PLACES) {
+      String tag = place[0];
+      String attribute = place[1];
+      XmlTagPattern.Capture tagPattern = XmlPatterns.xmlTag().withName(tag);
+      XmlNamedElementPattern.XmlAttributePattern attributePattern =
+        XmlPatterns.xmlAttribute(attribute).withParent(tagPattern);
+      XmlAttributeValuePattern attributeValuePattern = XmlPatterns.xmlAttributeValue(attributePattern);
+      registrar.registerReferenceProvider(attributeValuePattern, provider, PsiReferenceRegistrar.HIGHER_PRIORITY);
+    }
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceProvider.java
similarity index 53%
copy from plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
copy to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceProvider.java
index 4d54b41..3079888 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetReferenceProvider.java
@@ -13,15 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.pluginSupport.assetPipeline;
 
-package org.jetbrains.plugins.grails.references.controller
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.PsiReferenceProvider;
+import com.intellij.util.ProcessingContext;
+import org.jetbrains.annotations.NotNull;
 
-import org.jetbrains.plugins.grails.lang.gsp.resolve.taglib.TagLibNamespaceDescriptor.GspTagMethod
-import org.jetbrains.plugins.groovy.lang.psi.api.GroovyMethodResult
-import org.jetbrains.plugins.groovy.lang.resolve.api.GroovyOverloadResolver
+public final class AssetReferenceProvider extends PsiReferenceProvider {
 
-final class GspTagMethodOverloadResolver : GroovyOverloadResolver {
-  override fun compare(left: GroovyMethodResult, right: GroovyMethodResult): Int {
-    return (left.element is GspTagMethod).compareTo(right.element is GspTagMethod)
+  @Override
+  public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
+    return new AssetsFileReferenceSet(element).getAllReferences();
   }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.java
new file mode 100644
index 0000000..2601936
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.pluginSupport.assetPipeline;
+
+import com.intellij.lang.Language;
+import com.intellij.lang.LanguageUtil;
+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.impl.source.html.HtmlScriptLanguageInjector;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.addins.js.JavaScriptIntegrationUtil;
+import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.api.GspOuterHtmlElement;
+import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.gtag.GspGrailsTag;
+
+import java.util.List;
+
+public final class AssetScriptInjector implements MultiHostInjector {
+
+  @Override
+  public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
+    GspGrailsTag tag = (GspGrailsTag)context;
+    if (!"asset".equals(tag.getNamespacePrefix())) return;
+    // JavaScript-body tags (e.g. asset:script) are owned by the core JS injector via
+    // JavaScriptIntegrationUtil.isJsInjectionTag, which also grants cross-tag symbol visibility;
+    // injecting here too would double-inject the same host.
+    if (JavaScriptIntegrationUtil.isJsInjectionTag(tag.getName())) return;
+    Language languageToInject = HtmlScriptLanguageInjector.getScriptLanguageToInject(tag);
+    if (languageToInject == null) return;
+    if (!LanguageUtil.isInjectableLanguage(languageToInject)) return;
+    boolean started = false;
+    for (PsiElement child : tag.getChildren()) {
+      if (child instanceof GspOuterHtmlElement outerHtml) {
+        if (!started) {
+          registrar.startInjecting(languageToInject);
+          started = true;
+        }
+        registrar.addPlace(null, null, outerHtml, TextRange.create(0, outerHtml.getTextLength()));
+      }
+    }
+    if (started) registrar.doneInjecting();
+  }
+
+  @Override
+  public @NotNull List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
+    return List.of(GspGrailsTag.class);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt
deleted file mode 100644
index eb3321e..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.pluginSupport.assetPipeline
-
-import com.intellij.lang.LanguageUtil
-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.impl.source.html.HtmlScriptLanguageInjector
-import org.jetbrains.plugins.grails.addins.js.JavaScriptIntegrationUtil
-import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.api.GspOuterHtmlElement
-import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.gtag.GspGrailsTag
-
-class AssetScriptInjector : MultiHostInjector {
-
-  override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) {
-    context as GspGrailsTag
-    if (context.namespacePrefix != "asset") return
-    // JavaScript-body tags (e.g. asset:script) are owned by the core JS injector via
-    // JavaScriptIntegrationUtil.isJsInjectionTag, which also grants cross-tag symbol visibility;
-    // injecting here too would double-inject the same host.
-    if (JavaScriptIntegrationUtil.isJsInjectionTag(context.name)) return
-    val languageToInject = HtmlScriptLanguageInjector.getScriptLanguageToInject(context) ?: return
-    if (!LanguageUtil.isInjectableLanguage(languageToInject)) return
-    var started = false
-    for (child in context.children) {
-      if (child is GspOuterHtmlElement) {
-        if (!started) {
-          registrar.startInjecting(languageToInject)
-          started = true
-        }
-        registrar.addPlace(
-            null, null, child, TextRange.create(0, child.getTextLength())
-        )
-      }
-    }
-    if (started) registrar.doneInjecting()
-  }
-
-  override fun elementsToInjectIn(): List<Class<GspGrailsTag>> = listOf(GspGrailsTag::class.java)
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetsFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetsFileReferenceSet.java
new file mode 100644
index 0000000..47384ed
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetsFileReferenceSet.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.pluginSupport.assetPipeline;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiPackage;
+import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference;
+import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceHelper;
+import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.plugins.Grails3SourcePluginDescriptor;
+import org.jetbrains.plugins.grails.plugins.GrailsPlugins;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Resolves an asset-pipeline path against every {@code assets} folder reachable from the current
+ * application: its own, those of source plugins, and the packaged ones inside plugin jars.
+ */
+public class AssetsFileReferenceSet extends FileReferenceSet {
+
+  private static final String[] ASSETS_PACKAGES = {
+    "META-INF.assets",
+    "META-INF.static",
+    "META-INF.resources",
+  };
+
+  public AssetsFileReferenceSet(@NotNull PsiElement element) {
+    super(element);
+  }
+
+  @Override
+  public @NotNull Collection<PsiFileSystemItem> computeDefaultContexts() {
+    GrailsApplication application = GrailsApplicationManager.findApplication(getElement());
+    return application == null ? Collections.emptyList() : getAssetFolders(application);
+  }
+
+  @Override
+  public FileReference createFileReference(TextRange range, int index, String text) {
+    return new FileReference(this, range, index, text) {
+      @Override
+      protected Collection<PsiFileSystemItem> getContextsForBindToElement(VirtualFile curVFile,
+                                                                          Project project,
+                                                                          FileReferenceHelper helper) {
+        return getContexts();
+      }
+    };
+  }
+
+  @Override
+  protected boolean isSoft() {
+    return true;
+  }
+
+  private static @NotNull Collection<PsiFileSystemItem> getAssetFolders(@NotNull GrailsApplication application) {
+    return CachedValuesManager.getManager(application.getProject()).getCachedValue(
+      application,
+      () -> Result.create(doGetAssetFolders(application), ProjectRootManager.getInstance(application.getProject())));
+  }
+
+  private static @NotNull Collection<PsiFileSystemItem> doGetAssetFolders(@NotNull GrailsApplication application) {
+    Project project = application.getProject();
+
+    List<VirtualFile> sourceFiles = new ArrayList<>();
+    addAssetsFolderChildren(application, sourceFiles);
+    for (Grails3SourcePluginDescriptor plugin : GrailsPlugins.getSourcePlugins(application)) {
+      addAssetsFolderChildren(plugin.getPluginApplication(), sourceFiles);
+    }
+
+    PsiManager manager = PsiManager.getInstance(project);
+    List<PsiFileSystemItem> result = new ArrayList<>();
+    for (VirtualFile file : sourceFiles) {
+      PsiDirectory directory = manager.findDirectory(file);
+      if (directory != null) result.add(directory);
+    }
+
+    JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
+    GlobalSearchScope scope = application.getScope(true, false);
+    for (String packageName : ASSETS_PACKAGES) {
+      PsiPackage pckg = facade.findPackage(packageName);
+      if (pckg == null) continue;
+      Collections.addAll(result, pckg.getDirectories(scope));
+    }
+
+    return result;
+  }
+
+  private static void addAssetsFolderChildren(@NotNull GrailsApplication application, @NotNull List<VirtualFile> into) {
+    VirtualFile assets = application.getAppRoot().findChild("assets");
+    if (assets == null) return;
+    VirtualFile[] children = assets.getChildren();
+    if (children == null) return;
+    Collections.addAll(into, children);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt
deleted file mode 100644
index edf45a1..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.pluginSupport.assetPipeline
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.util.TextRange
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.patterns.XmlPatterns
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiFileSystemItem
-import com.intellij.psi.PsiManager
-import com.intellij.psi.PsiReference
-import com.intellij.psi.PsiReferenceContributor
-import com.intellij.psi.PsiReferenceProvider
-import com.intellij.psi.PsiReferenceRegistrar
-import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
-import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceHelper
-import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import com.intellij.util.ProcessingContext
-import org.jetbrains.plugins.grails.plugins.getSourcePlugins
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-
-// #CHECK# asset.pipeline.grails.AssetsTagLib
-private val ASSET_PLACES = listOf(
-    "asset:javascript" to "src",
-    "asset:stylesheet" to "href",
-    "asset:stylesheet" to "src",
-    "asset:image" to "src",
-    "asset:link" to "href",
-    "asset:assetPathExists" to "src"
-)
-
-class AssetReferenceContributor : PsiReferenceContributor() {
-
-  override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
-    val provider = AssetReferenceProvider()
-    for ((tag, attribute) in ASSET_PLACES) {
-      val tagPattern = XmlPatterns.xmlTag().withName(tag)
-      val attributePattern = XmlPatterns.xmlAttribute(attribute).withParent(tagPattern)
-      val attributeValuePattern = XmlPatterns.xmlAttributeValue(attributePattern)
-      registrar.registerReferenceProvider(attributeValuePattern, provider, PsiReferenceRegistrar.HIGHER_PRIORITY)
-    }
-  }
-}
-
-class AssetReferenceProvider : PsiReferenceProvider() {
-
-  override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<out PsiReference> {
-    return AssetsFileReferenceSet(element).allReferences
-  }
-}
-
-class AssetsFileReferenceSet(element: PsiElement) : FileReferenceSet(element) {
-
-  override fun computeDefaultContexts(): Collection<PsiFileSystemItem> = GrailsApplicationManager.findApplication(element)?.getAssetFolders() ?: emptyList()
-
-  override fun createFileReference(range: TextRange?, index: Int, text: String?): FileReference = object : FileReference(this, range, index, text) {
-    override fun getContextsForBindToElement(curVFile: VirtualFile?, project: Project?, helper: FileReferenceHelper?) = contexts
-  }
-
-  override fun isSoft(): Boolean = true
-}
-
-private fun GrailsApplication.getAssetFolders() = CachedValuesManager.getManager(project).getCachedValue(this) {
-  Result.create(doGesAssetFolders(), ProjectRootManager.getInstance(project))
-}
-
-private fun GrailsApplication.doGesAssetFolders(): Collection<PsiFileSystemItem> {
-  fun GrailsApplication.findAssetsFolder() = appRoot.findChild("assets")?.children
-
-  val ownAssets = this.findAssetsFolder()?.toList() ?: emptyList()
-  val pluginAssets = this.getSourcePlugins().flatMap { plugin ->
-    plugin.pluginApplication.findAssetsFolder()?.toList() ?: emptyList()
-  }
-
-  val manager = PsiManager.getInstance(project)
-  val sourceAssets = (ownAssets + pluginAssets).mapNotNull {
-    manager.findDirectory(it)
-  }
-
-  val facade = JavaPsiFacade.getInstance(project)
-  val scope = getScope(true, false)
-  val compiledAssets = ASSETS_PACKAGES.flatMap { pckg ->
-    facade.findPackage(pckg)?.getDirectories(scope)?.toList() ?: emptyList()
-  }
-
-  return sourceAssets + compiledAssets
-}
-
-private val ASSETS_PACKAGES = arrayOf(
-    "META-INF.assets",
-    "META-INF.static",
-    "META-INF.resources"
-)
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3CompiledPluginDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3CompiledPluginDescriptor.java
new file mode 100644
index 0000000..ce1aabd
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3CompiledPluginDescriptor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.plugins;
+
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Supplier;
+
+/**
+ * A plugin that arrives as a jar. The version is read lazily from the plugin's
+ * {@code META-INF/grails-plugin.xml}, which is why it comes in as a supplier.
+ */
+public final class Grails3CompiledPluginDescriptor implements GrailsPluginDescriptor {
+
+  private final @NotNull PsiClass myPluginClass;
+  private final @NotNull Supplier<String> myPluginVersionGetter;
+
+  public Grails3CompiledPluginDescriptor(@NotNull PsiClass pluginClass, @NotNull Supplier<String> pluginVersionGetter) {
+    myPluginClass = pluginClass;
+    myPluginVersionGetter = pluginVersionGetter;
+  }
+
+  @Override
+  public @NotNull PsiClass getPluginClass() {
+    return myPluginClass;
+  }
+
+  @Override
+  public @Nullable String getPluginVersion() {
+    return myPluginVersionGetter.get();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3SourcePluginDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3SourcePluginDescriptor.java
new file mode 100644
index 0000000..6194e92
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/Grails3SourcePluginDescriptor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.plugins;
+
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+/** A plugin whose source lives in another module of this project. */
+public final class Grails3SourcePluginDescriptor implements GrailsPluginDescriptor {
+
+  private final @NotNull PsiClass myPluginClass;
+  private final @NotNull GrailsApplication myPluginApplication;
+
+  public Grails3SourcePluginDescriptor(@NotNull PsiClass pluginClass, @NotNull GrailsApplication pluginApplication) {
+    myPluginClass = pluginClass;
+    myPluginApplication = pluginApplication;
+  }
+
+  @Override
+  public @NotNull PsiClass getPluginClass() {
+    return myPluginClass;
+  }
+
+  public @NotNull GrailsApplication getPluginApplication() {
+    return myPluginApplication;
+  }
+
+  @Override
+  public @Nullable String getPluginVersion() {
+    return myPluginApplication.getAppVersion();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPluginDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPluginDescriptor.java
new file mode 100644
index 0000000..523ae15
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPluginDescriptor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.plugins;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface GrailsPluginDescriptor {
+
+  /** The {@code *GrailsPlugin} class that declares the plugin. */
+  @NotNull PsiClass getPluginClass();
+
+  @Nullable String getPluginVersion();
+
+  default @NotNull String getPluginName() {
+    String name = getPluginClass().getName();
+    if (name == null) return "";
+    String trimmed = StringUtil.trimEnd(name, GrailsPlugins.PLUGIN_CLASS_SUFFIX);
+    // Lowercase the first character unconditionally. StringUtil.decapitalize would leave an
+    // all-caps prefix alone (URLMappingsGrailsPlugin), which is not what Grails does.
+    if (trimmed.isEmpty()) return trimmed;
+    return Character.toLowerCase(trimmed.charAt(0)) + trimmed.substring(1);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPlugins.java b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPlugins.java
new file mode 100644
index 0000000..73324cf
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/GrailsPlugins.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.plugins;
+
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiPackage;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.searches.AllClassesSearch;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.psi.xml.XmlTag;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/** Discovery of the Grails plugins an application depends on, both source and compiled. */
+public final class GrailsPlugins {
+
+  private GrailsPlugins() {
+  }
+
+  static final String PLUGIN_CLASS_SUFFIX = "GrailsPlugin";
+
+  public static @NotNull Collection<GrailsPluginDescriptor> computePlugins(@NotNull GrailsApplication application) {
+    return ReadAction.compute(() -> CachedValuesManager.getManager(application.getProject()).getCachedValue(
+      application,
+      () -> {
+        List<GrailsPluginDescriptor> all = new ArrayList<>(getSourcePlugins(application));
+        all.addAll(doComputeCompiledPlugins(application));
+        return Result.create(all, ProjectRootManager.getInstance(application.getProject()));
+      }));
+  }
+
+  public static @NotNull Collection<Grails3SourcePluginDescriptor> getSourcePlugins(@NotNull GrailsApplication application) {
+    return ReadAction.compute(() -> CachedValuesManager.getManager(application.getProject()).getCachedValue(
+      application,
+      () -> Result.create(doComputeSourcePlugins(application),
+                          ProjectRootManager.getInstance(application.getProject()))));
+  }
+
+  private static @NotNull Collection<Grails3SourcePluginDescriptor> doComputeSourcePlugins(@NotNull GrailsApplication application) {
+    List<Grails3SourcePluginDescriptor> result = new ArrayList<>();
+    Collection<PsiClass> candidates = AllClassesSearch
+      .search(application.getScope(true, false), application.getProject(), name -> name.endsWith(PLUGIN_CLASS_SUFFIX))
+      .findAll();
+    for (PsiClass candidate : candidates) {
+      GrailsApplication owner = GrailsApplicationManager.findApplication(candidate);
+      // Skip a plugin class belonging to this application itself; only siblings count as plugins.
+      if (owner == null || owner.equals(application)) continue;
+      result.add(new Grails3SourcePluginDescriptor(candidate, owner));
+    }
+    return result;
+  }
+
+  private static @NotNull Collection<Grails3CompiledPluginDescriptor> doComputeCompiledPlugins(@NotNull GrailsApplication application) {
+    GlobalSearchScope scope = application.getScope(true, false);
+    JavaPsiFacade facade = JavaPsiFacade.getInstance(application.getProject());
+    PsiPackage metaInf = facade.findPackage("META-INF");
+    if (metaInf == null) return Collections.emptyList();
+
+    List<Grails3CompiledPluginDescriptor> result = new ArrayList<>();
+    for (PsiDirectory directory : metaInf.getDirectories(scope)) {
+      PsiFile file = directory.findFile("grails-plugin.xml");
+      if (!(file instanceof XmlFile pluginXml)) continue;
+      String pluginClassFqn = findPluginClassFqn(pluginXml);
+      if (pluginClassFqn == null) continue;
+      PsiClass pluginClass = facade.findClass(pluginClassFqn, scope);
+      if (pluginClass == null) continue;
+      result.add(new Grails3CompiledPluginDescriptor(pluginClass, () -> {
+        XmlTag rootTag = pluginXml.getRootTag();
+        return rootTag == null ? null : rootTag.getAttributeValue("version");
+      }));
+    }
+    return result;
+  }
+
+  private static @Nullable String findPluginClassFqn(@NotNull XmlFile pluginXml) {
+    XmlTag rootTag = pluginXml.getRootTag();
+    if (rootTag == null) return null;
+    XmlTag[] typeTags = rootTag.findSubTags("type");
+    if (typeTags.length == 0) return null;
+    return typeTags[0].getValue().getTrimmedText();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/descriptors.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/descriptors.kt
deleted file mode 100644
index 54fdc93..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/descriptors.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.plugins
-
-import com.intellij.psi.PsiClass
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-interface GrailsPluginDescriptor {
-
-  val pluginClass: PsiClass
-  val pluginVersion: String?
-  val pluginName: String
-    get() = pluginClass.name?.removeSuffix(pluginClassSuffix)?.replaceFirstChar { it.lowercaseChar() } ?: ""
-
-}
-
-class Grails3SourcePluginDescriptor(
-    override val pluginClass: PsiClass,
-    val pluginApplication: GrailsApplication
-) : GrailsPluginDescriptor {
-
-  override val pluginVersion: String? get() = pluginApplication.appVersion
-}
-
-class Grails3CompiledPluginDescriptor(
-    override val pluginClass: PsiClass,
-    private val pluginVersionGetter: () -> String?
-) : GrailsPluginDescriptor {
-
-  override val pluginVersion: String? get() = pluginVersionGetter()
-
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/impl.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/impl.kt
deleted file mode 100644
index 92ecc21..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/impl.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.plugins
-
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiDirectory
-import com.intellij.psi.search.searches.AllClassesSearch
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import com.intellij.psi.xml.XmlFile
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-
-internal const val pluginClassSuffix = "GrailsPlugin"
-
-fun GrailsApplication.computePlugins(): Collection<GrailsPluginDescriptor> = runReadAction {
-  CachedValuesManager.getManager(project).getCachedValue(this) {
-    Result.create(getSourcePlugins() + doComputeCompiledPlugins(), ProjectRootManager.getInstance(project))
-  }
-}
-
-fun GrailsApplication.getSourcePlugins(): Collection<Grails3SourcePluginDescriptor> = runReadAction {
-  CachedValuesManager.getManager(project).getCachedValue(this) {
-    Result.create(doComputeSourcePlugins(), ProjectRootManager.getInstance(project))
-  }
-}
-
-private fun GrailsApplication.doComputeSourcePlugins(): Collection<Grails3SourcePluginDescriptor> {
-  return AllClassesSearch.search(getScope(true, false), project) { className ->
-    className.endsWith(pluginClassSuffix)
-  }.findAll().mapNotNull {
-    val application = GrailsApplicationManager.findApplication(it)
-    if (application == null || application == this) null
-    else Grails3SourcePluginDescriptor(it, application)
-  }
-}
-
-private fun GrailsApplication.doComputeCompiledPlugins(): Collection<Grails3CompiledPluginDescriptor> {
-  val scope = getScope(true, false)
-  val facade = JavaPsiFacade.getInstance(project)
-  val directories = facade.findPackage("META-INF")?.getDirectories(scope) ?: return emptyList()
-
-  return directories.mapNotNull(fun(directory: PsiDirectory): Grails3CompiledPluginDescriptor? {
-    val pluginXml = directory.findFile("grails-plugin.xml") as? XmlFile ?: return null
-    val pluginClassFqn = pluginXml.rootTag?.findSubTags("type")?.firstOrNull()?.value?.trimmedText ?: return null
-    val pluginClass = facade.findClass(pluginClassFqn, scope) ?: return null
-    return Grails3CompiledPluginDescriptor(pluginClass) {
-      pluginXml.rootTag?.getAttributeValue("version")
-    }
-  })
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
index e6aca5c..9b57e43 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
@@ -25,7 +25,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.grails.GroovyMvcIcons;
 import org.jetbrains.plugins.grails.plugins.GrailsPluginDescriptor;
-import org.jetbrains.plugins.grails.plugins.ImplKt;
+import org.jetbrains.plugins.grails.plugins.GrailsPlugins;
 import org.jetbrains.plugins.grails.projectView.nodes.GrailsPluginNode;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 
@@ -47,7 +47,7 @@ public class GrailsPluginsNode extends ProjectViewNode<String> {
     GrailsApplication application = getGrailsApplication();
     Project project = application.getProject();
     Collection<AbstractTreeNode<?>> result = new ArrayList<>();
-    for (GrailsPluginDescriptor descriptor : ImplKt.computePlugins(application)) {
+    for (GrailsPluginDescriptor descriptor : GrailsPlugins.computePlugins(application)) {
       result.add(new GrailsPluginNode(project, descriptor, getSettings()));
     }
     return result;
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
index 60c507f..a7f876e 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
@@ -23,7 +23,7 @@ import com.intellij.psi.PsiClass;
 import com.intellij.psi.PsiMember;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.grails.artefact.impl.controllers.FunctionsKt;
+import org.jetbrains.plugins.grails.artefact.impl.controllers.ControllerActions;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
 
@@ -54,7 +54,7 @@ public class GrailsControllerNode extends ClassTreeNode {
     if (!(getValue() instanceof GrTypeDefinition clazz)) return null;
 
     Collection<AbstractTreeNode<?>> result = new ArrayList<>();
-    for (Map.Entry<String, PsiMember> action : FunctionsKt.getActions(clazz, getGrailsApplication()).entrySet()) {
+    for (Map.Entry<String, PsiMember> action : ControllerActions.getActions(clazz, getGrailsApplication()).entrySet()) {
       result.add(new GrailsActionNode(action.getKey(), action.getValue(), getSettings()));
     }
     return result;
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.java
new file mode 100644
index 0000000..d34548c
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.references;
+
+import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.searches.AnnotatedElementsSearch;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.util.PsiModificationTracker;
+import com.intellij.util.containers.ConcurrentFactoryMap;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil;
+import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Traits contributed to an artefact type by {@code @grails.artefact.Enhances}, collected once per
+ * resolve scope and invalidated on any PSI change.
+ */
+public final class EnhancesTraitsCollector {
+
+  private EnhancesTraitsCollector() {
+  }
+
+  private static final String ANNOTATION_FQN = "grails.artefact.Enhances";
+
+  @ApiStatus.Internal
+  public static @NotNull Collection<String> doGetEnhancesTraits(@NotNull PsiElement context, @NotNull String artefactType) {
+    Collection<String> traits = getAllEnhancesTraits(context).get(artefactType);
+    return traits == null ? Collections.emptyList() : traits;
+  }
+
+  private static @NotNull Map<String, Collection<String>> getAllEnhancesTraits(@NotNull PsiElement context) {
+    Project project = context.getProject();
+    ConcurrentMap<GlobalSearchScope, Map<String, Collection<String>>> byScope =
+      CachedValuesManager.getManager(project).getCachedValue(project, () -> Result.create(
+        ConcurrentFactoryMap.<GlobalSearchScope, Map<String, Collection<String>>>createMap(
+          scope -> doFindTraits(project, scope)),
+        PsiModificationTracker.MODIFICATION_COUNT));
+    Map<String, Collection<String>> result = byScope.get(context.getResolveScope());
+    return result == null ? Collections.emptyMap() : result;
+  }
+
+  private static @NotNull Map<String, Collection<String>> doFindTraits(@NotNull Project project,
+                                                                      @NotNull GlobalSearchScope scope) {
+    PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(ANNOTATION_FQN, scope);
+    if (annotationClass == null) return Collections.emptyMap();
+
+    Map<String, Collection<String>> result = new HashMap<>();
+    for (PsiClass clazz : AnnotatedElementsSearch.searchPsiClasses(annotationClass, scope).findAll()) {
+      if (!GrTraitUtil.isTrait(clazz)) continue;
+      String traitFqn = clazz.getQualifiedName();
+      if (traitFqn == null) continue;
+      PsiAnnotation annotation = AnnotationUtil.findAnnotation(clazz, ANNOTATION_FQN);
+      if (annotation == null) continue;
+      List<String> artefactTypes = GrAnnotationUtil.getStringArrayValue(annotation, "value", false);
+      for (String artefactType : artefactTypes) {
+        result.computeIfAbsent(artefactType, k -> new ArrayList<>()).add(traitFqn);
+      }
+    }
+    return result;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt
deleted file mode 100644
index 127cfc6..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:JvmName("EnhancesTraitsCollector")
-
-package org.jetbrains.plugins.grails.references
-
-import com.intellij.codeInsight.AnnotationUtil
-import com.intellij.openapi.project.Project
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiElement
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.search.searches.AnnotatedElementsSearch
-import com.intellij.psi.util.CachedValueProvider.Result
-import com.intellij.psi.util.CachedValuesManager
-import com.intellij.psi.util.PsiModificationTracker
-import com.intellij.util.containers.ConcurrentFactoryMap
-import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil
-import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil
-import java.util.concurrent.ConcurrentMap
-
-internal fun doGetEnhancesTraits(context: PsiElement, artefactType: String): Collection<String> {
-  return getAllEnhancesTraits(context)[artefactType] ?: emptyList()
-}
-
-private fun getAllEnhancesTraits(context: PsiElement): Map<String, Collection<String>> {
-  val project = context.project
-  return CachedValuesManager.getManager(project).getCachedValue(project) {
-    val map: ConcurrentMap<GlobalSearchScope, Map<String, Collection<String>>> = ConcurrentFactoryMap.createMap { doFindTraits(project, it) }
-    Result.create(map, PsiModificationTracker.MODIFICATION_COUNT)
-  }[context.resolveScope] ?: emptyMap()
-}
-
-private const val annotationFqn = "grails.artefact.Enhances"
-
-private fun doFindTraits(project: Project, scope: GlobalSearchScope): Map<String, Collection<String>> {
-  val annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationFqn, scope) ?: return emptyMap()
-  val annotatedClasses = AnnotatedElementsSearch.searchPsiClasses(annotationClass, scope)
-  val result = mutableMapOf<String, MutableCollection<String>>()
-  for (clazz in annotatedClasses.findAll()) {
-    if (!GrTraitUtil.isTrait(clazz)) continue
-    val traitFqn = clazz.qualifiedName ?: continue
-    val annotation = AnnotationUtil.findAnnotation(clazz, annotationFqn) ?: continue
-    val artefactTypes = GrAnnotationUtil.getStringArrayValue(annotation, "value", false)
-    for (artefactType in artefactTypes) {
-      result.getOrPut(artefactType, { mutableListOf() }).add(traitFqn)
-    }
-  }
-  return result
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
index f77e28f..e3268de 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
@@ -319,7 +319,7 @@ class TraitInjectorService(
     fun getInjectedTraits(context: PsiElement, artefactType: String): Collection<String> {
       val result: MutableSet<String> = LinkedHashSet()
       result.addAll(doGetInjectedTraits(context, artefactType))
-      result.addAll(doGetEnhancesTraits(context, artefactType))
+      result.addAll(EnhancesTraitsCollector.doGetEnhancesTraits(context, artefactType))
       return result
     }
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.java
similarity index 60%
rename from plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.java
index 4d54b41..d656b78 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.java
@@ -13,15 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.jetbrains.plugins.grails.references.controller;
 
-package org.jetbrains.plugins.grails.references.controller
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.lang.gsp.resolve.taglib.TagLibNamespaceDescriptor.GspTagMethod;
+import org.jetbrains.plugins.groovy.lang.psi.api.GroovyMethodResult;
+import org.jetbrains.plugins.groovy.lang.resolve.api.GroovyOverloadResolver;
 
-import org.jetbrains.plugins.grails.lang.gsp.resolve.taglib.TagLibNamespaceDescriptor.GspTagMethod
-import org.jetbrains.plugins.groovy.lang.psi.api.GroovyMethodResult
-import org.jetbrains.plugins.groovy.lang.resolve.api.GroovyOverloadResolver
+/** Prefers a real method over a synthesised GSP tag method when both are applicable. */
+public final class GspTagMethodOverloadResolver implements GroovyOverloadResolver {
 
-final class GspTagMethodOverloadResolver : GroovyOverloadResolver {
-  override fun compare(left: GroovyMethodResult, right: GroovyMethodResult): Int {
-    return (left.element is GspTagMethod).compareTo(right.element is GspTagMethod)
+  @Override
+  public int compare(@NotNull GroovyMethodResult left, @NotNull GroovyMethodResult right) {
+    return Boolean.compare(left.getElement() instanceof GspTagMethod, right.getElement() instanceof GspTagMethod);
   }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.java
new file mode 100644
index 0000000..410ba75
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.references.domain.detachedCriteria;
+
+import com.intellij.psi.PsiType;
+import com.intellij.util.ProcessingContext;
+import groovy.lang.Closure;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.lang.psi.api.GrFunctionalExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyClosurePattern;
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatternsKt;
+import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo;
+import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.GrDelegatesToProvider;
+
+public final class DetachedCriteriaDelegatesToProvider implements GrDelegatesToProvider {
+
+  private static final GroovyClosurePattern CLOSURE_PATTERN = GroovyPatternsKt.groovyClosure().inMethod(
+    GroovyPatternsKt.psiMethod(
+      "grails.gorm.DetachedCriteria",
+      "and", "or", "not", // DetachedCriteria#handleJunction()
+      "get", "list", "count", "exists", "find", // DetachedCriteria#withPopulatedQuery()
+      "eqAll", "gtAll", "ltAll", "geAll", "leAll", // DetachedCriteria#buildQueryableCriteria()
+      "build", "where" // DetachedCriteria#build()
+    )
+  );
+
+  @Override
+  public @Nullable DelegatesToInfo getDelegatesToInfo(@NotNull GrFunctionalExpression expression) {
+    ProcessingContext context = new ProcessingContext();
+    if (!CLOSURE_PATTERN.accepts(expression, context)) return null;
+    GrCall call = context.get(GroovyPatternsKt.getClosureCallKey());
+    if (!(call instanceof GrMethodCall methodCall)) return null;
+    if (!(methodCall.getInvokedExpression() instanceof GrReferenceExpression referenceExpression)) return null;
+    GrExpression qualifier = referenceExpression.getQualifierExpression();
+    if (qualifier == null) return null;
+    PsiType type = qualifier.getType();
+    if (type == null) return null;
+    return new DelegatesToInfo(type, Closure.DELEGATE_FIRST);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt
deleted file mode 100644
index fddc203..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.references.domain.detachedCriteria
-
-import com.intellij.util.ProcessingContext
-import groovy.lang.Closure
-import org.jetbrains.plugins.groovy.lang.psi.api.GrFunctionalExpression
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
-import org.jetbrains.plugins.groovy.lang.psi.patterns.closureCallKey
-import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyClosure
-import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
-import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo
-import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.GrDelegatesToProvider
-
-class DetachedCriteriaDelegatesToProvider : GrDelegatesToProvider {
-
-  private companion object {
-    val closurePattern = groovyClosure().inMethod(
-        psiMethod(
-            "grails.gorm.DetachedCriteria",
-            "and", "or", "not", // DetachedCriteria#handleJunction()
-            "get", "list", "count", "exists", "find", // DetachedCriteria#withPopulatedQuery()
-            "eqAll", "gtAll", "ltAll", "geAll", "leAll", // DetachedCriteria#buildQueryableCriteria()
-            "build", "where" // DetachedCriteria#build()
-        )
-    )
-  }
-
-  override fun getDelegatesToInfo(expression: GrFunctionalExpression): DelegatesToInfo? {
-    val context = ProcessingContext()
-    if (!closurePattern.accepts(expression, context)) return null
-    val call = context.get(closureCallKey) as? GrMethodCall
-    val referenceExpression = call?.invokedExpression as? GrReferenceExpression
-    return referenceExpression?.qualifierExpression?.type?.let {
-      DelegatesToInfo(it, Closure.DELEGATE_FIRST)
-    }
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java
index 5576c77..0daead5 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java
@@ -46,7 +46,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.grails.config.GrailsFramework;
 import org.jetbrains.plugins.grails.config.GrailsStructure;
-import org.jetbrains.plugins.grails.plugins.ImplKt;
+import org.jetbrains.plugins.grails.plugins.GrailsPlugins;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
 import org.jetbrains.plugins.grails.util.GrailsArtifact;
@@ -187,7 +187,7 @@ public final class GrailsSpringBeanDiscoverer extends CustomModuleComponentsDisc
   private static @NotNull List<String> getPluginsNamesFromDescriptors(Module module) {
     final GrailsApplication application = GrailsApplicationManager.getInstance(module.getProject()).findApplication(module.getModuleFile());
     if (application == null) return Collections.emptyList();
-    return ContainerUtil.map(ImplKt.computePlugins(application), p -> p.getPluginName());
+    return ContainerUtil.map(GrailsPlugins.computePlugins(application), p -> p.getPluginName());
   }
 
   private static void addBeanIfClassExists(List<CommonSpringBean> result,
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.java
new file mode 100644
index 0000000..022c298
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.spring;
+
+import com.intellij.java.library.JavaLibraryUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.patterns.PsiJavaPatterns;
+import com.intellij.semantic.SemContributor;
+import com.intellij.semantic.SemRegistrar;
+import com.intellij.spring.model.jam.stereotype.SpringConfiguration;
+import org.jetbrains.annotations.NotNull;
+
+public class GrailsSpringSemContributor extends SemContributor {
+
+  private static final String GRAILS_AUTO_CONFIGURATION = "grails.boot.config.GrailsAutoConfiguration";
+
+  @Override
+  protected boolean isAvailable(@NotNull Project project) {
+    return JavaLibraryUtil.hasLibraryClass(project, GRAILS_AUTO_CONFIGURATION);
+  }
+
+  @Override
+  public void registerSemProviders(@NotNull SemRegistrar registrar, @NotNull Project project) {
+    registrar.registerSemElementProvider(
+      SpringConfiguration.META_KEY,
+      PsiJavaPatterns.psiClass().inheritorOf(true, GRAILS_AUTO_CONFIGURATION),
+      psiClass -> SpringConfiguration.META
+    );
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt
deleted file mode 100644
index a6cb25b..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.spring
-
-import com.intellij.java.library.JavaLibraryUtil
-import com.intellij.openapi.project.Project
-import com.intellij.patterns.PsiJavaPatterns.psiClass
-import com.intellij.semantic.SemContributor
-import com.intellij.semantic.SemRegistrar
-import com.intellij.spring.model.jam.stereotype.SpringConfiguration
-
-class GrailsSpringSemContributor : SemContributor() {
-  protected override fun isAvailable(project: Project): Boolean {
-    return JavaLibraryUtil.hasLibraryClass(project, "grails.boot.config.GrailsAutoConfiguration")
-  }
-
-  override fun registerSemProviders(registrar: SemRegistrar, project: Project) {
-    registrar.registerSemElementProvider(
-      SpringConfiguration.META_KEY,
-      psiClass().inheritorOf(true, "grails.boot.config.GrailsAutoConfiguration")
-    ) { SpringConfiguration.META }
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java
index 6f469b4..fb5397b 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java
@@ -49,8 +49,8 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefini
 
 import java.util.Objects;
 
-import static org.jetbrains.plugins.grails.tests.runner.ByClassKt.setupConfigurationByClass;
-import static org.jetbrains.plugins.grails.tests.runner.ByDirectoryKt.setupConfigurationByDir;
+import static org.jetbrains.plugins.grails.tests.runner.GrailsTestConfigurationSetup.setupConfigurationByClass;
+import static org.jetbrains.plugins.grails.tests.runner.GrailsTestConfigurationSetup.setupConfigurationByDir;
 
 final class GrailsTestConfigurationProducer extends LazyRunConfigurationProducer<GrailsRunConfiguration> {
   @Override
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationSetup.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationSetup.java
new file mode 100644
index 0000000..45608ae
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationSetup.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.tests.runner;
+
+import com.intellij.openapi.roots.PackageIndex;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMethod;
+import com.intellij.util.execution.ParametersListUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.util.version.Version;
+
+import java.util.List;
+
+/**
+ * Builds the {@code test-app} command line for a run configuration created from a test class or a
+ * test directory. Grails 3 selects unit vs integration tests with a flag; earlier versions used the
+ * name of the test source root.
+ */
+public final class GrailsTestConfigurationSetup {
+
+  private GrailsTestConfigurationSetup() {
+  }
+
+  static final String UNIT_TEST_KEY_V3 = "-unit";
+  static final String INTEGRATION_TEST_KEY_V3 = "-integration";
+
+  private static final List<String> TEST_FOLDER_NAMES = List.of("integration", "unit", "functional");
+  private static final String[] TEST_SUFFIXES = {"Tests", "Test"};
+
+  public static @Nullable PsiElement setupConfigurationByClass(@NotNull GrailsRunConfiguration configuration,
+                                                               @NotNull GrailsApplication application,
+                                                               @NotNull PsiClass aClass,
+                                                               @Nullable PsiMethod method) {
+    String classFqn = aClass.getQualifiedName();
+    if (classFqn == null) return null;
+    String className = aClass.getName();
+    if (className == null) return null;
+
+    ProjectFileIndex fileIndex = ProjectRootManager.getInstance(aClass.getProject()).getFileIndex();
+    VirtualFile srcRoot = fileIndex.getSourceRootForFile(aClass.getContainingFile().getVirtualFile());
+    if (srcRoot == null) return null;
+    boolean isV3 = application.getGrailsVersion().isAtLeast(Version.GRAILS_3_0);
+
+    StringBuilder parameters = new StringBuilder("test-app");
+
+    if (!isV3) {
+      String testKind = getParamKeyByTestRoot(application, srcRoot);
+      if (testKind == null) return null;
+      parameters.append(" ").append(testKind);
+    }
+
+    String testName = trimTestSuffix(classFqn);
+
+    String configurationName;
+    String testFilter;
+    PsiElement sourceElement;
+
+    if (method != null && useMethod(application, method)) {
+      String methodName = method.getName();
+      configurationName = methodName + "()";
+      testFilter = ParametersListUtil.join(testName + "." + methodName);
+      sourceElement = method;
+    }
+    else {
+      configurationName = className;
+      testFilter = testName;
+      sourceElement = aClass;
+    }
+
+    parameters.append(" ").append(testFilter);
+
+    if (isV3) {
+      VirtualFile integrationTestRoot = findIntegrationTestDirV3(application);
+      boolean integrationTest = integrationTestRoot != null && VfsUtilCore.isAncestor(integrationTestRoot, srcRoot, true);
+      parameters.append(" ").append(integrationTest ? INTEGRATION_TEST_KEY_V3 : UNIT_TEST_KEY_V3);
+    }
+
+    configuration.setName(configurationName);
+    configuration.setProgramParameters(parameters.toString());
+
+    return sourceElement;
+  }
+
+  private static boolean useMethod(@NotNull GrailsApplication application, @NotNull PsiMethod method) {
+    Version version = application.getGrailsVersion();
+    if (!version.isAtLeast(Version.GRAILS_1_1) || !GrailsTestConfigurationProducer.isGrailsTestMethod(method)) return false;
+    // Spock method names with spaces cannot be passed through the Grails 3 test filter.
+    if (version.compareTo(Version.GRAILS_3_0) > 0 && method.getName().contains(" ")) return false;
+    return true;
+  }
+
+  private static @NotNull String trimTestSuffix(@NotNull String name) {
+    for (String suffix : TEST_SUFFIXES) {
+      if (name.endsWith(suffix)) return StringUtil.trimEnd(name, suffix);
+    }
+    return name;
+  }
+
+  public static boolean setupConfigurationByDir(@NotNull GrailsRunConfiguration configuration,
+                                                @NotNull GrailsApplication application,
+                                                @NotNull PsiDirectory dir) {
+    return application.getGrailsVersion().isAtLeast(Version.GRAILS_3_0)
+           ? setupConfigurationByDirV3(configuration, application, dir)
+           : setupConfigurationByDirOld(configuration, application, dir);
+  }
+
+  private static boolean setupConfigurationByDirV3(@NotNull GrailsRunConfiguration configuration,
+                                                   @NotNull GrailsApplication application,
+                                                   @NotNull PsiDirectory dir) {
+    VirtualFile directory = dir.getVirtualFile();
+    String packageName = PackageIndex.getInstance(dir.getProject()).getPackageNameByDirectory(directory);
+
+    VirtualFile integrationTestsDir = findIntegrationTestDirV3(application);
+    VirtualFile testsDir = findTestDirV3(application);
+
+    boolean integrationTest;
+    if (packageName == null) {
+      if (directory.equals(testsDir)) {
+        integrationTest = false;
+      }
+      else if (directory.equals(integrationTestsDir)) {
+        integrationTest = true;
+      }
+      else {
+        return false;
+      }
+    }
+    else {
+      if (testsDir != null && VfsUtilCore.isAncestor(testsDir, directory, true)) {
+        integrationTest = false;
+      }
+      else if (integrationTestsDir != null && VfsUtilCore.isAncestor(integrationTestsDir, directory, true)) {
+        integrationTest = true;
+      }
+      else {
+        return false;
+      }
+    }
+
+    StringBuilder configurationName = new StringBuilder(integrationTest ? "Grails integration tests" : "Grails tests");
+    StringBuilder parameters = new StringBuilder("test-app");
+
+    if (!StringUtil.isEmpty(packageName)) {
+      configurationName.append(": ").append(packageName);
+      parameters.append(" ").append(packageName).append(".**");
+    }
+
+    parameters.append(" ").append(integrationTest ? INTEGRATION_TEST_KEY_V3 : UNIT_TEST_KEY_V3);
+
+    configuration.setName(configurationName.toString());
+    configuration.setProgramParameters(parameters.toString());
+
+    return true;
+  }
+
+  private static boolean setupConfigurationByDirOld(@NotNull GrailsRunConfiguration configuration,
+                                                    @NotNull GrailsApplication application,
+                                                    @NotNull PsiDirectory dir) {
+    VirtualFile directory = dir.getVirtualFile();
+
+    ProjectFileIndex fileIndex = ProjectRootManager.getInstance(dir.getProject()).getFileIndex();
+    if (!fileIndex.isInTestSourceContent(directory)) return false;
+
+    VirtualFile srcRoot = fileIndex.getSourceRootForFile(directory);
+    if (srcRoot == null) return false;
+    String testKind = getParamKeyByTestRoot(application, srcRoot);
+    if (testKind == null) return false;
+
+    StringBuilder configurationName = new StringBuilder("Grails " + testKind + " tests");
+    StringBuilder parameters = new StringBuilder("test-app");
+
+    parameters.append(" ").append(testKind);
+
+    String packageName = PackageIndex.getInstance(dir.getProject()).getPackageNameByDirectory(directory);
+    if (!StringUtil.isEmpty(packageName)) {
+      configurationName.append(": ").append(packageName);
+      parameters.append(" ").append(packageName).append(".**");
+    }
+
+    configuration.setName(configurationName.toString());
+    configuration.setProgramParameters(parameters.toString());
+
+    return true;
+  }
+
+  private static @Nullable VirtualFile findTestDirV3(@NotNull GrailsApplication application) {
+    return VfsUtil.findRelativeFile(application.getRoot(), "src", "test");
+  }
+
+  static @Nullable VirtualFile findIntegrationTestDirV3(@NotNull GrailsApplication application) {
+    return VfsUtil.findRelativeFile(application.getRoot(), "src", "integration-test");
+  }
+
+  /**
+   * Grails 1.3 switched the test-kind argument from {@code -unit} to {@code unit:}.
+   */
+  static @Nullable String getParamKeyByTestRoot(@NotNull GrailsApplication application, @NotNull VirtualFile testRoot) {
+    String name = testRoot.getName();
+    if (TEST_FOLDER_NAMES.contains(name)) {
+      return application.getGrailsVersion().isAtLeast(Version.GRAILS_1_3) ? name + ":" : "-" + name;
+    }
+    return null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byClass.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byClass.kt
deleted file mode 100644
index 0baf0fc..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byClass.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.tests.runner
-
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.vfs.VfsUtilCore
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiMethod
-import com.intellij.util.execution.ParametersListUtil
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.tests.runner.GrailsTestConfigurationProducer.isGrailsTestMethod
-import org.jetbrains.plugins.grails.util.version.v11
-import org.jetbrains.plugins.grails.util.version.v3
-
-internal fun setupConfigurationByClass(configuration: GrailsRunConfiguration,
-                                       application: GrailsApplication,
-                                       aClass: PsiClass,
-                                       method: PsiMethod?): PsiElement? {
-  val classFqn = aClass.qualifiedName ?: return null
-  val className = aClass.name ?: return null
-
-  val fileIndex = ProjectRootManager.getInstance(aClass.project).fileIndex
-  val srcRoot = fileIndex.getSourceRootForFile(aClass.containingFile.virtualFile) ?: return null
-  val v3 = application.grailsVersion >= v3
-
-  val parameters = StringBuilder("test-app")
-
-  if (!v3) {
-    val testKind = getParamKeyByTestRoot(application, srcRoot) ?: return null
-    parameters.append(" ").append(testKind)
-  }
-
-  val testName = trimTestSuffix(classFqn)
-
-  val configurationName: String
-  val testFilter: String
-  val sourceElement: PsiElement
-
-  if (method != null && useMethod(application, method)) {
-    val methodName = method.name
-    configurationName = "$methodName()"
-    testFilter = ParametersListUtil.join("$testName.$methodName")
-    sourceElement = method
-  }
-  else {
-    configurationName = className
-    testFilter = testName
-    sourceElement = aClass
-  }
-
-  parameters.append(" ").append(testFilter)
-
-  if (v3) {
-    val integrationTestRoot = application.findIntegrationTestDirV3()
-    val integrationTest = integrationTestRoot != null && VfsUtilCore.isAncestor(integrationTestRoot, srcRoot, true)
-    parameters.append(" ").append(if (integrationTest) integrationTestKeyV3 else unitTestKeyV3)
-  }
-
-  configuration.name = configurationName
-  configuration.programParameters = parameters.toString()
-
-  return sourceElement
-}
-
-private fun useMethod(application: GrailsApplication, method: PsiMethod): Boolean {
-  val version = application.grailsVersion
-  if (version < v11 || !isGrailsTestMethod(method)) return false
-  if (version > v3 && method.name.contains(" ")) return false
-  return true
-}
-
-private val suffixes = arrayOf("Tests", "Test")
-
-private fun trimTestSuffix(name: String): String {
-  for (suffix in suffixes) {
-    if (name.endsWith(suffix)) return name.removeSuffix(suffix)
-  }
-  return name
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt
deleted file mode 100644
index 2de9997..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.tests.runner
-
-import com.intellij.openapi.roots.PackageIndex
-import com.intellij.openapi.roots.ProjectRootManager
-import com.intellij.openapi.vfs.VfsUtil.findRelativeFile
-import com.intellij.openapi.vfs.VfsUtilCore.isAncestor
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiDirectory
-import org.jetbrains.plugins.grails.runner.GrailsRunConfiguration
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.util.version.v3
-
-internal fun setupConfigurationByDir(configuration: GrailsRunConfiguration, application: GrailsApplication, dir: PsiDirectory): Boolean {
-  return if (application.grailsVersion >= v3) {
-    setupConfigurationByDirV3(configuration, application, dir)
-  }
-  else {
-    setupConfigurationByDirOld(configuration, application, dir)
-  }
-}
-
-private fun setupConfigurationByDirV3(configuration: GrailsRunConfiguration, application: GrailsApplication, dir: PsiDirectory): Boolean {
-  val directory = dir.virtualFile
-  val packageName = PackageIndex.getInstance(dir.project).getPackageNameByDirectory(directory)
-
-  val integrationTestsDir = application.findIntegrationTestDirV3()
-  val testsDir = application.findTestDirV3()
-
-  val integrationTest: Boolean = if (packageName == null) {
-    when (directory) {
-      testsDir -> false
-      integrationTestsDir -> true
-      else -> return false
-    }
-  }
-  else {
-    when {
-      testsDir != null && isAncestor(testsDir, directory, true) -> false
-      integrationTestsDir != null && isAncestor(integrationTestsDir, directory, true) -> true
-      else -> return false
-    }
-  }
-
-  val configurationName = StringBuilder(if (integrationTest) "Grails integration tests" else "Grails tests")
-  val parameters = StringBuilder("test-app")
-
-  if (!packageName.isNullOrEmpty()) {
-    configurationName.append(": ").append(packageName)
-    parameters.append(" ").append(packageName).append(".**")
-  }
-
-  parameters.append(" ").append(if (integrationTest) integrationTestKeyV3 else unitTestKeyV3)
-
-  configuration.name = configurationName.toString()
-  configuration.programParameters = parameters.toString()
-
-  return true
-}
-
-internal const val unitTestKeyV3 = "-unit"
-internal const val integrationTestKeyV3 = "-integration"
-
-private fun GrailsApplication.findTestDirV3(): VirtualFile? = findRelativeFile(root, "src", "test")
-internal fun GrailsApplication.findIntegrationTestDirV3(): VirtualFile? = findRelativeFile(root, "src", "integration-test")
-
-private fun setupConfigurationByDirOld(configuration: GrailsRunConfiguration, application: GrailsApplication, dir: PsiDirectory): Boolean {
-  val directory = dir.virtualFile
-
-  val fileIndex = ProjectRootManager.getInstance(dir.project).fileIndex
-  if (!fileIndex.isInTestSourceContent(directory)) return false
-
-  val srcRoot = fileIndex.getSourceRootForFile(directory) ?: return false
-  val testKind = getParamKeyByTestRoot(application, srcRoot) ?: return false
-
-  val configurationName = StringBuilder("Grails $testKind tests")
-  val parameters = StringBuilder("test-app")
-
-  parameters.append(" ").append(testKind)
-
-  val packageName = PackageIndex.getInstance(dir.project).getPackageNameByDirectory(directory)
-  if (!packageName.isNullOrEmpty()) {
-    configurationName.append(": ").append(packageName)
-    parameters.append(" ").append(packageName).append(".**")
-  }
-
-  configuration.name = configurationName.toString()
-  configuration.programParameters = parameters.toString()
-
-  return true
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt
deleted file mode 100644
index 3cad107..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.tests.runner
-
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.util.version.v13
-
-private val testFolderNames = listOf("integration", "unit", "functional")
-
-internal fun getParamKeyByTestRoot(application: GrailsApplication, testRoot: VirtualFile): String? {
-  val name = testRoot.name
-  if (name in testFolderNames) {
-    return if (application.grailsVersion >= v13) {
-      "$name:"
-    }
-    else {
-      "-$name"
-    }
-  }
-  return null
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Version.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Version.java
index 13050d3..e923408 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Version.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Version.java
@@ -38,6 +38,7 @@ public interface Version extends Comparable<Version> {
 
   Version GRAILS_1_1 = new VersionImpl("1.1");
   Version GRAILS_1_2 = new VersionImpl("1.2");
+  Version GRAILS_1_3 = new VersionImpl("1.3");
   Version GRAILS_1_3_4 = new VersionImpl("1.3.4");
   Version GRAILS_2_0 = new VersionImpl("2.0");
   Version GRAILS_2_3_0 = new VersionImpl("2.3.0");
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/versions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/versions.kt
deleted file mode 100644
index 6038e2b..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/versions.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.util.version
-
-val v11: Version = VersionImpl("1.1")
-val v13: Version = VersionImpl("1.3")
-val v3: Version = VersionImpl("3.0")


(grails-intellij-plugin) 34/35: Resolve a spec back to its artefact across modules and via the unit-test trait

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 2f37ce342aed9427373d8e1d5685242272731cc9
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 16:13:23 2026 -0400

    Resolve a spec back to its artefact across modules and via the unit-test trait
    
    The editor toolbar was inert whenever a Grails 3+ spec was open, while the same
    toolbar worked from the artefact side. Both directions go through
    GrailsActionUtil.getArtefactData, but for a test file that delegates to
    GrailsTestUtils.getTestedClass, which had no way to answer:
    
      - Nothing in the plugin read the Grails 3+ unit-test traits. The test-linking
        code is all Grails 2 era (@TestFor, ControllerUnitTestMixin,
        ControllerUnitTestCase), so `implements ControllerUnitTest<BookController>`
        -- the authoritative link, and the one that needs no naming convention --
        was ignored entirely.
      - That left only a name-suffix fallback, scoped to
        findAppDirectory(module) of the spec's own module. A spec lives in a test
        source set, so under Gradle's module-per-source-set layout that module's
        content roots are under src/test and have no grails-app child: the lookup
        returned null before searching anything. In a multi-project build the
        artefact can also live in an upstream project altogether.
    
    So getTestedClass now prefers the trait's type argument, resolved with
    getSuperClassSubstitutor so a shared base spec that implements the trait also
    works, and the name fallback searches the related-module set instead of one
    grails-app. This mirrors what the artefact -> test direction already did, and is
    the same class of bug as dea2ce5 (Go To View across modules).
    
    Widening the scope cannot produce false matches: GrailsArtifact.getType still
    confirms each candidate sits in an artefact source root.
    
    The trait test uses a non-conventional spec name (BookCtrlSpec) so the name
    fallback cannot satisfy it -- verified by disabling the trait path, which fails
    that test alone and leaves the name-convention test passing.
    
    Verified: 991 tests / 0 failures (188 classes, two new); rat clean.
---
 .../plugins/grails/tests/GrailsTestUtils.java      | 67 ++++++++++++++--
 .../groovy/grails/tests/GrailsTestedClassTest.java | 90 ++++++++++++++++++++++
 2 files changed, 150 insertions(+), 7 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
index 72fcb57..6789a2c 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
@@ -30,9 +30,13 @@ import com.intellij.psi.PsiAnnotationMemberValue;
 import com.intellij.psi.PsiAnnotationOwner;
 import com.intellij.psi.PsiClass;
 import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiSubstitutor;
+import com.intellij.psi.PsiTypeParameter;
 import com.intellij.psi.PsiModifierList;
 import com.intellij.psi.PsiReference;
 import com.intellij.psi.PsiType;
+import com.intellij.psi.util.InheritanceUtil;
+import com.intellij.psi.util.TypeConversionUtil;
 import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.psi.search.GlobalSearchScopesCore;
 import com.intellij.psi.search.PsiShortNamesCache;
@@ -180,22 +184,24 @@ public final class GrailsTestUtils {
       return aClass;
     }
 
+    // Grails 3+ specs declare the artefact as the type argument of a unit-test trait. This is the
+    // authoritative link and needs no naming convention, so it is preferred over the fallback.
+    PsiClass fromTrait = getTestedClassFromUnitTestTrait(testClass);
+    if (fromTrait != null) return fromTrait;
+
     Module module = ModuleUtilCore.findModuleForPsiElement(testClass);
     if (module == null) return null;
 
-    VirtualFile appDir = GrailsFramework.getInstance().findAppDirectory(module);
-    if (appDir == null) return null;
-
-    GlobalSearchScope appDirScope = GlobalSearchScopesCore.directoryScope(module.getProject(), appDir, true);
+    String testClassName = testClass.getName();
+    if (testClassName == null) return null;
 
     PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(module.getProject());
-
-    String testClassName = testClass.getName();
+    GlobalSearchScope scope = relatedModulesScope(module);
 
     for (String suffix : TEST_SUFFIXES) {
       if (testClassName.endsWith(suffix)) {
         String artifactName = StringUtil.trimEnd(testClassName, suffix);
-        for (PsiClass artifact : shortNamesCache.getClassesByName(artifactName, appDirScope)) {
+        for (PsiClass artifact : shortNamesCache.getClassesByName(artifactName, scope)) {
           if (GrailsArtifact.getType(artifact) != null) {
             return artifact;
           }
@@ -206,6 +212,53 @@ public final class GrailsTestUtils {
     return null;
   }
 
+  /**
+   * Every module that shares artefacts with {@code module}, rather than just its own grails-app.
+   *
+   * <p>A spec lives in a test source set, so under Gradle's module-per-source-set layout its module's
+   * content roots are under {@code src/test} and have no {@code grails-app} child at all - an
+   * app-directory-scoped search would find nothing. In a multi-project build the artefact can also
+   * live in an upstream project entirely (a controller in the app, its domain in a shared library).
+   * {@link GrailsArtifact#getType} still confirms each candidate really is an artefact, so widening
+   * the scope cannot produce false matches.
+   */
+  private static @NotNull GlobalSearchScope relatedModulesScope(@NotNull Module module) {
+    List<GlobalSearchScope> scopes = new ArrayList<>();
+    for (Module related : GrailsArtifact.getRelatedModules(module)) {
+      scopes.add(related.getModuleScope());
+    }
+    return scopes.isEmpty() ? GlobalSearchScope.EMPTY_SCOPE : GlobalSearchScope.union(scopes);
+  }
+
+  /**
+   * Grails 3+ unit-test traits. The tested artefact is the trait's single type argument, e.g.
+   * {@code class ReleaseControllerSpec extends Specification implements ControllerUnitTest<ReleaseController>}.
+   */
+  private static final List<String> UNIT_TEST_TRAITS = List.of(
+    "grails.testing.web.controllers.ControllerUnitTest",
+    "grails.testing.gorm.DomainUnitTest",
+    "grails.testing.services.ServiceUnitTest",
+    "grails.testing.web.taglib.TagLibUnitTest",
+    "grails.testing.web.interceptor.InterceptorUnitTest",
+    "grails.testing.web.UrlMappingsUnitTest"
+  );
+
+  private static @Nullable PsiClass getTestedClassFromUnitTestTrait(@NotNull PsiClass testClass) {
+    JavaPsiFacade facade = JavaPsiFacade.getInstance(testClass.getProject());
+    for (String traitFqn : UNIT_TEST_TRAITS) {
+      if (!InheritanceUtil.isInheritor(testClass, traitFqn)) continue;
+      PsiClass trait = facade.findClass(traitFqn, testClass.getResolveScope());
+      if (trait == null) continue;
+      PsiTypeParameter[] typeParameters = trait.getTypeParameters();
+      if (typeParameters.length == 0) continue;
+      // Resolves through the whole hierarchy, so a shared base spec that implements the trait works.
+      PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(trait, testClass, PsiSubstitutor.EMPTY);
+      PsiClass tested = com.intellij.psi.util.PsiUtil.resolveClassInClassTypeOnly(substitutor.substitute(typeParameters[0]));
+      if (tested != null && GrailsArtifact.getType(tested) != null) return tested;
+    }
+    return null;
+  }
+
   public static @Nullable String getTestType(@NotNull PsiClass testClass) {
     VirtualFile virtualFile = testClass.getContainingFile().getOriginalFile().getVirtualFile();
     if (virtualFile == null) return null;
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestedClassTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestedClassTest.java
new file mode 100644
index 0000000..d4cef78
--- /dev/null
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestedClassTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jetbrains.plugins.groovy.grails.tests;
+
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.plugins.grails.tests.GrailsTestUtils;
+import org.jetbrains.plugins.groovy.grails.GrailsTestCase;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+
+/**
+ * Resolving a spec back to the artefact it tests. This is what the editor toolbar needs: without it
+ * {@code GrailsActionUtil.getArtefactData} returns null and every toolbar action is inert while a
+ * spec is open, even though the same toolbar works from the artefact side.
+ */
+public class GrailsTestedClassTest extends GrailsTestCase {
+
+  /** Stub of the Grails 3+ trait; the real one comes from grails-testing-support. */
+  private void addUnitTestTrait() {
+    myFixture.addFileToProject("src/groovy/grails/testing/web/controllers/ControllerUnitTest.groovy", """
+
+package grails.testing.web.controllers
+
+trait ControllerUnitTest<T> {
+}
+""");
+  }
+
+  private static PsiClass singleClass(PsiFile file) {
+    return ((GroovyFile)file).getClasses()[0];
+  }
+
+  /**
+   * The type argument of the unit-test trait is the authoritative link, and the only one that works
+   * when the spec name does not follow the {@code <Artefact>Spec} convention.
+   */
+  public void testResolvesFromUnitTestTraitTypeArgument() {
+    addUnitTestTrait();
+    addController("""
+
+class BookController {
+  def index() {}
+}
+""");
+    PsiFile spec = myFixture.addFileToProject("test/unit/BookCtrlSpec.groovy", """
+
+class BookCtrlSpec implements grails.testing.web.controllers.ControllerUnitTest<BookController> {
+}
+""");
+
+    PsiClass tested = GrailsTestUtils.getTestedClass(singleClass(spec));
+    assertNotNull("spec must resolve to its artefact through the trait type argument", tested);
+    assertEquals("BookController", tested.getName());
+  }
+
+  /** The name-convention fallback, for specs that declare no trait. */
+  public void testResolvesFromNameConvention() {
+    addController("""
+
+class BookController {
+  def index() {}
+}
+""");
+    PsiFile spec = myFixture.addFileToProject("test/unit/BookControllerSpec.groovy", """
+
+class BookControllerSpec {
+}
+""");
+
+    PsiClass tested = GrailsTestUtils.getTestedClass(singleClass(spec));
+    assertNotNull("spec must resolve to its artefact by name", tested);
+    assertEquals("BookController", tested.getName());
+  }
+}


(grails-intellij-plugin) 27/35: Convert the editor toolbar and action-context helpers to Java

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit fa7001cf065367020ce7428b2a2e03c3743079ff
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 14:43:14 2026 -0400

    Convert the editor toolbar and action-context helpers to Java
    
    Finishes the editor/ and editor/toolbar/ packages, and converts the two
    Kotlin files they leaned on so the new Java does not have to reach through
    synthetic *Kt facades:
    
      - GrailsActionUtil.kt becomes GrailsActionUtil (static) plus ArtefactData
        in its own file. Fourteen Java files already called this through
        GrailsActionUtilKt; they now call the class directly, and the three
        DataKey references drop the getGRAILS_X() accessor form for plain fields.
      - GrailsTestUtils.kt held a single helper that only GrailsTestUtils.java
        called. It moves into that class as collectGrails3TestsForArtifact and
        the file goes away, so the package no longer has a Java class and a
        Kotlin facade sharing one name.
    
    The Kotlin files with several public classes each split by class:
    GrailsToolbarActions.kt into the five GoTo*Action files, and
    GrailsGenerateActions.kt into GenerateActionBase plus its four subclasses.
    
    Two details worth recording:
    
      - showEditorToolBar used propComponentProperty, which has no Java
        equivalent. GrailsEditorToolbar.isShowEditorToolbar/setShowEditorToolbar
        reproduce what the delegate compiles to, verified against its bytecode:
        getBoolean(name, default) / setValue(name, value, default) on the
        application-level PropertiesComponent, guarded by
        LoadingState.COMPONENTS_LOADED. The property key is unchanged, so
        existing user settings carry over.
      - The default came from toBooleanStrict(), which throws on anything that
        is not exactly "true"/"false". Boolean.parseBoolean treats a malformed
        -Dgrails.show.editor.toolbar as false instead of failing class init.
    
    Verified: 186 classes / 987 tests / 0 failures / 0 errors, matching the
    pre-conversion baseline; rat clean; all 220 descriptor-referenced classes
    resolve against the compiled output.
---
 .../plugins/grails/actions/ArtefactData.java       |  85 ++++++++++++
 .../plugins/grails/actions/GrailsActionUtil.java   | 108 +++++++++++++++
 .../plugins/grails/actions/GrailsActionUtil.kt     |  98 --------------
 .../grails/actions/GrailsConfigureSDKAction.java   |   2 +-
 .../grails/actions/GrailsRunCommandAction.java     |   2 +-
 .../plugins/grails/actions/GrailsWarAction.java    |   4 +-
 .../plugins/grails/actions/NewGrailsXXXAction.java |  12 +-
 .../plugins/grails/actions/NewGspAction.java       |   2 +-
 .../grails/actions/UpdateGrailsSettingsAction.java |   4 +-
 .../plugins/grails/editor/GenerateTestsAction.java |   4 +-
 .../grails/editor/GrailsEditorDecorator.java       |  54 ++++++++
 .../plugins/grails/editor/GrailsEditorDecorator.kt | 102 --------------
 .../plugins/grails/editor/GrailsEditorToolbar.java | 107 +++++++++++++++
 .../editor/ViewGrailsEditorToolbarAction.java      |  63 +++++++++
 .../grails/editor/ViewGrailsEditorToolbarAction.kt |  52 --------
 .../grails/editor/toolbar/GenerateActionBase.java  | 102 ++++++++++++++
 .../toolbar/GenerateAsyncControllerAction.java     |  25 ++++
 .../editor/toolbar/GenerateControllerAction.java   |  25 ++++
 .../toolbar/GenerateControllerActionBase.java      |  47 +++++++
 .../grails/editor/toolbar/GenerateViewsAction.java |  26 ++++
 .../editor/toolbar/GoToControllerAction.java       |  73 +++++++++++
 .../grails/editor/toolbar/GoToDomainAction.java    |  37 ++++++
 .../grails/editor/toolbar/GoToServiceAction.java   |  37 ++++++
 .../grails/editor/toolbar/GoToTestAction.java      |  94 +++++++++++++
 .../grails/editor/toolbar/GoToViewAction.java      |  83 ++++++++++++
 .../grails/editor/toolbar/GrailsGenerateActions.kt |  87 ------------
 .../toolbar/GrailsGoToArtefactActionBase.java      |  75 +++++++++++
 .../editor/toolbar/GrailsGoToArtefactActionBase.kt |  49 -------
 .../editor/toolbar/GrailsToolbarActionBase.java    |  86 ++++++++++++
 .../editor/toolbar/GrailsToolbarActionBase.kt      |  59 ---------
 .../grails/editor/toolbar/GrailsToolbarActions.kt  | 146 ---------------------
 .../editor/toolbar/GrailsToolbarTargetAction.java  |  68 ++++++++++
 .../editor/toolbar/GrailsToolbarTargetAction.kt    |  51 -------
 .../editor/toolbar/GrailsToolbarVfileAction.java   |  44 +++++++
 .../editor/toolbar/GrailsToolbarVfileAction.kt     |  33 -----
 .../projectView/GrailsTreeStructureProvider.java   |   8 +-
 .../GrailsScriptRunConfigurationProducer.java      |   4 +-
 .../plugins/grails/tests/GrailsTestUtils.java      |  24 +++-
 .../plugins/grails/tests/GrailsTestUtils.kt        |  37 ------
 .../groovy/mvc/plugins/MvcPluginsAction.java       |   6 +-
 40 files changed, 1286 insertions(+), 739 deletions(-)

diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/ArtefactData.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/ArtefactData.java
new file mode 100644
index 0000000..eda010f
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/ArtefactData.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.actions;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+
+/**
+ * The Grails artefact the current data context points at, resolved from either the artefact class
+ * itself or one of its views.
+ *
+ * @see GrailsActionUtil#getArtefactData
+ */
+public final class ArtefactData {
+
+  private final @NotNull Project myProject;
+  private final @NotNull Module myModule;
+  private final @NotNull VirtualFile myFile;
+  private final @Nullable String myPackageName;
+  private final @NotNull String myArtefactName;
+  private final @NotNull GrailsApplication myApplication;
+  private final boolean myIsView;
+
+  public ArtefactData(@NotNull Project project,
+                      @NotNull Module module,
+                      @NotNull VirtualFile file,
+                      @Nullable String packageName,
+                      @NotNull String artefactName,
+                      @NotNull GrailsApplication application,
+                      boolean isView) {
+    myProject = project;
+    myModule = module;
+    myFile = file;
+    myPackageName = packageName;
+    myArtefactName = artefactName;
+    myApplication = application;
+    myIsView = isView;
+  }
+
+  public @NotNull Project getProject() {
+    return myProject;
+  }
+
+  public @NotNull Module getModule() {
+    return myModule;
+  }
+
+  public @NotNull VirtualFile getFile() {
+    return myFile;
+  }
+
+  /** {@code null} when resolved from a view, where the package is not known. */
+  public @Nullable String getPackageName() {
+    return myPackageName;
+  }
+
+  public @NotNull String getArtefactName() {
+    return myArtefactName;
+  }
+
+  public @NotNull GrailsApplication getApplication() {
+    return myApplication;
+  }
+
+  public boolean isView() {
+    return myIsView;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.java
new file mode 100644
index 0000000..86551ee
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.actions;
+
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataKey;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.PlatformCoreDataKeys;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+import org.jetbrains.plugins.grails.tests.GrailsTestUtils;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+import org.jetbrains.plugins.groovy.util.GroovyUtils;
+
+public final class GrailsActionUtil {
+
+  private GrailsActionUtil() {
+  }
+
+  public static final DataKey<GrailsApplication> GRAILS_APPLICATION = DataKey.create("grails.application");
+  public static final DataKey<GrailsArtefactHandler> GRAILS_ARTEFACT_HANDLER = DataKey.create("grails.artefact.handler");
+  public static final DataKey<String> GRAILS_ARTEFACT_PACKAGE = DataKey.create("grails.artefact.package");
+
+  public static @Nullable ArtefactData getArtefactData(@Nullable DataContext context) {
+    if (context == null) return null;
+    Project project = context.getData(LangDataKeys.PROJECT);
+    if (project == null) return null;
+    if (DumbService.isDumb(project)) return null;
+    Module module = context.getData(PlatformCoreDataKeys.MODULE);
+    if (module == null) return null;
+    VirtualFile file = context.getData(LangDataKeys.VIRTUAL_FILE);
+    if (file == null) return null;
+    GrailsApplication application = GrailsApplicationManager.getInstance(project).findApplication(file);
+    if (application == null) return null;
+    PsiClass publicClass = GroovyUtils.getPublicClass(project, file);
+
+    boolean isView;
+    String packageName;
+    String artefactName;
+
+    if (publicClass == null) {
+      // inside a view
+      String controllerName = GrailsUtils.getControllerNameByGsp(file);
+      if (controllerName == null) return null;
+      if ("layouts".equals(controllerName) || !StringUtil.isJavaIdentifier(controllerName)) return null;
+      isView = true;
+      packageName = null; // we do not know package here
+      artefactName = controllerName;
+    }
+    else {
+      PsiClass artefactClass = GrailsUtils.isInGrailsTests(file, project)
+                               ? GrailsTestUtils.getTestedClass(publicClass)
+                               : publicClass;
+      if (artefactClass == null) return null;
+      String qualifiedName = artefactClass.getQualifiedName();
+      if (qualifiedName == null) return null;
+      isView = false;
+      packageName = StringUtil.getPackageName(qualifiedName);
+      GrailsArtifact type = GrailsArtifact.getType(artefactClass);
+      if (type == null) return null;
+      artefactName = type.getArtifactName(artefactClass);
+    }
+
+    return new ArtefactData(project, module, file, packageName, artefactName, application, isView);
+  }
+
+  public static @Nullable GrailsApplication getGrailsApplication(@NotNull DataContext dataContext) {
+    GrailsApplication fromContext = dataContext.getData(GRAILS_APPLICATION);
+    if (fromContext != null) return fromContext;
+    Project project = CommonDataKeys.PROJECT.getData(dataContext);
+    if (project == null) return null;
+    VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
+    if (virtualFile == null) return null;
+    return GrailsApplicationManager.getInstance(project).findApplication(virtualFile);
+  }
+
+  public static @Nullable GrailsArtefactHandler getArtefactHandler(@NotNull DataContext dataContext) {
+    return dataContext.getData(GRAILS_ARTEFACT_HANDLER);
+  }
+
+  public static @Nullable String getArtefactPackage(@NotNull DataContext dataContext) {
+    return dataContext.getData(GRAILS_ARTEFACT_PACKAGE);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt
deleted file mode 100644
index f8e5e2f..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.actions
-
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.actionSystem.DataKey
-import com.intellij.openapi.actionSystem.LangDataKeys
-import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.project.DumbService
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-import org.jetbrains.plugins.grails.tests.GrailsTestUtils
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.grails.util.GrailsUtils
-import org.jetbrains.plugins.groovy.util.GroovyUtils
-
-val GRAILS_APPLICATION: DataKey<GrailsApplication> = DataKey.create("grails.application")
-val GRAILS_ARTEFACT_HANDLER: DataKey<GrailsArtefactHandler> = DataKey.create("grails.artefact.handler")
-val GRAILS_ARTEFACT_PACKAGE: DataKey<String> = DataKey.create("grails.artefact.package")
-
-class ArtefactData(
-    val project: Project,
-    val module: Module,
-    val file: VirtualFile,
-    val packageName: String?,
-    val artefactName: String,
-    val application: GrailsApplication,
-    val isView: Boolean = false
-)
-
-fun getArtefactData(context: DataContext?): ArtefactData? {
-  if (context == null) return null
-  val project = context.getData(LangDataKeys.PROJECT) ?: return null
-  if (DumbService.isDumb(project)) return null
-  val module = context.getData(PlatformCoreDataKeys.MODULE) ?: return null
-  val file = context.getData(LangDataKeys.VIRTUAL_FILE) ?: return null
-  val application = GrailsApplicationManager.getInstance(project).findApplication(file) ?: return null
-  val publicClass = GroovyUtils.getPublicClass(project, file)
-
-  val isView: Boolean
-  val packageName: String?
-  val artefactName: String
-
-  if (publicClass == null) {
-    // inside a view
-    val controllerName = GrailsUtils.getControllerNameByGsp(file) ?: return null
-    if (controllerName == "layouts" || !StringUtil.isJavaIdentifier(controllerName)) return null
-    isView = true
-    packageName = null // we do not know package here
-    artefactName = controllerName
-  }
-  else {
-    val artefactClass = if (GrailsUtils.isInGrailsTests(file, project)) {
-      GrailsTestUtils.getTestedClass(publicClass)
-    }
-    else {
-      publicClass
-    }
-    artefactClass ?: return null
-    isView = false
-    packageName = StringUtil.getPackageName(artefactClass.qualifiedName ?: return null)
-    artefactName = GrailsArtifact.getType(artefactClass)?.getArtifactName(artefactClass) ?: return null
-  }
-
-  return ArtefactData(project, module, file, packageName, artefactName, application, isView)
-}
-
-fun getGrailsApplication(dataContext: DataContext): GrailsApplication? {
-  dataContext.getData(GRAILS_APPLICATION)?.let { return it }
-  val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return null
-  val instance = GrailsApplicationManager.getInstance(project)
-  val virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext) ?: return null
-  return instance.findApplication(virtualFile)
-}
-
-fun getArtefactHandler(dataContext: DataContext): GrailsArtefactHandler? = dataContext.getData(GRAILS_ARTEFACT_HANDLER)
-
-fun getArtefactPackage(dataContext: DataContext): String? = dataContext.getData(GRAILS_ARTEFACT_PACKAGE)
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java
index 7667292..4117330 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java
@@ -52,7 +52,7 @@ public class GrailsConfigureSDKAction extends DumbAwareAction {
     final Project project = e.getProject();
     if (project == null) return;
 
-    final GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    final GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     new GrailsConfigureSDKDialog(project).setGrailsApplication(application).show();
   }
 }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java
index 3570c0c..e49dbfe 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java
@@ -64,7 +64,7 @@ public final class GrailsRunCommandAction extends AnAction {
   }
 
   private static @Nullable GrailsApplication getApplicationForSelection(@NotNull DataContext context, @NotNull Project project) {
-    GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(context);
+    GrailsApplication application = GrailsActionUtil.getGrailsApplication(context);
     if (application != null) return application;
 
     GrailsApplicationManager applicationManager = GrailsApplicationManager.getInstance(project);
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsWarAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsWarAction.java
index 91da7ab..56b9376 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsWarAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsWarAction.java
@@ -33,7 +33,7 @@ public class GrailsWarAction extends AnAction {
 
   @Override
   public void actionPerformed(@NotNull AnActionEvent e) {
-    GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     GrailsCommandExecutor executor = GrailsCommandExecutor.getGrailsExecutor(application);
     if (executor == null) return;
 
@@ -49,7 +49,7 @@ public class GrailsWarAction extends AnAction {
 
   @Override
   public void update(@NotNull AnActionEvent e) {
-    GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     e.getPresentation().setVisible(GrailsCommandExecutor.getGrailsExecutor(application) != null);
   }
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java
index 10a77ef..c9410c9 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java
@@ -75,7 +75,7 @@ public abstract class NewGrailsXXXAction extends AnAction implements DumbAware {
 
   @Override
   public void actionPerformed(final @NotNull AnActionEvent e) {
-    final GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    final GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     if (application == null) return;
 
     final Project project = application.getProject();
@@ -109,7 +109,7 @@ public abstract class NewGrailsXXXAction extends AnAction implements DumbAware {
       }
     }
     else {
-      String aPackage = GrailsActionUtilKt.getArtefactPackage(e.getDataContext());
+      String aPackage = GrailsActionUtil.getArtefactPackage(e.getDataContext());
       if (aPackage != null) packageName = aPackage;
     }
 
@@ -127,7 +127,7 @@ public abstract class NewGrailsXXXAction extends AnAction implements DumbAware {
       );
     }
     else {
-      ArtefactData artefactData = GrailsActionUtilKt.getArtefactData(e.getDataContext());
+      ArtefactData artefactData = GrailsActionUtil.getArtefactData(e.getDataContext());
       return artefactData == null ? null : artefactData.getArtefactName();
     }
   }
@@ -157,7 +157,7 @@ public abstract class NewGrailsXXXAction extends AnAction implements DumbAware {
   }
 
   protected boolean isEnabled(AnActionEvent e) {
-    final GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    final GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     if (application == null) return false;
     if (e.getData(LangDataKeys.IDE_VIEW) == null) return false;
 
@@ -168,7 +168,7 @@ public abstract class NewGrailsXXXAction extends AnAction implements DumbAware {
 
     if (!e.isFromActionToolbar()) {
       GrailsArtefactHandler expectedHandler = getArtefactHandler();
-      if (expectedHandler == null || GrailsActionUtilKt.getArtefactHandler(e.getDataContext()) != expectedHandler) {
+      if (expectedHandler == null || GrailsActionUtil.getArtefactHandler(e.getDataContext()) != expectedHandler) {
         VirtualFile vfile = e.getData(CommonDataKeys.VIRTUAL_FILE);
         if (vfile == null) return false;
         if (!vfile.isDirectory()) {
@@ -184,7 +184,7 @@ public abstract class NewGrailsXXXAction extends AnAction implements DumbAware {
       }
     }
     else {
-      if (GrailsActionUtilKt.getArtefactData(e.getDataContext()) == null) return false;
+      if (GrailsActionUtil.getArtefactData(e.getDataContext()) == null) return false;
     }
 
     return isEnabled(application);
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGspAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGspAction.java
index dcedefa..1e9f1a2 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGspAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGspAction.java
@@ -84,7 +84,7 @@ public class NewGspAction extends NewGroovyActionBase {
       return false;
     }
 
-    final GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(dataContext);
+    final GrailsApplication application = GrailsActionUtil.getGrailsApplication(dataContext);
     if (application == null) {
       return false;
     }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java
index 4daa9f1..8b3d3ee 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java
@@ -27,7 +27,7 @@ public class UpdateGrailsSettingsAction extends DumbAwareAction {
 
   @Override
   public void update(@NotNull AnActionEvent e) {
-    final GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    final GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     e.getPresentation().setEnabledAndVisible(application instanceof OldGrailsApplication);
   }
 
@@ -38,7 +38,7 @@ public class UpdateGrailsSettingsAction extends DumbAwareAction {
 
   @Override
   public void actionPerformed(@NotNull AnActionEvent e) {
-    final GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    final GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     if (application instanceof OldGrailsApplication) {
       GrailsFramework.forceSynchronizationSetting(((OldGrailsApplication)application).getModule());
     }
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java
index 2168c00..f7969f0 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java
@@ -27,7 +27,7 @@ import com.intellij.psi.PsiClass;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.grails.GrailsBundle;
-import org.jetbrains.plugins.grails.actions.GrailsActionUtilKt;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtil;
 import org.jetbrains.plugins.grails.runner.GrailsCommandExecutorUtil;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 import org.jetbrains.plugins.grails.tests.GrailsTestUtils;
@@ -61,7 +61,7 @@ public class GenerateTestsAction extends AnAction {
     final Module module = e.getData(PlatformCoreDataKeys.MODULE);
     if (module == null) return;
 
-    GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     if (application == null) return;
 
     final PsiClass testedClass = getTestedClass(module);
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.java
new file mode 100644
index 0000000..729cbba
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor;
+
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.ActionToolbar;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.EditorNotificationProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.JComponent;
+import java.util.function.Function;
+
+public final class GrailsEditorDecorator implements EditorNotificationProvider {
+
+  @Override
+  public @Nullable Function<? super FileEditor, ? extends JComponent> collectNotificationData(@NotNull Project project,
+                                                                                             @NotNull VirtualFile file) {
+    if (!GrailsEditorToolbar.isShowEditorToolbar()) return null;
+    if (!GrailsEditorToolbar.shouldBeDecorated(project, file)) {
+      return null;
+    }
+    return GrailsEditorDecorator::createNotificationPanel;
+  }
+
+  private static @Nullable JComponent createNotificationPanel(@NotNull FileEditor fileEditor) {
+    if (!GrailsEditorToolbar.shouldBeDecorated(fileEditor)) {
+      return null;
+    }
+    ActionManager manager = ActionManager.getInstance();
+    ActionGroup group = (ActionGroup)manager.getAction("grails.toolbar");
+    ActionToolbar toolbar = manager.createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true);
+    toolbar.setTargetComponent(fileEditor.getComponent());
+    return toolbar.getComponent();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt
deleted file mode 100644
index b190fda..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor
-
-import com.intellij.ide.actions.DistractionFreeModeController
-import com.intellij.ide.ui.UISettings
-import com.intellij.ide.util.propComponentProperty
-import com.intellij.openapi.actionSystem.ActionGroup
-import com.intellij.openapi.actionSystem.ActionManager
-import com.intellij.openapi.actionSystem.ActionPlaces
-import com.intellij.openapi.fileEditor.FileEditor
-import com.intellij.openapi.fileEditor.TextEditor
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.ui.EditorNotificationProvider
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.grails.fileType.GspFileType
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.grails.util.GrailsUtils
-import org.jetbrains.plugins.groovy.GroovyFileType
-import org.jetbrains.plugins.groovy.util.GroovyUtils
-import java.util.function.Function
-import javax.swing.JComponent
-
-internal class GrailsEditorDecorator : EditorNotificationProvider {
-  override fun collectNotificationData(project: Project, file: VirtualFile): Function<in FileEditor, out JComponent?>? {
-    if (!showEditorToolBar) return null
-    if (!shouldBeDecorated(project, file)) {
-      return null
-    }
-    return Function(::createNotificationPanel)
-  }
-
-  private fun createNotificationPanel(fileEditor: FileEditor): JComponent? {
-    if (!shouldBeDecorated(fileEditor)) {
-      return null
-    }
-    val manager = ActionManager.getInstance()
-    val group = manager.getAction("grails.toolbar") as ActionGroup
-    val toolbar = manager.createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true)
-    toolbar.setTargetComponent(fileEditor.component)
-    return toolbar.component
-  }
-}
-
-var showEditorToolBar: Boolean by propComponentProperty(name = "grails.show.editor.toolbar",
-                                                        defaultValue = System.getProperty("grails.show.editor.toolbar", "true").toBooleanStrict())
-
-private fun shouldBeDecorated(fileEditor: FileEditor): Boolean {
-  return fileEditor is TextEditor
-         && !UISettings.getInstance().presentationMode
-         && !DistractionFreeModeController.isDistractionFreeModeEnabled()
-}
-
-fun shouldBeDecorated(file: VirtualFile, fileEditor: FileEditor, project: Project): Boolean {
-  return shouldBeDecorated(fileEditor)
-         && shouldBeDecorated(project, file)
-}
-
-val DECORATED_ARTEFACT_TYPES: Array<GrailsArtifact> = arrayOf(
-  GrailsArtifact.DOMAIN,
-  GrailsArtifact.CONTROLLER,
-  GrailsArtifact.SERVICE
-)
-
-private fun shouldBeDecorated(project: Project, file: VirtualFile): Boolean {
-  val fileType = file.fileType
-  if (fileType === GroovyFileType.GROOVY_FILE_TYPE) {
-    if (file.extension == GsonConstants.EXTENSION || GrailsUtils.isInGrailsTests(file, project)) {
-      return true
-    }
-    else {
-      val classDefinition = GroovyUtils.getPublicClass(project, file)
-      val artifact = GrailsArtifact.getType(classDefinition)
-      if (DECORATED_ARTEFACT_TYPES.contains(artifact)) {
-        return true
-      }
-    }
-  }
-  else if (fileType === GspFileType.GSP_FILE_TYPE || "jsp" == file.extension) {
-    val controllerName = GrailsUtils.getControllerNameByGsp(file)
-    if (controllerName != null && controllerName != "layouts" && StringUtil.isJavaIdentifier(controllerName)) {
-      return true
-    }
-  }
-  return false
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorToolbar.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorToolbar.java
new file mode 100644
index 0000000..975d649
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorToolbar.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor;
+
+import com.intellij.diagnostic.LoadingState;
+import com.intellij.ide.actions.DistractionFreeModeController;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.TextEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.grails.fileType.GspFileType;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+import org.jetbrains.plugins.groovy.GroovyFileType;
+import org.jetbrains.plugins.groovy.util.GroovyUtils;
+
+/**
+ * Which editors get the Grails artefact toolbar, and whether the user wants it at all.
+ *
+ * @see GrailsEditorDecorator
+ * @see ViewGrailsEditorToolbarAction
+ */
+public final class GrailsEditorToolbar {
+
+  private GrailsEditorToolbar() {
+  }
+
+  /** Artefact types whose editors carry the toolbar. */
+  public static final GrailsArtifact[] DECORATED_ARTEFACT_TYPES = {
+    GrailsArtifact.DOMAIN,
+    GrailsArtifact.CONTROLLER,
+    GrailsArtifact.SERVICE
+  };
+
+  private static final String SHOW_TOOLBAR_PROPERTY = "grails.show.editor.toolbar";
+
+  private static final boolean SHOW_TOOLBAR_DEFAULT =
+    Boolean.parseBoolean(System.getProperty(SHOW_TOOLBAR_PROPERTY, "true"));
+
+  /**
+   * Mirrors what {@code propComponentProperty} did for the Kotlin {@code var} this replaces: read
+   * from the application-level {@link PropertiesComponent}, fall back to the default before
+   * components are loaded, and store nothing when the value equals the default.
+   */
+  public static boolean isShowEditorToolbar() {
+    if (!LoadingState.COMPONENTS_LOADED.isOccurred()) return SHOW_TOOLBAR_DEFAULT;
+    return PropertiesComponent.getInstance().getBoolean(SHOW_TOOLBAR_PROPERTY, SHOW_TOOLBAR_DEFAULT);
+  }
+
+  public static void setShowEditorToolbar(boolean value) {
+    PropertiesComponent.getInstance().setValue(SHOW_TOOLBAR_PROPERTY, value, SHOW_TOOLBAR_DEFAULT);
+  }
+
+  public static boolean shouldBeDecorated(@NotNull VirtualFile file,
+                                          @NotNull FileEditor fileEditor,
+                                          @NotNull Project project) {
+    return shouldBeDecorated(fileEditor) && shouldBeDecorated(project, file);
+  }
+
+  static boolean shouldBeDecorated(@NotNull FileEditor fileEditor) {
+    return fileEditor instanceof TextEditor
+           && !UISettings.getInstance().getPresentationMode()
+           && !DistractionFreeModeController.isDistractionFreeModeEnabled();
+  }
+
+  static boolean shouldBeDecorated(@NotNull Project project, @NotNull VirtualFile file) {
+    var fileType = file.getFileType();
+    if (fileType == GroovyFileType.GROOVY_FILE_TYPE) {
+      if (GsonConstants.EXTENSION.equals(file.getExtension()) || GrailsUtils.isInGrailsTests(file, project)) {
+        return true;
+      }
+      else {
+        PsiClass classDefinition = GroovyUtils.getPublicClass(project, file);
+        GrailsArtifact artifact = GrailsArtifact.getType(classDefinition);
+        for (GrailsArtifact decorated : DECORATED_ARTEFACT_TYPES) {
+          if (decorated == artifact) return true;
+        }
+      }
+    }
+    else if (fileType == GspFileType.GSP_FILE_TYPE || "jsp".equals(file.getExtension())) {
+      String controllerName = GrailsUtils.getControllerNameByGsp(file);
+      if (controllerName != null && !"layouts".equals(controllerName) && StringUtil.isJavaIdentifier(controllerName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.java
new file mode 100644
index 0000000..641f718
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor;
+
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformCoreDataKeys;
+import com.intellij.openapi.actionSystem.ToggleAction;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.EditorNotifications;
+import org.jetbrains.annotations.NotNull;
+
+public class ViewGrailsEditorToolbarAction extends ToggleAction {
+
+  @Override
+  public void update(@NotNull AnActionEvent e) {
+    super.update(e);
+    e.getPresentation().setVisible(isVisible(e));
+  }
+
+  private static boolean isVisible(@NotNull AnActionEvent e) {
+    Project project = e.getProject();
+    if (project == null) return false;
+    FileEditor fileEditor = e.getData(PlatformCoreDataKeys.FILE_EDITOR);
+    if (fileEditor == null) return false;
+    VirtualFile file = fileEditor.getFile();
+    if (file == null) return false;
+    return GrailsEditorToolbar.shouldBeDecorated(file, fileEditor, project);
+  }
+
+  @Override
+  public @NotNull ActionUpdateThread getActionUpdateThread() {
+    return ActionUpdateThread.BGT;
+  }
+
+  @Override
+  public boolean isSelected(@NotNull AnActionEvent e) {
+    return GrailsEditorToolbar.isShowEditorToolbar();
+  }
+
+  @Override
+  public void setSelected(@NotNull AnActionEvent e, boolean state) {
+    GrailsEditorToolbar.setShowEditorToolbar(state);
+    Project project = e.getProject();
+    if (project == null) return;
+    EditorNotifications.getInstance(project).updateAllNotifications();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt
deleted file mode 100644
index b5158c0..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor
-
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
-import com.intellij.openapi.actionSystem.ToggleAction
-import com.intellij.ui.EditorNotifications
-
-class ViewGrailsEditorToolbarAction : ToggleAction() {
-
-  override fun update(e: AnActionEvent) {
-    super.update(e)
-    e.presentation.isVisible = isVisible(e)
-  }
-
-  private fun isVisible(e: AnActionEvent): Boolean {
-    val project = e.project ?: return false
-    val fileEditor = e.getData(PlatformCoreDataKeys.FILE_EDITOR) ?: return false
-    val file = fileEditor.file ?: return false
-    return shouldBeDecorated(file, fileEditor, project)
-  }
-
-  override fun getActionUpdateThread(): ActionUpdateThread {
-    return ActionUpdateThread.BGT
-  }
-
-  override fun isSelected(e: AnActionEvent): Boolean {
-    return showEditorToolBar
-  }
-
-  override fun setSelected(e: AnActionEvent, state: Boolean) {
-    showEditorToolBar = state
-    val project = e.project ?: return
-    EditorNotifications.getInstance(project).updateAllNotifications()
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateActionBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateActionBase.java
new file mode 100644
index 0000000..8fa7ee2
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateActionBase.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtil;
+import org.jetbrains.plugins.grails.runner.GrailsCommandExecutor;
+import org.jetbrains.plugins.grails.runner.GrailsCommandExecutorUtil;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition;
+import org.jetbrains.plugins.groovy.mvc.MvcCommand;
+
+import javax.swing.Icon;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Runs a Grails {@code generate-*} command against the domain class the current artefact maps to,
+ * then refreshes the application root so the newly written files appear.
+ */
+public abstract class GenerateActionBase extends AnAction {
+
+  private final String myCommand;
+
+  protected GenerateActionBase(@NotNull String command, @ActionText @Nullable String text, @Nullable Icon icon) {
+    super(text, null, icon);
+    myCommand = command;
+  }
+
+  public @NotNull String getCommand() {
+    return myCommand;
+  }
+
+  @Override
+  public @NotNull ActionUpdateThread getActionUpdateThread() {
+    return ActionUpdateThread.BGT;
+  }
+
+  @Override
+  public void update(@NotNull AnActionEvent e) {
+    e.getPresentation().setEnabledAndVisible(isEnabled(e.getDataContext()));
+  }
+
+  @Override
+  public void actionPerformed(@NotNull AnActionEvent e) {
+    ArtefactData data = GrailsActionUtil.getArtefactData(e.getDataContext());
+    if (data == null) return;
+    GrClassDefinition domainClass = findDomainClass(data);
+    if (domainClass == null) return;
+    String domainClassName = domainClass.getQualifiedName();
+    if (domainClassName == null) return;
+
+    GrailsCommandExecutorUtil.execute(
+      data.getApplication(),
+      new MvcCommand(myCommand, domainClassName),
+      () -> LocalFileSystem.getInstance()
+        .refreshFiles(List.of(data.getApplication().getRoot()), true, true, () -> onDone(data)));
+  }
+
+  public boolean isEnabled(@NotNull DataContext dataContext) {
+    ArtefactData data = GrailsActionUtil.getArtefactData(dataContext);
+    if (data == null) return false;
+    return findDomainClass(data) != null && isEnabled(data);
+  }
+
+  public boolean isEnabled(@NotNull ArtefactData data) {
+    return GrailsCommandExecutor.getGrailsExecutor(data.getApplication()) != null;
+  }
+
+  public void onDone(@NotNull ArtefactData data) {
+  }
+
+  static @Nullable GrClassDefinition findDomainClass(@NotNull ArtefactData data) {
+    return single(GrailsArtifact.DOMAIN.getInstances(data.getModule(), data.getPackageName(), data.getArtefactName()));
+  }
+
+  /** Kotlin's {@code singleOrNull()}: the element only when there is exactly one. */
+  static @Nullable GrClassDefinition single(@NotNull Collection<GrClassDefinition> classes) {
+    return classes.size() == 1 ? classes.iterator().next() : null;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateAsyncControllerAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateAsyncControllerAction.java
new file mode 100644
index 0000000..6e938b3
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateAsyncControllerAction.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import org.jetbrains.plugins.grails.GrailsBundle;
+
+public class GenerateAsyncControllerAction extends GenerateControllerActionBase {
+
+  public GenerateAsyncControllerAction() {
+    super("generate-async-controller", GrailsBundle.message("action.text.generate.async.controller"));
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerAction.java
new file mode 100644
index 0000000..ce24061
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerAction.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import org.jetbrains.plugins.grails.GrailsBundle;
+
+public class GenerateControllerAction extends GenerateControllerActionBase {
+
+  public GenerateControllerAction() {
+    super("generate-controller", GrailsBundle.message("action.text.generate.controller"));
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerActionBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerActionBase.java
new file mode 100644
index 0000000..216530e
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateControllerActionBase.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition;
+
+public abstract class GenerateControllerActionBase extends GenerateActionBase {
+
+  protected GenerateControllerActionBase(@NotNull String command, @ActionText @Nullable String text) {
+    super(command, text, AllIcons.Nodes.Controller);
+  }
+
+  /** Offer generation only while the controller does not exist yet. */
+  @Override
+  public boolean isEnabled(@NotNull ArtefactData data) {
+    return findController(data) == null;
+  }
+
+  @Override
+  public void onDone(@NotNull ArtefactData data) {
+    GrClassDefinition controller = findController(data);
+    if (controller != null) controller.navigate(true);
+  }
+
+  private static @Nullable GrClassDefinition findController(@NotNull ArtefactData data) {
+    return single(GrailsArtifact.CONTROLLER.getInstances(data.getModule(), data.getPackageName(), data.getArtefactName()));
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateViewsAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateViewsAction.java
new file mode 100644
index 0000000..e3b8831
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GenerateViewsAction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import org.jetbrains.plugins.grails.GrailsBundle;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+
+public class GenerateViewsAction extends GenerateActionBase {
+
+  public GenerateViewsAction() {
+    super("generate-views", GrailsBundle.message("action.text.generate.views"), GroovyMvcIcons.Gsp_logo);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToControllerAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToControllerAction.java
new file mode 100644
index 0000000..061a598
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToControllerAction.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.util.NlsSafe;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiMethod;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+
+import java.util.Collection;
+import java.util.List;
+
+public class GoToControllerAction extends GrailsGoToArtefactActionBase {
+
+  public GoToControllerAction() {
+    super(GrailsArtifact.CONTROLLER);
+  }
+
+  @Override
+  public @NlsSafe @NotNull String getTitle(@NotNull ArtefactData artefactData) {
+    String actionName = getActionName(artefactData);
+    return super.getTitle(artefactData) + (actionName == null ? "" : ":" + actionName);
+  }
+
+  @Override
+  public void navigate(@NotNull ArtefactData artefactData, PsiClass target) {
+    // From a view, jump straight to the action that renders it rather than to the controller class.
+    PsiMethod action = GrailsUtils
+      .getControllerActions(artefactData.getArtefactName(), artefactData.getModule())
+      .get(getActionName(artefactData));
+    if (action != null) {
+      action.navigate(true);
+    }
+    else {
+      super.navigate(artefactData, target);
+    }
+  }
+
+  @Override
+  public @NotNull Collection<AnAction> createGenerateActions(@NotNull ArtefactData artefactData) {
+    return List.of(
+      ActionManager.getInstance().getAction("Grails.Controller"),
+      new GenerateControllerAction(),
+      new GenerateAsyncControllerAction()
+    );
+  }
+
+  /** The action a view belongs to, or {@code null} for a template (leading underscore). */
+  private static @Nullable String getActionName(@NotNull ArtefactData artefactData) {
+    if (!artefactData.isView()) return null;
+    String name = artefactData.getFile().getNameWithoutExtension();
+    return name.startsWith("_") ? null : name;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToDomainAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToDomainAction.java
new file mode 100644
index 0000000..6e66bda
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToDomainAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+
+import java.util.Collection;
+import java.util.List;
+
+public class GoToDomainAction extends GrailsGoToArtefactActionBase {
+
+  public GoToDomainAction() {
+    super(GrailsArtifact.DOMAIN);
+  }
+
+  @Override
+  public @NotNull Collection<AnAction> createGenerateActions(@NotNull ArtefactData artefactData) {
+    return List.of(ActionManager.getInstance().getAction("Grails.DomainClass"));
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToServiceAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToServiceAction.java
new file mode 100644
index 0000000..f2b5183
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToServiceAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+
+import java.util.Collection;
+import java.util.List;
+
+public class GoToServiceAction extends GrailsGoToArtefactActionBase {
+
+  public GoToServiceAction() {
+    super(GrailsArtifact.SERVICE);
+  }
+
+  @Override
+  public @NotNull Collection<AnAction> createGenerateActions(@NotNull ArtefactData artefactData) {
+    return List.of(ActionManager.getInstance().getAction("Grails.Service"));
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToTestAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToTestAction.java
new file mode 100644
index 0000000..d1bec41
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToTestAction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GrailsBundle;
+import org.jetbrains.plugins.grails.GroovyMvcIcons;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.editor.GenerateTestsAction;
+import org.jetbrains.plugins.grails.editor.GrailsEditorToolbar;
+import org.jetbrains.plugins.grails.tests.GrailsTestUtils;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class GoToTestAction extends GrailsToolbarVfileAction {
+
+  @Override
+  public boolean isOpenSingle() {
+    return false;
+  }
+
+  @Override
+  public @ActionText @NotNull String getTitle(@NotNull ArtefactData artefactData) {
+    return GrailsBundle.message("action.text.go.to.tests", StringUtil.capitalize(artefactData.getArtefactName()));
+  }
+
+  @Override
+  public @NotNull Collection<VirtualFile> getNavigateTargets(@NotNull ArtefactData artefactData) {
+    List<VirtualFile> result = new ArrayList<>();
+
+    for (GrailsArtifact artefactType : GrailsEditorToolbar.DECORATED_ARTEFACT_TYPES) {
+      for (GrClassDefinition artifact : artefactType.getInstances(artefactData.getModule(),
+                                                                 artefactData.getPackageName(),
+                                                                 artefactData.getArtefactName())) {
+        for (PsiClass testClass : GrailsTestUtils.getTestsForArtifact(artifact, true)) {
+          ContainerUtil.addIfNotNull(result, testClass.getContainingFile().getVirtualFile());
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public @NotNull Collection<AnAction> createGenerateActions(@NotNull ArtefactData artefactData) {
+    String capitalized = StringUtil.capitalize(artefactData.getArtefactName());
+    List<AnAction> actions = List.of(
+      generateTests(false, artefactData, GrailsArtifact.DOMAIN,
+                    GrailsBundle.message("action.text.generate.tests.unit", capitalized)),
+      generateTests(true, artefactData, GrailsArtifact.DOMAIN,
+                    GrailsBundle.message("action.text.generate.tests.integration", capitalized)),
+      generateTests(false, artefactData, GrailsArtifact.CONTROLLER,
+                    GrailsBundle.message("action.text.generate.controller.tests.unit", capitalized)),
+      generateTests(true, artefactData, GrailsArtifact.CONTROLLER,
+                    GrailsBundle.message("action.text.generate.controller.tests.integration", capitalized))
+    );
+    for (AnAction action : actions) {
+      action.getTemplatePresentation().setIcon(GroovyMvcIcons.Grails_test);
+    }
+    return actions;
+  }
+
+  private static @NotNull GenerateTestsAction generateTests(boolean integration,
+                                                            @NotNull ArtefactData artefactData,
+                                                            @NotNull GrailsArtifact artifactType,
+                                                            @ActionText String text) {
+    GenerateTestsAction action = new GenerateTestsAction(integration, artefactData.getArtefactName(), artifactType);
+    action.getTemplatePresentation().setText(text);
+    return action;
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToViewAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToViewAction.java
new file mode 100644
index 0000000..84b8e5f
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GoToViewAction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.grails.GrailsBundle;
+import org.jetbrains.plugins.grails.GsonConstants;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.structure.GrailsApplication;
+import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.grails.util.GrailsUtils;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class GoToViewAction extends GrailsToolbarVfileAction {
+
+  @Override
+  public boolean isOpenSingle() {
+    return false;
+  }
+
+  @Override
+  public @ActionText @NotNull String getTitle(@NotNull ArtefactData artefactData) {
+    return GrailsBundle.message("action.text.go.to.views", StringUtil.capitalize(artefactData.getArtefactName()));
+  }
+
+  @Override
+  public @NotNull Collection<VirtualFile> getNavigateTargets(@NotNull ArtefactData artefactData) {
+    // Views for an artefact may live in a different module than the artefact itself (multi-project
+    // build): e.g. a service in an upstream project whose views are defined by a downstream app.
+    // Search every Grails application belonging to the related-module set (the same set artefact
+    // instance discovery uses) rather than only this artefact's own application.
+    Set<Module> related = GrailsArtifact.getRelatedModules(artefactData.getModule());
+    ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(artefactData.getProject());
+    Set<VirtualFile> result = new LinkedHashSet<>();
+    for (GrailsApplication application : GrailsApplicationManager.getInstance(artefactData.getProject()).getApplications()) {
+      if (!related.contains(fileIndex.getModuleForFile(application.getAppRoot()))) continue;
+      VirtualFile views = application.getAppRoot().findChild(GrailsUtils.VIEWS_DIRECTORY);
+      if (views == null) continue;
+      VirtualFile viewDir = views.findChild(artefactData.getArtefactName());
+      if (viewDir == null) continue;
+      VirtualFile[] children = viewDir.getChildren();
+      if (children == null) continue;
+      for (VirtualFile child : children) {
+        CharSequence name = child.getNameSequence();
+        if (StringUtil.endsWith(name, ".gsp")
+            || StringUtil.endsWith(name, ".jsp")
+            || StringUtil.endsWith(name, GsonConstants.FILE_SUFFIX)) {
+          result.add(child);
+        }
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public @NotNull Collection<AnAction> createGenerateActions(@NotNull ArtefactData artefactData) {
+    return List.of(new GenerateViewsAction());
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt
deleted file mode 100644
index 2fc6a89..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor.toolbar
-
-import com.intellij.icons.AllIcons
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.util.NlsActions.ActionText
-import com.intellij.openapi.vfs.LocalFileSystem
-import org.jetbrains.plugins.grails.GrailsBundle
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.actions.ArtefactData
-import org.jetbrains.plugins.grails.actions.getArtefactData
-import org.jetbrains.plugins.grails.runner.GrailsCommandExecutor
-import org.jetbrains.plugins.grails.runner.GrailsCommandExecutorUtil
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.groovy.mvc.MvcCommand
-import javax.swing.Icon
-
-abstract class GenerateActionBase(val command: String, @ActionText text: String?, icon: Icon?) : AnAction(text, null, icon) {
-
-  override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
-
-  override fun update(e: AnActionEvent) {
-    e.presentation.isEnabledAndVisible = isEnabled(e.dataContext)
-  }
-
-  override fun actionPerformed(e: AnActionEvent) {
-    val data = getArtefactData(e.dataContext) ?: return
-    val domainClass = GrailsArtifact.DOMAIN.getInstances(data.module, data.packageName, data.artefactName).singleOrNull() ?: return
-    val domainClassName = domainClass.qualifiedName ?: return
-
-    GrailsCommandExecutorUtil.execute(data.application, MvcCommand(command, domainClassName)) {
-      LocalFileSystem.getInstance().refreshFiles(listOf(data.application.root), true, true) {
-        onDone(data)
-      }
-    }
-  }
-
-  open fun isEnabled(dataContext: DataContext): Boolean {
-    val data = getArtefactData(dataContext) ?: return false
-    val domainClass = GrailsArtifact.DOMAIN.getInstances(data.module, data.packageName, data.artefactName).singleOrNull()
-    return domainClass != null && isEnabled(data)
-  }
-
-  open fun isEnabled(data: ArtefactData): Boolean = GrailsCommandExecutor.getGrailsExecutor(data.application) != null
-
-  open fun onDone(data: ArtefactData): Unit = Unit
-}
-
-abstract class GenerateControllerActionBase(command: String, @ActionText text: String?)
-  : GenerateActionBase(command, text, AllIcons.Nodes.Controller) {
-
-  override fun isEnabled(data: ArtefactData): Boolean = GrailsArtifact.CONTROLLER.getInstances(
-    data.module, data.packageName, data.artefactName
-  ).singleOrNull() == null
-
-  override fun onDone(data: ArtefactData) {
-    GrailsArtifact.CONTROLLER.getInstances(data.module, data.packageName, data.artefactName).singleOrNull()?.navigate(true)
-  }
-}
-
-class GenerateControllerAction : GenerateControllerActionBase("generate-controller",
-                                                              GrailsBundle.message("action.text.generate.controller"))
-
-class GenerateAsyncControllerAction : GenerateControllerActionBase("generate-async-controller",
-                                                                   GrailsBundle.message("action.text.generate.async.controller"))
-
-class GenerateViewsAction : GenerateActionBase("generate-views",
-                                               GrailsBundle.message("action.text.generate.views"),
-                                               GroovyMvcIcons.Gsp_logo)
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.java
new file mode 100644
index 0000000..62d2d97
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.util.NlsActions.ActionText;
+import com.intellij.openapi.util.NlsSafe;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiClass;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.GrailsBundle;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition;
+
+import javax.swing.Icon;
+import java.util.Collection;
+
+public abstract class GrailsGoToArtefactActionBase extends GrailsToolbarTargetAction<PsiClass> {
+
+  private final GrailsArtifact myArtefactType;
+
+  protected GrailsGoToArtefactActionBase(GrailsArtifact artefactType) {
+    myArtefactType = artefactType;
+  }
+
+  protected final GrailsArtifact getArtefactType() {
+    return myArtefactType;
+  }
+
+  @Override
+  public @NlsSafe @NotNull String getTitle(@NotNull ArtefactData artefactData) {
+    return StringUtil.capitalize(artefactData.getArtefactName()) + myArtefactType.suffix;
+  }
+
+  @Override
+  public @NotNull Collection<GrClassDefinition> getNavigateTargets(@NotNull ArtefactData artefactData) {
+    // Prefer an artefact in the same package as the current one, but fall back to matching by name
+    // alone: in multi-project builds a shared artefact (e.g. a domain in an upstream project) often
+    // lives in a different package than the controller/service that uses it.
+    Collection<GrClassDefinition> samePackage =
+      myArtefactType.getInstances(artefactData.getModule(), artefactData.getPackageName(), artefactData.getArtefactName());
+    return samePackage.isEmpty()
+           ? myArtefactType.getInstances(artefactData.getModule(), artefactData.getArtefactName())
+           : samePackage;
+  }
+
+  @Override
+  public @ActionText @NotNull String getNavigateTitle(PsiClass target) {
+    return GrailsBundle.message("action.text.go.to.artefact", target.getName());
+  }
+
+  @Override
+  public @Nullable Icon getNavigateIcon(PsiClass target) {
+    return myArtefactType.getIcon();
+  }
+
+  @Override
+  public void navigate(@NotNull ArtefactData artefactData, PsiClass target) {
+    target.navigate(true);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
deleted file mode 100644
index 092d4d0..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor.toolbar
-
-import com.intellij.openapi.util.NlsActions.ActionText
-import com.intellij.openapi.util.NlsSafe
-import com.intellij.psi.PsiClass
-import org.jetbrains.plugins.grails.GrailsBundle
-import org.jetbrains.plugins.grails.actions.ArtefactData
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrClassDefinition
-import javax.swing.Icon
-
-abstract class GrailsGoToArtefactActionBase(private val artefactType: GrailsArtifact) : GrailsToolbarTargetAction<PsiClass>() {
-
-  @NlsSafe override fun getTitle(artefactData: ArtefactData): String {
-    return artefactData.artefactName.replaceFirstChar { it.uppercaseChar() } + artefactType.suffix
-  }
-
-  override fun getNavigateTargets(artefactData: ArtefactData): Collection<GrClassDefinition> {
-    // Prefer an artefact in the same package as the current one, but fall back to matching by name
-    // alone: in multi-project builds a shared artefact (e.g. a domain in an upstream project) often
-    // lives in a different package than the controller/service that uses it.
-    val samePackage = artefactType.getInstances(artefactData.module, artefactData.packageName, artefactData.artefactName)
-    return samePackage.ifEmpty { artefactType.getInstances(artefactData.module, artefactData.artefactName) }
-  }
-
-
-  @ActionText override fun getNavigateTitle(target: PsiClass): String =
-    GrailsBundle.message("action.text.go.to.artefact", target.name)
-
-  override fun getNavigateIcon(target: PsiClass): Icon? = artefactType.icon
-
-  override fun navigate(artefactData: ArtefactData, target: PsiClass): Unit = target.navigate(true)
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.java
new file mode 100644
index 0000000..9859c51
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Separator;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class GrailsToolbarActionBase extends ActionGroup {
+
+  @Override
+  public @NotNull ActionUpdateThread getActionUpdateThread() {
+    return ActionUpdateThread.BGT;
+  }
+
+  @Override
+  public boolean displayTextInToolbar() {
+    return true;
+  }
+
+  @Override
+  public void update(@NotNull AnActionEvent e) {
+    ArtefactData data = GrailsActionUtil.getArtefactData(e.getDataContext());
+    e.getPresentation().setText(data == null ? getTemplateText() : getTitle(data));
+    e.getPresentation().setPerformGroup(isOpenSingle() && data != null && createNavigateActions(data).size() == 1);
+    e.getPresentation().setPopupGroup(true);
+  }
+
+  @Override
+  public void actionPerformed(@NotNull AnActionEvent e) {
+    assert isOpenSingle();
+    ArtefactData data = GrailsActionUtil.getArtefactData(e.getDataContext());
+    if (data == null) return;
+    Collection<AnAction> actions = createNavigateActions(data);
+    if (actions.size() == 1) {
+      actions.iterator().next().actionPerformed(e);
+    }
+  }
+
+  @Override
+  public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) {
+    ArtefactData data = GrailsActionUtil.getArtefactData(e == null ? null : e.getDataContext());
+    if (data == null) return AnAction.EMPTY_ARRAY;
+    List<AnAction> children = new ArrayList<>(createNavigateActions(data));
+    children.add(new Separator());
+    children.addAll(createGenerateActions(data));
+    return children.toArray(AnAction.EMPTY_ARRAY);
+  }
+
+  public boolean isOpenSingle() {
+    return true;
+  }
+
+  public abstract @ActionText @Nullable String getTitle(@NotNull ArtefactData artefactData);
+
+  public abstract @NotNull Collection<AnAction> createNavigateActions(@NotNull ArtefactData artefactData);
+
+  public @NotNull Collection<AnAction> createGenerateActions(@NotNull ArtefactData artefactData) {
+    return Collections.emptyList();
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt
deleted file mode 100644
index 6486aae..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor.toolbar
-
-import com.intellij.openapi.actionSystem.ActionGroup
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.Separator
-import com.intellij.openapi.util.NlsActions.ActionText
-import org.jetbrains.plugins.grails.actions.ArtefactData
-import org.jetbrains.plugins.grails.actions.getArtefactData
-
-abstract class GrailsToolbarActionBase : ActionGroup() {
-
-  override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
-  override fun displayTextInToolbar(): Boolean = true
-
-  override fun update(e: AnActionEvent) {
-    val data = getArtefactData(e.dataContext)
-    e.presentation.text = data?.let { getTitle(it) } ?: templateText
-    e.presentation.isPerformGroup = isOpenSingle() && data != null && createNavigateActions(data).size == 1
-    e.presentation.isPopupGroup = true
-  }
-
-  override fun actionPerformed(e: AnActionEvent) {
-    assert(isOpenSingle())
-    val data = getArtefactData(e.dataContext) ?: return
-    createNavigateActions(data).singleOrNull()?.actionPerformed(e)
-  }
-
-  override fun getChildren(e: AnActionEvent?): Array<AnAction> = getArtefactData(e?.dataContext)?.let {
-    createNavigateActions(it) + Separator() + createGenerateActions(it)
-  }?.toTypedArray() ?: emptyArray()
-
-
-  open fun isOpenSingle(): Boolean = true
-
-  @ActionText abstract fun getTitle(artefactData: ArtefactData): String?
-
-  abstract fun createNavigateActions(artefactData: ArtefactData): Collection<AnAction>
-
-  open fun createGenerateActions(artefactData: ArtefactData): Collection<AnAction> = emptyList()
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt
deleted file mode 100644
index 79e25a4..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor.toolbar
-
-import com.intellij.openapi.actionSystem.ActionManager
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.util.NlsActions.ActionText
-import com.intellij.openapi.roots.ProjectFileIndex
-import com.intellij.openapi.util.NlsSafe
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.PsiClass
-import com.intellij.util.containers.ContainerUtil
-import org.jetbrains.plugins.grails.GrailsBundle
-import org.jetbrains.plugins.grails.GroovyMvcIcons
-import org.jetbrains.plugins.grails.GsonConstants
-import org.jetbrains.plugins.grails.actions.ArtefactData
-import org.jetbrains.plugins.grails.editor.DECORATED_ARTEFACT_TYPES
-import org.jetbrains.plugins.grails.editor.GenerateTestsAction
-import org.jetbrains.plugins.grails.structure.GrailsApplicationManager
-import org.jetbrains.plugins.grails.tests.GrailsTestUtils
-import org.jetbrains.plugins.grails.util.GrailsArtifact
-import org.jetbrains.plugins.grails.util.GrailsUtils
-
-class GoToDomainAction : GrailsGoToArtefactActionBase(GrailsArtifact.DOMAIN) {
-  override fun createGenerateActions(artefactData: ArtefactData): List<AnAction> = listOf(
-    ActionManager.getInstance().getAction("Grails.DomainClass")
-  )
-}
-
-class GoToServiceAction : GrailsGoToArtefactActionBase(GrailsArtifact.SERVICE) {
-  override fun createGenerateActions(artefactData: ArtefactData): List<AnAction> = listOf(
-    ActionManager.getInstance().getAction("Grails.Service")
-  )
-}
-
-class GoToControllerAction : GrailsGoToArtefactActionBase(GrailsArtifact.CONTROLLER) {
-
-  @NlsSafe override fun getTitle(artefactData: ArtefactData): String =
-      super.getTitle(artefactData) + (getActionName(artefactData)?.let { ":$it" } ?: "")
-
-  override fun navigate(artefactData: ArtefactData, target: PsiClass): Unit = GrailsUtils.getControllerActions(
-    artefactData.artefactName, artefactData.module
-  )[getActionName(artefactData)]?.navigate(true) ?: super.navigate(artefactData, target)
-
-  override fun createGenerateActions(artefactData: ArtefactData): List<AnAction> = listOf(
-    ActionManager.getInstance().getAction("Grails.Controller"),
-    GenerateControllerAction(),
-    GenerateAsyncControllerAction()
-  )
-
-  private fun getActionName(artefactData: ArtefactData) = if (artefactData.isView) {
-    artefactData.file.nameWithoutExtension.let { if (it.startsWith("_")) null else it }
-  }
-  else {
-    null
-  }
-}
-
-class GoToViewAction : GrailsToolbarVfileAction() {
-
-  override fun isOpenSingle(): Boolean = false
-
-  @ActionText override fun getTitle(artefactData: ArtefactData): String =
-    GrailsBundle.message("action.text.go.to.views", artefactData.artefactName.replaceFirstChar { it.uppercaseChar() })
-
-  override fun getNavigateTargets(artefactData: ArtefactData): Collection<VirtualFile> {
-    // Views for an artefact may live in a different module than the artefact itself (multi-project
-    // build): e.g. a service in an upstream project whose views are defined by a downstream app.
-    // Search every Grails application belonging to the related-module set (the same set artefact
-    // instance discovery uses) rather than only this artefact's own application.
-    val related = GrailsArtifact.getRelatedModules(artefactData.module)
-    val fileIndex = ProjectFileIndex.getInstance(artefactData.project)
-    val result = LinkedHashSet<VirtualFile>()
-    for (application in GrailsApplicationManager.getInstance(artefactData.project).applications) {
-      if (fileIndex.getModuleForFile(application.appRoot) !in related) continue
-      val viewDir = application.appRoot
-        .findChild(GrailsUtils.VIEWS_DIRECTORY)
-        ?.findChild(artefactData.artefactName) ?: continue
-      viewDir.children?.filterTo(result) {
-        val name = it.nameSequence
-        name.endsWith(".gsp") || name.endsWith(".jsp") || name.endsWith(GsonConstants.FILE_SUFFIX)
-      }
-    }
-    return result
-  }
-
-
-  override fun createGenerateActions(artefactData: ArtefactData): Collection<AnAction> = listOf(
-    GenerateViewsAction()
-  )
-}
-
-class GoToTestAction : GrailsToolbarVfileAction() {
-
-  override fun isOpenSingle(): Boolean = false
-
-  @ActionText override fun getTitle(artefactData: ArtefactData): String =
-    GrailsBundle.message("action.text.go.to.tests", artefactData.artefactName.replaceFirstChar { it.uppercaseChar() })
-
-  override fun getNavigateTargets(artefactData: ArtefactData): Collection<VirtualFile> {
-    val result = mutableListOf<VirtualFile>()
-
-    for (artefactType in DECORATED_ARTEFACT_TYPES) {
-      for (artifact in artefactType.getInstances(artefactData.module, artefactData.packageName, artefactData.artefactName)) {
-        for (testClass in GrailsTestUtils.getTestsForArtifact(artifact, true)) {
-          ContainerUtil.addIfNotNull(result, testClass.containingFile.virtualFile)
-        }
-      }
-    }
-
-    return result
-  }
-
-  override fun createGenerateActions(artefactData: ArtefactData): Collection<AnAction> = artefactData.artefactName.replaceFirstChar { it.uppercaseChar() }.let {
-    listOf(
-      GenerateTestsAction(false, artefactData.artefactName, GrailsArtifact.DOMAIN).apply {
-        templatePresentation.text = GrailsBundle.message("action.text.generate.tests.unit", it)
-      },
-      GenerateTestsAction(true, artefactData.artefactName, GrailsArtifact.DOMAIN).apply {
-        templatePresentation.text = GrailsBundle.message("action.text.generate.tests.integration", it)
-      },
-      GenerateTestsAction(false, artefactData.artefactName, GrailsArtifact.CONTROLLER).apply {
-        templatePresentation.text = GrailsBundle.message("action.text.generate.controller.tests.unit", it)
-      },
-      GenerateTestsAction(true, artefactData.artefactName, GrailsArtifact.CONTROLLER).apply {
-        templatePresentation.text = GrailsBundle.message("action.text.generate.controller.tests.integration", it)
-      }
-    ).apply {
-      forEach { it.templatePresentation.icon = GroovyMvcIcons.Grails_test }
-    }
-  }
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.java
new file mode 100644
index 0000000..2762803
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+
+import javax.swing.Icon;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class constructs navigate actions from targets.
+ * It does not care about type of navigatable because actual navigate logic is delegated to inheritors.
+ *
+ * @param <T> type of target, e.g. PsiClass or VirtualFile.
+ */
+public abstract class GrailsToolbarTargetAction<T> extends GrailsToolbarActionBase {
+
+  @Override
+  public @NotNull Collection<AnAction> createNavigateActions(@NotNull ArtefactData artefactData) {
+    Collection<? extends T> targets = tryGetNavigateTargets(artefactData);
+    List<AnAction> actions = new ArrayList<>(targets.size());
+    for (T target : targets) {
+      actions.add(new AnAction(getNavigateTitle(target), null, getNavigateIcon(target)) {
+        @Override
+        public void actionPerformed(@NotNull AnActionEvent e) {
+          navigate(artefactData, target);
+        }
+      });
+    }
+    return actions;
+  }
+
+  public abstract @NotNull Collection<? extends T> getNavigateTargets(@NotNull ArtefactData artefactData);
+
+  public abstract @ActionText @Nullable String getNavigateTitle(T target);
+
+  public abstract @Nullable Icon getNavigateIcon(T target);
+
+  public abstract void navigate(@NotNull ArtefactData artefactData, T target);
+
+  private @NotNull Collection<? extends T> tryGetNavigateTargets(@NotNull ArtefactData artefactData) {
+    return DumbService.isDumb(artefactData.getProject())
+           ? Collections.emptyList()
+           : getNavigateTargets(artefactData);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt
deleted file mode 100644
index ed3e748..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor.toolbar
-
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.project.DumbService
-import com.intellij.openapi.util.NlsActions.ActionText
-import org.jetbrains.plugins.grails.actions.ArtefactData
-import javax.swing.Icon
-
-/**
- * This class constructs navigate actions from targets.
- * It does not care about type of navigatable because actual navigate logic is delegated to inheritors.
- *
- * @param T type of target, e.g. PsiClass or VirtualFile.
- */
-abstract class GrailsToolbarTargetAction<T> : GrailsToolbarActionBase() {
-
-  override fun createNavigateActions(artefactData: ArtefactData): Collection<AnAction> = tryGetNavigateTargets(artefactData).map {
-    object : AnAction(getNavigateTitle(it), null, getNavigateIcon(it)) {
-      override fun actionPerformed(e: AnActionEvent) = navigate(artefactData, it)
-    }
-  }
-
-  abstract fun getNavigateTargets(artefactData: ArtefactData): Collection<T>
-
-  @ActionText abstract fun getNavigateTitle(target: T): String?
-
-  abstract fun getNavigateIcon(target: T): Icon?
-
-  abstract fun navigate(artefactData: ArtefactData, target: T): Unit
-
-  private fun tryGetNavigateTargets(artefactData: ArtefactData): Collection<T> =
-      if (DumbService.isDumb(artefactData.project)) emptyList()
-      else getNavigateTargets(artefactData)
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.java
new file mode 100644
index 0000000..a4e07d2
--- /dev/null
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.grails.editor.toolbar;
+
+import com.intellij.ide.presentation.VirtualFilePresentation;
+import com.intellij.ide.util.PsiNavigationSupport;
+import com.intellij.openapi.util.NlsActions.ActionText;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.grails.actions.ArtefactData;
+
+import javax.swing.Icon;
+
+public abstract class GrailsToolbarVfileAction extends GrailsToolbarTargetAction<VirtualFile> {
+
+  @Override
+  public @ActionText @NotNull String getNavigateTitle(VirtualFile target) {
+    return target.getName();
+  }
+
+  @Override
+  public @Nullable Icon getNavigateIcon(VirtualFile target) {
+    return VirtualFilePresentation.getIcon(target);
+  }
+
+  @Override
+  public void navigate(@NotNull ArtefactData artefactData, VirtualFile target) {
+    PsiNavigationSupport.getInstance().createNavigatable(artefactData.getProject(), target, -1).navigate(true);
+  }
+}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt
deleted file mode 100644
index 3ff4f5e..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.editor.toolbar
-
-import com.intellij.ide.presentation.VirtualFilePresentation
-import com.intellij.ide.util.PsiNavigationSupport
-import com.intellij.openapi.util.NlsActions.ActionText
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.plugins.grails.actions.ArtefactData
-import javax.swing.Icon
-
-abstract class GrailsToolbarVfileAction : GrailsToolbarTargetAction<VirtualFile>() {
-
-  @ActionText override fun getNavigateTitle(target: VirtualFile): String = target.name
-
-  override fun getNavigateIcon(target: VirtualFile): Icon? = VirtualFilePresentation.getIcon(target)
-
-  override fun navigate(artefactData: ArtefactData, target: VirtualFile): Unit = PsiNavigationSupport.getInstance().createNavigatable(artefactData.project, target, -1).navigate(true)
-}
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
index 1a97ecf..db0b21f 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
@@ -22,7 +22,7 @@ import com.intellij.ide.util.treeView.AbstractTreeNode;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataSink;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.plugins.grails.actions.GrailsActionUtilKt;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtil;
 import org.jetbrains.plugins.grails.artefact.api.GrailsArtefactHandler;
 import org.jetbrains.plugins.grails.projectView.nodes.GrailsApplicationNode;
 import org.jetbrains.plugins.grails.projectView.nodes.GrailsArtefactHandlerNode;
@@ -56,9 +56,9 @@ public final class GrailsTreeStructureProvider implements TreeStructureProvider
       }
       return null;
     });
-    sink.lazy(GrailsActionUtilKt.getGRAILS_APPLICATION(), () -> TreeNodeUtil.findValueOfType(single, GrailsApplication.class));
-    sink.lazy(GrailsActionUtilKt.getGRAILS_ARTEFACT_HANDLER(), () -> TreeNodeUtil.findValueOfType(single, GrailsArtefactHandler.class));
-    sink.lazy(GrailsActionUtilKt.getGRAILS_ARTEFACT_PACKAGE(),
+    sink.lazy(GrailsActionUtil.GRAILS_APPLICATION, () -> TreeNodeUtil.findValueOfType(single, GrailsApplication.class));
+    sink.lazy(GrailsActionUtil.GRAILS_ARTEFACT_HANDLER, () -> TreeNodeUtil.findValueOfType(single, GrailsArtefactHandler.class));
+    sink.lazy(GrailsActionUtil.GRAILS_ARTEFACT_PACKAGE,
               () -> single instanceof GrailsArtefactPackageNode<?> packageNode ? packageNode.getPackageFqn() : null);
   }
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java
index ee8f3f0..c2c658b 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java
@@ -26,7 +26,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiElement;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.grails.actions.GrailsActionUtilKt;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtil;
 import org.jetbrains.plugins.grails.config.GrailsFramework;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
 import org.jetbrains.plugins.grails.util.version.Version;
@@ -45,7 +45,7 @@ final class GrailsScriptRunConfigurationProducer extends LazyRunConfigurationPro
   protected boolean setupConfigurationFromContext(@NotNull GrailsRunConfiguration configuration,
                                                   @NotNull ConfigurationContext context,
                                                   @NotNull Ref<PsiElement> sourceElement) {
-    GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(context.getDataContext());
+    GrailsApplication application = GrailsActionUtil.getGrailsApplication(context.getDataContext());
     if (application == null) return false;
     if (application.getGrailsVersion().isAtLeast(Version.GRAILS_6_0)) return false;
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
index 4e3c617..72fcb57 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
@@ -75,13 +75,35 @@ public final class GrailsTestUtils {
 
   private GrailsTestUtils() { }
 
+  /**
+   * Grails 3 dropped the per-suffix test naming conventions in favour of a single {@code ...Spec}
+   * class next to the artefact, so it needs none of the short-name searching below.
+   */
+  private static void collectGrails3TestsForArtifact(@NotNull GrailsApplication application,
+                                                    @NotNull PsiClass artefact,
+                                                    @NotNull Collection<? super PsiClass> result) {
+    Module module = ModuleUtilCore.findModuleForPsiElement(artefact);
+    if (module == null) return;
+
+    String qualifiedName = artefact.getQualifiedName();
+    if (qualifiedName == null) return;
+    String packageName = StringUtil.getPackageName(qualifiedName);
+    String shortName = StringUtil.getShortName(qualifiedName);
+    String unitTestFqn = StringUtil.getQualifiedName(packageName, shortName + "Spec");
+
+    GlobalSearchScope scope = GlobalSearchScope.moduleWithDependentsScope(module);
+    PsiClass clazz = JavaPsiFacade.getInstance(application.getProject()).findClass(unitTestFqn, scope);
+    if (clazz == null) return;
+    result.add(clazz);
+  }
+
   public static Collection<PsiClass> getTestsForArtifact(@NotNull PsiClass psiClass, boolean searchTestForAnnotation) {
     final GrailsApplication application = GrailsApplicationManager.findApplication(psiClass);
     if (application == null) return Collections.emptyList();
 
     if (application.getGrailsVersion().isAtLeast(Version.GRAILS_3_0)) {
       Collection<PsiClass> result = new SmartList<>();
-      GrailsTestUtilsKt.getTestsForArtifact(application, psiClass, result);
+      collectGrails3TestsForArtifact(application, psiClass, result);
       return result;
     }
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt
deleted file mode 100644
index f60aa0c..0000000
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jetbrains.plugins.grails.tests
-
-import com.intellij.openapi.module.ModuleUtil
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiClass
-import com.intellij.psi.search.GlobalSearchScope
-import org.jetbrains.plugins.grails.structure.GrailsApplication
-
-fun getTestsForArtifact(application: GrailsApplication, artefact: PsiClass, result: MutableCollection<in PsiClass>): Unit {
-  val module = ModuleUtil.findModuleForPsiElement(artefact) ?: return
-
-  val qualifiedName = artefact.qualifiedName ?: return
-  val packageName = StringUtil.getPackageName(qualifiedName)
-  val shortName = StringUtil.getShortName(qualifiedName)
-  val unitTestFqn = StringUtil.getQualifiedName(packageName, shortName + "Spec")
-
-  val scope = GlobalSearchScope.moduleWithDependentsScope(module)
-  val clazz = JavaPsiFacade.getInstance(application.project).findClass(unitTestFqn, scope) ?: return
-  result.add(clazz)
-}
\ No newline at end of file
diff --git a/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java
index d96abb6..5780674 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java
@@ -27,7 +27,7 @@ import com.intellij.openapi.wm.StatusBar;
 import com.intellij.openapi.wm.WindowManager;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.grails.GrailsBundle;
-import org.jetbrains.plugins.grails.actions.GrailsActionUtilKt;
+import org.jetbrains.plugins.grails.actions.GrailsActionUtil;
 import org.jetbrains.plugins.grails.runner.GrailsCommandExecutor;
 import org.jetbrains.plugins.grails.runner.GrailsConsole;
 import org.jetbrains.plugins.grails.structure.GrailsApplication;
@@ -46,7 +46,7 @@ public class MvcPluginsAction extends AnAction {
 
   @Override
   public void update(@NotNull AnActionEvent e) {
-    GrailsApplication application = GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    GrailsApplication application = GrailsActionUtil.getGrailsApplication(e.getDataContext());
     e.getPresentation().setEnabledAndVisible(
       application instanceof OldGrailsApplication &&
       application.getGrailsVersion().isLessThan(Version.GRAILS_2_3_0) &&
@@ -61,7 +61,7 @@ public class MvcPluginsAction extends AnAction {
 
   @Override
   public void actionPerformed(@NotNull AnActionEvent e) {
-    OldGrailsApplication application = (OldGrailsApplication)GrailsActionUtilKt.getGrailsApplication(e.getDataContext());
+    OldGrailsApplication application = (OldGrailsApplication)GrailsActionUtil.getGrailsApplication(e.getDataContext());
     assert application != null;
     final Project project = application.getProject();
 


(grails-intellij-plugin) 32/35: Stop GSP taglib lookup throwing IndexNotReadyException during indexing

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit c97f99a330ec5171e3282ab2f622c050f71b5c12
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 15:57:58 2026 -0400

    Stop GSP taglib lookup throwing IndexNotReadyException during indexing
    
    Opening a GSP before Gradle import finished threw IndexNotReadyException. The
    platform's XML machinery calls XmlTag.getNamespace() from the daemon (in the
    report, via the XSLT plugin's UnusedElementInspection), which reaches
    GspXmlRootTagImpl.getNamespaceByPrefix -> GspTagLibUtil -> GrailsArtifact and
    queried the stub index while dumb.
    
    The interesting part is where it actually breaks. Discovering taglib classes
    from grails-app/taglib/ is a plain directory scan and needs no index, so the
    obvious guard around the index-based lookups is not enough: resolving a found
    class's `namespace` field goes through getPrefixByTagLibClass ->
    findCodeFieldByName, which resolves supertypes and hits JavaFullClassNameIndex.
    My first attempt guarded only the lookups and the test still reproduced the
    crash on that path. So computeCustomTaglibClasses now returns just the default
    "g" prefix while dumb, which keeps core GSP editing working, and the two
    lookups (GrailsArtifact.collectFromPluginXmls and doSearchTaglibsInClassPath)
    are guarded too since the toolbar Go-To actions reach them directly — that is
    what was being clicked when this fired.
    
    Every cache in the chain (GspXmlRootTagImpl.getNamespaceData,
    GspTagLibUtil.getTagLibClasses, searchTaglibsInClassPath,
    GrailsArtifact.getCache) additionally depends on the DumbService modification
    tracker, so a degraded result cannot outlive indexing.
    
    The new test reproduces the reported stack (it failed with exactly that
    exception before the fix) and asserts recovery afterwards. It asserts recovery
    end-to-end rather than isolating the tracker: the fixture bumps
    PsiModificationTracker often enough that it still passes with the tracker
    removed, which I verified. The tracker is kept regardless, because indexing
    finishing does not inherently modify PSI, so in a real IDE it is the only
    dependency guaranteed to change.
    
    Closes the "dumb-mode safety of the heavier contributors" item in
    IMPROVEMENT-PLAN Phase 1 for this path.
    
    Verified: 989 tests / 0 failures (187 classes, two new); rat clean.
---
 IMPROVEMENT-PLAN.md                                |  25 +-
 MIGRATION-PLAN.md                                  | 425 ++++++---------------
 .../gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java   |   7 +-
 .../lang/gsp/resolve/taglib/GspTagLibUtil.java     |  25 +-
 .../plugins/grails/util/GrailsArtifact.java        |  15 +-
 .../groovy/grails/gsp/GspTagLibDumbModeTest.java   |  95 +++++
 6 files changed, 269 insertions(+), 323 deletions(-)

diff --git a/IMPROVEMENT-PLAN.md b/IMPROVEMENT-PLAN.md
index fbca14d..331368f 100644
--- a/IMPROVEMENT-PLAN.md
+++ b/IMPROVEMENT-PLAN.md
@@ -18,9 +18,17 @@ the phases below target the **new** plugin; the legacy plugin receives only Phas
 > (artifact renames, gradle plugin ids), Phase 0 must verify against a real 7.0.x app
 > — the facts below are authoritative for 8.x and directionally right for 7.x.
 
-## What we have today (surveyed 2026-07-07)
-
-- ~635 main sources (525 Java, 110 Kotlin) + 11 submodules; 184 test classes.
+## What we have today (updated 2026-07-26)
+
+- 709 main sources, of which **19 are Kotlin** — 3 in `plugin/` and 16 vendored under
+  `libs/testFramework/`. Everything else was converted to Java. 188 test files; the suite
+  is 987 tests across 186 classes.
+- Composed Gradle build (Groovy DSL) with 12 subprojects in tier directories —
+  `plugin/`, `pluginModules/*`, `libs/*`, `compilers/*` — plus `build-logic` as an
+  included build holding every convention plugin. See
+  [AGENTS.md](AGENTS.md#project-structure).
+- Platform 2026.2 (`sinceBuild=262`), IntelliJ Platform Gradle Plugin 2.18.1, version
+  `262.0.0`.
 - Strong core: full GSP language (lexer/parser/PSI, formatting, folding, completion,
   HTML/Groovy/CSS/JS injection), 20+ Groovy member contributors (GORM criteria, named
   queries, constraints), artefact handlers, URL-mapping references, Spring bean
@@ -28,9 +36,16 @@ the phases below target the **new** plugin; the legacy plugin receives only Phas
 - Dual-era structure model: `Grails2Application`/`OldGrails*` (BuildConfig.groovy,
   Gant) alongside `Grails3*` (Gradle-based).
 - Era artifacts: `pluginSupport/` for Searchable, Shiro, WebFlow, Resources (dead);
-  `testdata/` mock Grails **1.x** JARs.
+  `plugin/testdata/` mock Grails **1.x** JARs.
 - Ultimate-only (`com.intellij.modules.ultimate` + javaee/persistence/jsp/spring/
-  database deps). Targets platform 2025.3, IntelliJ Platform Gradle Plugin 2.11.0.
+  database deps).
+
+> **Kotlin cannot be dropped from the build yet.** The 3 files in `plugin/` are genuine
+> rewrite-blockers: `TraitInjectorService.kt` uses coroutines and `suspend fun`;
+> `GrailsForgeModuleBuilder.kt` and `proxySettings.kt` use the Kotlin UI DSL, which has no
+> Java equivalent. The 16 under `libs/testFramework/` are vendored copies of platform
+> test-framework sources, so rewriting them would make future re-syncs painful. Both sets
+> are deliberate; only those two projects still apply the Kotlin convention plugin.
 
 ---
 
diff --git a/MIGRATION-PLAN.md b/MIGRATION-PLAN.md
index 9c4d3aa..db8cecd 100644
--- a/MIGRATION-PLAN.md
+++ b/MIGRATION-PLAN.md
@@ -6,79 +6,73 @@ Actions for building and publishing.
 
 Unlike prior plugin migrations, this repo **stays standalone** — it cannot merge into the
 grails-core monorepo because it builds against the IntelliJ Platform Gradle Plugin
-(`org.jetbrains.intellij.platform` 2.11.0), not the Grails BOM/shared-gradle world. We
-therefore *port* the grails-core compliance/CI conventions here rather than rewiring onto
-shared config. The `mono-repo-integration` skill's principles still apply where relevant
+(`org.jetbrains.intellij.platform`), not the Grails BOM/shared-gradle world. We therefore
+*port* the grails-core compliance/CI conventions here rather than rewiring onto shared
+config. The `mono-repo-integration` skill's principles still apply where relevant
 (license headers, RAT excludes for templates/test data, CI gating publish on tests, no
 blind deletion of imported infra).
 
-## Current state (surveyed 2026-07-06)
+**Status: Phases 1–7 are complete** (see "Completed work" below). What remains is Phase 8
+(the parts that need a real GitHub runner and a PMC vote) and Phase 9 (blocked on the
+software grant). For the current build layout see [AGENTS.md](AGENTS.md#project-structure);
+for the release procedure see [RELEASE.md](RELEASE.md).
 
-- **Build**: Gradle Kotlin DSL, root plugin + 11 subprojects (`copyright`, `coverage`,
-  `gradle-tooling`, `grails-compiler-patch`, `grails-rt`, `hibernate`, `i18n`,
-  `jps-plugin`, `langInjection`, `maven`, `testFramework`). IntelliJ Platform Gradle
-  Plugin 2.11.0, Kotlin 2.3.0, platform 2025.3.1 (`sinceBuild=253`), Java 21 (8/11 for the
-  runtime/JPS legacy modules). `pluginVersion=253.0.0`.
+## State at import (surveyed 2026-07-06)
+
+Kept as the record of what was inherited — this is **not** current state.
+
+- **Build**: Gradle Kotlin DSL, root plugin + 11 flat subprojects. IntelliJ Platform
+  Gradle Plugin 2.11.0, Kotlin 2.3.0, platform 2025.3.1 (`sinceBuild=253`), Java 21 (8/11
+  for the runtime/JPS legacy modules). `pluginVersion=253.0.0`.
 - **Identity**: plugin id `org.intellij.grails`, vendor **JetBrains**, packages
   `org.jetbrains.plugins.grails`.
-- **Licensing**: `LICENSE.txt` = Apache-2.0 full text. **No NOTICE.** UPDATED
-  (2026-07-18): the ~909 source files that carried the abbreviated JetBrains one-line
-  header (`Copyright 2000-20xx JetBrains s.r.o. and contributors. Use of this source
-  code is governed by the Apache 2.0 license.`) now carry the **full standard Apache 2.0
-  license header** (the license Appendix boilerplate) with the JetBrains copyright line
-  preserved — *not* the ASF "Licensed to the Apache Software Foundation" header. ~44
-  files still have no header (testdata fixtures, `.ft`/`.gsp` templates, properties,
-  XML). `gen/` is generated code; `testdata/` contains mock Grails 1.x JARs (test
-  fixtures, not shipped).
+- **Licensing**: `LICENSE.txt` = Apache-2.0 full text, **no NOTICE**, ~909 sources
+  carrying the abbreviated JetBrains one-line header (`Copyright 2000-20xx JetBrains
+  s.r.o. and contributors. Use of this source code is governed by the Apache 2.0
+  license.`).
 - **CI/publishing**: none. No `.github/`, no `.asf.yaml`, no publish or signing config.
 
-## Open decisions (resolve before/while executing)
+## Decisions of record
+
+Numbering is stable — later phases cite these by number.
 
 1. **Header strategy for JetBrains-copyright files.** RESOLVED (2026-07-18): **no
-   software grant / IP clearance is recorded yet**, so the licensing terms in the
-   per-file headers must not be changed — the files stay attributed to JetBrains and
-   must NOT receive the ASF "Licensed to the Apache Software Foundation" header until a
-   grant is provided. What we did instead: expanded every abbreviated JetBrains header
-   to the full standard Apache 2.0 license header (Appendix boilerplate) while keeping
-   the `Copyright 2000-20xx JetBrains s.r.o. and contributors.` line. This satisfies
-   RAT's stock Apache-2.0 detection with no custom matcher. The wholesale ASF-header
-   replacement (former "option A") moves to Phase 9 (post-grant follow-up) and is
-   blocked on the software grant being recorded with the ASF Secretary.
-2. **Plugin id & Marketplace listing.** RESOLVED (2026-07-06): JetBrains has already
-   handed the Marketplace listing over. The plugin id `org.intellij.grails` is permanent —
-   Marketplace ids cannot be changed on an existing listing, and the IDE matches updates
-   by id, so a new id would orphan the entire install base and break `<depends>` in
-   third-party plugins. Keep the id forever; update the listing's name/vendor to Apache.
-   AMENDED (2026-07-07): under the two-plugin strategy below, the id and existing listing
-   stay with the **legacy** plugin line; the new Grails 7+ plugin gets a fresh Apache id.
-   The Java packages (`org.jetbrains.plugins.grails.*`) CAN be renamed (nothing external
-   keys on them), but it means renaming every class reference in plugin.xml/module XMLs,
-   resets FQN-keyed persisted state for users, and forces stub-index version bumps — and
-   ASF policy does not require it (cf. Apache Groovy keeping `org.codehaus.groovy`). If
-   wanted, do it as a dedicated follow-up effort after this migration, never mixed into
-   the compliance commits.
-3. **Develocity / TestLens**: grails-core CI wires `DEVELOCITY_ACCESS_KEY` and TestLens.
-   Decide whether this repo joins the same Develocity instance; if not, drop those steps.
-4. **Snapshot distribution**: attach the plugin ZIP as a workflow artifact on every main
-   build (simplest, recommended), and/or publish to a Marketplace `eap`/`snapshot`
-   channel.
-5. **Versioning**: keep the `<platform-branch>.x.y` scheme (`253.0.0`) or align with a
-   Grails-style scheme. Marketplace convention favors the platform-branch scheme — keep it.
-6. **Maven publishing** RESOLVED (2026-07-18): **none**. The plugin ZIP is never
-   consumed from Maven Central/ASF Nexus, so there is no Maven/Nexus publishing at all.
-   Distribution channels are: the GitHub release assets, `dist.apache.org` dev → release
-   via svn as part of the PMC vote, and the JetBrains Marketplace convenience binary.
-   A trial adoption of the Grails Publish plugin was reverted as unnecessary; two rough
-   edges found during that trial are candidate fixes on the grails-gradle-publish **1.x
-   branch** if it is ever adopted here: (a) it requires `project.version` at plugin apply
-   time, and (b) its pom generation is not configuration-cache compatible. Kept from the
-   trial: the project version now uses the standard `version` property in
-   gradle.properties (was `pluginVersion`); workflows pass `-Pversion=`.
+   software grant / IP clearance is recorded yet**, so per-file licensing terms must not
+   change. Files stay attributed to JetBrains and must NOT receive the ASF "Licensed to
+   the Apache Software Foundation" header until a grant is provided. Instead every
+   abbreviated JetBrains header was expanded to the full standard Apache 2.0 header
+   (Appendix boilerplate) keeping the `Copyright 2000-20xx JetBrains s.r.o. and
+   contributors.` line, which satisfies RAT's stock Apache-2.0 detection with no custom
+   matcher. Wholesale ASF-header replacement is Phase 9, blocked on the grant.
+2. **Plugin id & Marketplace listing.** RESOLVED (2026-07-06): the listing is already
+   transferred. `org.intellij.grails` is permanent — Marketplace ids cannot change on an
+   existing listing and the IDE matches updates by id, so a new id would orphan the
+   install base and break third-party `<depends>`. AMENDED (2026-07-07): under the
+   two-plugin strategy the id and existing listing stay with the **legacy** line; the new
+   Grails 7+ plugin gets a fresh Apache id. Java packages CAN be renamed (nothing
+   external keys on them) but ASF policy does not require it (cf. Apache Groovy keeping
+   `org.codehaus.groovy`); see Phase 3 of `IMPROVEMENT-PLAN.md`.
+3. **Develocity / TestLens.** RESOLVED: **not adopted.** grails-core wires
+   `DEVELOCITY_ACCESS_KEY` and TestLens; those steps are dropped here, so CI needs no
+   Develocity secret.
+4. **Snapshot distribution.** RESOLVED: the plugin ZIP is attached as a **workflow
+   artifact** on every build (`gradle.yml`). No Marketplace `eap`/`snapshot` channel
+   publishing, so CI performs no publishing at all.
+5. **Versioning.** RESOLVED: keep the `<platform-branch>.x.y` scheme (currently
+   `262.0.0`), which matches Marketplace convention.
+6. **Maven publishing.** RESOLVED (2026-07-18): **none.** The plugin ZIP is never
+   consumed from Maven Central/ASF Nexus. Distribution is: GitHub release assets,
+   `dist.apache.org` dev → release via svn as part of the PMC vote, and the JetBrains
+   Marketplace convenience binary. A trial of the Grails Publish plugin was reverted as
+   unnecessary; two rough edges found are candidate fixes on the grails-gradle-publish
+   **1.x branch** if ever adopted here: (a) it requires `project.version` at plugin apply
+   time, (b) its pom generation is not configuration-cache compatible. Kept from the
+   trial: the project version uses the standard `version` property in gradle.properties
+   (was `pluginVersion`); workflows pass `-Pversion=`.
 7. **Signing keys** (added 2026-07-18): the existing Grails release GPG key (from the
-   project `KEYS` file) covers all ASF signing — source zip, convenience binary, Maven
-   `.asc` files. JetBrains Marketplace plugin signing is a separate mechanism: it
-   requires an X.509 certificate chain + RSA private key (PEM), not PGP, so a dedicated
-   key/cert pair must be generated (per JetBrains docs) and stored as the
+   project `KEYS` file) covers all ASF signing. JetBrains Marketplace plugin signing is a
+   separate mechanism — X.509 certificate chain + RSA private key (PEM), not PGP — so a
+   dedicated key/cert pair must be generated per JetBrains docs and stored as the
    `CERTIFICATE_CHAIN`/`PRIVATE_KEY`/`PRIVATE_KEY_PASSWORD` secrets.
 
 ---
@@ -129,189 +123,33 @@ feasible; the constraints and the plan:
   publishes) and platform bumps twice a year on the legacy line. Mitigated by the
   maintenance-only policy and shared workflow templates.
 
-## Phase 1 — ASF repo metadata
-
-DONE (2026-07-18): committed as "Add ASF repository metadata" — NOTICE, HEADER,
-LICENSE rename, `.asf.yaml` (release environment + branch protection + notifications),
-README.md. Also added later: `INSTALL` + `.sdkmanrc` + `gradle-bootstrap/` (source
-distro ships without the wrapper jar; `cd gradle-bootstrap && gradle bootstrap`
-regenerates it pinned to `.sdkmanrc`, grails-core's pattern).
-
-- **`NOTICE`** (new, required by policy). Model on grails-core's:
-
-  ```
-  Apache Grails IntelliJ Plugin
-  Copyright 2025-2026 The Apache Software Foundation
-
-  This product includes software developed at
-  The Apache Software Foundation (http://www.apache.org/).
-
-  This product includes software originally developed by JetBrains s.r.o.
-  and contributors (https://www.jetbrains.com/).
-  Copyright 2000-2026 JetBrains s.r.o. and contributors.
-  Licensed under the Apache License, Version 2.0.
-  ```
-
-  Do **not** say "donated to the ASF" until the software grant is recorded; add that
-  wording (confirmed against the grant text) in Phase 9.
-- **`HEADER`** (new): copy grails-core's root `HEADER` file verbatim — the canonical ASF
-  header text ("Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements…"). Pre-grant it applies to **new files only**
-  (decision 1); post-grant it becomes the single source of truth for the wholesale
-  replacement in Phase 9.
-- **`LICENSE`**: rename `LICENSE.txt` → `LICENSE` (grails-core convention). Create a
-  `licenses/` directory only if/when non-Apache third-party assets are identified (none
-  found so far — icons are JetBrains-authored and covered by the grant; testdata JARs are
-  Apache-2.0 Grails/Spring artifacts and are excluded from the source distro anyway —
-  see Phase 3 decision on testdata JARs).
-- **`.asf.yaml`**: adapt grails-core's — `description`, `homepage:
-  https://grails.apache.org/`, notifications to `commits@grails.apache.org` /
-  `notifications@grails.apache.org`, default-branch protection ruleset
-  (restrict deletion/force-push), and a `release` deployment environment with the same
-  required reviewers (jdaugherty, matrei, jamesfredley, sbglasius) to gate the release
-  workflow.
-- **`README.md`** (new): what the plugin is, supported IntelliJ versions, build
-  instructions, ASF links (no release history section, per grails-core docs convention).
-- Optional: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` pointers to the main project.
-
-## Phase 2 — License headers (pre-grant form)
-
-One commit: "ASF Compliance - license headers". **Constraint (decision 1): until a
-software grant is recorded, headers keep JetBrains attribution and standard Apache 2.0
-terms — no ASF licensing language on JetBrains-authored files.**
-
-- DONE (2026-07-18, uncommitted): mechanically expanded every abbreviated JetBrains
-  header (the `//`, `/* ... */`, `<!-- -->`, and `#` one-line forms, any year range,
-  ~909 files) to the full standard Apache 2.0 header (Appendix boilerplate:
-  "Licensed under the Apache License, Version 2.0 …") with the original
-  `Copyright 2000-20xx JetBrains s.r.o. and contributors.` line and year range
-  preserved. Files that already carried the full old-style (2007/2008) JetBrains Apache
-  header were left untouched; a duplicated embedded header in `GspUtil.java` was
-  de-duplicated. The expansion script lives in the session scratchpad — commit a copy to
-  `etc/bin/expand-jetbrains-headers.py` for auditability.
-- DONE (2026-07-18, uncommitted): headered the previously-unheadered JetBrains-authored
-  files that RAT flags — grails-rt runtime sources/scripts, `GrailsStandardDSL.gdsl`,
-  the non-generated `META-INF/*.xml` descriptors, `liveTemplates/gsp.xml`, `grails.tld`,
-  bundle/log4j/gradle `.properties`, and the JPS services file — all with the same
-  JetBrains-attributed Apache 2.0 header. Files that are RAT-excluded (testdata
-  fixtures, templates, forms, description HTML, `MANIFEST.MF`) were left alone.
-  Genuinely new ASF-authored files get the canonical ASF header.
-- **New files** authored after the import (by ICLA-covered contributors) use the
-  canonical ASF header from the root `HEADER` file — no grant needed for new
-  contributions.
-- Do **not** header: `gen/**` (generated), `testdata/**` fixtures whose content *is* the
-  test input (adding headers can break parser/position-sensitive tests — same reason
-  grails-core excludes test resources), file templates under `resources/fileTemplates/**`
-  (they materialize inside end-user projects — grails-core's "templates people are
-  expected to use" rule), `.form` GUI-builder files (machine-edited XML), inspection/
-  intention description HTML (grails-core excludes `**/*.html`).
-- Verify with a grep audit: every non-excluded source file contains either the full
-  Apache 2.0 header (JetBrains-attributed) or the ASF header (new files).
-
-## Phase 3 — RAT verification in the build
-
-One commit: "ASF Compliance - RAT license audit".
-
-Because Phase 2 expanded the headers to the standard Apache 2.0 boilerplate, RAT's
-stock license matcher recognizes them — **no custom JetBrains matcher is needed**.
-
-DONE (2026-07-18, uncommitted): `org.nosphere.apache.rat` 0.8.1 added to the root
-`plugins {}` block with a kts `tasks.rat { excludes... }` config (grails-core's shape,
-translated; every exclude justified in a comment; `outputs.upToDateWhen { false }`).
-`./gradlew rat` passes: 927 files audited, 0 unknown licenses. Notable repo-specific
-exclude: `.intellijPlatform/**` (IntelliJ Platform Gradle Plugin's local cache of
-generated Ivy descriptors). Remaining details below for reference.
-
-- Add the `org.nosphere.apache.rat` plugin (grails-core pins **0.8.1** via
-  `apacheRatVersion` on the buildSrc classpath; this repo has no buildSrc, so declare
-  `id("org.nosphere.apache.rat") version "0.8.1"` in the root `plugins {}` block) and port
-  grails-core's `gradle/rat-root-config.gradle` shape (this repo is Kotlin DSL; either
-  keep a Groovy `gradle/rat-root-config.gradle` applied via `apply(from = ...)` — closest
-  to grails-core — or translate to kts).
-- Excludes tailored to this repo (mirroring grails-core's categories):
-  - generated: `gen/**`
-  - test fixtures: `testdata/**` (includes the mock Grails JARs; binary anyway)
-  - templates shipped into user projects: `resources/fileTemplates/**`,
-    `resources/liveTemplates/**` (verify), `**/*.ft`, template `.gsp`
-  - images: `**/*.png`, `**/*.svg`, `**/*.ico`
-  - html/json descriptions: `**/*.html`, `**/spring-configuration-metadata.json`-style
-    JSON (JSON can't carry headers)
-  - infra: `.asf.yaml`, `.github/**`, `**/.gitignore`, `gradlew*`, `**/wrapper/**`,
-    `.idea/**`, `**/*.iml`, `out/**`, `build/**` + per-subproject build dirs (use the
-    same `rootProject.subprojects.collect{...}` trick), `**/*.form` if not headered
-  - `MIGRATION-PLAN.md` (this file) until deleted
-- Set `outputs.upToDateWhen { false }` (never cache audits), like grails-core.
-- Iterate `./gradlew rat` until clean; every exclude must carry a justification comment
-  (grails-core style) — no blanket excludes to make it pass.
-- Decision to record: whether the `testdata/**/*.jar` binaries may remain in the
-  **source release** at all. ASF source releases must not contain compiled code without
-  justification; likely fine as clearly-labeled test fixtures, but flag it for the PMC
-  and document in the RAT config comment. Alternative: fetch them at test time.
-
-## Phase 4 — Identity & conventions cleanup
-
-DONE (2026-07-18): committed as "Update plugin vendor and Gradle groups to Apache" —
-vendor is Apache Software Foundation (url grails.apache.org), Gradle groups are
-`org.apache.grails.intellij*`. Plugin id and extension-point namespaces stay
-`org.intellij.grails` per decision 2. Package renames remain out of scope.
-
-- `plugin.xml`: `vendor` → Apache Software Foundation (Grails), vendor URL
-  `https://grails.apache.org/`, plugin description/links updated; keep plugin id per
-  decision 2.
-- Gradle `group` coordinates: `org.intellij.grails` → `org.apache.grails.intellij` (or
-  similar) for the module groups — these are internal (nothing publishes to Maven), so
-  this is low-risk; record the rename in the repo README. Package renames
-  (`org.jetbrains.plugins.grails` → `org.apache...`) are **out of scope** for this
-  migration (huge churn, breaks settings/serialized state and plugin.xml extension
-  points); revisit later if the PMC wants it.
-- Apply the grails-core best-practice conventions that translate: 4-space indent, no
-  wildcard imports, Apache header on every **new** file. CodeNarc does not apply (Java/
-  Kotlin codebase); consider ktlint/detekt later, not in this migration.
-
-## Phase 5 — GitHub Actions: build & verification CI
-
-DONE (2026-07-18): committed as "Add CI workflows for build, verification, and RAT" —
-`gradle.yml` (build+test with plugin ZIP artifact, Plugin Verifier job with
-`recommended()` IDEs) and `rat.yml` (grails-core's, adapted to `main`, Develocity/
-TestLens dropped per decision 3). All actions pinned to grails-core's SHAs. The
-`test.idea.home.path` property is now only applied when the path exists, so `check`
-runs on CI. NOT yet validated on a real GitHub runner — first PR will shake it out.
-
-One commit: "ASF Compliance - CI workflows". Follow grails-core's patterns: all actions
-**pinned to full commit SHAs**, `concurrency` group per workflow+ref, publish gated on
-test jobs via `needs:` **and** `if:` result guards.
-
-- **`.github/workflows/gradle.yml`** — on push/PR to `main` (+ `workflow_dispatch`):
-  - `build`: checkout (`actions/checkout` @ pinned v6.0.2), `actions/setup-java`
-    (liberica, 21), `gradle/actions/setup-gradle` @ pinned v6.1.0 with
-    `cache-provider: basic` (the MIT-licensed provider — grails-core deliberately avoids
-    the proprietary default) and Develocity key per decision 3; run
-    `./gradlew check buildPlugin`.
-  - `verify`: `./gradlew verifyPlugin` (IntelliJ Plugin Verifier against the
-    `sinceBuild`/target IDE matrix — configure `pluginVerification.ides` in the build to
-    cover 2025.3 and latest EAP; this is the IntelliJ-world analog of grails-core's
-    functional matrix and the skill's "reproduce every test axis" rule).
-  - `snapshot` (main-branch pushes only): upload the `buildPlugin` ZIP as a workflow
-    artifact; optionally `publishPlugin` to a Marketplace snapshot channel (decision 4).
-    `needs: [build, verify]` **and** `if:` guards on both results — publishing must be
-    blocked when tests fail.
-- **`.github/workflows/rat.yml`** — copy grails-core's nearly verbatim (adapt branch
-  filter to `main`): run `./gradlew rat`, upload the HTML report artifact, inject the
-  report into the job summary.
-- Later/optional, mirroring grails-core: `codeql.yml`, dependabot config.
-
-## Phase 6 — GitHub Actions: ASF release & Marketplace publishing
-
-DONE (2026-07-18, initial draft): committed as "Add release workflow and Marketplace
-signing config" (Maven/Nexus staging later removed per decision 6). `release.yml` jobs:
-`publish` (check/buildPlugin/signPlugin, GPG-sign + sha512, attach ZIP to GitHub
-release), `source` (src zip without wrapper/.github, GPG-sign + sha512), `upload` (svn
-to dist/dev for the vote), `release` (environment-gated: manual dev->release promotion
-via `.github/scripts/releaseDistributions.sh` run locally by the RM, `publishPlugin` to
-Marketplace, reporter/email reminders). Secrets needed:
-GRAILS_GPG_KEY, GPG_KEY_ID, SVC_DIST_GRAILS_USERNAME/PASSWORD, CERTIFICATE_CHAIN,
-PRIVATE_KEY, PRIVATE_KEY_PASSWORD, PUBLISH_TOKEN. Needs a dry-run against a test tag
-before first use (Phase 8).
+## Completed work (Phases 1–7)
+
+All seven landed between 2026-07-18 and the restructure. The how-to prose has been removed;
+what follows is the record of what exists, with the live detail now kept in the files
+themselves.
+
+| Phase | Landed as | What exists now |
+|---|---|---|
+| 1 — ASF repo metadata | "Add ASF repository metadata" | `NOTICE`, `HEADER`, `LICENSE` (renamed from `LICENSE.txt`), `.asf.yaml` (release environment, branch protection, notifications), `README.md`. Later: `INSTALL`, `.sdkmanrc`, `gradle-bootstrap/` so the source distro can regenerate the wrapper (`cd gradle-bootstrap && gradle bootstrap`). |
+| 2 — License headers (pre-grant) | "ASF Compliance - license headers" | ~909 abbreviated JetBrains headers expanded to the full Apache 2.0 boilerplate with the JetBrains copyright preserved, per decision 1. Previously-unheadered JetBrains files headered too. Not headered, by design: `gen/**`, `testdata/**`, `fileTemplates/**`, `.form`, inspection-description HTML. |
+| 3 — RAT verification | "ASF Compliance - RAT license audit" | `org.nosphere.apache.rat` 0.8.1, configured in the `rat` convention plugin with every exclude justified inline and audits never cached. `./gradlew rat` clean. |
+| 4 — Identity & conventions | "Update plugin vendor and Gradle groups to Apache" | Vendor = Apache Software Foundation (grails.apache.org); Gradle groups `org.apache.grails.intellij*`. Plugin id and EP namespaces stay `org.intellij.grails` per decision 2. |
+| 5 — Build & verification CI | "Add CI workflows for build, verification, and RAT" | `gradle.yml` (build + test + plugin-ZIP artifact; Plugin Verifier job) and `rat.yml`. Also since added: `coverage.yml`, `codeql.yml`, `vulnerability-scan.yml`. All actions pinned to full SHAs. |
+| 6 — Release & Marketplace publishing | "Add release workflow and Marketplace signing config" | `release.yml` (`publish` → `source` → `upload` → `release`), `release-abort.yml`, checked-in vote/result/announcement templates under `.github/vote_templates/`. Nexus staging removed per decision 6. |
+| 7 — Artifact compliance | — | `LICENSE`/`NOTICE` packaged into the plugin jar's `META-INF` (verified in the built ZIP). Reproducible-build pinning and the `etc/bin/verify*.sh` release-verification scripts, listed as Phase 7 stretch goals, are also done — see [RELEASE.md](RELEASE.md). CycloneDX SBOM remains **not** done. |
+
+Still open from Phase 7's stretch list:
+
+- [ ] CycloneDX SBOM with a `LICENSE_MAPPING` for transitives (grails-core parity). Not a
+      blocker for a first compliant release.
+
+Still open from Phase 3:
+
+- [ ] PMC decision to record: whether the `testdata/**/*.jar` binaries may remain in the
+      **source release**. ASF source releases should not carry compiled code without
+      justification; they are clearly-labeled test fixtures, but flag it for the PMC.
+      Alternative: fetch them at test time (see `IMPROVEMENT-PLAN.md` 2.7).
 
 ### Secrets chart (all used by `release.yml` only; CI needs none)
 
@@ -331,68 +169,42 @@ repository variables (`vars.*`) are required. Secrets for ASF repos are provisio
 an INFRA Jira ticket (or inherited if already defined at the `apache` org level for
 Grails, as grails-core's are).
 
-One commit: "ASF Compliance - release workflow". Modeled on grails-core's `release.yml`
-and the `apache/grails-github-actions` composite actions, adapted because the deliverable
-is a plugin ZIP, not Maven artifacts (no Nexus staging at all):
-
-ASF policy framing: the **official release is the signed source archive**, voted on by
-the PMC and published to `dist.apache.org`; the plugin ZIP on JetBrains Marketplace is a
-convenience binary.
-
-- Trigger: GitHub `release` event (tag `vX.Y.Z`), same as grails-core.
-- Jobs:
-  1. `publish` — build: `apache/grails-github-actions/pre-release@asf` (sets release
-     version), `./gradlew buildPlugin`, JetBrains `signPlugin` (Marketplace code-signing
-     chain: `CERTIFICATE_CHAIN`, `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD` secrets), attach
-     ZIP to the GitHub release (`softprops/action-gh-release` @ pinned SHA).
-  2. `source` — produce the source distribution (git-archive based zip excluding
-     `.github` etc.), generate `sha512`, GPG-sign (`.asc`) with the release manager's key
-     from the Grails `KEYS` file.
-  3. `upload` — `needs: [publish, source]`; svn-commit source + convenience artifacts to
-     `dist.apache.org/repos/dist/dev/grails/` for the vote.
-  4. `release` — `environment: release` (required reviewers from `.asf.yaml`; this is the
-     post-vote gate): move dev → `dist/release`, then `./gradlew publishPlugin` to
-     JetBrains Marketplace (`PUBLISH_TOKEN` / `ORG_GRADLE_PROJECT_...PublishingToken`
-     secret — the listing is already transferred, so this just needs a token minted
-     under the ASF-controlled Marketplace account and stored as a repo secret).
-  5. `close` — `apache/grails-github-actions/post-release@asf` (bump to next snapshot,
-     housekeeping).
-- Secrets to provision (INFRA/PMC): GPG signing key material, `GRAILS_GHTOKEN`-style
-  token if needed, JetBrains Marketplace token + code-signing certs, Develocity key
-  (optional).
-- Also add `release-abort`/`release-notes` style helpers later if useful; not required
-  for the first release.
-
-## Phase 7 — Artifact compliance
-
-DONE (2026-07-18): LICENSE/NOTICE are packaged into the plugin jar META-INF via
-`processResources` (verified in the built ZIP). Stretch goals below remain open.
-
-- Ensure `LICENSE` and `NOTICE` are packaged **inside the plugin ZIP / jar META-INF**
-  (grails-core gets this from its shared build-logic plugins; here add a small Gradle
-  convention — `processResources`/`buildPlugin` copy spec — in the root build).
-- Optional stretch goals, ported from grails-core if the PMC wants parity: CycloneDX
-  SBOM (`SbomPlugin.groovy`-style `LICENSE_MAPPING` for transitives), reproducible-build
-  config (stable timestamps/ordering in the ZIP), `verify.sh`-style release verification
-  scripts. Not blockers for the first compliant release.
+
 
 ## Phase 8 — Verify
 
-- [ ] `./gradlew rat` passes locally and in CI; every exclude justified in a comment.
-- [ ] Header audit grep: no non-excluded source file lacks the full Apache 2.0 header
-      (JetBrains-attributed on imported files, ASF on new files).
-- [ ] No imported file carries ASF licensing language (grep for "Licensed to the Apache
-      Software Foundation" must only hit files authored post-import) — guard until the
-      software grant is recorded.
-- [ ] `NOTICE` + `LICENSE` present at root **and** inside the built plugin ZIP.
-- [ ] `.asf.yaml` valid (notifications, branch protection, release environment).
-- [ ] `./gradlew check buildPlugin verifyPlugin` green in CI on a PR.
-- [ ] Snapshot publish job gated on build+verify via `needs:` and `if:` result guards.
-- [ ] All workflow actions pinned to commit SHAs.
+Verified locally (2026-07-26):
+
+- [x] `./gradlew rat` passes; every exclude carries an inline justification.
+- [x] Header audit: every non-excluded source carries the full Apache 2.0 header. The 38
+      files without one are all under `plugin/testdata/`, which Phase 2 deliberately does
+      not header and RAT excludes.
+- [x] No imported file carries ASF licensing language. The 28 hits for "Licensed to the
+      Apache Software Foundation" are all post-import files — `build-logic/**`,
+      `gradle-bootstrap/`, and seven sources authored in this repo — so the pre-grant
+      guard holds.
+- [x] `NOTICE` + `LICENSE` present at root and inside the built plugin ZIP (in the
+      composed jar's `META-INF`).
+- [x] All workflow actions pinned to full commit SHAs.
+- [x] `./gradlew check buildPlugin verifyPlugin` green locally.
+
+Cannot be closed without a real runner, a tag, or the PMC:
+
+- [ ] `./gradlew check buildPlugin verifyPlugin` and `rat` green **in CI on a PR** —
+      nothing has been pushed yet, so no workflow has ever executed on a GitHub runner.
+      This is the single largest untested area of the migration.
+- [ ] `.asf.yaml` accepted by INFRA (notifications, branch protection, release
+      environment). Structurally complete but never applied.
 - [ ] Dry-run the release workflow to a test tag (skip the `release` environment job).
 - [ ] PMC sign-off on: pre-grant header posture (JetBrains-attributed Apache 2.0),
       plugin id/Marketplace transfer, testdata JARs in the source release.
 
+Dropped as no longer applicable:
+
+- ~~Snapshot publish job gated on build+verify via `needs:`/`if:`~~ — decision 4 settled on
+  a workflow artifact rather than channel publishing, so CI publishes nothing and there is
+  no job to gate.
+
 ## Phase 9 — Post-grant follow-up (BLOCKED until software grant recorded)
 
 Do **not** start any of this until the JetBrains software grant is recorded with the
@@ -408,16 +220,3 @@ non-incubating PMC receiving a codebase).
   files; the JetBrains-attributed form should no longer appear.
 - Revisit RAT config: no changes expected (both header forms are Apache-2.0 to RAT),
   but re-run the audit after the replacement.
-
-## Suggested commit sequence
-
-1. `ASF Compliance - license headers` (expand JetBrains headers to full Apache 2.0 —
-   done in working tree + commit the `etc/bin` expansion script)
-2. `ASF Compliance - repository metadata` (NOTICE, LICENSE rename, .asf.yaml, README,
-   HEADER)
-3. `ASF Compliance - RAT license audit` (rat plugin + config, passing)
-4. `ASF Compliance - plugin identity` (vendor, groups)
-5. `ASF Compliance - CI workflows` (gradle.yml, rat.yml)
-6. `ASF Compliance - release workflow` (release.yml, packaging LICENSE/NOTICE)
-7. *(post-grant, Phase 9)* `ASF Compliance - ASF license headers` (replacement + NOTICE
-   update)
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java
index 637d5dd..a7e5cfe 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java
@@ -20,6 +20,7 @@ import com.intellij.jsp.JspManager;
 import com.intellij.jsp.impl.TldDescriptor;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.FileViewProvider;
@@ -109,7 +110,11 @@ public class GspXmlRootTagImpl extends GspXmlTagBaseImpl implements GspXmlRootTa
 
   private NamespaceData getNamespaceData() {
     return CachedValuesManager.getCachedValue(this, () -> Result.create(
-      new NamespaceData(this), PsiModificationTracker.MODIFICATION_COUNT
+      new NamespaceData(this),
+      PsiModificationTracker.MODIFICATION_COUNT,
+      // NamespaceData is built from the index-derived taglib set, which is intentionally partial
+      // during indexing; drop it when indexing ends so prefixes resolve fully afterwards.
+      DumbService.getInstance(getProject()).getModificationTracker()
     ));
   }
 
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java
index 1216373..a9b694f 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java
@@ -18,6 +18,7 @@ package org.jetbrains.plugins.grails.lang.gsp.resolve.taglib;
 
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
@@ -103,7 +104,10 @@ public final class GspTagLibUtil {
       module,
       () -> CachedValueProvider.Result.create(
         computeCustomTaglibClasses(module),
-        PsiModificationTracker.MODIFICATION_COUNT
+        PsiModificationTracker.MODIFICATION_COUNT,
+        // The taglib set is index-derived and degrades in dumb mode; expire it once indexing ends
+        // so a partial namespace map cannot outlive indexing.
+        DumbService.getInstance(module.getProject()).getModificationTracker()
       )
     );
   }
@@ -113,6 +117,16 @@ public final class GspTagLibUtil {
 
     res.put(DEFAULT_TAGLIB_PREFIX, new TagLibNamespaceDescriptor(DEFAULT_TAGLIB_PREFIX, module)); // Maps key set must  contain default prefix!!!
 
+    // Everything past this point needs indexes. Discovering the taglib classes is one thing, but
+    // even reading a found class's `namespace` field goes through getPrefixByTagLibClass ->
+    // findCodeFieldByName, which resolves supertypes and therefore queries the stub index. In dumb
+    // mode that throws IndexNotReadyException, and this method sits under XmlTag.getNamespace(),
+    // which the daemon calls on any GSP - so it must return something rather than fail.
+    //
+    // Degrading to the default "g" prefix keeps core GSP editing working while indexing; the
+    // custom prefixes appear when getTagLibClasses() recomputes on the DumbService tracker.
+    if (DumbService.isDumb(module.getProject())) return res;
+
     final Collection<GrClassDefinition> taglibs = new LinkedHashSet<>();
     taglibs.addAll(GrailsArtifact.TAGLIB.getInstances(module).values());
     taglibs.addAll(searchTaglibsInClassPath(module));
@@ -158,12 +172,19 @@ public final class GspTagLibUtil {
     return CachedValuesManager.getManager(project).getCachedValue(module, () -> CachedValueProvider.Result.create(
       doSearchTaglibsInClassPath(module),
       PsiModificationTracker.MODIFICATION_COUNT,
-      MvcModuleStructureSynchronizer.getInstance(project).getFileAndRootsModificationTracker()
+      MvcModuleStructureSynchronizer.getInstance(project).getFileAndRootsModificationTracker(),
+      DumbService.getInstance(project).getModificationTracker()
     ));
   }
 
   private static @NotNull Collection<GrClassDefinition> doSearchTaglibsInClassPath(final @NotNull Module module) {
     final Project project = module.getProject();
+
+    // Scanning class names needs the stub index, which throws IndexNotReadyException in dumb mode.
+    // Returning nothing leaves the source taglibs and the default "g" prefix in place, so GSP
+    // editing degrades instead of failing; searchTaglibsInClassPath() expires this on smart mode.
+    if (DumbService.isDumb(project)) return Collections.emptyList();
+
     final GlobalSearchScope scope = module.getModuleRuntimeScope(false);
 
     final List<String> taglibNames = new ArrayList<>();
diff --git a/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifact.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifact.java
index eb5250c..945e608 100644
--- a/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifact.java
+++ b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifact.java
@@ -20,6 +20,7 @@ import com.intellij.icons.AllIcons;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleManager;
 import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ModuleRootManager;
 import com.intellij.openapi.roots.ProjectFileIndex;
@@ -284,7 +285,9 @@ public enum GrailsArtifact {
       return CachedValueProvider.Result.create(
         new GrailsArtifactCache(map, project),
         synchronizer.getFileAndRootsModificationTracker(),
-        GrailsApplicationManager.getInstance(project)
+        GrailsApplicationManager.getInstance(project),
+        // calculateInstances() degrades in dumb mode; expire that result when indexing ends.
+        DumbService.getInstance(project).getModificationTracker()
       );
     }, false);
   }
@@ -338,7 +341,15 @@ public enum GrailsArtifact {
     // Grails plugin descriptors are resolved against the module's whole dependency+libraries scope,
     // so this must run once for the module - not once per dependency, which would add every declared
     // artefact as many times as there are modules in the dependency graph (duplicate navigate targets).
-    collectFromPluginXmls(module, res);
+    //
+    // Skipped during indexing: it queries FilenameIndex and JavaPsiFacade, which throw
+    // IndexNotReadyException in dumb mode. The directory scan above needs no index, so artefacts
+    // declared in source still resolve; only plugin-descriptor ones are missing until indexing
+    // finishes. getCache() depends on the DumbService modification tracker, so the partial result
+    // is discarded and recomputed as soon as we are smart again.
+    if (!DumbService.isDumb(module.getProject())) {
+      collectFromPluginXmls(module, res);
+    }
 
     return res;
   }
diff --git a/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTagLibDumbModeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTagLibDumbModeTest.java
new file mode 100644
index 0000000..9e207c8
--- /dev/null
+++ b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTagLibDumbModeTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jetbrains.plugins.groovy.grails.gsp;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.testFramework.DumbModeTestUtils;
+import org.jetbrains.plugins.grails.lang.gsp.resolve.taglib.GspTagLibUtil;
+import org.jetbrains.plugins.grails.lang.gsp.resolve.taglib.TagLibNamespaceDescriptor;
+import org.jetbrains.plugins.grails.util.GrailsArtifact;
+import org.jetbrains.plugins.groovy.grails.GrailsTestCase;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Opening a GSP while indexing is still running used to throw IndexNotReadyException: the platform's
+ * XML machinery calls XmlTag.getNamespace() from the daemon, which reaches GspXmlRootTagImpl ->
+ * GspTagLibUtil -> GrailsArtifact and hit the stub index in dumb mode.
+ *
+ * <p>The taglib set is now allowed to be partial while indexing, and every cache in that chain
+ * depends on the DumbService modification tracker so the partial result cannot outlive indexing.
+ */
+public class GspTagLibDumbModeTest extends GrailsTestCase {
+
+  /** The reported crash: any of these calls threw while indexing. */
+  public void testTaglibLookupDoesNotThrowWhileIndexing() {
+    addTaglib("""
+
+class MyTagLib {
+  static namespace = "my"
+  def hello = { attrs -> }
+}
+""");
+    addView("book/list.gsp", "<g:message code='x'/>");
+    Module module = getModule();
+
+    DumbModeTestUtils.runInDumbModeSynchronously(getProject(), () -> {
+      Map<String, TagLibNamespaceDescriptor> taglibs = GspTagLibUtil.getTagLibClasses(module);
+      assertNotNull(taglibs);
+      // The default "g" prefix is unconditional, so GSP editing still resolves g: tags.
+      assertTrue("default taglib prefix must survive dumb mode",
+                 taglibs.containsKey(GspTagLibUtil.DEFAULT_TAGLIB_PREFIX));
+      // The direct throw site from the reported stack.
+      assertNotNull(GrailsArtifact.CONTROLLER.getInstances(module));
+      assertNotNull(GrailsArtifact.TAGLIB.getInstances(module));
+    });
+  }
+
+  /**
+   * The degraded dumb-mode result must be discarded once indexing finishes, otherwise custom
+   * prefixes would stay broken for the rest of the session. A custom-namespace taglib is the
+   * signal: unavailable while dumb (its prefix cannot be resolved without the index), present
+   * afterwards.
+   *
+   * <p>Note this asserts recovery end-to-end, not which cache dependency delivers it — the
+   * fixture bumps {@code PsiModificationTracker} often enough that this test still passes with
+   * the DumbService tracker removed. The tracker is kept because indexing finishing does not
+   * inherently modify PSI, so in a real IDE it is the only dependency guaranteed to change.
+   */
+  public void testCustomPrefixAppearsAfterIndexing() {
+    addTaglib("""
+
+class MyTagLib {
+  static namespace = "my"
+  def hello = { attrs -> }
+}
+""");
+    Module module = getModule();
+
+    Map<String, TagLibNamespaceDescriptor> whileDumb =
+      DumbModeTestUtils.computeInDumbModeSynchronously(getProject(), () -> GspTagLibUtil.getTagLibClasses(module));
+    assertEquals("only the default prefix is resolvable while indexing",
+                 Set.of(GspTagLibUtil.DEFAULT_TAGLIB_PREFIX), whileDumb.keySet());
+
+    Map<String, TagLibNamespaceDescriptor> whenSmart = GspTagLibUtil.getTagLibClasses(module);
+    assertTrue("dumb-mode result must not outlive indexing; got " + whenSmart.keySet(),
+               whenSmart.containsKey("my"));
+  }
+}


(grails-intellij-plugin) 14/35: Nest the compiler tier under libs and complete the group hierarchy

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 369fa5731db2f148ac6d3168c1e1c34c86973521
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:29:14 2026 -0400

    Nest the compiler tier under libs and complete the group hierarchy
    
    Two corrections.
    
    The compiler plugins move from a top-level compilerPlugins tier to a
    subdirectory of the lib tier, so the source tiers mirror the packaged layout:
    pluginModules/* are content modules and ship to lib/modules/, while everything
    under libs/ -- compiler plugins included -- ships to lib/. The compiler
    subdivision is a grouping within that tier, not a separate destination. The
    convention plugin is renamed accordingly (build.compiler-plugin ->
    build.lib-compiler) and the group becomes org.apache.grails.intellij.lib.compiler.
    
    Two groups did not match the intended hierarchy:
    
      plugin project   org.apache.grails.intellij  ->  org.apache.grails.intellij.plugin
      build-logic      (unset)                     ->  org.apache.grails.intellij.build
    
    The full hierarchy is now:
    
      org.apache.grails.intellij.build           build-logic (Gradle DSL build code)
      org.apache.grails.intellij.plugin          the IntelliJ plugin
      org.apache.grails.intellij.module          the six content modules
      org.apache.grails.intellij.lib             gradle-tooling, grails-rt, testFramework
      org.apache.grails.intellij.lib.compiler    jps-plugin, grails-compiler-patch
    
    Packaged jar names follow the same tiers, and the compileServer.plugin classpath
    in plugin.xml is updated to match. Group is not part of any archive name, so the
    distribution layout is unchanged by the group fixes -- verified.
    
    Still outstanding, and deliberately last: the six content module NAMES are still
    intellij.groovy.grails.*, because those are a three-way runtime contract with the
    descriptor filenames and lib/modules/ jar names. They change in the dedicated
    rename step, gated on runIde.
---
 build-logic/build.gradle                                 |  3 +++
 ...g.apache.grails.intellij.build.intellij-plugin.gradle |  2 +-
 ...org.apache.grails.intellij.build.lib-compiler.gradle} | 16 ++++++++++------
 grails-compiler-patch/build.gradle                       |  2 +-
 jps-plugin/build.gradle                                  |  2 +-
 resources/META-INF/plugin.xml                            |  2 +-
 6 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/build-logic/build.gradle b/build-logic/build.gradle
index 91b51c2..1668b53 100644
--- a/build-logic/build.gradle
+++ b/build-logic/build.gradle
@@ -21,6 +21,9 @@ plugins {
     id 'groovy-gradle-plugin'
 }
 
+// The Gradle DSL build code tier.
+group = 'org.apache.grails.intellij.build'
+
 // Gradle plugins that the convention plugins apply. Declaring them here is what lets the
 // convention plugins apply them WITHOUT a version, and it removes the previous fragility
 // where 'org.jetbrains.intellij.platform.module' was applied version-free in every module
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle
index 247590c..86f6fe9 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle
@@ -30,7 +30,7 @@ plugins {
     id 'org.jetbrains.intellij.platform'
 }
 
-group = 'org.apache.grails.intellij'
+group = 'org.apache.grails.intellij.plugin'
 
 intellijPlatform {
     // Pinned explicitly. This drives the distribution ZIP name, the plugin directory inside it,
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler-plugin.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib-compiler.gradle
similarity index 65%
rename from build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler-plugin.gradle
rename to build-logic/src/main/groovy/org.apache.grails.intellij.build.lib-compiler.gradle
index 7f81405..03f8e29 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler-plugin.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib-compiler.gradle
@@ -20,10 +20,14 @@
 // A compiler plugin: code that runs inside the end user's JPS build process rather than in the
 // IDE. These are the jars named by <compileServer.plugin classpath="..."/> in plugin.xml.
 //
-// This is a distinct tier from lib/ on purpose. JPS resolves the classpath attribute by file
-// name and silently skips entries it cannot find, so these jar names are part of the plugin's
-// contract and must stay pinned and in sync with the descriptor -- unlike a plain library, whose
-// name nothing depends on. Keeping them in their own directory makes that obligation visible.
+// It lives under the lib tier (libs/compiler/*) rather than beside it, because the source tiers
+// mirror the packaged layout: pluginModules/* are content modules and ship to lib/modules/, while
+// everything in libs/ -- compiler plugins included -- ships to lib/. The compiler subdirectory is
+// a sub-grouping within that tier, not a separate destination.
+//
+// It is still called out separately because JPS resolves the classpath attribute by file name and
+// silently skips entries it cannot find, so these jar names are part of the plugin's contract and
+// must stay pinned and in sync with the descriptor. Nothing depends on a plain library's name.
 
 plugins {
     id 'org.apache.grails.intellij.build.intellij-platform'
@@ -32,12 +36,12 @@ plugins {
 
 def shortName = projectDir.name
 
-group = 'org.apache.grails.intellij.compiler'
+group = 'org.apache.grails.intellij.lib.compiler'
 base.archivesName = shortName
 
 // Pinned, not derived. IPGP's default is "${rootProject.name}.${project.path with ':' -> '.'}",
 // which tracks both the root project name and the project's depth in the tree; that is exactly
 // how the descriptor's classpath attribute went stale before.
 tasks.named('composedJar') { task ->
-    task.archiveBaseName = "org.apache.grails.intellij.compiler.$shortName"
+    task.archiveBaseName = "org.apache.grails.intellij.lib.compiler.$shortName"
 }
diff --git a/grails-compiler-patch/build.gradle b/grails-compiler-patch/build.gradle
index dad67cd..6bf6df5 100644
--- a/grails-compiler-patch/build.gradle
+++ b/grails-compiler-patch/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.compiler-plugin'
+    id 'org.apache.grails.intellij.build.lib-compiler'
     id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
diff --git a/jps-plugin/build.gradle b/jps-plugin/build.gradle
index 261d571..061ecac 100644
--- a/jps-plugin/build.gradle
+++ b/jps-plugin/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 plugins {
-    id 'org.apache.grails.intellij.build.compiler-plugin'
+    id 'org.apache.grails.intellij.build.lib-compiler'
     id 'org.apache.grails.intellij.build.java-legacy'
     id 'org.apache.grails.intellij.build.legacy-layout'
 }
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index ca6f492..7536939 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -323,7 +323,7 @@
          builders with nothing more than a line in idea.log. These names are pinned by the
          org.apache.grails.intellij.build.intellij-lib convention plugin rather than derived from
          rootProject.name, which is what let this attribute go stale previously. -->
-    <compileServer.plugin classpath="org.apache.grails.intellij.compiler.jps-plugin.jar;org.apache.grails.intellij.compiler.grails-compiler-patch.jar"/>
+    <compileServer.plugin classpath="org.apache.grails.intellij.lib.compiler.jps-plugin.jar;org.apache.grails.intellij.lib.compiler.grails-compiler-patch.jar"/>
 
     <multiHostInjector implementation="org.jetbrains.plugins.grails.lang.gsp.GspCssInjector"/>
     <multiHostInjector implementation="org.jetbrains.plugins.grails.references.constraints.GormRegexInjector"/>


(grails-intellij-plugin) 11/35: Add the Groovy convention plugins and convert hibernate as a pilot

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit cb817c4c141aa7179a690bd59f3822b511425276
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:08:08 2026 -0400

    Add the Groovy convention plugins and convert hibernate as a pilot
    
    Step 2 of the restructure: authors the convention plugins that the tiered
    projects will apply, and converts one module (hibernate) to prove the whole
    chain end to end. hibernate/build.gradle is now 27 lines of pure content,
    down from 68, and still produces intellij.groovy.grails.hibernate.jar.
    
    Plugins added under build-logic/src/main/groovy:
      base, java, java-legacy (+LegacyBytecodeExtension), kotlin, test,
      intellij-platform, intellij-plugin, intellij-module, lib, intellij-lib,
      rat, coverage-aggregation, and a transitional legacy-layout.
    
    Corrections to the plan, found by testing rather than reading:
    
    * archivesName does NOT name the shipped artifact for IPGP projects. hibernate
      ships intellij.groovy.grails.hibernate.jar (its composedJar pin) while
      hibernate-262.0.0-base.jar is a throwaway intermediate, and gradle-tooling
      ships "Grails IntelliJ Plugin.gradle-tooling.jar" rather than its
      archivesName. IPGP's ComposedJarTask applies its own archiveBaseName
      convention after the base plugin's, so composedJar.archiveBaseName is the
      only knob that decides what users get. Both are now set, with the pin
      carrying the comment explaining which one matters.
    
    * IPGP registers THREE extensions named intellijPlatform -- on the Project, on
      RepositoryHandler and on DependencyHandler. Groovy resolves closures
      owner-first, so a bare `intellijPlatform { }` nested inside `repositories { }`
      resolves to the PROJECT extension and dies with "Could not find method
      defaultRepositories()". Kotlin scopes by receiver type and has no such clash.
      Every nested closure now binds its target to an explicit parameter.
    
    * A convention plugin that configures the intellijPlatform extensions must
      apply org.jetbrains.intellij.platform.base itself. Relying on the consuming
      tier plugin to apply .module afterwards is too late: the script body runs
      first, when the extensions do not yet exist.
    
    * rat had outputs.upToDateWhen { false } but RatTask is @CacheableTask and
      org.gradle.caching=true, so a build-cache hit skipped the audit entirely.
      Added outputs.cacheIf { false }.
    
    * The rat convention plugin applies lifecycle-base. RatPlugin hooks rat into
      `check` only via plugins.withType(LifecycleBasePlugin), so once the root
      project stops applying java, `./gradlew check buildPlugin` in CI would have
      silently stopped auditing licences.
    
    * jacoco-report-aggregation only auto-registers <suite>CodeCoverageReport for
      projects applying jvm-test-suite. The root compiles nothing, so the report is
      registered explicitly -- this keeps the XML at the path coverage.yml uploads
      instead of moving it under plugin/.
    
    Also drops kotlin.jvm from i18n, which applies it with zero Kotlin sources.
---
 .../org.apache.grails.intellij.build.base.gradle   | 28 +++++++
 ...ails.intellij.build.coverage-aggregation.gradle | 46 ++++++++++
 ...pache.grails.intellij.build.intellij-lib.gradle | 42 ++++++++++
 ...he.grails.intellij.build.intellij-module.gradle | 50 +++++++++++
 ....grails.intellij.build.intellij-platform.gradle | 49 +++++++++++
 ...he.grails.intellij.build.intellij-plugin.gradle | 98 ++++++++++++++++++++++
 ...apache.grails.intellij.build.java-legacy.gradle | 39 +++++++++
 .../org.apache.grails.intellij.build.java.gradle   | 45 ++++++++++
 .../org.apache.grails.intellij.build.kotlin.gradle | 34 ++++++++
 ...ache.grails.intellij.build.legacy-layout.gradle | 40 +++++++++
 .../org.apache.grails.intellij.build.lib.gradle    | 28 +++++++
 .../org.apache.grails.intellij.build.rat.gradle    | 96 +++++++++++++++++++++
 .../org.apache.grails.intellij.build.test.gradle   | 31 +++++++
 hibernate/build.gradle                             | 27 ++++++
 hibernate/build.gradle.kts                         | 64 --------------
 i18n/build.gradle.kts                              |  8 --
 16 files changed, 653 insertions(+), 72 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle
new file mode 100644
index 0000000..51fc71c
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.base.gradle
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Conventions shared by every project, including ones that compile nothing.
+
+plugins {
+    id 'org.apache.grails.intellij.build.reproducible'
+}
+
+repositories {
+    mavenCentral()
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle
new file mode 100644
index 0000000..33998cc
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.coverage-aggregation.gradle
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
+
+// Cross-project JaCoCo aggregation, applied to the root project so the report path that CI
+// uploads (build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml) is unchanged
+// by the restructure.
+//
+// jacoco-report-aggregation only auto-registers <suite>CodeCoverageReport for projects that
+// apply jvm-test-suite, and the root project compiles nothing. The report is therefore
+// registered explicitly here; without this the task simply would not exist and the coverage
+// workflow would fail with "task not found".
+//
+// Report formats are configured by org.apache.grails.intellij.build.jacoco where it is applied;
+// this project has no test suite of its own, so the formats are set on the aggregate directly.
+
+plugins {
+    id 'jacoco-report-aggregation'
+}
+
+reporting.reports.register('testCodeCoverageReport', JacocoCoverageReport) { report ->
+    report.testSuiteName = 'test'
+}
+
+tasks.named('testCodeCoverageReport') { task ->
+    task.reports.xml.required = true
+    task.reports.html.required = true
+    task.reports.csv.required = false
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle
new file mode 100644
index 0000000..d6ce900
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// A support library that compiles against the IntelliJ Platform. Same tier as
+// org.apache.grails.intellij.build.lib, but it applies the platform module plugin.
+//
+// The composed jar name is pinned rather than derived by IPGP, whose default is
+// "${rootProject.name}.${project.path with ':' replaced by '.'}". That default is why these
+// jars are currently called "Grails IntelliJ Plugin.jps-plugin.jar" -- it tracks both
+// rootProject.name and the project's depth in the tree, so nesting these projects or renaming
+// the root would silently rename them. plugin.xml's compileServer.plugin classpath names two
+// of these jars literally, so the name has to be stable and space-free.
+
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-platform'
+    id 'org.jetbrains.intellij.platform.module'
+}
+
+def shortName = projectDir.name
+
+group = 'org.apache.grails.intellij.lib'
+base.archivesName = shortName
+
+tasks.named('composedJar') { task ->
+    task.archiveBaseName = "org.apache.grails.intellij.lib.$shortName"
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle
new file mode 100644
index 0000000..a902eac
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-module.gradle
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// An IntelliJ *content module* (plugin.xml <content><module name="..."/></content>).
+//
+// NAMING IS A RUNTIME CONTRACT. The platform resolves a content module from
+// lib/modules/<module-name>.jar and reads its descriptor from <module-name>.xml at that jar's
+// root, so three things must agree exactly: the <module name> in plugin.xml, the descriptor
+// file name, and the composed jar's base name. Getting it wrong builds a valid ZIP that fails
+// to load the module at IDE runtime -- invisible to compilation, tests and verifyPlugin.
+//
+// The name is derived from the project DIRECTORY, not project.name: projects are named with
+// their subpath built in (pluginModules-hibernate), so project.name carries a prefix that must
+// not leak into the module name.
+//
+// archivesName only renames the throwaway -base/-instrumented intermediates; IPGP's
+// ComposedJarTask applies its own archiveBaseName convention afterwards, so the pin below is
+// what actually determines the shipped file name.
+
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-platform'
+    id 'org.jetbrains.intellij.platform.module'
+}
+
+def shortName = projectDir.name
+
+group = 'org.apache.grails.intellij.module'
+base.archivesName = shortName
+
+// Prefix is changed to 'org.apache.grails.intellij.module.' in the content-module rename step;
+// deriving it here is what keeps the three coupled strings from drifting apart again.
+tasks.named('composedJar') { task ->
+    task.archiveBaseName = "intellij.groovy.grails.$shortName"
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
new file mode 100644
index 0000000..c660c33
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-platform.gradle
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Shared IntelliJ Platform wiring: the artifact repositories and the platform dependency.
+// The IPGP version comes from build-logic's classpath, so this is applied without a version.
+//
+// EVERY closure below binds its target to an explicit parameter, and that is load-bearing.
+// IPGP registers THREE extensions all named `intellijPlatform` -- one on the Project, one on
+// RepositoryHandler, one on DependencyHandler. Groovy closures resolve owner-first, so a bare
+// `intellijPlatform { }` nested inside `repositories { }` or `dependencies { }` resolves to the
+// PROJECT extension and fails with "Could not find method defaultRepositories() ... on extension
+// 'intellijPlatform'". Kotlin's DSL scopes by receiver type, so it has no such clash.
+
+plugins {
+    id 'org.apache.grails.intellij.build.java'
+    // Registers the intellijPlatform extensions used below. The consuming tier plugin adds
+    // the full plugin (.platform) or module (.module) variant on top; both re-apply this base
+    // idempotently. Without it, this script's body runs before any IPGP plugin is applied and
+    // the repository/dependency extensions do not exist yet.
+    id 'org.jetbrains.intellij.platform.base'
+}
+
+repositories { repositoryHandler ->
+    repositoryHandler.intellijPlatform { repos ->
+        repos.defaultRepositories()
+    }
+}
+
+dependencies { dependencyHandler ->
+    dependencyHandler.intellijPlatform { platform ->
+        platform.intellijIdea providers.gradleProperty('platformVersion')
+    }
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle
new file mode 100644
index 0000000..247590c
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-plugin.gradle
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask
+import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask
+
+// The IntelliJ plugin project itself. Everything here is plugin-wide configuration that has no
+// second consumer; it lives in a convention plugin so plugin/build.gradle carries only content
+// (the bundled-plugin list and project dependencies).
+
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-platform'
+    id 'org.apache.grails.intellij.build.kotlin'
+    id 'org.jetbrains.intellij.platform'
+}
+
+group = 'org.apache.grails.intellij'
+
+intellijPlatform {
+    // Pinned explicitly. This drives the distribution ZIP name, the plugin directory inside it,
+    // and the sandbox directory; its convention is project.name, which would make all three
+    // 'plugin'. It is NOT the Marketplace display name -- that comes from <name> in plugin.xml.
+    projectName = 'apache-grails-intellij-plugin'
+
+    pluginConfiguration {
+        ideaVersion {
+            sinceBuild = providers.gradleProperty('pluginSinceBuild')
+        }
+        changeNotes = ''
+    }
+
+    pluginVerification {
+        // the legacy plugin id org.intellij.grails is grandfathered on Marketplace and
+        // permanent (see MIGRATION-PLAN.md decision 2); mute the new-plugin naming check
+        freeArgs = ['-mute', 'TemplateWordInPluginId']
+        // gate on real incompatibilities only; the inherited internal/deprecated API
+        // usages are a tracked cleanup item, not a release blocker
+        failureLevel = [
+                VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS,
+                VerifyPluginTask.FailureLevel.MISSING_DEPENDENCIES,
+                VerifyPluginTask.FailureLevel.INVALID_PLUGIN,
+        ]
+        ides {
+            recommended()
+        }
+    }
+
+    // JetBrains Marketplace code signing / publishing; secrets provided by the release workflow.
+    // Providers are assigned rather than read, so absent secrets neither fail configuration nor
+    // land in the configuration cache.
+    signing {
+        certificateChain = providers.environmentVariable('CERTIFICATE_CHAIN')
+        privateKey = providers.environmentVariable('PRIVATE_KEY')
+        password = providers.environmentVariable('PRIVATE_KEY_PASSWORD')
+    }
+
+    publishing {
+        token = providers.environmentVariable('PUBLISH_TOKEN')
+    }
+}
+
+// The composed jar is what ships in lib/; its base name is conventioned from project.name and
+// ignores archivesName, so it is pinned for the same reason projectName is.
+tasks.named('composedJar') { task ->
+    task.archiveBaseName = 'apache-grails-intellij-plugin'
+}
+
+// ASF policy: ship LICENSE and NOTICE inside the plugin jar. They live at the repository root,
+// which is the settings directory rather than this project's directory.
+tasks.named('processResources') { task ->
+    task.from(layout.settingsDirectory.file('LICENSE')) { into 'META-INF' }
+    task.from(layout.settingsDirectory.file('NOTICE')) { into 'META-INF' }
+}
+
+// standardDsls/ is packaged as loose files under <plugin>/lib/standardDsls/ rather than as jar
+// resources, so it is excluded from the resource set and copied into the sandbox instead.
+// pluginName is a Property mapped lazily so it tracks projectName above.
+tasks.withType(PrepareSandboxTask).configureEach { task ->
+    task.from(layout.projectDirectory.dir('standardDsls')) {
+        into task.pluginName.map { "$it/lib/standardDsls/" }
+    }
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.java-legacy.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.java-legacy.gradle
new file mode 100644
index 0000000..8cc4b87
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.java-legacy.gradle
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.grails.intellij.build.LegacyBytecodeExtension
+
+// For projects whose output runs inside an end user's JVM (Grails builds, the JPS build
+// process) and therefore must emit older bytecode than the IntelliJ platform's Java level.
+//
+// Usage, in the consuming build file:
+//     legacyBytecode { release = 11 }
+
+plugins {
+    id 'org.apache.grails.intellij.build.java'
+}
+
+def legacyBytecode = extensions.create('legacyBytecode', LegacyBytecodeExtension)
+legacyBytecode.release.convention(8)
+
+// options.release wins over source/targetCompatibility, and is re-applied here because the
+// IntelliJ Platform Gradle Plugin force-sets it to the platform's Java version.
+tasks.withType(JavaCompile).configureEach { task ->
+    task.options.release.set(legacyBytecode.release)
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.java.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.java.gradle
new file mode 100644
index 0000000..08c62c2
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.java.gradle
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Java compilation conventions for every project that compiles code, plus the per-project
+// hygiene plugins so subproject build files do not have to list them.
+
+plugins {
+    id 'java'
+    id 'org.apache.grails.intellij.build.base'
+    id 'org.apache.grails.intellij.build.jacoco'
+    id 'org.apache.grails.intellij.build.vulnerability-scan'
+    id 'org.apache.grails.intellij.build.test'
+}
+
+def platformJavaVersion = providers.gradleProperty('platformJavaVersion')
+
+java.sourceCompatibility = platformJavaVersion.get()
+java.targetCompatibility = platformJavaVersion.get()
+
+// Checked-in generated sources (JFlex lexers) live in their own root so regenerating them
+// cannot clobber hand-written code. This is the only source-layout override that survives
+// the restructure.
+sourceSets.named('main') { main ->
+    main.java.srcDir 'src/main/gen'
+}
+
+tasks.withType(JavaCompile).configureEach { task ->
+    task.options.encoding = 'UTF-8'
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.kotlin.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.kotlin.gradle
new file mode 100644
index 0000000..7c3571b
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.kotlin.gradle
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+// Kotlin conventions. The version comes from build-logic's plugin classpath, so consuming
+// build files apply this without a version -- previously 'org.jetbrains.kotlin.jvm' was
+// declared with an explicit version in five separate places.
+
+plugins {
+    id 'org.jetbrains.kotlin.jvm'
+    id 'org.apache.grails.intellij.build.java'
+}
+
+def platformJavaVersion = providers.gradleProperty('platformJavaVersion')
+
+// No toolchain on purpose: the JDK is pinned via .sdkmanrc for reproducible builds.
+kotlin.compilerOptions.jvmTarget.set(JvmTarget.fromTarget(platformJavaVersion.get()))
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
new file mode 100644
index 0000000..663f4f0
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// TRANSITIONAL. Declares the pre-restructure source layout inherited from the JetBrains JPS
+// monorepo (src/, gen/, resources/, compatibilityResources/, test/) so that the convention
+// plugins can be introduced before any files move.
+//
+// Every project applies this while the restructure is in flight; it is deleted once all three
+// tiers have been normalized to src/main/java, src/main/resources and src/test/java. Declaring
+// a srcDir that does not exist is harmless, so one shared block covers all projects even
+// though only the plugin project has gen/, compatibilityResources/ and test/.
+//
+// Do NOT add anything here that is not purely about the old layout.
+
+sourceSets.named('main') { main ->
+    main.java.srcDir 'src'
+    main.java.srcDir 'gen'
+    main.resources.srcDir 'resources'
+    main.resources.srcDir 'compatibilityResources'
+}
+
+sourceSets.named('test') { test ->
+    test.java.srcDir 'test'
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle
new file mode 100644
index 0000000..86fe2ea
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// A plain support library: compiled and bundled into the plugin's lib/ directory, but not an
+// IntelliJ content module and not published.
+
+plugins {
+    id 'org.apache.grails.intellij.build.java'
+}
+
+group = 'org.apache.grails.intellij.lib'
+base.archivesName = projectDir.name
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
new file mode 100644
index 0000000..883d127
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Apache RAT license audit. Applied to the root project only: the exclude patterns below are
+// anchored at the audited directory, which is the project that owns the task.
+//
+// lifecycle-base is applied because RatPlugin hooks rat into `check` via
+// plugins.withType(LifecycleBasePlugin). Without it the root has no `check` task and
+// `./gradlew check buildPlugin` in CI would stop auditing licences -- a green build that
+// verifies nothing.
+
+plugins {
+    id 'lifecycle-base'
+    id 'org.nosphere.apache.rat'
+}
+
+// Every exclude must carry a justification; no blanket excludes to make the audit pass.
+tasks.named('rat') { task ->
+    task.excludes.addAll([
+            // planning documents, removed before the first release
+            'MIGRATION-PLAN.md',
+            'IMPROVEMENT-PLAN.md',
+            // documentation
+            '**/README.md',
+            // git configuration isn't code
+            '**/.gitignore',
+            '**/.gitattributes',
+            // gradle wrapper files, excluded from the source distro
+            'gradlew*',
+            'gradle/wrapper/**',
+            '**/.gradle/**',
+            // github/ASF infra configuration, not shipped in the source distro
+            '.github/**',
+            '.asf.yaml',
+            // IDE-generated files
+            '.idea/**',
+            '**/*.iml',
+            'out/**',
+            // IntelliJ Platform Gradle Plugin local cache (generated Ivy descriptors, git-ignored)
+            '.intellijPlatform/**',
+            // build-logic included build: generated output and local caches (sources are headered)
+            'build-logic/build/**',
+            'build-logic/.gradle/**',
+            // reproducible-build and release-verification output (git-ignored)
+            'etc/bin/results/**',
+            // generated code (JFlex lexers; headers are injected by the generator config)
+            'gen/**',
+            '**/src/main/gen/**',
+            // test fixtures: content *is* the test input (mock Grails projects/JARs);
+            // adding headers would break parser/position-sensitive tests
+            'testdata/**',
+            '**/testdata/**',
+            // file templates that materialize inside end-user projects
+            '**/resources/fileTemplates/**',
+            // intention/inspection description snippets and before/after templates
+            // rendered verbatim in the IDE settings UI
+            '**/intentionDescriptions/**',
+            '**/inspectionDescriptions/**',
+            // html files are IDE description snippets / template previews only
+            '**/*.html',
+            // GUI-designer .form files are machine-edited XML
+            '**/*.form',
+            // images
+            '**/*.png', '**/*.svg', '**/*.ico', '**/*.jpg', '**/*.gif',
+            // JAR manifest format has no comment syntax
+            '**/META-INF/MANIFEST.MF',
+            // build outputs are generated and never shipped in the source distro. Anchored to
+            // explicit depths rather than "**/build/**" so a source package named "build" would
+            // still be audited. Three levels are needed: the root, the tier directories, and the
+            // nested projects under pluginModules/ and libs/.
+            'build/**',
+            '*/build/**',
+            '*/*/build/**',
+    ])
+    // Never let a license audit be skipped. upToDateWhen alone is not enough: RatTask is
+    // @CacheableTask and org.gradle.caching=true, so a build-cache hit would skip the audit
+    // entirely even though it can never be considered up to date.
+    task.outputs.upToDateWhen { false }
+    task.outputs.cacheIf { false }
+}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.test.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.test.gradle
new file mode 100644
index 0000000..a56fd35
--- /dev/null
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.test.gradle
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Test conventions.
+
+def ideaHome = providers.gradleProperty('test.idea.home.path')
+
+tasks.withType(Test).configureEach { task ->
+    // Opt-in for the handful of tests that need IntelliJ sources on disk. Unset on CI, and
+    // ignored when it points somewhere that does not exist, so a stale value in a developer's
+    // gradle.properties cannot fail the build.
+    if (ideaHome.isPresent() && file(ideaHome.get()).exists()) {
+        task.systemProperty 'idea.home.path', ideaHome.get()
+    }
+}
diff --git a/hibernate/build.gradle b/hibernate/build.gradle
new file mode 100644
index 0000000..640e48c
--- /dev/null
+++ b/hibernate/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2026 JetBrains s.r.o. and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+plugins {
+    id 'org.apache.grails.intellij.build.intellij-module'
+    id 'org.apache.grails.intellij.build.legacy-layout'
+}
+
+dependencies { deps ->
+    deps.intellijPlatform { platform ->
+        platform.bundledPlugin 'com.intellij.hibernate'
+    }
+
+    compileOnly project(':')
+}
diff --git a/hibernate/build.gradle.kts b/hibernate/build.gradle.kts
deleted file mode 100644
index ff36d98..0000000
--- a/hibernate/build.gradle.kts
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2000-2026 JetBrains s.r.o. and contributors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-fun properties(key: String) = project.findProperty(key).toString()
-
-plugins {
-    id("java")
-    id("org.apache.grails.intellij.build.reproducible")
-    id("org.apache.grails.intellij.build.jacoco")
-    id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.intellij.platform.module")
-}
-
-group = "org.apache.grails.intellij.hibernate"
-
-repositories {
-    mavenCentral()
-    intellijPlatform {
-        defaultRepositories()
-    }
-}
-
-dependencies {
-    intellijPlatform {
-        intellijIdea(properties("platformVersion"))
-        bundledPlugin("com.intellij.hibernate")
-    }
-
-    compileOnly(project(":"))
-}
-
-java.sourceSets["main"].java {
-    srcDir("src")
-}
-
-java.sourceSets["main"].resources {
-    srcDir("resources")
-}
-
-tasks {
-        withType<JavaCompile> {
-        sourceCompatibility = properties("platformJavaVersion")
-        targetCompatibility = properties("platformJavaVersion")
-    }
-
-}
-
-// 2026.2 resolves content-module descriptors from lib/modules/<module-name>.jar,
-// so the composed jar must be named after the content module, not the project
-tasks.named<org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask>("composedJar") {
-    archiveBaseName = "intellij.groovy.grails.hibernate"
-}
diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts
index efd45f1..c83209a 100644
--- a/i18n/build.gradle.kts
+++ b/i18n/build.gradle.kts
@@ -20,7 +20,6 @@ plugins {
     id("org.apache.grails.intellij.build.reproducible")
     id("org.apache.grails.intellij.build.jacoco")
     id("org.apache.grails.intellij.build.vulnerability-scan")
-    id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
 
@@ -43,13 +42,6 @@ dependencies {
     compileOnly(project(":"))
 }
 
-kotlin {
-    // no toolchain on purpose: the JDK is pinned via .sdkmanrc for reproducible builds
-    compilerOptions {
-        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(properties("platformJavaVersion"))
-    }
-}
-
 java.sourceSets["main"].java {
     srcDir("src")
 }


(grails-intellij-plugin) 16/35: Document every subproject, and restore the grails-rt agent manifest

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit a26b55228761455487f2e9d224c633c6a8c018d5
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:46:00 2026 -0400

    Document every subproject, and restore the grails-rt agent manifest
    
    Adds a README.md to all eleven subprojects plus build-logic, each covering what
    the project is for, what it contains, how it is packaged, and the Grails versions
    it supports. Version ranges are derived from the version gates actually present
    in the code rather than asserted, and the READMEs say "no version gating" where
    the code has none.
    
      grails-compiler-patch   1.0  - 2.x     (jps-plugin injects it only below 3.0)
      jps-plugin              1.0  - 2.x     (shouldInjectGrails() false from 3.0)
      maven                   1.x  - 2.x     (Maven-built Grails; 3+ is Gradle)
      gradle-tooling          3.0+           (Gradle-built projects only)
      grails-rt               1.1+ , no max  (see below)
      copyright, coverage, hibernate, i18n, langInjection   no version gating
      testFramework           n/a (build-time only, not shipped)
    
    grails-rt turned out to be split by era rather than legacy-only, which is worth
    recording because the name suggests otherwise. Its Java classes are Grails 2.x
    vintage and dormant -- GrailsIdeaTestListener implements
    grails.build.GrailsBuildListener, which does not exist in Grails 3+ -- while the
    two Groovy resources it ships are what the Grails 3.0+ code paths actually use
    (TraitInjectorService, Grails3TestCommandLineState, GrailsProjectResolverExtension,
    GrailsTaskManagerExtension). Grails 3+ never loads the legacy classes, so their
    being unloadable there is harmless.
    
    Also fixes the agent manifest. grails-rt declares Premain-Class in a
    META-INF/MANIFEST.MF *resource*, but Gradle's Jar task writes its own manifest and
    silently shadows a resource at that path, so the packaged jar carried only
    "Manifest-Version: 1.0" and a -javaagent: launch would have failed with "Failed to
    find Premain-Class manifest attribute". The attributes are now declared on the jar
    task and the dead resource is deleted.
    
    Confirmed pre-existing, not caused by this restructure: building grails-rt from
    the pre-restructure branch produces the same stripped manifest. It also changes no
    behaviour today, because GrailsExecutionUtils.addAgentJar has no callers --
    git log -S shows it arrived already-orphaned in the initial JetBrains import
    (3d4bc40) and was never wired up. Both dormant paths are documented as such rather
    than left to be rediscovered.
---
 build-logic/README.md                              | 51 ++++++++++++++++++++++
 .../org.apache.grails.intellij.build.lib.gradle    | 14 +++++-
 copyright/README.md                                | 20 +++++++++
 coverage/README.md                                 | 17 ++++++++
 gradle-tooling/README.md                           | 25 +++++++++++
 grails-compiler-patch/README.md                    | 36 +++++++++++++++
 grails-rt/README.md                                | 47 ++++++++++++++++++++
 grails-rt/build.gradle                             | 12 +++++
 grails-rt/resources/META-INF/MANIFEST.MF           |  2 -
 hibernate/README.md                                | 16 +++++++
 i18n/README.md                                     | 20 +++++++++
 jps-plugin/README.md                               | 30 +++++++++++++
 langInjection/README.md                            | 20 +++++++++
 maven/README.md                                    | 27 ++++++++++++
 testFramework/README.md                            | 33 +++++++++++++-
 15 files changed, 365 insertions(+), 5 deletions(-)

diff --git a/build-logic/README.md b/build-logic/README.md
new file mode 100644
index 0000000..2de40f6
--- /dev/null
+++ b/build-logic/README.md
@@ -0,0 +1,51 @@
+# build-logic
+
+The build's convention plugins, as an **included build** (`pluginManagement { includeBuild }` in
+`settings.gradle`). Not shipped, and not published.
+
+Written as **precompiled Groovy script plugins** in `src/main/groovy`, named after their plugin id,
+so a subproject build file is one `plugins { }` line plus its own dependencies. Deliberately not
+imperative `Plugin<Project>` classes.
+
+## Why plugin versions live here
+
+`build-logic/build.gradle` declares the IntelliJ Platform, Kotlin, RAT and Sonatype scan plugins as
+`implementation` dependencies. That is what lets the convention plugins apply them **without a
+version**, and it removes the previous fragility where `org.jetbrains.intellij.platform.module` was
+applied version-free in every module and resolved only by inheriting the root build script's plugin
+classpath.
+
+These versions are literals here rather than in `gradle.properties`, because build-logic is a
+separate build and cannot read the root build's properties. Dependency versions used by the main
+build still come from the root `gradle.properties` via `providers.gradleProperty(...)`.
+
+## The plugins
+
+| Plugin (`org.apache.grails.intellij.build.` + …) | Applied by |
+| --- | --- |
+| `base` | every project — repositories, reproducible archives |
+| `java` | every project that compiles — layout, release level, jacoco, vulnerability scan, test |
+| `java-legacy` | projects needing pre-25 bytecode; `legacyBytecode { release = N }` |
+| `kotlin` | projects with Kotlin sources |
+| `test` | test conventions |
+| `intellij-platform` | shared IPGP repositories + platform dependency |
+| `intellij-plugin` | the plugin project |
+| `intellij-module` | the six content modules |
+| `lib` | plain libraries (no IntelliJ Platform) |
+| `intellij-lib` | libraries that compile against the platform |
+| `compiler` | compiler plugins named in `compileServer.plugin` |
+| `rat` | root only — the licence audit |
+| `coverage-aggregation` | root only — cross-project JaCoCo |
+| `reproducible`, `jacoco`, `vulnerability-scan` | applied transitively |
+| `legacy-layout` | **transitional** — delete once all tiers use standard source layout |
+
+## Groovy DSL gotchas
+
+- Groovy resolves closures **owner-first**, so `extensions.configure(SomeType) { foo = ... }` assigns
+  to the `Project` and fails with `MissingPropertyException`. Bind an explicit parameter.
+- A convention plugin that configures another plugin's extensions must **apply that plugin in its own
+  `plugins { }` block**; the consuming plugin applies it too late, after this body has run.
+- Kotlin `object`s need `.INSTANCE` from Groovy, and Kotlin named/default arguments are unreachable —
+  see how `testFramework(...)` is called in `testFramework/build.gradle`.
+- There are no type-safe accessors. Run a real build after each change; a typo is a
+  configuration-time `MissingMethodException`, not a compile error.
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle
index 86fe2ea..13eba94 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.lib.gradle
@@ -24,5 +24,17 @@ plugins {
     id 'org.apache.grails.intellij.build.java'
 }
 
+def shortName = projectDir.name
+
 group = 'org.apache.grails.intellij.lib'
-base.archivesName = projectDir.name
+
+// No IntelliJ Platform plugin here, so the `jar` task output is what ships -- there is no
+// composedJar to pin. Name it the same way the IPGP tiers name theirs, and drop the version:
+// IPGP already forces an empty archiveVersion on module composed jars, and every artifact in
+// this build carries the one project version, so a version in the file name only makes the
+// name churn from release to release.
+base.archivesName = "org.apache.grails.intellij.lib.$shortName"
+
+tasks.named('jar') { task ->
+    task.archiveVersion = ''
+}
diff --git a/copyright/README.md b/copyright/README.md
new file mode 100644
index 0000000..1f1b693
--- /dev/null
+++ b/copyright/README.md
@@ -0,0 +1,20 @@
+# copyright
+
+IntelliJ **content module** adding copyright-header support to GSP files, so the IDE's Copyright
+feature can insert and update headers in `.gsp` sources.
+
+`UpdateGspFileCopyright` derives from the Copyright plugin's `UpdatePsiFileCopyright` and needs
+nothing from javaee/JSP — see that class for why the JSP original's checks were inert here.
+
+## Grails version support
+
+- **Minimum / maximum:** no version gating. The feature is about GSP file syntax, which has not
+  changed in a way this code cares about.
+
+## Packaging
+
+Content module `intellij.groovy.grails.copyright`, shipped as
+`lib/modules/intellij.groovy.grails.copyright.jar`. The module name, the descriptor file name and the
+jar base name are one three-way contract — see the `intellij-module` convention plugin.
+
+Requires the `com.intellij.copyright` plugin at runtime.
diff --git a/coverage/README.md b/coverage/README.md
new file mode 100644
index 0000000..3a86cdb
--- /dev/null
+++ b/coverage/README.md
@@ -0,0 +1,17 @@
+# coverage
+
+IntelliJ **content module** integrating Grails test runs with the IDE's code-coverage tooling.
+
+## Grails version support
+
+- **Minimum / maximum:** no version gating.
+
+## Packaging
+
+Content module `intellij.groovy.grails.coverage`, shipped as
+`lib/modules/intellij.groovy.grails.coverage.jar`.
+
+Its descriptor declares `<plugin id="Coverage"/>`. This matters: depending on the
+`com.intellij.modules.coverage` alias instead caused a `NoClassDefFoundError` at IDE startup, because
+the alias does not carry the classes. A content module's `<dependencies>` must name the
+class-bearing plugin id.
diff --git a/gradle-tooling/README.md b/gradle-tooling/README.md
new file mode 100644
index 0000000..d691d3a
--- /dev/null
+++ b/gradle-tooling/README.md
@@ -0,0 +1,25 @@
+# gradle-tooling
+
+A Gradle **tooling API model builder**. It runs inside the Gradle process during project import and
+reports Grails-specific information back to the IDE as a `GrailsModule` model.
+
+Registered through `META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService`.
+
+## What it does
+
+`GrailsModuleModelBuilderImpl` determines, per Gradle project:
+
+- the Grails version — preferring the explicit `grailsVersion` project property, because it must be
+  known before the Grails shell CLI dependency can be added (the artifact coordinates changed across
+  versions, so adding it unversioned fails to resolve and breaks the whole import)
+- the Grails plugin id
+- the shell CLI dependency URLs
+
+## Grails version support
+
+- **Minimum:** 3.0 — this only applies to Gradle-built Grails projects, which is Grails 3 and newer
+- **Maximum:** none
+
+## Build
+
+Java 8 bytecode: it is loaded by the Gradle daemon, which may run an older JVM than the IDE.
diff --git a/grails-compiler-patch/README.md b/grails-compiler-patch/README.md
new file mode 100644
index 0000000..b53381e
--- /dev/null
+++ b/grails-compiler-patch/README.md
@@ -0,0 +1,36 @@
+# grails-compiler-patch
+
+Groovy AST compilation-unit patchers pushed onto the Groovy compiler classpath **inside the user's
+JPS build**, so that Grails 1.x/2.x sources compile the way Grails itself compiles them.
+
+This is a **compiler plugin**, not a library: its jar name appears literally in
+`<compileServer.plugin classpath="..."/>` in `plugin.xml`. JPS resolves those entries by file name
+and silently skips anything it cannot find, so the name is pinned by the
+`org.apache.grails.intellij.build.compiler` convention plugin and must stay in sync with the
+descriptor.
+
+## Contents
+
+| Patcher | Applies to |
+| --- | --- |
+| `GrailsDomainClassPatcher` | Grails < 1.2 |
+| `GrailsJUnitPatcher` | all supported legacy versions |
+| `Grails2_0_JUnitPatcher` | Grails >= 2.0 |
+| `EmptyGrailsAwarePatcher` | Grails >= 2.0 |
+| `GrailsCompilerRtMarker` | locator marker |
+
+## Grails version support
+
+- **Minimum:** 1.0
+- **Maximum:** 2.x
+
+`jps-plugin`'s `GrailsBuilderExtension.shouldInjectGrails()` only injects this jar when the module's
+`grails-core-<version>.jar` is **older than 3.0**, so it is inert for Grails 3+ projects. Grails 3
+moved to Gradle and no longer needs the patchers.
+
+## Build
+
+Java 8 bytecode, and it deliberately compiles against era-correct dependencies —
+`grails-core:1.2.0` and `groovy-all:2.4.21` — with the IDE's bundled Groovy excluded from
+`compileClasspath`. It must not be built against modern Groovy, because it runs inside Grails 2.x
+builds.
diff --git a/grails-rt/README.md b/grails-rt/README.md
new file mode 100644
index 0000000..3838080
--- /dev/null
+++ b/grails-rt/README.md
@@ -0,0 +1,47 @@
+# grails-rt
+
+Runtime payload injected into the **user's** Grails/Gradle JVM. `rt` is the JVM convention for a
+runtime sidecar (as in the old JDK `rt.jar`); IntelliJ plugins ship `*-rt.jar` for this pattern.
+
+Nothing here runs in the IDE. The plugin locates this jar on disk with
+`PathUtil.getJarPathForClass(GrailsRtMarker.class)` and adds it to a launched process's classpath —
+so the **jar's file name is not a contract**, unlike the compiler plugins.
+
+## Contents, and which Grails era uses each
+
+| Entry | Grails versions | Status |
+| --- | --- | --- |
+| `idea-injected-traits.groovy` | 3.0+ | **live** — GORM trait discovery (`TraitInjectorService`) |
+| `META-INF/commands/intellij-command-proxy.groovy` | 3.0+ | **live** — `Grails3TestCommandLineState` |
+| `GrailsRtMarker` | any | **live** — empty class, exists only so the jar can be located |
+| `GrailsIdeaTestListener` | 1.x–2.x | **dormant** |
+| `Agent`, `AddAgentJarToClassPathTransformer`, `ForkListenerTransformer` | 1.x–2.x | **dormant** |
+
+## Grails version support
+
+- **Minimum:** 1.1 (the legacy classes compile against `grails-bootstrap:1.2.0`)
+- **Maximum:** none — the two Groovy resources are used by the Grails 3.0+ code paths
+  (`Grails3GradleCommandExecutor`, `Grails3InstallationCommandExecutor`,
+  `GrailsProjectResolverExtension`, `GrailsTaskManagerExtension`)
+
+Grails 3+ only ever loads the Groovy resources and the marker, none of which touch
+`grails-bootstrap`, so the legacy classes being unloadable there is harmless.
+
+## Dormant code
+
+Both of these arrived already-orphaned in the initial JetBrains import (`3d4bc40`); neither was
+broken by later work.
+
+- **`GrailsExecutionUtils.addAgentJar` has no callers**, so the `-javaagent:` path never executes.
+  The jar *is* a valid agent (`Premain-Class` is declared on the `jar` task), but the capability is
+  unused. The attributes must be set on the task, not via a `META-INF/MANIFEST.MF` resource —
+  Gradle's `Jar` task writes its own manifest and silently shadows such a resource, which is why the
+  packaged jar previously carried only `Manifest-Version: 1.0`.
+- **`GrailsIdeaTestListener`** implements `grails.build.GrailsBuildListener`, the Grails 2 build
+  system API. Its only reference is `GrailsTaskManagerExtension`, gated on a Gradle task named
+  `grails-test-app` that only the old Grails Gradle plugin defines — so it is unreachable on
+  Grails 3+ as well.
+
+## Build
+
+Java 8 bytecode: this runs in whatever JVM the user's Grails build uses.
diff --git a/grails-rt/build.gradle b/grails-rt/build.gradle
index 515f2e5..f237080 100644
--- a/grails-rt/build.gradle
+++ b/grails-rt/build.gradle
@@ -27,3 +27,15 @@ legacyBytecode {
 dependencies {
     compileOnly 'org.grails:grails-bootstrap:1.2.0'
 }
+
+// This jar is a Java agent. The attributes have to be declared on the task: Gradle's Jar task
+// writes its own META-INF/MANIFEST.MF and silently shadows a resource at the same path, which is
+// why the packaged jar carried only "Manifest-Version: 1.0" and would have failed a -javaagent:
+// launch with "Failed to find Premain-Class manifest attribute". Verified against the
+// pre-restructure branch: the resource-based manifest never made it into the jar.
+tasks.named('jar') {
+    manifest.attributes(
+            'Premain-Class': 'org.jetbrains.groovy.grails.rt.Agent',
+            'Can-Redefine-Classes': 'true',
+    )
+}
diff --git a/grails-rt/resources/META-INF/MANIFEST.MF b/grails-rt/resources/META-INF/MANIFEST.MF
deleted file mode 100644
index bfa57e9..0000000
--- a/grails-rt/resources/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,2 +0,0 @@
-Premain-Class: org.jetbrains.groovy.grails.rt.Agent
-Can-Redefine-Classes: true
diff --git a/hibernate/README.md b/hibernate/README.md
new file mode 100644
index 0000000..53d304e
--- /dev/null
+++ b/hibernate/README.md
@@ -0,0 +1,16 @@
+# hibernate
+
+IntelliJ **content module** wiring GORM domain classes into the IDE's Hibernate/JPA support —
+persistence facets, mapping awareness and the Hibernate console.
+
+## Grails version support
+
+- **Minimum / maximum:** no version gating. GORM's Hibernate backing is present across the versions
+  this plugin supports.
+
+## Packaging
+
+Content module `intellij.groovy.grails.hibernate`, shipped as
+`lib/modules/intellij.groovy.grails.hibernate.jar`.
+
+Requires the `com.intellij.hibernate` plugin at runtime.
diff --git a/i18n/README.md b/i18n/README.md
new file mode 100644
index 0000000..021e1c0
--- /dev/null
+++ b/i18n/README.md
@@ -0,0 +1,20 @@
+# i18n
+
+IntelliJ **content module** for Grails message bundles: `i18n/messages*.properties` support,
+extract-to-bundle intentions and inspections for hard-coded strings in GSP and Groovy.
+
+## Grails version support
+
+- **Minimum / maximum:** no version gating. The `grails-app/i18n` message-bundle convention is stable
+  across supported versions.
+
+## Packaging
+
+Content module `intellij.groovy.grails.i18n`, shipped as
+`lib/modules/intellij.groovy.grails.i18n.jar`. Its descriptor also declares
+`<resource-bundle>messages.GrailsBundle</resource-bundle>`.
+
+Requires `com.intellij.java-i18n` and `org.intellij.groovy` at runtime.
+
+This module contains **no Kotlin** — it previously applied the Kotlin plugin with zero Kotlin
+sources, which has been removed.
diff --git a/jps-plugin/README.md b/jps-plugin/README.md
new file mode 100644
index 0000000..a211ea5
--- /dev/null
+++ b/jps-plugin/README.md
@@ -0,0 +1,30 @@
+# jps-plugin
+
+The plugin's participant in the **user's JPS build process** (the external build process IntelliJ
+IDEA runs when compiling a project). Registered through
+`META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension`.
+
+Despite the name this is **shipped product code**, not build tooling for this repository.
+
+This is a **compiler plugin**: its jar name appears literally in
+`<compileServer.plugin classpath="..."/>` in `plugin.xml`, resolved by JPS by file name, so the name
+is pinned by the `org.apache.grails.intellij.build.compiler` convention plugin.
+
+## What it does
+
+`GrailsBuilderExtension` inspects each JPS module's library dependencies for a
+`grails-core-<version>.jar`. When it finds one older than 3.0 it:
+
+1. adds `grails-compiler-patch` to the Groovy compilation classpath, and
+2. registers the AST compilation-unit patchers appropriate to that Grails version.
+
+## Grails version support
+
+- **Minimum:** 1.0
+- **Maximum:** 2.x — `shouldInjectGrails()` returns false for 3.0 and above
+
+The jar is always shipped, but it does nothing for Grails 3+ projects.
+
+## Build
+
+Java 11 bytecode: it is loaded by the JPS build process, not by the IDE.
diff --git a/langInjection/README.md b/langInjection/README.md
new file mode 100644
index 0000000..4952fa7
--- /dev/null
+++ b/langInjection/README.md
@@ -0,0 +1,20 @@
+# langInjection
+
+IntelliJ **content module** providing language injection for Grails code — injecting HQL, SQL and
+other languages into the string literals and GSP positions where Grails expects them.
+
+Configuration is data rather than code: the module ships `META-INF/languageInjections.xml` and points
+at it from its descriptor with `<injectionConfig config="META-INF/languageInjections.xml"/>`.
+
+## Grails version support
+
+- **Minimum / maximum:** no version gating.
+
+## Packaging
+
+Content module `intellij.groovy.grails.langInjection`, shipped as
+`lib/modules/intellij.groovy.grails.langInjection.jar`.
+
+This is the only module whose descriptor depends on a bundled **module** rather than a plugin
+(`<module name="intellij.platform.langInjection"/>`), and the only one with no Java/Kotlin sources at
+all — it is resources only.
diff --git a/maven/README.md b/maven/README.md
new file mode 100644
index 0000000..504f3cf
--- /dev/null
+++ b/maven/README.md
@@ -0,0 +1,27 @@
+# maven
+
+IntelliJ **content module** supporting Grails projects built with **Maven** rather than Grails' own
+build system or Gradle: project import, a Maven-backed application model, run/debug via Maven goals,
+and a Maven node in the Grails project view.
+
+## Grails version support
+
+- **Minimum:** 1.x
+- **Maximum:** effectively 2.x
+
+There is no hard upper gate in the code, but Maven-built Grails is a 1.x/2.x arrangement
+(`grails-maven-plugin`); Grails 3 and later are Gradle-based and are handled by `gradle-tooling`
+instead.
+
+One explicit version gate exists, in `MavenCommandExecutor`: for Grails **< 2.1.0** the `test-app`
+goal mapping is skipped, because those versions must run tests as
+`mvn -Dcommand=test-app -Dargs=<TestClassName> grails:exec` rather than
+`mvn grails:test-app -Dgrails.cli.args=<TestClassName>` (IDEA-105206).
+
+## Packaging
+
+Content module `intellij.groovy.grails.maven`, shipped as
+`lib/modules/intellij.groovy.grails.maven.jar`.
+
+Requires `org.jetbrains.idea.maven` and `com.intellij.java` at runtime — the Java debugger classes
+moved into the java plugin modules in 2026.2.
diff --git a/testFramework/README.md b/testFramework/README.md
index 6846899..29e4cff 100644
--- a/testFramework/README.md
+++ b/testFramework/README.md
@@ -1,2 +1,31 @@
-Contains base test classes for testing Grails plugin as well as a number of Groovy/Gradle Plugin-related 
-test base classes copied from the respective original plugin's test sources.
\ No newline at end of file
+# testFramework
+
+Test fixtures and base classes for **this plugin's own tests**. Not shipped — it is consumed as
+`testImplementation project(':testFramework')` and does not appear in the distribution ZIP.
+
+It also vendors copies of IntelliJ Platform test infrastructure (Gradle importing test cases, SDK
+test helpers, EEL test utilities) that the platform does not publish in a consumable form.
+
+## Grails version support
+
+Not applicable: this code targets the IntelliJ Platform test framework, not Grails.
+
+## Gotcha: test data is resolved from the working directory
+
+`GrailsTestUtil.getTestRootPath()` resolves paths with `new File("")`, i.e. the **test JVM's working
+directory**, which Gradle defaults to the project directory of the project that owns the `test` task:
+
+```java
+public static String getTestRootPath(String directory) {
+  return FileUtil.toSystemIndependentName(new File("").getAbsoluteFile() + directory);
+}
+```
+
+Roughly 25 test classes call `getTestRootPath("/testdata/...")`. `testdata/` must therefore live in
+the project that owns the tests. Do not "fix" this by setting `test { workingDir = ... }` — that
+hides the coupling rather than removing it.
+
+## Build
+
+Kotlin is still required here: 16 of the vendored platform files are Kotlin and are deliberately
+kept as-is so future platform upgrades can be reconciled against upstream.


(grails-intellij-plugin) 05/35: Add release-abort workflow and drop the stale test.idea.home.path default

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit ecfd3f0843451d6f05dac10e2033f0feec66d88f
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 07:20:22 2026 -0400

    Add release-abort workflow and drop the stale test.idea.home.path default
    
    Adds .github/workflows/release-abort.yml, modelled on grails-core's but without
    the Nexus staging drop -- this repository publishes no Maven artifacts, only the
    dist.apache.org distributions and the Marketplace plugin.
    
    The workflow removes the staged artifacts from dist/dev, cancels in-flight
    Release runs, and deletes the GitHub Release and tag. It requires an explicit
    confirmation input, and hard-fails if the version already exists under
    dist/release, since that means the vote passed and the release is public.
    
    Also removes the test.idea.home.path default from gradle.properties, which
    pointed at a JetBrains developer's local checkout (/Users/yann/...). The
    build.gradle.kts block that reads the property stays, so it can still be set
    per-developer with -Ptest.idea.home.path.
---
 .github/workflows/release-abort.yml | 134 ++++++++++++++++++++++++++++++++++++
 RELEASE.md                          |  38 ++++++----
 gradle.properties                   |   4 --
 3 files changed, 158 insertions(+), 18 deletions(-)

diff --git a/.github/workflows/release-abort.yml b/.github/workflows/release-abort.yml
new file mode 100644
index 0000000..eadc290
--- /dev/null
+++ b/.github/workflows/release-abort.yml
@@ -0,0 +1,134 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Withdraws a staged release. This is only valid BEFORE the vote passes: it removes the
+# artifacts staged in dist/dev, cancels the in-flight Release workflow, and deletes the
+# GitHub Release and tag.
+#
+# Once artifacts have been promoted to dist/release and published to the JetBrains
+# Marketplace they are public, and the correct response is a new version rather than an
+# abort. The job hard-fails if it finds the version already promoted.
+#
+# Unlike grails-core's equivalent there is no Nexus staging repository to drop -- this
+# repository publishes no Maven artifacts, only the dist.apache.org distributions and the
+# Marketplace plugin.
+name: "Release - Abort Release"
+on:
+  workflow_dispatch:
+    inputs:
+      release_tag:
+        description: 'Release tag to abort (e.g. v262.0.0)'
+        required: true
+        type: string
+      confirm:
+        description: 'I understand this deletes the staged artifacts, the GitHub Release, and the tag'
+        required: true
+        type: boolean
+        default: false
+permissions:
+  contents: write # to delete the release and tag
+  actions: write # to cancel the in-flight release run
+env:
+  SVN_FOLDER: INTELLIJ
+  SVN_PROJECT: grails
+  TAG: ${{ github.event.inputs.release_tag }}
+jobs:
+  abort:
+    name: "Abort Release"
+    runs-on: ubuntu-latest
+    steps:
+      - name: "🛑 Require explicit confirmation"
+        if: ${{ github.event.inputs.confirm != 'true' }}
+        run: |
+          echo "::error::Re-run with the confirmation box checked to abort ${TAG}."
+          exit 1
+      - name: "📝 Establish release version"
+        run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
+      - name: "🛠️ Install tools"
+        run: sudo apt-get install -y subversion subversion-tools
+      - name: "🔎 Refuse to abort an already-released version"
+        env:
+          SVN_USERNAME: ${{ secrets.SVC_DIST_GRAILS_USERNAME }}
+          SVN_PASSWORD: ${{ secrets.SVC_DIST_GRAILS_PASSWORD }}
+        # A version present under dist/release has passed its vote and is public. Deleting
+        # it would violate ASF release policy, so stop before touching anything.
+        run: |
+          set -euo pipefail
+          RELEASED="https://dist.apache.org/repos/dist/release/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}"
+          if svn ls --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" --non-interactive \
+                    --depth=empty "${RELEASED}" > /dev/null 2>&1; then
+            echo "::error::${VERSION} is already published at ${RELEASED} and cannot be aborted. Release a new version instead."
+            exit 1
+          fi
+          echo "✅ ${VERSION} is not published; safe to abort"
+      - name: "🗑️ Remove staged artifacts from dist.apache.org"
+        env:
+          SVN_USERNAME: ${{ secrets.SVC_DIST_GRAILS_USERNAME }}
+          SVN_PASSWORD: ${{ secrets.SVC_DIST_GRAILS_PASSWORD }}
+        run: |
+          set -euo pipefail
+          DEV="https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}"
+          if svn ls --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" --non-interactive \
+                    --depth=empty "${DEV}" > /dev/null 2>&1; then
+            svnmucc --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" --non-interactive \
+              -m "Remove ${SVN_PROJECT} ${SVN_FOLDER} dev version ${VERSION}" \
+              rm "${DEV}"
+            echo "✅ Removed ${DEV}"
+          else
+            echo "No staged artifacts at ${DEV} — nothing to remove"
+          fi
+      - name: "🛑 Cancel in-flight Release workflow runs"
+        continue-on-error: true
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          REPO: ${{ github.repository }}
+        # The Release workflow is triggered by `release: published`, so filter on that
+        # event to avoid cancelling unrelated runs.
+        run: |
+          set -euo pipefail
+          for status in queued in_progress; do
+            gh api -H 'Accept: application/vnd.github+json' \
+              "/repos/${REPO}/actions/runs?event=release&status=${status}&per_page=100" \
+              --jq '.workflow_runs[].id'
+          done > run-ids.txt
+          while read -r run_id; do
+            [ -n "${run_id}" ] || continue
+            echo "Cancelling run ${run_id}"
+            gh api -X POST "/repos/${REPO}/actions/runs/${run_id}/cancel" || true
+          done < run-ids.txt
+          rm -f run-ids.txt
+      - name: "🗑️ Remove the GitHub Release and tag"
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          REPO: ${{ github.repository }}
+        run: |
+          set -euo pipefail
+          if release_json="$(gh api -H 'Accept: application/vnd.github+json' \
+                                    "/repos/${REPO}/releases/tags/${TAG}" 2>/dev/null)"; then
+            release_id="$(jq -r '.id' <<< "${release_json}")"
+            echo "Deleting GitHub Release ${release_id} for tag ${TAG}"
+            gh api -X DELETE "/repos/${REPO}/releases/${release_id}"
+          else
+            echo "No GitHub Release found for tag ${TAG} — skipping release deletion"
+          fi
+
+          echo "Deleting git tag ${TAG}"
+          gh api -X DELETE "/repos/${REPO}/git/refs/tags/${TAG}" || echo "Tag ${TAG} already absent"
+      - name: "📧 MANUAL - Cancel the vote thread"
+        run: |
+          echo "::group::Manual Follow-up"
+          echo "If a [VOTE] thread was already started for ${TAG}, reply to it on"
+          echo "dev@grails.apache.org with [CANCEL][VOTE] and the reason."
+          echo "::endgroup::"
diff --git a/RELEASE.md b/RELEASE.md
index 406a791..df505c6 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -290,21 +290,31 @@ A release can be withdrawn cleanly **only before the vote passes**. Once artifac
 promoted to `dist/release` and published to the Marketplace, they are public and the
 correct response is a new version, not a rollback.
 
-To abort a staged release:
+Run the **Release - Abort Release** workflow
+([`.github/workflows/release-abort.yml`](.github/workflows/release-abort.yml)) from the
+Actions tab, giving it the release tag and ticking the confirmation box. It will:
 
-1. **Remove the staged distributions** from the ASF dev area:
-   ```bash
-   svn rm -m "Abort Apache Grails IntelliJ Plugin <version>" \
-     https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/<version>
-   ```
-2. **Cancel the `Release` workflow run** if it is still in progress.
-3. **Delete the GitHub Release** (this does not delete the tag).
-4. **Delete the tag:**
-   ```bash
-   git push --delete origin v<version>
-   ```
-5. If a `[VOTE]` thread was already started, reply to it with `[CANCEL][VOTE]` and the
-   reason.
+1. Refuse to continue if the version already exists under
+   `dist/release/grails/INTELLIJ` — that means the vote passed and the release is public.
+2. Remove the staged distributions from `dist/dev/grails/INTELLIJ/<version>`.
+3. Cancel any in-flight `Release` workflow runs.
+4. Delete the GitHub Release and the git tag.
+
+Unlike `grails-core` there is no Nexus staging repository to drop; this repository
+publishes no Maven artifacts.
+
+Afterwards, if a `[VOTE]` thread was already started, reply to it with `[CANCEL][VOTE]`
+and the reason. The workflow prints this reminder at the end.
+
+The equivalent manual steps, if the workflow cannot be used:
+
+```bash
+svn rm -m "Abort Apache Grails IntelliJ Plugin <version>" \
+  https://dist.apache.org/repos/dist/dev/grails/INTELLIJ/<version>
+git push --delete origin v<version>
+```
+
+plus deleting the GitHub Release from the Releases page.
 
 Nothing is published to the JetBrains Marketplace until the post-vote `release` job runs,
 so aborting before that point leaves no public trace.
diff --git a/gradle.properties b/gradle.properties
index 473db41..ba7b62e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,10 +21,6 @@ kotlin.stdlib.default.dependency=false
 # Build-hygiene tool versions
 jacocoVersion=0.8.14
 
-# https://plugins.jetbrains.com/docs/intellij/testing-faq.html#how-to-test-a-jvm-language
-# set path to local intellij-community project
-test.idea.home.path=/Users/yann/idea-ultimate/ultimate/community
-
 # Gradle config
 org.gradle.configuration-cache=true
 org.gradle.caching=true


(grails-intellij-plugin) 20/35: Promote compilers/ to a top-level tier alongside libs/

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 9250ef6ebf3e53605b94695a7ace91f7c3797829
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 12:25:26 2026 -0400

    Promote compilers/ to a top-level tier alongside libs/
    
    The compiler plugins were nested at libs/compiler/* while their namespace was
    already a sibling of lib rather than a child of it. That asymmetry had no upside:
    the directory layout said one thing and the coordinates said another.
    
    They are now a top-level compilers/ tier, so the four tiers line up:
    
      build-logic/      org.apache.grails.intellij.build
      pluginModules/    org.apache.grails.intellij.module
      libs/             org.apache.grails.intellij.lib
      compilers/        org.apache.grails.intellij.compiler
    
    Directory plural, namespace singular, matching libs/ -> ...intellij.lib: the
    directory holds several projects, the namespace names one tier.
    
    Where a jar lands in the distribution is a separate question and unchanged --
    compiler plugins still ship to lib/ alongside the libraries, and only content
    modules go to lib/modules/. Packaging is a detail of the artifact; the tier
    records what the project is.
    
    Flattening also removes the fourth RAT depth glob that libs/compiler/<project>/build
    had required, since Ant patterns do not cross '/'. Back to three levels.
    
    Artifact names are unchanged, verified against the pre-move baseline: the
    convention plugin derives them from projectDir.name, which is still jps-plugin and
    grails-compiler-patch. compileServer.plugin classpath still resolves, bytecode
    levels still 55 and 52, 987 tests / 0 failures, RAT clean, no stale references to
    the old paths anywhere.
---
 .../groovy/org.apache.grails.intellij.build.compiler.gradle  | 12 +++++++-----
 .../main/groovy/org.apache.grails.intellij.build.rat.gradle  |  6 ++----
 build.gradle                                                 |  8 ++++----
 {libs/compiler => compilers}/grails-compiler-patch/README.md |  0
 .../grails-compiler-patch/build.gradle                       |  0
 .../groovy/grails/compiler/EmptyGrailsAwarePatcher.java      |  0
 .../groovy/grails/compiler/Grails2_0_JUnitPatcher.java       |  0
 .../groovy/grails/compiler/GrailsCompilerRtMarker.java       |  0
 .../groovy/grails/compiler/GrailsDomainClassPatcher.java     |  0
 .../jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java |  0
 .../src/main/resources/log4j.properties                      |  0
 {libs/compiler => compilers}/jps-plugin/README.md            |  0
 {libs/compiler => compilers}/jps-plugin/build.gradle         |  2 +-
 .../jetbrains/jps/grails/builder/GrailsBuilderExtension.java |  0
 ...g.jetbrains.jps.incremental.groovy.GroovyBuilderExtension |  0
 settings.gradle                                              |  4 ++--
 16 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
index 2f72f99..c290b1b 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
@@ -20,12 +20,14 @@
 // A compiler plugin: code that runs inside the end user's JPS build process rather than in the
 // IDE. These are the jars named by <compileServer.plugin classpath="..."/> in plugin.xml.
 //
-// It sits at libs/compiler/* on disk, because the source tiers mirror the packaged layout:
-// pluginModules/* are content modules and ship to lib/modules/, while everything under libs/ --
-// compiler plugins included -- ships to lib/.
+// Its own top-level tier, compilers/*, a sibling of libs/ rather than nested inside it, so no
+// project's coordinates are a prefix of another's and the directory layout says the same thing the
+// coordinates do. The directory is plural and the namespace singular, matching libs/ ->
+// org.apache.grails.intellij.lib: the directory holds several projects, the namespace names one tier.
 //
-// Its NAMESPACE is a sibling of lib rather than a child of it (org.apache.grails.intellij.compiler,
-// not ...intellij.lib.compiler), so no project's coordinates are a prefix of another's.
+// These jars still ship to lib/ in the distribution alongside the libraries -- only content
+// modules go to lib/modules/ -- but where a jar lands is a packaging detail, whereas the tier
+// records what the project IS.
 //
 // It is still called out separately because JPS resolves the classpath attribute by file name and
 // silently skips entries it cannot find, so these jar names are part of the plugin's contract and
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
index b862c40..5e7cca7 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
@@ -82,13 +82,11 @@ tasks.named('rat') { task ->
             '**/META-INF/MANIFEST.MF',
             // build outputs are generated and never shipped in the source distro. Anchored to
             // explicit depths rather than "**/build/**" so a source package named "build" would
-            // still be audited. Four levels are needed: the root, the tier directories, the
-            // projects under pluginModules/ and libs/, and the compiler projects one level
-            // deeper again at libs/compiler/<project>/build.
+            // still be audited. Three levels are needed: the root, the tier directories, and
+            // the projects nested one deep under pluginModules/, libs/ and compiler/.
             'build/**',
             '*/build/**',
             '*/*/build/**',
-            '*/*/*/build/**',
     ])
     // Never let a license audit be skipped. upToDateWhen alone is not enough: RatTask is
     // @CacheableTask and org.gradle.caching=true, so a build-cache hit would skip the audit
diff --git a/build.gradle b/build.gradle
index 8ba5ac2..32adff9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -75,18 +75,18 @@ dependencies {
     implementation project(':libs-grails-rt')
 
     // JPS/build
-    runtimeOnly project(':libs-compiler-grails-compiler-patch')
-    runtimeOnly project(':libs-compiler-jps-plugin')
+    runtimeOnly project(':compilers-grails-compiler-patch')
+    runtimeOnly project(':compilers-jps-plugin')
 
     // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
     jacocoAggregation project(':pluginModules-copyright')
     jacocoAggregation project(':pluginModules-coverage')
     jacocoAggregation project(':libs-gradle-tooling')
-    jacocoAggregation project(':libs-compiler-grails-compiler-patch')
+    jacocoAggregation project(':compilers-grails-compiler-patch')
     jacocoAggregation project(':libs-grails-rt')
     jacocoAggregation project(':pluginModules-hibernate')
     jacocoAggregation project(':pluginModules-i18n')
-    jacocoAggregation project(':libs-compiler-jps-plugin')
+    jacocoAggregation project(':compilers-jps-plugin')
     jacocoAggregation project(':pluginModules-langInjection')
     jacocoAggregation project(':pluginModules-maven')
     jacocoAggregation project(':libs-testFramework')
diff --git a/libs/compiler/grails-compiler-patch/README.md b/compilers/grails-compiler-patch/README.md
similarity index 100%
rename from libs/compiler/grails-compiler-patch/README.md
rename to compilers/grails-compiler-patch/README.md
diff --git a/libs/compiler/grails-compiler-patch/build.gradle b/compilers/grails-compiler-patch/build.gradle
similarity index 100%
rename from libs/compiler/grails-compiler-patch/build.gradle
rename to compilers/grails-compiler-patch/build.gradle
diff --git a/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java b/compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java
similarity index 100%
rename from libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java
rename to compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/EmptyGrailsAwarePatcher.java
diff --git a/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java b/compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java
similarity index 100%
rename from libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java
rename to compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/Grails2_0_JUnitPatcher.java
diff --git a/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java b/compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java
similarity index 100%
rename from libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java
rename to compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsCompilerRtMarker.java
diff --git a/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java b/compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java
similarity index 100%
rename from libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java
rename to compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsDomainClassPatcher.java
diff --git a/libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java b/compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java
similarity index 100%
rename from libs/compiler/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java
rename to compilers/grails-compiler-patch/src/main/java/org/jetbrains/groovy/grails/compiler/GrailsJUnitPatcher.java
diff --git a/libs/compiler/grails-compiler-patch/src/main/resources/log4j.properties b/compilers/grails-compiler-patch/src/main/resources/log4j.properties
similarity index 100%
rename from libs/compiler/grails-compiler-patch/src/main/resources/log4j.properties
rename to compilers/grails-compiler-patch/src/main/resources/log4j.properties
diff --git a/libs/compiler/jps-plugin/README.md b/compilers/jps-plugin/README.md
similarity index 100%
rename from libs/compiler/jps-plugin/README.md
rename to compilers/jps-plugin/README.md
diff --git a/libs/compiler/jps-plugin/build.gradle b/compilers/jps-plugin/build.gradle
similarity index 93%
rename from libs/compiler/jps-plugin/build.gradle
rename to compilers/jps-plugin/build.gradle
index f4681e2..0ccc5b5 100644
--- a/libs/compiler/jps-plugin/build.gradle
+++ b/compilers/jps-plugin/build.gradle
@@ -28,5 +28,5 @@ dependencies {
         bundledPlugin 'org.intellij.groovy'
     }
 
-    compileOnly project(':libs-compiler-grails-compiler-patch')
+    compileOnly project(':compilers-grails-compiler-patch')
 }
diff --git a/libs/compiler/jps-plugin/src/main/java/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java b/compilers/jps-plugin/src/main/java/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java
similarity index 100%
rename from libs/compiler/jps-plugin/src/main/java/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java
rename to compilers/jps-plugin/src/main/java/org/jetbrains/jps/grails/builder/GrailsBuilderExtension.java
diff --git a/libs/compiler/jps-plugin/src/main/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension b/compilers/jps-plugin/src/main/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension
similarity index 100%
rename from libs/compiler/jps-plugin/src/main/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension
rename to compilers/jps-plugin/src/main/resources/META-INF/services/org.jetbrains.jps.incremental.groovy.GroovyBuilderExtension
diff --git a/settings.gradle b/settings.gradle
index e0ee8f7..9bc9b90 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,6 +33,6 @@ rootProject.name = 'grails-intellij-plugin'
 }
 
 ['jps-plugin', 'grails-compiler-patch'].each { name ->
-    include "libs-compiler-$name"
-    project(":libs-compiler-$name").projectDir = file("libs/compiler/$name")
+    include "compilers-$name"
+    project(":compilers-$name").projectDir = file("compilers/$name")
 }


(grails-intellij-plugin) 21/35: Move the plugin into plugin/, leaving the root a pure aggregator

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 88f836a96ce79c10cf447eae41eeb2d870ca39d9
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 12:48:20 2026 -0400

    Move the plugin into plugin/, leaving the root a pure aggregator
    
    Step 6, the last relocation. src/ -> plugin/src/main/java, gen/ ->
    plugin/src/main/gen, resources/ -> plugin/src/main/resources, test/ ->
    plugin/src/test/java, testdata/ -> plugin/testdata, and the build file with them.
    The root project now compiles nothing: 42 lines owning only the licence audit and
    the coverage aggregation, both of which must run from the repository root.
    
    testdata/ moved in the same commit as the tests, which it has to: GrailsTestUtil
    resolves it from new File(""), the Test task's working directory, which Gradle
    defaults to the owning project's directory. 987 tests / 0 failures confirms it.
    
    The transitional legacy-layout convention plugin is deleted -- every project is on
    standard layout now, and src/main/gen is the only remaining override.
    
    Three real problems surfaced, all of which built green:
    
    * The ZIP diff caught IPGP moving gradle-tooling, jps-plugin and
      grails-compiler-patch from lib/ into lib/modules/. IPGP infers content modules
      from api/implementation/runtimeOnly project deps that apply .module, and the
      inference is configuration-order dependent -- it only bites once the plugin
      project is configured after them, which is exactly what moving it off the root
      did. The consequence would have been silent: compileServer.plugin resolves its
      classpath from lib/ by file name, and a jar in lib/modules/ without a
      <content><module> entry is never loaded at all. Fixed by modelling these three
      correctly -- they are plain jars bundled into lib/, not IntelliJ modules, so
      they no longer apply .module and pin their jar name instead of composedJar.
    
    * standardDsls packaging was broken earlier in this restructure, by me, when the
      Groovy conversion dropped the resources exclude: the .gdsl ended up inside the
      main jar and absent from the ZIP, so the platform -- which loads GDSL from
      <plugin>/lib/standardDsls/ -- would have silently lost Grails GDSL completion.
      Fixed properly rather than restored: standardDsls now lives outside the resource
      root at plugin/standardDsls, so no exclude is needed and the two cannot drift
      apart again.
    
    * The root lost its repositories along with the java plugin, so RAT could not
      resolve its own tool jar (org.apache.rat:apache-rat) and failed the
      configuration cache. The rat convention plugin now applies base.
    
    Verified after the move: artifact names identical to the pre-move baseline apart
    from standardDsls being added back, lib/modules/ holds exactly the six content
    modules, compileServer.plugin classpath resolves, :rat is still in the check graph,
    and testCodeCoverageReport still writes to the root path coverage.yml uploads.
    
    CI and script paths updated for the ZIP's new location under plugin/build; the rat
    and coverage report paths stay at the root, which is why aggregation was kept there.
---
 .github/workflows/gradle.yml                       |   8 +--
 .github/workflows/release.yml                      |  12 ++--
 ...rg.apache.grails.intellij.build.compiler.gradle |  22 ++++--
 ...pache.grails.intellij.build.intellij-lib.gradle |  18 +++--
 ...ache.grails.intellij.build.legacy-layout.gradle |  39 -----------
 .../org.apache.grails.intellij.build.rat.gradle    |   6 +-
 build.gradle                                       |  75 ++++-----------------
 etc/bin/test-reproducible-build.sh                 |   2 +-
 etc/bin/verify-reproducible.sh                     |   4 +-
 libs/testFramework/build.gradle                    |   2 +-
 build.gradle => plugin/build.gradle                |  15 -----
 .../jetbrains/plugins/grails/GroovyMvcIcons.java   |   0
 .../lang/gsp/lexer/core/_GspDirectiveLexer.java    |   0
 .../grails/lang/gsp/lexer/core/_GspLexer.java      |   0
 .../jetbrains/plugins/grails/GantFileDetector.java |   0
 .../grails/GrailsAttachSourcesProvider.java        |   0
 .../org/jetbrains/plugins/grails/GrailsBundle.java |   0
 .../grails/GrailsCompletionContributor.java        |   0
 .../grails/GrailsConsoleFilterProvider.java        |   0
 .../grails/GrailsFlashMapContentProvider.java      |   0
 .../GrailsGroovyCodeReferenceContributor.java      |   0
 .../grails/GrailsGspReferenceContributor.java      |   0
 .../grails/GrailsHtmlReferenceContributor.java     |   0
 .../grails/GrailsImplicitUsageProvider.java        |   0
 .../GrailsPluginFieldCompletionProvider.java       |   0
 .../GrailsStaticFieldCompletionProvider.java       |   0
 .../jetbrains/plugins/grails/GsonConstants.java    |   0
 .../plugins/grails/GspCompletionContributor.java   |   0
 .../plugins/grails/GspFindUsagesProvider.java      |   0
 .../plugins/grails/GspTemplateContextType.java     |   0
 .../grails/TemplateHighlightErrorFilter.java       |   0
 .../grails/actions/DomainFieldIntention.java       |   0
 .../grails/actions/GenerateViewIntention.java      |   0
 .../plugins/grails/actions/GrailsActionGroup.java  |   0
 .../plugins/grails/actions/GrailsActionUtil.kt     |   0
 .../grails/actions/GrailsConfigureSDKAction.java   |   0
 .../GrailsCreateActionByUsageIntention.java        |   0
 .../grails/actions/GrailsGenerateViewAction.java   |   0
 .../grails/actions/GrailsGoToClassContributor.java |   0
 .../grails/actions/GrailsGotoRelatedProvider.java  |   0
 .../actions/GrailsGroovySourceFolderDetector.java  |   0
 .../grails/actions/GrailsRunCommandAction.java     |   0
 .../plugins/grails/actions/GrailsWarAction.java    |   0
 .../grails/actions/MakeNullableIntention.java      |   0
 .../grails/actions/MakeUniqueIntention.java        |   0
 .../grails/actions/NewGrailsControllerAction.java  |   0
 .../grails/actions/NewGrailsDomainClassAction.java |   0
 .../grails/actions/NewGrailsFilterAction.java      |   0
 .../grails/actions/NewGrailsInterceptorAction.java |   0
 .../grails/actions/NewGrailsScriptAction.java      |   0
 .../grails/actions/NewGrailsServiceAction.java     |   0
 .../grails/actions/NewGrailsTagLibAction.java      |   0
 .../plugins/grails/actions/NewGrailsXXXAction.java |   0
 .../plugins/grails/actions/NewGspAction.java       |   0
 .../grails/actions/UpdateGrailsSettingsAction.java |   0
 .../grails/addins/GrailsIntegrationUtil.java       |   0
 .../grails/addins/js/CssIntegrationUtil.java       |   0
 .../grails/addins/js/GrailsJavaScriptInjector.java |   0
 .../grails/addins/js/GspJsInjectionTagBean.java    |   0
 .../addins/js/JavaScriptIntegrationUtil.java       |   0
 .../grails/artefact/api/ArtefactHandlers.java      |   0
 .../grails/artefact/api/GrailsArtefactHandler.java |   0
 .../api/GrailsDisplayableArtefactHandler.java      |   0
 .../plugins/grails/artefact/api/HandlerCache.java  |   0
 .../plugins/grails/artefact/api/IconOwner.java     |   0
 .../artefact/impl/ControllerArtefactHandler.java   |   0
 .../artefact/impl/DomainArtefactHandler.java       |   0
 .../artefact/impl/FilterArtefactHandler.java       |   0
 .../grails/artefact/impl/GrailsArtefacts.java      |   0
 .../artefact/impl/InterceptorArtefactHandler.java  |   0
 .../artefact/impl/ServiceArtefactHandler.java      |   0
 .../artefact/impl/TaglibArtefactHandler.java       |   0
 .../grails/artefact/impl/controllers/functions.kt  |   0
 .../grails/commands/Grails3CommandProvider.java    |   0
 .../commands/GrailsCommandCompletionUtil.java      |   0
 .../grails/commands/GrailsCommandProvider.java     |   0
 .../grails/commands/OldGrailsCommandProvider.java  |   0
 .../grails/config/AbstractConfigSupport.java       |   0
 .../BuildConfigClosureMemberContributor.java       |   0
 .../config/BuildConfigMemberContributor.java       |   0
 .../plugins/grails/config/BuildConfigSupport.java  |   0
 .../grails/config/ConfigMemberContributor.java     |   0
 .../plugins/grails/config/ConfigSupport.java       |   0
 .../plugins/grails/config/DataSourcesSupport.java  |   0
 .../grails/config/GradleParentProjectForm.form     |   0
 .../grails/config/GradleParentProjectForm.java     |   0
 .../grails/config/GrailsAbstractConfigSupport.java |   0
 .../config/GrailsConfigUnresolvedFileFilter.java   |   0
 .../plugins/grails/config/GrailsConfigUtils.java   |   0
 .../plugins/grails/config/GrailsConstants.java     |   0
 .../GrailsEnvironmentClosureMemberContributor.java |   0
 .../plugins/grails/config/GrailsFramework.java     |   0
 .../config/GrailsGradleAwareModuleBuilder.kt       |   0
 .../plugins/grails/config/GrailsIgnoredProvider.kt |   0
 .../config/GrailsLibraryPresentationProvider.java  |   0
 .../plugins/grails/config/GrailsModuleBuilder.java |   0
 .../grails/config/GrailsModuleStructureUtil.java   |   0
 .../grails/config/GrailsOptionsWizardStep.form     |   0
 .../grails/config/GrailsOptionsWizardStep.java     |   0
 .../GrailsPluginConfigMethodContributor.java       |   0
 .../GrailsPluginNameCompletionContributor.java     |   0
 .../GrailsPluginWebHelpReferenceProvider.java      |   0
 .../config/GrailsProjectStructureDetector.java     |   0
 .../grails/config/GrailsPropertiesFileCache.java   |   0
 .../grails/config/GrailsSettingSynchronizer.java   |   0
 .../plugins/grails/config/GrailsSettings.java      |   0
 .../grails/config/GrailsSettingsService.java       |   0
 .../plugins/grails/config/GrailsStructure.java     |   0
 .../plugins/grails/config/GroovyConfigReader.java  |   0
 .../config/PrintGrailsSettingsConstants.java       |   0
 .../grails/database/GrailsDataSourceDetector.java  |   0
 .../plugins/grails/editor/GenerateTestsAction.java |   0
 .../plugins/grails/editor/GrailsEditorDecorator.kt |   0
 .../grails/editor/ViewGrailsEditorToolbarAction.kt |   0
 .../selection/GrailsDependencySelectioner.java     |   0
 .../GrailsGroovyInjectionSelectioner.java          |   0
 .../grails/editor/toolbar/GrailsGenerateActions.kt |   0
 .../editor/toolbar/GrailsGoToArtefactActionBase.kt |   0
 .../editor/toolbar/GrailsToolbarActionBase.kt      |   0
 .../grails/editor/toolbar/GrailsToolbarActions.kt  |   0
 .../editor/toolbar/GrailsToolbarTargetAction.kt    |   0
 .../editor/toolbar/GrailsToolbarVfileAction.kt     |   0
 .../feature/forge/GrailsForgeModuleBuilder.kt      |   0
 .../grails/feature/linkable/namedArguments.kt      |   0
 .../plugins/grails/feature/linkable/transform.kt   |   0
 .../plugins/grails/fileType/GspFileType.java       |   0
 .../plugins/grails/gorm/GormClassNames.java        |   0
 .../jetbrains/plugins/grails/gorm/gormVersion.kt   |   0
 .../plugins/grails/gradle/GrailsModuleData.java    |   0
 .../gradle/GrailsModuleGradleDataService.java      |   0
 .../gradle/GrailsProjectResolverExtension.java     |   0
 .../grails/gradle/GrailsTaskManagerExtension.java  |   0
 .../GrailsStreamingJsonBuilderContributor.java     |   0
 .../grails/gson/GsonControllerReferences.kt        |   0
 .../grails/gson/GsonExpectedPackageProvider.kt     |   0
 .../plugins/grails/gson/GsonImportContributor.kt   |   0
 .../grails/gson/GsonModelArgumentsProvider.kt      |   0
 .../gson/GsonModelFieldArgumentDescriptor.kt       |   0
 .../grails/gson/GsonPositionManagerDelegate.kt     |   0
 .../grails/gson/GsonReferenceContributor.kt        |   0
 .../plugins/grails/gson/GsonRenderReferences.kt    |   0
 .../grails/gson/GsonTemplateRendererContributor.kt |   0
 .../grails/gson/GsonTransformationSupport.kt       |   0
 .../org/jetbrains/plugins/grails/gson/GsonUtils.kt |   0
 .../inspections/GrailsMethodMayBeStaticFilter.java |   0
 .../grails/inspections/GspAndGroovyInspection.java |   0
 .../grails/inspections/GspInspectionUtil.java      |   0
 .../grails/inspections/GspSuppressionProvider.java |   0
 .../grails/lang/DirtyCheckableContributor.kt       |   0
 .../plugins/grails/lang/GormTraitContributor.kt    |   0
 .../lang/Grails3TraitInjectorContributor.java      |   0
 .../plugins/grails/lang/gsp/GspCssInjector.java    |   0
 .../plugins/grails/lang/gsp/GspDirectiveKind.java  |   0
 .../plugins/grails/lang/gsp/GspErrorFilter.java    |   0
 .../grails/lang/gsp/GspFileViewProvider.java       |   0
 .../lang/gsp/GspFileviewProviderFactory.java       |   0
 .../plugins/grails/lang/gsp/GspLanguage.java       |   0
 .../lang/gsp/GspTagDocumentationProvider.java      |   0
 .../plugins/grails/lang/gsp/GspTagInspection.java  |   0
 .../gsp/completion/GspXmlFileNSInfoProvider.java   |   0
 .../lang/gsp/debug/GspCodeFragmentFactory.java     |   0
 .../grails/lang/gsp/debug/GspPositionManager.java  |   0
 .../lang/gsp/debug/GspPositionManagerFactory.java  |   0
 .../gsp/editor/actions/GspEditorActionsUtil.java   |   0
 .../lang/gsp/editor/actions/GspEnterHandler.java   |   0
 .../lang/gsp/editor/actions/GspTypedHandler.java   |   0
 .../grails/lang/gsp/folding/GspFoldingBuilder.java |   0
 .../lang/gsp/formatter/AbstractGspBlock.java       |   0
 .../lang/gsp/formatter/ForeignLanguageBlock.java   |   0
 .../grails/lang/gsp/formatter/GspBlock.java        |   0
 .../lang/gsp/formatter/GspBlockGenerator.java      |   0
 .../lang/gsp/formatter/GspFormattingHelper.java    |   0
 .../gsp/formatter/GspFormattingModelBuilder.java   |   0
 .../grails/lang/gsp/formatter/GspGroovyBlock.java  |   0
 .../grails/lang/gsp/formatter/GspHtmlBlock.java    |   0
 .../lang/gsp/formatter/GspTextSyntheticBlock.java  |   0
 .../formatter/processors/GspIndentProcessor.java   |   0
 .../formatter/processors/GspSpacingProcessor.java  |   0
 .../settings/GspCodeStyleSettingsProvider.java     |   0
 .../GspLanguageCodeStyleSettingsProvider.java      |   0
 .../grails/lang/gsp/gspIndex/GspIncludeIndex.java  |   0
 .../grails/lang/gsp/gspIndex/GspIncludeInfo.java   |   0
 .../grails/lang/gsp/highlighter/GspCommenter.java  |   0
 .../gsp/highlighter/GspDirectiveHighlighter.java   |   0
 .../highlighter/GspDirectiveHighlightingLexer.java |   0
 .../lang/gsp/highlighter/GspEditorHighlighter.java |   0
 .../highlighter/GspEditorHighlighterProvider.kt    |   0
 .../lang/gsp/highlighter/GspFileBraceMatcher.java  |   0
 .../gsp/highlighter/GspIndexPatternBuilder.java    |   0
 .../gsp/highlighter/GspPairedBraceMatcher.java     |   0
 .../lang/gsp/highlighter/GspSyntaxHighlighter.java |   0
 .../lang/gsp/lexer/GspDirectiveFlexLexer.java      |   0
 .../grails/lang/gsp/lexer/GspFlexLexer.java        |   0
 .../grails/lang/gsp/lexer/GspTokenTypesEx.java     |   0
 .../grails/lang/gsp/lexer/IGspElementType.java     |   0
 .../grails/lang/gsp/lexer/core/GspTokenTypes.java  |   0
 .../plugins/grails/lang/gsp/lexer/core/gsp.flex    |   0
 .../grails/lang/gsp/lexer/core/gsp_directive.flex  |   0
 .../grails/lang/gsp/parsing/GspElementTypes.java   |   0
 .../lang/gsp/parsing/GspGroovyElementTypes.java    |   0
 .../grails/lang/gsp/parsing/GspPsiCreator.java     |   0
 .../parsing/composite/GspCompositeElementType.java |   0
 .../gsp/parsing/groovy/GspAwareGroovyParser.java   |   0
 .../lang/gsp/parsing/groovy/GspGroovyParser.java   |   0
 .../gsp/parsing/groovy/GspTemplateStmtParsing.java |   0
 .../chameleons/GroovyDeclarationsElementType.kt    |   0
 .../GroovyDeclarationsInGspFileRoot.java           |   0
 .../chameleons/GroovyExpressionElementType.kt      |   0
 .../chameleons/GroovyMapAttributeValueType.kt      |   0
 .../groovy/chameleons/GspGroovyLazyElementType.kt  |   0
 .../gsp/parsing/groovy/lexer/GspGroovyLexer.java   |   0
 .../grails/lang/gsp/parsing/gsp/GrailsTag.java     |   0
 .../grails/lang/gsp/parsing/gsp/GspParser.java     |   0
 .../lang/gsp/parsing/gsp/GspParserDefinition.java  |   0
 .../gsp/chameleons/GspDirectiveElement.java        |   0
 .../lang/gsp/parsing/gsp/lexer/GspLexer.java       |   0
 .../gsp/parsing/html/GspHtmlTemplateRootType.java  |   0
 .../gsp/parsing/html/elements/GspXmlDocument.java  |   0
 .../parsing/outers/GspGroovyCodeElementType.java   |   0
 .../gsp/parsing/outers/GspInHtmlElementType.java   |   0
 .../parsing/outers/GspTemplateDataElementType.java |   0
 .../grails/lang/gsp/psi/GspPsiElementFactory.java  |   0
 .../lang/gsp/psi/GspPsiElementFactoryImpl.java     |   0
 .../plugins/grails/lang/gsp/psi/GspPsiUtil.java    |   0
 .../grails/lang/gsp/psi/groovy/GspLazyElement.java |   0
 .../grails/lang/gsp/psi/groovy/api/GrGspClass.java |   0
 .../gsp/psi/groovy/api/GrGspDeclarationHolder.java |   0
 .../gsp/psi/groovy/api/GrGspExprInjection.java     |   0
 .../lang/gsp/psi/groovy/api/GrGspRunBlock.java     |   0
 .../lang/gsp/psi/groovy/api/GrGspRunMethod.java    |   0
 .../gsp/psi/groovy/api/GrMapAttributeValue.java    |   0
 .../lang/gsp/psi/groovy/api/GspGroovyFile.java     |   0
 .../gsp/psi/groovy/api/GspOuterGroovyElement.java  |   0
 .../gsp/psi/groovy/api/GspOuterHtmlElement.java    |   0
 .../groovy/api/template/GspTemplateStatement.java  |   0
 .../lang/gsp/psi/groovy/impl/GrGspClassImpl.java   |   0
 .../groovy/impl/GrGspDeclarationHolderImpl.java    |   0
 .../psi/groovy/impl/GrGspExprInjectionImpl.java    |   0
 .../gsp/psi/groovy/impl/GrGspRunBlockImpl.java     |   0
 .../gsp/psi/groovy/impl/GrGspRunMethodImpl.java    |   0
 .../gsp/psi/groovy/impl/GrMapAttrValueImpl.java    |   0
 .../gsp/psi/groovy/impl/GspGroovyFileImpl.java     |   0
 .../gsp/psi/groovy/impl/GspModelVariableModel.java |   0
 .../psi/groovy/impl/GspOuterGroovyElementImpl.java |   0
 .../psi/groovy/impl/GspOuterHtmlElementImpl.java   |   0
 .../impl/GspOuterHtmlElementManipulator.java       |   0
 .../lang/gsp/psi/groovy/impl/GspPageSkeleton.java  |   0
 .../impl/ModelVariableDeclarationSearcher.java     |   0
 .../impl/template/GspTemplateStatementImpl.java    |   0
 .../lang/gsp/psi/gsp/api/GspDeclarationTag.java    |   0
 .../lang/gsp/psi/gsp/api/GspExpressionTag.java     |   0
 .../grails/lang/gsp/psi/gsp/api/GspFile.java       |   0
 .../lang/gsp/psi/gsp/api/GspScriptletTag.java      |   0
 .../grails/lang/gsp/psi/gsp/api/GspTag.java        |   0
 .../grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java |   0
 .../gsp/psi/gsp/api/directive/GspDirective.java    |   0
 .../gsp/api/directive/GspDirectiveAttribute.java   |   0
 .../api/directive/GspDirectiveAttributeValue.java  |   0
 .../lang/gsp/psi/gsp/api/gtag/GspAttribute.java    |   0
 .../gsp/psi/gsp/api/gtag/GspAttributeValue.java    |   0
 .../lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java    |   0
 .../gsp/psi/gsp/api/gtag/GspTaggedElement.java     |   0
 .../lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java   |   0
 .../gsp/psi/gsp/impl/GspDeclarationTagImpl.java    |   0
 .../lang/gsp/psi/gsp/impl/GspExprTagImpl.java      |   0
 .../grails/lang/gsp/psi/gsp/impl/GspFileImpl.java  |   0
 .../lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java |   0
 .../lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java   |   0
 .../impl/directive/GspDirectiveAttributeImpl.java  |   0
 .../directive/GspDirectiveAttributeValueImpl.java  |   0
 .../psi/gsp/impl/directive/GspDirectiveImpl.java   |   0
 .../gsp/psi/gsp/impl/gtag/GspAttributeImpl.java    |   0
 .../psi/gsp/impl/gtag/GspAttributeValueImpl.java   |   0
 .../impl/gtag/GspContentTagDescriptorProvider.java |   0
 .../gsp/impl/gtag/GspElementDescriptorBase.java    |   0
 .../gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java    |   0
 .../gsp/impl/gtag/GspLinkElementDescriptor.java    |   0
 .../gsp/impl/gtag/GspLinkNamespaceDescriptor.java  |   0
 .../psi/gsp/impl/gtag/GspNamespaceDescriptor.java  |   0
 .../impl/gtag/GspPropertyElementDescriptor.java    |   0
 .../psi/gsp/impl/gtag/GspSdkElementDescriptor.java |   0
 .../psi/gsp/impl/gtag/GspTagDescriptorService.java |   0
 .../gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java  |   0
 .../gtag/GspTagVariableDeclarationSearcher.java    |   0
 .../gsp/impl/gtag/GspTmplNamespaceDescriptor.java  |   0
 .../gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java   |   0
 .../DefaultCodecDirectiveReferenceProvider.java    |   0
 .../GspCssClassReferenceContributor.java           |   0
 .../lang/gsp/psi/html/api/GspHtmlOuterElement.java |   0
 .../grails/lang/gsp/psi/html/api/GspLikeFile.java  |   0
 .../lang/gsp/psi/html/impl/GspHtmlFileImpl.java    |   0
 .../gsp/psi/html/impl/GspHtmlOuterElementImpl.java |   0
 .../gsp/psi/html/impl/GspHtmlXmlExtension.java     |   0
 .../grails/lang/gsp/psi/impl/gspImportUtil.kt      |   0
 .../taglib/BuiltInTagReferenceSearcher.java        |   0
 .../taglib/GrailsTaglibNamedArgumentProvider.java  |   0
 .../lang/gsp/resolve/taglib/GspTagLibUtil.java     |   0
 .../resolve/taglib/TagLibNamespaceDescriptor.java  |   0
 .../gsp/search/GspControllerReferenceSearcher.java |   0
 .../lang/gsp/search/GspPageReferenceSearcher.java  |   0
 .../lang/gsp/search/JspPageReferenceSearcher.java  |   0
 .../plugins/grails/lang/gsp/util/GspUtil.java      |   0
 .../perspectives/DomainClassNodeRenderer.java      |   0
 .../perspectives/DomainClassesRelationsEditor.java |   0
 .../DomainClassesRelationsEditorProvider.java      |   0
 .../PerspectiveFileEditorComponent.java            |   0
 .../perspectives/create/CreateNewRelation.form     |   0
 .../perspectives/create/CreateNewRelation.java     |   0
 .../create/RelationsCreationsProvider.java         |   0
 .../delete/RelationDeleteProvider.java             |   0
 .../DataModelAndSelectionModificationTracker.java  |   0
 .../graph/DomainClassDependencyPresentation.java   |   0
 .../grails/perspectives/graph/DomainClassNode.java |   0
 .../graph/DomainClassRelationsInfo.java            |   0
 .../graph/DomainClassesRelationsDataModel.java     |   0
 .../assetPipeline/AssetScriptInjector.kt           |   0
 .../pluginSupport/assetPipeline/references.kt      |   0
 .../GrailsBuildTestDataMemberProvider.java         |   0
 .../resources/GrailsResourceModuleReference.java   |   0
 .../resources/GrailsResourceReferenceSearcher.java |   0
 .../GrailsResourcesClosureMethodContributor.java   |   0
 .../GrailsResourcesPomDeclarationSearcher.java     |   0
 .../GrailsResourcesReferenceProvider.java          |   0
 .../resources/GrailsResourcesUtil.java             |   0
 .../seachable/GrailsSearchableMemberProvider.java  |   0
 .../GrailsSearchableReturnTypeCalculator.java      |   0
 .../seachable/GrailsSearchableUtil.java            |   0
 .../SearchableFieldReferenceProvider.java          |   0
 .../SearchableMemberMethodContributor.java         |   0
 .../GrailsShiroAccessControlMethodProvider.java    |   0
 .../shiro/GrailsShiroReferenceProvider.java        |   0
 .../pluginSupport/spock/GrailsSpockEnhancer.java   |   0
 .../WebFlowEventDefinitionMethodContributor.java   |   0
 .../webflow/WebFlowMethodContributor.java          |   0
 .../webflow/WebFlowPomDeclarationSearcher.java     |   0
 .../webflow/WebFlowStateNameReferenceProvider.java |   0
 .../grails/pluginSupport/webflow/WebFlowUtils.java |   0
 .../plugins/grails/plugins/descriptors.kt          |   0
 .../org/jetbrains/plugins/grails/plugins/impl.kt   |   0
 .../grails/projectView/GrailsIconProvider.java     |   0
 .../grails/projectView/GrailsPluginsNode.java      |   0
 .../grails/projectView/GrailsProjectViewPane.java  |   0
 .../grails/projectView/GrailsProjectViewPanes.java |   0
 .../projectView/GrailsTreeStructureProvider.java   |   0
 .../plugins/grails/projectView/NodeWeights.java    |   0
 .../projectView/api/GrailsSingleNodeProvider.java  |   0
 .../projectView/api/GrailsViewNodeProvider.java    |   0
 .../projectView/impl/Grails3NodeProvider.java      |   0
 .../projectView/impl/GrailsAppNodeProvider.java    |   0
 .../projectView/impl/GrailsNodeComparator.java     |   0
 .../grails/projectView/impl/GrailsViewItems.java   |   0
 .../projectView/impl/OldGrailsNodeProvider.java    |   0
 .../grails/projectView/nodes/CompactedFqn.java     |   0
 .../grails/projectView/nodes/GrailsActionNode.java |   0
 .../projectView/nodes/GrailsApplicationNode.java   |   0
 .../nodes/GrailsArtefactHandlerNode.java           |   0
 .../nodes/GrailsArtefactHandlerNodeBase.java       |   0
 .../nodes/GrailsArtefactPackageNode.java           |   0
 .../projectView/nodes/GrailsControllerNode.java    |   0
 .../projectView/nodes/GrailsFlatPackageNode.java   |   0
 .../grails/projectView/nodes/GrailsNodes.java      |   0
 .../projectView/nodes/GrailsPackageValue.java      |   0
 .../grails/projectView/nodes/GrailsPluginNode.java |   0
 .../projectView/nodes/GrailsPsiDirectoryNode.java  |   0
 .../nodes/GrailsRegularPackageNode.java            |   0
 .../grails/projectView/nodes/GrailsRootNode.java   |   0
 .../projectView/nodes/OldGrailsPluginsNode.java    |   0
 .../nodes/OtherGrailsAppSourcesNode.java           |   0
 .../grails/projectView/nodes/TreeNodeUtil.java     |   0
 .../references/CodecNonCodeMemberContributor.java  |   0
 .../grails/references/EnhancesTraitsCollector.kt   |   0
 .../GrailsArtifactNonCodeMemberProcessor.java      |   0
 .../references/GrailsArtifactRenameProcessor.java  |   0
 .../references/GrailsClosureMemberContributor.java |   0
 ...GrailsMethodNamedArgumentReferenceProvider.java |   0
 .../GrailsResourceClassMemberContributor.java      |   0
 .../plugins/grails/references/MemberProvider.java  |   0
 .../grails/references/TraitInjectorService.kt      |   0
 .../bootstrap/GrailsBootStrapMemberProvider.java   |   0
 .../BuildConfigFileReferenceProvider.java          |   0
 .../common/ArtifactFieldReferenceSearcher.java     |   0
 .../ArtifactPropertyGetterReferenceSearcher.java   |   0
 .../grails/references/common/CodecReference.java   |   0
 .../references/common/ContentTypeReference.java    |   0
 .../common/ContentTypeReferenceProvider.java       |   0
 .../common/ContextPathReferenceProvider.java       |   0
 .../common/GrailsEncodingReferenceProvider.java    |   0
 .../common/GrailsFileReferenceSetBase.java         |   0
 .../common/GrailsLayoutFileReferenceSet.java       |   0
 .../common/GrailsRootBasedFileReferenceSet.java    |   0
 .../common/GrailsViewFileReferenceSet.java         |   0
 .../common/GroovyGspAttributeWrapper.java          |   0
 .../references/common/GroovyGspTagWrapper.java     |   0
 .../references/common/GspAttributeWrapper.java     |   0
 .../grails/references/common/GspTagWrapper.java    |   0
 .../grails/references/common/PluginReference.java  |   0
 .../common/PluginSupportFileReferenceSet.java      |   0
 .../ResourceDirAttributeFileReferenceSet.java      |   0
 .../common/TemplateFileReferenceSet.java           |   0
 .../common/WebAppFolderFileReferenceSet.java       |   0
 .../references/common/XmlGspAttributeWrapper.java  |   0
 .../grails/references/common/XmlGspTagWrapper.java |   0
 .../references/constraints/GormRegexInjector.java  |   0
 ...GrailsConstraintGroupNamedArgumentProvider.java |   0
 .../GrailsConstraintNamedArgumentProvider.java     |   0
 .../GrailsConstraintsClosureMemberProvider.java    |   0
 ...railsConstraintsGroupClosureMemberProvider.java |   0
 .../GrailsConstraintsSharedReferenceProvider.java  |   0
 .../constraints/GrailsConstraintsUtil.java         |   0
 .../GrailsValidationMethodsContributor.java        |   0
 .../GrailsValidatorParamTypeEnhancer.java          |   0
 .../references/controller/ActionReference.java     |   0
 .../controller/ActionRenameProcessor.java          |   0
 .../ControllerAllowedMethodCompletionProvider.java |   0
 .../ControllerAllowedMethodReferenceProvider.java  |   0
 .../ControllerFieldReferenceProvider.java          |   0
 .../ControllerLayoutReferenceProvider.java         |   0
 .../controller/ControllerMembersProvider.java      |   0
 .../references/controller/ControllerReference.java |   0
 .../controller/ControllerReferenceProvider.java    |   0
 .../controller/ControllerRenameProcessor.java      |   0
 .../ControllerRenderTemplateRefProvider.java       |   0
 .../controller/GrailsNamespaceReference.java       |   0
 .../GrailsWithFormatClosureMethodContributor.java  |   0
 .../controller/GspTagMethodComparator.java         |   0
 .../controller/GspTagMethodOverloadResolver.kt     |   0
 .../controller/HttpRequestMemberContributor.java   |   0
 .../controller/HttpResponseMemberContributor.java  |   0
 .../grails/references/domain/DomainClassUtils.java |   0
 .../grails/references/domain/DomainDescriptor.java |   0
 .../domain/DomainFieldRenameProcessor.java         |   0
 .../references/domain/DomainMembersProvider.java   |   0
 .../DomainStaticMemberCompletionProvider.java      |   0
 .../domain/GormAddToNamedArgumentProvider.java     |   0
 .../domain/GormAstTransformationContributor.java   |   0
 .../domain/GormConstraintMethodProvider.java       |   0
 .../GormDynamicFinderCompletionProvider.java       |   0
 .../GormEmbeddedPropertyListReferenceProvider.java |   0
 .../domain/GormFetchModeCompletionProvider.java    |   0
 .../domain/GormFetchModeReferenceProvider.java     |   0
 ...GormGetPersistentValueReturnTypeCalculator.java |   0
 .../domain/GormMappingMethodProvider.java          |   0
 .../domain/GormNamedArgumentReferenceProvider.java |   0
 .../GormPersistentPropertiesNamedArgProvider.java  |   0
 ...rmPropertiesListReferenceReferenceProvider.java |   0
 .../GormPropertyConstraintReferenceProvider.java   |   0
 .../references/domain/GormPropertyReference.java   |   0
 .../domain/GormPropertyReferenceUnique.java        |   0
 .../domain/GormSortNamedArgumentProvider.java      |   0
 .../GormUniqueConstraintReferenceProvider.java     |   0
 .../grails/references/domain/GormUtils.java        |   0
 .../GrailsHasManyBelongsToReferencesProvider.java  |   0
 ...lsHasManyBelongsToValuesReferencesProvider.java |   0
 .../GrailsSyntheticFieldDeclarationSearcher.java   |   0
 .../CriteriaBuilderImplicitMemberContributor.java  |   0
 .../CriteriaBuilderNamedArgumentProvider.java      |   0
 .../domain/criteria/CriteriaBuilderUtil.java       |   0
 .../criteria/CriteriaClosureMemberContributor.java |   0
 .../CriteriaPropertyReferenceProvider.java         |   0
 .../criteria/CriteriaReturnTypeCalculator.java     |   0
 .../DetachedCriteriaClosureMemberProvider.java     |   0
 .../DetachedCriteriaDelegatesToProvider.kt         |   0
 .../DetachedCriteriaMemberProvider.java            |   0
 .../DetachedCriteriaPropertyNamedArgProvider.java  |   0
 .../DetachedCriteriaReferenceProvider.java         |   0
 .../detachedCriteria/DetachedCriteriaUtil.java     |   0
 .../GormNamedQueryCallTypeCalculator.java          |   0
 .../GormNamedQueryDeclarationSearcher.java         |   0
 .../NamedCriteriaProxyMembersContributor.java      |   0
 .../NamedCriteriaProxyNamedArgumentProvider.java   |   0
 .../NamedCriteriaProxyTypeCalculator.java          |   0
 .../domain/namedQuery/NamedQueryDescriptor.java    |   0
 .../domain/persistent/GormBasicAttribute.java      |   0
 .../domain/persistent/GormCollectionAttribute.java |   0
 .../domain/persistent/GormEmbeddedAttribute.java   |   0
 .../references/domain/persistent/GormEntity.java   |   0
 .../domain/persistent/GormIdAttribute.java         |   0
 .../domain/persistent/GormPersistenceMapping.java  |   0
 .../domain/persistent/GormPersistentAttribute.java |   0
 .../filter/FilterClosureMemberProvider.java        |   0
 .../references/filter/FilterMemberProvider.java    |   0
 .../references/filter/FilterReferenceProvider.java |   0
 .../GrailsFilterClosureMemberContributor.java      |   0
 .../grails/references/filter/GrailsFilterUtil.java |   0
 .../references/jobs/JobClosureMethodProvider.java  |   0
 .../grails/references/jobs/JobsMemberProvider.java |   0
 ...ailsTemplateNamedArgumentReferenceProvider.java |   0
 .../GrailsPluginClassMemberContributor.java        |   0
 .../GrailsPluginExcludeReferenceProvider.java      |   0
 .../GrailsPluginListReferenceProvider.java         |   0
 .../pluginClass/GrailsPluginParameterEnhancer.java |   0
 .../providers/GspImportListReferenceProvider.java  |   0
 .../tagSupport/CodecAttributeSupport.java          |   0
 .../tagSupport/GspActionAttributeSupport.java      |   0
 .../GspApplyLayoutNameAttributeSupport.java        |   0
 .../tagSupport/GspContextPathAttributeSupport.java |   0
 .../tagSupport/GspControllerAttributeSupport.java  |   0
 .../tagSupport/GspFieldValueTagSupport.java        |   0
 .../tagSupport/GspIncludeViewAttributeSupport.java |   0
 .../tagSupport/GspLayoutReferenceProvider.java     |   0
 .../tagSupport/GspMappingAttributeSupport.java     |   0
 .../references/tagSupport/GspMetaTagSupport.java   |   0
 .../tagSupport/GspPluginAttributeSupport.java      |   0
 .../tagSupport/GspRResourceModuleAttrSupport.java  |   0
 .../GspRResourceRequireTagModulesAttrSupport.java  |   0
 .../GspRResourceUriAttributeSupport.java           |   0
 .../GspResourceContextPathAttributeSupport.java    |   0
 .../tagSupport/GspResourceDirAttributeSupport.java |   0
 .../GspResourceFileAttributeSupport.java           |   0
 .../references/tagSupport/GspSelectTagSupport.java |   0
 .../tagSupport/GspSrcJsAttributeSupport.java       |   0
 .../GspTagSupportGspReferenceProvider.java         |   0
 .../tagSupport/GspTemplateAttributeSupport.java    |   0
 .../tagSupport/GspUpdateAttributeSupport.java      |   0
 .../tagSupport/TagAttributeReferenceProvider.java  |   0
 .../taglib/TaglibClosureParamEnhancer.java         |   0
 .../references/taglib/TaglibMembersProvider.java   |   0
 .../UrlMappingConstraintClosureMemberProvider.java |   0
 .../urlMappings/UrlMappingMemberProvider.java      |   0
 .../UrlMappingNamedArgumentProvider.java           |   0
 .../urlMappings/UrlMappingReferenceProvider.java   |   0
 .../UrlMappingUnresolvedHighlightingFilter.java    |   0
 .../references/urlMappings/UrlMappingUtil.java     |   0
 .../plugins/grails/references/util/CodecUtil.java  |   0
 .../grails/references/util/PsiFieldReference.java  |   0
 .../runner/GrailsAppEngineDebuggerRunner.java      |   0
 .../grails/runner/GrailsCommandExecutor.java       |   0
 .../grails/runner/GrailsCommandExecutorUtil.java   |   0
 .../grails/runner/GrailsCommandLineExecutor.java   |   0
 .../grails/runner/GrailsConfigurationFactory.java  |   0
 .../plugins/grails/runner/GrailsConsole.java       |   0
 .../plugins/grails/runner/GrailsConsoleState.kt    |   0
 .../plugins/grails/runner/GrailsDebuggerRunner.kt  |   0
 .../grails/runner/GrailsInstallationExecutor.java  |   0
 .../runner/GrailsRerunFailedTestsAction.java       |   0
 .../grails/runner/GrailsRunConfiguration.java      |   0
 .../runner/GrailsRunConfigurationExtension.java    |   0
 .../grails/runner/GrailsRunConfigurationType.java  |   0
 .../GrailsScriptRunConfigurationProducer.java      |   0
 ...atableRunConfigurationWithCommonParameters.java |   0
 .../runner/impl/BaseGrailsCommandLineState.kt      |   0
 .../runner/impl/Grails3GradleCommandExecutor.java  |   0
 .../impl/Grails3InstallationCommandExecutor.java   |   0
 .../runner/impl/Grails3TestCommandLineState.kt     |   0
 .../grails/runner/impl/GrailsCommandLineState.java |   0
 .../impl/GrailsInstallationCommandExecutor.java    |   0
 .../runner/impl/GrailsRunAppCommandLineState.kt    |   0
 .../runner/impl/GrailsTestAppCommandLineState.kt   |   0
 .../runner/impl/GrailsTestCommandLineState.java    |   0
 .../org/jetbrains/plugins/grails/runner/setup.kt   |   0
 .../runner/ui/GrailsApplicationCombobox.java       |   0
 .../runner/ui/GrailsRunConfigurationEditor.form    |   0
 .../runner/ui/GrailsRunConfigurationEditor.java    |   0
 .../ui/GrailsRunConfigurationEditorListener.java   |   0
 .../GrailsRunConfigurationEditorWithListener.java  |   0
 .../grails/runner/util/GrailsExecutionUtils.java   |   0
 .../jetbrains/plugins/grails/sdk/GrailsSDK.java    |   0
 .../plugins/grails/sdk/GrailsSDKManager.java       |   0
 .../grails/service/GrailsBackgroundService.java    |   0
 .../spring/GrLightVariableRenameProcessor.java     |   0
 .../grails/spring/GrailsCustomSpringComponent.java |   0
 .../GrailsInjectedBeanCompletionContributor.java   |   0
 .../grails/spring/GrailsResourceBeanExtractor.java |   0
 .../GrailsResourcesGroovyMemberContributor.java    |   0
 .../grails/spring/GrailsSpringBeanDiscoverer.java  |   0
 .../grails/spring/GrailsSpringFacetProvider.java   |   0
 .../GrailsSpringMethodReferenceProvider.java       |   0
 .../grails/spring/GrailsSpringSemContributor.kt    |   0
 ...ilsSpringStereotypeAliasingPsiTargetMapper.java |   0
 ...ilsWebApplicationContextMembersContributor.java |   0
 .../grails/spring/InjectedSpringBeanProvider.java  |   0
 .../grails/spring/SpringBeanReferenceSearcher.java |   0
 .../grails/spring/SpringGrailsTargetMapper.java    |   0
 .../grails/spring/SpringInjectionAnnotator.java    |   0
 .../plugins/grails/structure/Grails3Application.kt |   0
 .../plugins/grails/structure/GrailsApplication.kt  |   0
 .../grails/structure/GrailsApplicationBase.kt      |   0
 .../structure/GrailsApplicationListener.java       |   0
 .../grails/structure/GrailsApplicationManager.java |   0
 .../GrailsApplicationManagerRefresher.java         |   0
 .../structure/GrailsApplicationProvider.java       |   0
 .../grails/structure/GrailsCommonClassNames.kt     |   0
 .../grails/structure/GrailsSDKListener.java        |   0
 .../grails/structure/OldGrailsApplication.kt       |   0
 .../grails/structure/impl/Grails2Application.kt    |   0
 .../structure/impl/Grails3ApplicationBase.kt       |   0
 .../structure/impl/Grails3ApplicationProvider.kt   |   0
 .../impl/Grails3MultiModuleApplication.kt          |   0
 .../impl/Grails3SingleModuleApplication.kt         |   0
 .../structure/impl/GrailsModuleBasedApplication.kt |   0
 .../impl/OldGrailsModuleBasedApplication.kt        |   0
 .../impl/PlainGrailsApplicationProvider.java       |   0
 .../plugins/grails/structure/impl/util.kt          |   0
 .../sync/GrailsApplicationBackgroundTask.java      |   0
 .../sync/GrailsMvcStructureRefresher.java          |   0
 .../structure/sync/GrailsRunConfigurationTask.java |   0
 .../grails/structure/sync/GrailsSdkCheckTask.java  |   0
 .../structure/sync/GrailsStartupActivity.java      |   0
 .../org/jetbrains/plugins/grails/structure/util.kt |   0
 .../grails/tests/GrailsJUnitRecognizer.java        |   0
 .../grails/tests/GrailsTestMemberContributor.java  |   0
 .../plugins/grails/tests/GrailsTestUtils.java      |   0
 .../plugins/grails/tests/GrailsTestUtils.kt        |   0
 .../grails/tests/GrailsTestVariableEnhancer.java   |   0
 .../runner/GrailsTestConfigurationProducer.java    |   0
 .../grails/tests/runner/GrailsUrlProvider.java     |   0
 .../plugins/grails/tests/runner/byClass.kt         |   0
 .../plugins/grails/tests/runner/byDirectory.kt     |   0
 .../plugins/grails/tests/runner/oldUtils.kt        |   0
 .../grails/ui/EditorComboBoxWithHistory.java       |   0
 .../grails/ui/GrailsConfigureSDKDialog.form        |   0
 .../grails/ui/GrailsConfigureSDKDialog.java        |   0
 .../plugins/grails/ui/GrailsRunCommandDialog.form  |   0
 .../plugins/grails/ui/GrailsRunCommandDialog.java  |   0
 .../plugins/grails/ui/GrailsSDKHomeForm.form       |   0
 .../plugins/grails/ui/GrailsSDKHomeForm.java       |   0
 .../plugins/grails/util/DelegateReference.java     |   0
 .../plugins/grails/util/GrailsArtifact.java        |   0
 .../util/GrailsArtifactTransformerUtils.java       |   0
 .../plugins/grails/util/GrailsCachedValue.java     |   0
 .../plugins/grails/util/GrailsFacetProvider.java   |   0
 .../plugins/grails/util/GrailsNameUtils.java       |   0
 .../plugins/grails/util/GrailsPatterns.java        |   0
 .../plugins/grails/util/GrailsPluginCondition.java |   0
 .../plugins/grails/util/GrailsPsiUtil.java         |   0
 .../jetbrains/plugins/grails/util/GrailsUtils.java |   0
 .../grails/util/GrailsVersionCondition.java        |   0
 .../grails/util/GrailsWebFacetProvider.java        |   0
 .../plugins/grails/util/ModuleCachedValue.java     |   0
 .../util/PredefinedTypeReferenceEnhancer.java      |   0
 .../grails/util/ReferenceGutterIconRenderer.java   |   0
 .../plugins/grails/util/SafeReference.java         |   0
 .../plugins/grails/util/version/Range.java         |   0
 .../plugins/grails/util/version/Version.java       |   0
 .../plugins/grails/util/version/VersionImpl.java   |   0
 .../plugins/grails/util/version/versions.kt        |   0
 .../plugins/groovy/dsl/GrailsScriptProvider.java   |   0
 .../annotator/GrailsControllerAnnotator.java       |   0
 .../grails/annotator/GrailsDomainAnnotator.java    |   0
 .../annotator/GrailsForkSettingsNotificator.java   |   0
 .../GrailsSynchronizeStructureAnnotator.java       |   0
 .../groovy/mvc/ConsoleProcessDescriptor.java       |   0
 .../jetbrains/plugins/groovy/mvc/MvcCommand.java   |   0
 .../groovy/mvc/MvcModuleStructureSynchronizer.java |   0
 .../plugins/groovy/mvc/MvcModuleStructureUtil.java |   0
 .../plugins/groovy/mvc/MvcPathMacros.java          |   0
 .../plugins/groovy/mvc/MvcProjectStructure.java    |   0
 .../plugins/groovy/mvc/MvcWatchedRootProvider.java |   0
 .../groovy/mvc/plugins/AvailablePluginsModel.java  |   0
 .../mvc/plugins/InstallUninstallPluginsDialog.java |   0
 .../groovy/mvc/plugins/MvcPluginColumnInfo.java    |   0
 .../groovy/mvc/plugins/MvcPluginDescriptor.java    |   0
 .../plugins/MvcPluginIsInstalledColumnInfo.java    |   0
 .../plugins/groovy/mvc/plugins/MvcPluginUtil.java  |   0
 .../groovy/mvc/plugins/MvcPluginsAction.java       |   0
 .../plugins/groovy/mvc/plugins/MvcPluginsMain.form |   0
 .../plugins/groovy/mvc/plugins/MvcPluginsMain.java |   0
 .../groovy/mvc/plugins/MvcPluginsTable.java        |   0
 .../mvc/plugins/actions/AddCustomPluginAction.java |   0
 .../plugins/actions/ReloadMvcPluginListAction.java |   0
 .../plugins/groovy/mvc/plugins/proxySettings.kt    |   0
 .../mvc/util/MvcTargetDialogCompletionUtils.java   |   0
 .../resources}/IdeaPrintProjectSettings.groovy     |   0
 .../resources}/META-INF/grails-css-integration.xml |   0
 .../resources}/META-INF/grails-js-integration.xml  |   0
 .../META-INF/grails-method-descriptors.xml         |   0
 .../src/main/resources}/META-INF/plugin.xml        |   0
 .../src/main/resources}/META-INF/pluginIcon.svg    |   0
 .../internal/Groovy Server Page.gsp.ft             |   0
 .../internal/Groovy Server Page.gsp.html           |   0
 .../src/main/resources}/icons/action_method.svg    |   0
 .../src/main/resources}/icons/grails.svg           |   0
 .../src/main/resources}/icons/grails_13.svg        |   0
 .../src/main/resources}/icons/grails_13_dark.svg   |   0
 .../src/main/resources}/icons/grails_app.svg       |   0
 .../src/main/resources}/icons/grails_sdk.svg       |   0
 .../src/main/resources}/icons/grails_test.svg      |   0
 .../main/resources}/icons/groovy_mvc_plugin.svg    |   0
 .../src/main/resources}/icons/gsp_logo.svg         |   0
 .../src/main/resources}/icons/taglib.svg           |   0
 .../inspectionDescriptions/GspInspection.html      |   0
 .../inspectionDescriptions/metaInformation.json    |   0
 .../GenerateViewIntention/after.gsp.template       |   0
 .../GenerateViewIntention/before.groovy.template   |   0
 .../GenerateViewIntention/description.html         |   0
 .../after.groovy.template                          |   0
 .../before.groovy.template                         |   0
 .../description.html                               |   0
 .../MakeNullableIntention/after.groovy.template    |   0
 .../MakeNullableIntention/before.groovy.template   |   0
 .../MakeNullableIntention/description.html         |   0
 .../MakeUniqueIntention/after.groovy.template      |   0
 .../MakeUniqueIntention/before.groovy.template     |   0
 .../MakeUniqueIntention/description.html           |   0
 .../src/main/resources}/liveTemplates/gsp.xml      |   0
 .../resources}/messages/GrailsBundle.properties    |   0
 .../grails/lang/gsp/resolve/taglib/tld/grails.tld  |   0
 .../grails/copyright/GspCopyrightUpdaterTest.java  |   0
 .../plugins/groovy/grails/DoublePluginTest.java    |   0
 .../plugins/groovy/grails/GrEditorActionTest.java  |   0
 .../plugins/groovy/grails/GrailsActionsTest.java   |   0
 .../plugins/groovy/grails/GrailsCodecTest.java     |   0
 .../groovy/grails/GrailsConsoleFilterTest.java     |   0
 ...HighlightGrailsSpecifiedFieldsAsUnusedTest.java |   0
 .../plugins/groovy/grails/GrailsGantTest.java      |   0
 .../groovy/grails/GrailsHighlightingTest.java      |   0
 .../groovy/grails/GrailsLightVariableInfoTest.java |   0
 .../groovy/grails/GrailsLinkGeneratorTest.java     |   0
 .../groovy/grails/GrailsLogVariableTest.java       |   0
 .../groovy/grails/GrailsMavenImporterTest.kt       |   0
 .../groovy/grails/GrailsPageRendererTest.java      |   0
 .../groovy/grails/GrailsPluginClassTest.java       |   0
 .../groovy/grails/GrailsProjectStructureTest.java  |   0
 .../grails/GrailsResolveInConfigFileTest.java      |   0
 .../plugins/groovy/grails/GrailsResolveTest.java   |   0
 .../grails/GrailsRunConfigurationProducerTest.java |   0
 .../grails/GrailsRunTargetCompletionTest.java      |   0
 .../groovy/grails/GspLiveTemplatesTest.java        |   0
 .../plugins/groovy/grails/JamInGroovyTest.java     |   0
 .../grails/VersionInDynamicMemberUtils14Test.java  |   0
 .../grails/VersionInDynamicMemberUtilsTest.java    |   0
 .../grails/action/DomainFieldIntentionsTest.kt     |   0
 .../grails/action/GenerateViewByActionTest.java    |   0
 .../grails/action/GrailsGenerateAction2Test.java   |   0
 .../grails/action/GrailsGenerateActionTest.java    |   0
 .../ControllerReferenceCompletionTest.java         |   0
 .../DomainClassMethodsCompletionTest.java          |   0
 .../grails/completion/GrailsCompletionTest.java    |   0
 .../completion/GrailsNamedAttributeTest.java       |   0
 ...GrailsTestAutoCompletionHardReferencesTest.java |   0
 .../completion/GspTagReferenceCompletionTest.java  |   0
 .../grails/completion/ServiceCompletionTest.java   |   0
 .../TaglibClassMethodsCompletionTest.java          |   0
 .../config/GrailsBuildConfigResolveTest.java       |   0
 .../groovy/grails/config/GrailsConfigFileTest.java |   0
 .../grails/config/GrailsConfigSelectWordTest.java  |   0
 .../grails/config/GrailsConfigSlurperTest.java     |   0
 .../grails/config/GrailsConstraintGroupTest.java   |   0
 .../GrailsPluginDependencyCompletionTest.java      |   0
 .../groovy/grails/domain/GormApiFromJavaTest.java  |   0
 .../groovy/grails/domain/GormBinding14Test.java    |   0
 .../groovy/grails/domain/GormBindingTest.java      |   0
 .../grails/domain/GormDetachedCriteriaTest.java    |   0
 .../grails/domain/GormDomainConstructorTest.java   |   0
 .../grails/domain/GormDomainFinderMethodTest.java  |   0
 .../groovy/grails/domain/GormFetchModeTest.java    |   0
 .../domain/GormFinderMethodsCompletionTest.java    |   0
 .../GormGetPersistentValueReturnTypeTest.java      |   0
 .../grails/domain/GormHasManyRelationTypeTest.java |   0
 .../groovy/grails/domain/GormMappingSortTest.java  |   0
 .../grails/domain/GormMultiDefinePropertyTest.java |   0
 ...amedArgumentForHibernateMappingBuilderTest.java |   0
 .../grails/domain/GormPersistentFacetTest.java     |   0
 .../grails/domain/GormPropertyAccessTest.java      |   0
 .../groovy/grails/domain/GormStandaloneTest.java   |   0
 .../grails/domain/GormSyntheticFieldTest.java      |   0
 .../domain/GormUniqueConstraintValueTest.java      |   0
 .../grails/domain/GrailsAddToMethodTest.java       |   0
 .../grails/domain/GrailsCriteriaBuilder14Test.java |   0
 .../grails/domain/GrailsCriteriaBuilderTest.java   |   0
 .../GrailsCriteriaBuilderToManyRelationTest.java   |   0
 .../domain/GrailsCriteriaProjections14Test.java    |   0
 .../domain/GrailsCriteriaProjectionsTest.java      |   0
 .../grails/domain/GrailsDomainConstraintTest.java  |   0
 .../domain/GrailsDomainFinderMethodsTest.java      |   0
 .../domain/GrailsDomainNamedAttribute14Test.java   |   0
 .../domain/GrailsDomainNamedAttributeTest.java     |   0
 .../GrailsDomainPropertiesFromParentTest.java      |   0
 .../grails/domain/GrailsDomainPropertiesTest.java  |   0
 .../domain/GrailsFindWhereParameterTest.java       |   0
 .../grails/domain/GrailsGormGetIdMethodTest.java   |   0
 .../grails/domain/GrailsInjectRegexTest.java       |   0
 .../grails/domain/GrailsNamedQuery14Test.java      |   0
 .../groovy/grails/domain/GrailsNamedQueryTest.java |   0
 .../domain/GrailsRenameDomainPropertyTest.java     |   0
 .../grails/domain/GrailsRenameDomainTest.java      |   0
 .../grails/domain/GrailsTransientPropertyTest.java |   0
 .../grails/domain/GrormEmbeddedFieldTest.java      |   0
 .../NamedArgumentsInFindOrCreateMethodTest.java    |   0
 .../grails/gradle/GradleGrailsImportingTest.java   |   0
 .../gsp/AssetPipelineScriptInjectionTest.java      |   0
 .../grails/gsp/GspActionToViewNavigation2Test.java |   0
 .../grails/gsp/GspActionToViewNavigationTest.java  |   0
 .../grails/gsp/GspAttributeFromJavadocTest.java    |   0
 .../groovy/grails/gsp/GspBuiltInTagTest.java       |   0
 .../groovy/grails/gsp/GspCharsetReferenceTest.java |   0
 .../gsp/GspClearCompletionBehaviourTest.java       |   0
 .../grails/gsp/GspCompletionEmptyTagTest.java      |   0
 .../GspCompletionInNonexistingNamespaceTest.java   |   0
 .../groovy/grails/gsp/GspContentTagTest.java       |   0
 .../grails/gsp/GspControllerReferenceTest.java     |   0
 .../grails/gsp/GspCreateTemplateQuickFixTest.java  |   0
 .../grails/gsp/GspCreateVariableQuickFixTest.java  |   0
 .../plugins/groovy/grails/gsp/GspCssTest.java      |   0
 .../grails/gsp/GspDirectiveDefaultCodecTest.java   |   0
 ...mentManipulatorForOuterLanguageElementTest.java |   0
 .../groovy/grails/gsp/GspFormatterTest.java        |   0
 .../groovy/grails/gsp/GspGroovyInspectionTest.java |   0
 .../groovy/grails/gsp/GspHighlightingTest.java     |   0
 .../groovy/grails/gsp/GspHtmlAttributesTest.java   |   0
 .../groovy/grails/gsp/GspIDEA68710Test.java        |   0
 .../plugins/groovy/grails/gsp/GspImportTest.java   |   0
 .../groovy/grails/gsp/GspJavascriptTest.java       |   0
 .../groovy/grails/gsp/GspJsFormatterTest.java      |   0
 .../groovy/grails/gsp/GspJsInjectionTagEpTest.java |   0
 .../grails/gsp/GspLanguageInjectionTest.java       |   0
 .../groovy/grails/gsp/GspLayoutTagTest.java        |   0
 .../groovy/grails/gsp/GspNamedMappingTest.java     |   0
 .../gsp/GspNamespacePrefixIsNotIdentifierTest.java |   0
 .../grails/gsp/GspNamespacePriorityTest.java       |   0
 .../grails/gsp/GspPropertyResolvePriorityTest.java |   0
 .../groovy/grails/gsp/GspResolveIndexesTest.java   |   0
 .../gsp/GspResolveVariablesFromModelTest.java      |   0
 .../groovy/grails/gsp/GspSdkTagCompletionTest.java |   0
 .../groovy/grails/gsp/GspSelectWordTest.java       |   0
 .../groovy/grails/gsp/GspTagDocumentationTest.java |   0
 .../grails/gsp/GspThisAndSuperReferenceTest.java   |   0
 .../plugins/groovy/grails/gsp/GspTypingTest.java   |   0
 .../groovy/grails/gsp/GstringAttributeTest.java    |   0
 .../grails/i18n/GrailsGroovyI18nIntentionTest.kt   |   0
 .../groovy/grails/i18n/GrailsI18nInspectionTest.kt |   0
 .../groovy/grails/i18n/GspI18nIntentionTest.kt     |   0
 .../GrailsMethodMayBeStaticInspectionTest.java     |   0
 .../groovy/grails/parser/GspGroovyParserTest.java  |   0
 .../groovy/grails/parser/GspHtmlParserTest.java    |   0
 .../groovy/grails/parser/GspParserTest.java        |   0
 .../groovy/grails/parser/GspParsingTestCase.java   |   0
 .../pluginSupport/GrailsBuildTestDataTest.java     |   0
 .../pluginSupport/GrailsResourcePluginTest.java    |   0
 .../grails/pluginSupport/GrailsSearchableTest.java |   0
 .../pluginSupport/GrailsShiroPluginTest.java       |   0
 .../grails/pluginSupport/GrailsWebflowTest.java    |   0
 .../pluginSupport/SpockMethodReturnTypeTest.java   |   0
 .../reference/GrailsBeanNameConventionTest.java    |   0
 .../groovy/grails/reference/GrailsFilterTest.java  |   0
 .../groovy/grails/reference/GrailsJobTest.java     |   0
 .../grails/reference/GrailsViewReferenceTest.java  |   0
 .../reference/bootstrap/GrailsBootStrapTest.java   |   0
 .../controller/Grails14MimePluginTest.java         |   0
 .../reference/controller/GrailsAction14Test.java   |   0
 .../controller/GrailsActionWithExtensionTest.java  |   0
 .../controller/GrailsController14Test.java         |   0
 .../GrailsControllerActionReferenceTest.java       |   0
 .../GrailsControllerAllowedMethodsTest.java        |   0
 ...ilsControllerAmbiguousMethodInspectionTest.java |   0
 .../GrailsControllerLayoutReferenceTest.java       |   0
 .../controller/GrailsControllerRenameTest.java     |   0
 .../GrailsControllerRespondMethodTest.java         |   0
 .../controller/GrailsHttpRequestTest.java          |   0
 .../controller/GrailsHttpResponseTest.java         |   0
 .../reference/controller/GrailsMimePluginTest.java |   0
 .../reference/controller/GrailsNamespaceTest.java  |   0
 .../controller/GrailsRenderMethodTest.java         |   0
 .../reference/spring/GrailsSpringDSLTest.java      |   0
 .../spring/GrailsSpringIntegrationTest.java        |   0
 .../taglib/GrailsTagLibNamedArgumentsTest.java     |   0
 .../reference/taglib/GrailsTaglib14Test.java       |   0
 .../taglib/GrailsTaglibParamTypeTest.java          |   0
 .../grails/reference/taglib/GspCustomTagTest.java  |   0
 .../GspResolveTagsFromSameNamespaceTest.java       |   0
 .../reference/urlMapping/GrailsUrlMappingTest.java |   0
 .../GrailsUrlMappingUnresolvedReferenceTest.java   |   0
 .../grails/tagSupport/AssetPipelineSrcTest.java    |   0
 .../grails/tagSupport/GspActionAttributeTest.java  |   0
 .../grails/tagSupport/GspCodecAttributeTest.java   |   0
 .../tagSupport/GspControllerAttributeTest.java     |   0
 .../groovy/grails/tagSupport/GspFieldTagTest.java  |   0
 .../tagSupport/GspIncludeViewAttributeTest.java    |   0
 .../groovy/grails/tagSupport/GspJsSrcTest.java     |   0
 .../groovy/grails/tagSupport/GspMetaTagTest.java   |   0
 .../grails/tagSupport/GspPluginAttributeTest.java  |   0
 .../groovy/grails/tagSupport/GspRenderTagTest.java |   0
 .../grails/tagSupport/GspResourceTagTest.java      |   0
 .../groovy/grails/tagSupport/GspSelectTagTest.java |   0
 .../grails/tagSupport/GspUpdateAttributeTest.java  |   0
 .../grails/tests/GrailsMockAnnotationTest.java     |   0
 .../grails/tests/GrailsTestForAnnotationTest.java  |   0
 .../tests/GrailsTestMixinAnnotationTest.java       |   0
 .../groovy/grails/tests/GrailsTestUtilTest.java    |   0
 .../grails/tests/GrailsVariableEnhance14Test.java  |   0
 .../grails/tests/GrailsVariableEnhanceTest.java    |   0
 .../groovy/grails/v3/GrailsEnhancesTest.java       |   0
 .../groovy/grails/v3/GrailsProjectDescriptor.java  |   0
 .../grails/validation/GrailsImportFromTest.java    |   0
 .../validation/GrailsValidatedObjectTest.java      |   0
 .../standardDsls/GrailsStandardDSL.gdsl            |   0
 .../testdata}/grails/actions/GspBlockComment.gsp   |   0
 .../grails/actions/GspBlockComment_after.gsp       |   0
 .../testdata}/grails/actions/GspLineComment.gsp    |   0
 .../grails/actions/GspLineComment_after.gsp        |   0
 .../testdata}/grails/actions/GspLineUncomment.gsp  |   0
 .../grails/actions/GspLineUncomment_after.gsp      |   0
 .../AllFinderMethodsInDomainClass.groovy           |   0
 .../completion/AttributeNameFinishWithEq.gsp       |   0
 .../completion/AttributeNameFinishWithEq_after.gsp |   0
 .../ConstrainsPropertyInDomainClass.groovy         |   0
 ...nstrainsPropertyInDomainClassConstraints.groovy |   0
 .../ConstrainsPropertyInDomainClass_after.groovy   |   0
 .../completion/DomainClassDynamicMethods.groovy    |   0
 .../completion/DomainClassNonStaticMethods.groovy  |   0
 .../completion/DomainClassStaticProperties.groovy  |   0
 .../DomainClassStaticProperties_after.groovy       |   0
 .../completion/FindByMethodInDomainClass.groovy    |   0
 .../FindByMethodWithThreeArgsInDomainClass.groovy  |   0
 .../completion/FinderMethodsInDomainClass.groovy   |   0
 .../grails/completion/FinderMethodsInGsp.groovy    |   0
 .../grails/completion/FinderMethodsInGsp.gsp       |   0
 .../grails/completion/FindersWithBelongsTo.groovy  |   0
 .../grails/completion/FindersWithHasMany.groovy    |   0
 .../grails/completion/GspInjectedJavaScript.gsp    |   0
 .../completion/GspInjectedJavaScript_after.gsp     |   0
 .../grails/completion/JspTagCompletion.gsp         |   0
 .../grails/completion/JspTagCompletion_after.gsp   |   0
 .../completion/ModifiersInGspGroovyDeclaration.gsp |   0
 .../grails/completion/OverrideStandardTag.gsp      |   0
 .../grails/completion/ServiceInjection.groovy      |   0
 .../completion/ServiceInjection_after.groovy       |   0
 .../completion/UnpackagedDomainClassInGsp.groovy   |   0
 .../completion/UnpackagedDomainClassInGsp.gsp      |   0
 .../UnpackagedDomainClassInGsp_after.gsp           |   0
 .../testdata}/grails/enterAction/dir.test          |   0
 .../testdata}/grails/enterAction/simple_type.test  |   0
 {testdata => plugin/testdata}/grails/fmt.tld       |   0
 .../grails/formatter/comments/schulz1.test         |   0
 .../testdata}/grails/formatter/comments/ven1.test  |   0
 .../testdata}/grails/formatter/groovy/groovy.test  |   0
 .../testdata}/grails/formatter/groovy/plain1.test  |   0
 .../testdata}/grails/formatter/groovy/sim1.test    |   0
 .../testdata}/grails/formatter/gsp/EA29587.test    |   0
 .../testdata}/grails/formatter/gsp/gsp1.test       |   0
 .../testdata}/grails/formatter/gsp/gsp2.test       |   0
 .../testdata}/grails/formatter/gsp/gsp3.test       |   0
 .../testdata}/grails/formatter/gsp/gsp8.test       |   0
 .../testdata}/grails/formatter/gsp/gsp9.test       |   0
 .../grails/formatter/html/error/giga_werle.test    |   0
 .../grails/formatter/html/error/nik1.test          |   0
 .../grails/formatter/html/error/show.test          |   0
 .../grails/formatter/html/error/werle1.test        |   0
 .../grails/formatter/html/error/werle30.test       |   0
 .../grails/formatter/html/inner/GRVY_1165.test     |   0
 .../grails/formatter/html/inner/GRVY_1165_1.test   |   0
 .../grails/formatter/html/inner/GRVY_876.test      |   0
 .../grails/formatter/html/inner/inner2.test        |   0
 .../grails/formatter/html/inner/inner3.test        |   0
 .../grails/formatter/html/megabug/GRVY_1046.test   |   0
 .../grails/formatter/html/megabug/bigTest.test     |   0
 .../grails/formatter/html/megabug/megap2.test      |   0
 .../grails/formatter/html/megabug/mmm3.test        |   0
 .../grails/formatter/html/megabug/peter.test       |   0
 .../grails/formatter/html/megabug/peter2.test      |   0
 .../grails/formatter/html/megabug/peter3.test      |   0
 .../grails/formatter/html/megabug/range1.test      |   0
 .../grails/formatter/html/nested/attr1.test        |   0
 .../grails/formatter/html/nested/gsp6.test         |   0
 .../grails/formatter/html/nested/gsp7.test         |   0
 .../grails/formatter/html/nested/peter7926.test    |   0
 .../grails/formatter/html/nested/peter_simple.test |   0
 .../grails/formatter/html/nested/range.test        |   0
 .../grails/formatter/html/nested/range2.test       |   0
 .../grails/formatter/html/nested/range4.test       |   0
 .../grails/formatter/html/nested/range5.test       |   0
 .../grails/formatter/html/simple/GRVY_1146.test    |   0
 .../formatter/html/simple/attributeSpaces.test     |   0
 .../grails/formatter/html/simple/gsp4.test         |   0
 .../grails/formatter/html/simple/gsp8.test         |   0
 .../grails/formatter/html/simple/htm1.test         |   0
 .../grails/formatter/html/simple/htm2.test         |   0
 .../grails/formatter/html/simple/range3.test       |   0
 .../grails/formatter/html/simple/sim2.test         |   0
 .../formatter/html/simple/tagNameSpaces.test       |   0
 .../grails/formatter/html/trash/bug1.test          |   0
 .../grails/formatter/html/trash/bug2.test          |   0
 .../grails/formatter/html/trash/bug3.test          |   0
 .../grails/formatter/html/trash/bug4.test          |   0
 .../grails/formatter/html/trash/bug5.test          |   0
 .../grails/formatter/html/trash/trash1.test        |   0
 .../grails/formatter/hyphenInTagName.test          |   0
 .../testdata}/grails/formatter/js/t1.test          |   0
 .../testdata}/grails/formatter/js/t2.test          |   0
 .../grails/formatter/spacing/groovy/gr1.test       |   0
 .../grails/formatter/spacing/groovy/gr2.test       |   0
 .../testdata}/grails/formatter/spacing/htm3.test   |   0
 .../testdata}/grails/formatter/spacing/spac2.test  |   0
 .../testdata}/grails/formatter/spacing/spac3.test  |   0
 .../testdata}/grails/formatter/spacing/tags2.test  |   0
 .../testdata}/grails/gsp/TestController.groovy     |   0
 .../DomainFindersWithInheritedProperties.groovy    |   0
 .../FileReferenceWithGspInjections.gsp             |   0
 .../grails/highlighting/FinderMethods.groovy       |   0
 .../grails/highlighting/HasManyBelongsTo.groovy    |   0
 .../highlighting/JavascriptWithGspInjections.gsp   |   0
 .../testdata}/grails/highlighting/JspTagInGsp.gsp  |   0
 .../testdata}/grails/highlighting/MappedBy.groovy  |   0
 .../grails/highlighting/MappedByErrors1.groovy     |   0
 .../grails/highlighting/Properties.groovy          |   0
 .../grails/highlighting/UnknownArgsAttribute.gsp   |   0
 .../controllers/contr1Controller.test              |   0
 .../controllers/contr2Controller.test              |   0
 .../controllers/contr3Controller.test              |   0
 .../grails/oldCompletion/domain/dyn1.test          |   0
 .../grails/oldCompletion/domain/finder10.test      |   0
 .../grails/oldCompletion/domain/finder11.test      |   0
 .../grails/oldCompletion/domain/finder2.test       |   0
 .../grails/oldCompletion/domain/finder3.test       |   0
 .../grails/oldCompletion/domain/finder4.test       |   0
 .../grails/oldCompletion/domain/finder5.test       |   0
 .../grails/oldCompletion/domain/finder6.test       |   0
 .../grails/oldCompletion/domain/finder7.test       |   0
 .../grails/oldCompletion/domain/finder8.test       |   0
 .../grails/oldCompletion/domain/finder_full.test   |   0
 .../grails/oldCompletion/domain/list_order.test    |   0
 .../grails/oldCompletion/domain/not_ref.test       |   0
 .../grails/oldCompletion/domain/qulaified.test     |   0
 .../grails/oldCompletion/domain/static.test        |   0
 .../testdata}/grails/oldCompletion/gsp/attr1.test  |   0
 .../grails/oldCompletion/gsp/custom1.test          |   0
 .../oldCompletion/gsp/customNamespacePrefix.test   |   0
 .../grails/oldCompletion/gsp/dirname.test          |   0
 .../grails/oldCompletion/gsp/endTagTest1.test      |   0
 .../grails/oldCompletion/gsp/endTagTest2.test      |   0
 .../grails/oldCompletion/gsp/endTagTest3.test      |   0
 .../grails/oldCompletion/gsp/endTagTest4.test      |   0
 .../grails/oldCompletion/gsp/endTagTest5.test      |   0
 .../oldCompletion/gsp/excludeNotATagGroovy.test    |   0
 .../oldCompletion/gsp/excludeNotATagHTML.test      |   0
 .../testdata}/grails/oldCompletion/gsp/g1.test     |   0
 .../testdata}/grails/oldCompletion/gsp/get1.test   |   0
 .../testdata}/grails/oldCompletion/gsp/groo1.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo2.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo3.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo4.test  |   0
 .../testdata}/grails/oldCompletion/gsp/groo5.test  |   0
 .../testdata}/grails/oldCompletion/gsp/html1.test  |   0
 .../grails/oldCompletion/gsp/htmlWithDoctype.test  |   0
 .../testdata}/grails/oldCompletion/gsp/link.test   |   0
 .../testdata}/grails/oldCompletion/gsp/link2.test  |   0
 .../testdata}/grails/oldCompletion/gsp/my1.test    |   0
 .../grails/oldCompletion/gsp/nonStatic.test        |   0
 .../grails/oldCompletion/gsp/packageTagLib1.test   |   0
 .../grails/oldCompletion/gsp/packageTagLib2.test   |   0
 .../testdata}/grails/oldCompletion/gsp/tail1.test  |   0
 .../testdata}/grails/oldCompletion/gsp/tail2.test  |   0
 .../testdata}/grails/oldCompletion/gsp/tail3.test  |   0
 .../testdata}/grails/oldCompletion/gsp/tail4.test  |   0
 .../testdata}/grails/parser/gsp/dir/dir1.test      |   0
 .../grails/parser/gsp/hyphenInTagName.test         |   0
 .../grails/parser/gsp/inject/GRVY_943.test         |   0
 .../grails/parser/gsp/inject/escaped1.test         |   0
 .../testdata}/grails/parser/gsp/simple/bubug1.test |   0
 .../testdata}/grails/parser/gsp/simple/clos1.test  |   0
 .../testdata}/grails/parser/gsp/simple/clos2.test  |   0
 .../testdata}/grails/parser/gsp/simple/common.test |   0
 .../testdata}/grails/parser/gsp/simple/form1.test  |   0
 .../testdata}/grails/parser/gsp/simple/megap1.test |   0
 .../testdata}/grails/parser/gsp/simple/mmm1.test   |   0
 .../testdata}/grails/parser/gsp/simple/peter2.test |   0
 .../testdata}/grails/parser/gsp/tags/act1.test     |   0
 .../testdata}/grails/parser/gsp/tags/act2.test     |   0
 .../testdata}/grails/parser/gsp/tags/gps9.test     |   0
 .../testdata}/grails/parser/gsp/tags/orph1.test    |   0
 .../testdata}/grails/parser/gsp/tags/tag3.test     |   0
 .../grails/parser/gsp/tags/tagWithSlash.test       |   0
 .../grails/parser/gsp/tags/uglyAttributeName.test  |   0
 .../grails/parser/gsp/tags/unendedAttrList.test    |   0
 .../grails/parser/gspGroovy/comments/com.test      |   0
 .../grails/parser/gspGroovy/comments/comm1.test    |   0
 .../grails/parser/gspGroovy/comments/commm2.test   |   0
 .../grails/parser/gspGroovy/control/common.test    |   0
 .../grails/parser/gspGroovy/control/for1.test      |   0
 .../grails/parser/gspGroovy/control/for2.test      |   0
 .../grails/parser/gspGroovy/control/foreach1.test  |   0
 .../grails/parser/gspGroovy/control/if1.test       |   0
 .../grails/parser/gspGroovy/control/if2.test       |   0
 .../grails/parser/gspGroovy/control/swit1.test     |   0
 .../grails/parser/gspGroovy/control/swit2.test     |   0
 .../grails/parser/gspGroovy/custom/tag1.test       |   0
 .../grails/parser/gspGroovy/custom/tag2.test       |   0
 .../grails/parser/gspGroovy/custom/tag3.test       |   0
 .../grails/parser/gspGroovy/declarations/dec1.test |   0
 .../grails/parser/gspGroovy/declarations/dec2.test |   0
 .../grails/parser/gspGroovy/declarations/dec3.test |   0
 .../grails/parser/gspGroovy/declarations/dec4.test |   0
 .../grails/parser/gspGroovy/direct/dir1.test       |   0
 .../grails/parser/gspGroovy/errors/err1.test       |   0
 .../grails/parser/gspGroovy/errors/err2.test       |   0
 .../grails/parser/gspGroovy/errors/err3.test       |   0
 .../grails/parser/gspGroovy/errors/err4.test       |   0
 .../grails/parser/gspGroovy/simple/clos1.test      |   0
 .../grails/parser/gspGroovy/simple/inj1.test       |   0
 .../grails/parser/gspGroovy/simple/inj2.test       |   0
 .../grails/parser/gspGroovy/simple/nl.test         |   0
 .../grails/parser/gspGroovy/simple/stat1.test      |   0
 .../grails/parser/gspHtml/afterWhitespace.test     |   0
 .../testdata}/grails/parser/gspHtml/alone.test     |   0
 .../testdata}/grails/parser/gspHtml/common.test    |   0
 .../testdata}/grails/parser/gspHtml/html1.test     |   0
 .../testdata}/grails/parser/gspHtml/inject1.test   |   0
 .../testdata}/grails/parser/gspHtml/mmm2.test      |   0
 .../testdata}/grails/parser/gspHtml/mmm5.test      |   0
 .../testdata}/grails/parser/gspHtml/peter1.test    |   0
 .../testdata}/grails/parser/gspHtml/peter2.test    |   0
 .../grails/parser/gspHtml/range_parse.test         |   0
 .../testdata}/grails/parser/gspHtml/werle.test     |   0
 .../testdata}/grails/parser/gspHtml/werle28.test   |   0
 .../testdata}/grails/parser/gspHtml/werle29.test   |   0
 .../grails/pluginList/plugins-list-grailsCore.xml  |   0
 .../resolve/ResolvePropertiesFromBelongsTo.groovy  |   0
 .../resolve/ResolvePropertiesFromBelongsTo2.groovy |   0
 .../resolve/ResolvePropertiesFromHasMany.groovy    |   0
 .../resolve/ResolvePropertyFromMappedBy.groovy     |   0
 .../testdata}/grails/resolve/StaticsOk.groovy      |   0
 .../grails/shiro/AccessControlBuilder.groovy       |   0
 .../mockDomainDir/grails-app/domain/Product.groovy |   0
 .../grails-app/domain/aaa/Book.groovy              |   0
 .../mockGrails11/conf/groovy-starter.conf          |   0
 .../testdata}/mockGrails11/conf/webdefault.xml     |   0
 .../mockGrails11/dist/grails-bootstrap-1.1.jar     | Bin
 .../mockGrails11/dist/grails-core-1.1.jar          | Bin
 .../mockGrails11/dist/grails-core-1.3.1.jar        | Bin
 .../mockGrails11/dist/grails-gorm-1.3.1.jar        | Bin
 .../dist/grails-plugin-url-mappings-2.0.4.jar      | Bin
 .../mockGrails11/dist/grails-scripts-1.3.7.jar     | Bin
 .../mockGrails11/dist/grails-spring-1.3.1.jar      | Bin
 .../mockGrails11/dist/grails-test-1.3.1.jar        | Bin
 .../mockGrails11/dist/grails-web-1.3.1.jar         | Bin
 .../mockGrails11/dist/grails-webflow-1.3.7.jar     | Bin
 .../testdata}/mockGrails11/lib/groovy-all.jar      | Bin
 .../mockGrails11/lib/hibernate-core-3.3.1.GA.jar   | Bin
 .../testdata}/mockGrails11/lib/junit-4.8.1.jar     | Bin
 .../org.springframework.beans-3.0.3.RELEASE.jar    | Bin
 .../org.springframework.context-3.0.3.RELEASE.jar  | Bin
 .../lib/org.springframework.test-3.0.2.RELEASE.jar | Bin
 .../testdata}/mockGrails11/lib/servlet-api-2.5.jar | Bin
 .../lib/spock-grails-support-0.5-groovy-1.7.jar    | Bin
 .../mockGrails11/lib/spring-core-3.0.3.RELEASE.jar | Bin
 .../mockGrails11/scripts/Bootstrap.groovy          |   0
 .../mockGrails11/scripts/BugReport.groovy          |   0
 .../testdata}/mockGrails11/scripts/Clean.groovy    |   0
 .../testdata}/mockGrails11/scripts/Compile.groovy  |   0
 .../testdata}/mockGrails11/scripts/Console.groovy  |   0
 .../mockGrails11/scripts/CreateApp_.groovy         |   0
 .../mockGrails11/scripts/CreateController.groovy   |   0
 .../mockGrails11/scripts/CreateDomainClass.groovy  |   0
 .../mockGrails11/scripts/CreateFilters.groovy      |   0
 .../scripts/CreateIntegrationTest.groovy           |   0
 .../mockGrails11/scripts/CreatePlugin_.groovy      |   0
 .../mockGrails11/scripts/CreateScript.groovy       |   0
 .../mockGrails11/scripts/CreateService.groovy      |   0
 .../mockGrails11/scripts/CreateTagLib.groovy       |   0
 .../mockGrails11/scripts/CreateUnitTest.groovy     |   0
 .../testdata}/mockGrails11/scripts/Doc.groovy      |   0
 .../mockGrails11/scripts/GenerateAll.groovy        |   0
 .../mockGrails11/scripts/GenerateController.groovy |   0
 .../mockGrails11/scripts/GenerateViews.groovy      |   0
 .../testdata}/mockGrails11/scripts/Help_.groovy    |   0
 .../testdata}/mockGrails11/scripts/Init.groovy     |   0
 .../mockGrails11/scripts/InstallPlugin.groovy      |   0
 .../mockGrails11/scripts/InstallTemplates.groovy   |   0
 .../mockGrails11/scripts/ListPlugins_.groovy       |   0
 .../testdata}/mockGrails11/scripts/Package.groovy  |   0
 .../mockGrails11/scripts/PackagePlugin_.groovy     |   0
 .../mockGrails11/scripts/PluginInfo_.groovy        |   0
 .../mockGrails11/scripts/ReleasePlugin.groovy      |   0
 .../testdata}/mockGrails11/scripts/RunApp.groovy   |   0
 .../testdata}/mockGrails11/scripts/RunWar.groovy   |   0
 .../mockGrails11/scripts/SchemaExport.groovy       |   0
 .../mockGrails11/scripts/SetProxy_.groovy          |   0
 .../mockGrails11/scripts/SetVersion.groovy         |   0
 .../testdata}/mockGrails11/scripts/Shell.groovy    |   0
 .../testdata}/mockGrails11/scripts/Stats.groovy    |   0
 .../testdata}/mockGrails11/scripts/TestApp.groovy  |   0
 .../mockGrails11/scripts/UninstallPlugin.groovy    |   0
 .../testdata}/mockGrails11/scripts/Upgrade.groovy  |   0
 .../testdata}/mockGrails11/scripts/War.groovy      |   0
 .../mockGrails11/scripts/_GrailsArgParsing.groovy  |   0
 .../mockGrails11/scripts/_GrailsBootstrap.groovy   |   0
 .../mockGrails11/scripts/_GrailsClasspath.groovy   |   0
 .../mockGrails11/scripts/_GrailsClean.groovy       |   0
 .../mockGrails11/scripts/_GrailsCompile.groovy     |   0
 .../scripts/_GrailsCreateArtifacts.groovy          |   0
 .../scripts/_GrailsCreateProject.groovy            |   0
 .../mockGrails11/scripts/_GrailsDocs.groovy        |   0
 .../mockGrails11/scripts/_GrailsEvents.groovy      |   0
 .../mockGrails11/scripts/_GrailsGenerate.groovy    |   0
 .../mockGrails11/scripts/_GrailsInit.groovy        |   0
 .../mockGrails11/scripts/_GrailsPackage.groovy     |   0
 .../mockGrails11/scripts/_GrailsPluginDev.groovy   |   0
 .../mockGrails11/scripts/_GrailsPlugins.groovy     |   0
 .../mockGrails11/scripts/_GrailsProxy.groovy       |   0
 .../mockGrails11/scripts/_GrailsRun.groovy         |   0
 .../mockGrails11/scripts/_GrailsSettings.groovy    |   0
 .../mockGrails11/scripts/_GrailsTest.groovy        |   0
 .../mockGrails11/scripts/_GrailsWar.groovy         |   0
 .../mockGrails11/scripts/_PackagePlugins.groovy    |   0
 .../scripts/_PluginDependencies.groovy             |   0
 .../mockGrails11/scripts/log4j.properties          |   0
 .../mockGrails14/grails-bootstrap-2.0.0.M2.jar     | Bin
 .../mockGrails14/grails-core-2.0.0.M1.jar          | Bin
 .../grails-datastore-gorm-1.0.0.RC1.jar            | Bin
 .../mockGrails14/grails-hibernate-2.0.0.M1.jar     | Bin
 .../grails-plugin-controllers-1.4.0.M1.jar         | Bin
 .../grails-plugin-converters-2.2.1.jar             | Bin
 .../grails-plugin-gsp-2.0.0.RC1-sources.jar        | Bin
 .../mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar   | Bin
 .../grails-plugin-mimetypes-1.4.0.M1.jar           | Bin
 .../mockGrails14/grails-plugin-rest-2.3.0.jar      | Bin
 .../mockGrails14/grails-plugin-servlets-2.2.1.jar  | Bin
 .../grails-plugin-testing-1.4.0.M1.jar             | Bin
 .../testdata}/mockGrails14/grails-spring-2.2.3.jar | Bin
 .../mockGrails14/grails-test-2.0.0.M2.jar          | Bin
 .../testdata}/mockGrails14/grails-web-2.0.0.M1.jar | Bin
 .../mockGrails14/spring-test-3.1.0.M2.jar          | Bin
 .../mockTagLib/grails-app/taglib/MyTagLib.groovy   |   0
 .../grails-app/taglib/NonStaticTagLib.groovy       |   0
 .../grails-app/taglib/UnnamedTagLib.groovy         |   0
 .../grails-app/taglib/apackage/PppTagLib.groovy    |   0
 pluginModules/copyright/build.gradle               |   2 +-
 pluginModules/coverage/build.gradle                |   2 +-
 pluginModules/hibernate/build.gradle               |   2 +-
 pluginModules/i18n/build.gradle                    |   2 +-
 pluginModules/maven/build.gradle                   |   2 +-
 settings.gradle                                    |   2 +
 1222 files changed, 66 insertions(+), 149 deletions(-)

diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 12a634a..f917c3c 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -47,13 +47,13 @@ jobs:
         run: ./gradlew check buildPlugin
       - name: "📝 Determine plugin ZIP name"
         id: plugin-zip
-        run: echo "name=$(basename build/distributions/*.zip .zip)" >> "$GITHUB_OUTPUT"
+        run: echo "name=$(basename plugin/build/distributions/*.zip .zip)" >> "$GITHUB_OUTPUT"
       - name: "📦 Upload plugin ZIP"
         id: upload-plugin
         uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: ${{ steps.plugin-zip.outputs.name }}
-          path: build/distributions/*.zip
+          path: plugin/build/distributions/*.zip
           archive: false # serve the plugin zip as-is instead of wrapping it in another zip (requires upload-artifact v7+)
       - name: "📢 Publish plugin to job summary"
         env:
@@ -64,7 +64,7 @@ jobs:
             echo ""
             echo "| File | Size | Download |"
             echo "|------|------|----------|"
-            for zip in build/distributions/*.zip; do
+            for zip in plugin/build/distributions/*.zip; do
               echo "| \`$(basename "$zip")\` | $(du -h "$zip" | cut -f1) | [${{ steps.plugin-zip.outputs.name }} artifact]($ARTIFACT_URL) |"
             done
           } >> "$GITHUB_STEP_SUMMARY"
@@ -93,4 +93,4 @@ jobs:
         uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
           name: plugin-verifier-report
-          path: build/reports/pluginVerifier
+          path: plugin/build/reports/pluginVerifier
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 04a2aaa..742a356 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -72,16 +72,16 @@ jobs:
           PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }}
         run: ./gradlew signPlugin -Pversion=${VERSION}
       - name: "📦 Rename plugin ZIP to distribution name"
-        # The ZIP base name is the IntelliJ plugin name (rootProject.name, currently
-        # "Grails IntelliJ Plugin"), not the ASF distribution name, and it contains spaces.
-        # Glob for it rather than hardcoding, so renaming the plugin cannot silently break
-        # the release; fail loudly if the match is not exactly one file.
+        # The ZIP base name comes from intellijPlatform.projectName, pinned to
+        # 'apache-grails-intellij-plugin' by the intellij-plugin convention plugin, so this
+        # rename is now effectively a copy. Glob rather than hardcode anyway, so a future
+        # rename cannot silently break the release; fail loudly unless exactly one file matches.
         run: |
           set -euo pipefail
           shopt -s nullglob
-          signed=(build/distributions/*-"${VERSION}"-signed.zip)
+          signed=(plugin/build/distributions/*-"${VERSION}"-signed.zip)
           if [[ ${#signed[@]} -ne 1 ]]; then
-            echo "::error::expected exactly 1 signed plugin ZIP for ${VERSION} in build/distributions, found ${#signed[@]}: ${signed[*]-}"
+            echo "::error::expected exactly 1 signed plugin ZIP for ${VERSION} in plugin/build/distributions, found ${#signed[@]}: ${signed[*]-}"
             exit 1
           fi
           cp "${signed[0]}" "${DIST_NAME}-${VERSION}-bin.zip"
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
index c290b1b..e5d4188 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.compiler.gradle
@@ -33,19 +33,27 @@
 // silently skips entries it cannot find, so these jar names are part of the plugin's contract and
 // must stay pinned and in sync with the descriptor. Nothing depends on a plain library's name.
 
+// It deliberately does NOT apply org.jetbrains.intellij.platform.module. IPGP treats any project
+// that applies .module and is reachable from the plugin via api/implementation/runtimeOnly as an
+// inferred *content module*, and packages it into lib/modules/. These projects are plain jars that
+// must sit in lib/: the compileServer.plugin classpath resolves them there by file name, and a jar
+// in lib/modules/ with no <content><module> entry is never loaded at all.
+//
+// That inference is also configuration-order dependent -- it only bites once the plugin project is
+// configured after these -- so it stayed invisible while the plugin lived at the root. Applying only
+// .base (via intellij-platform) gives the platform compile classpath without claiming to be a module.
+
 plugins {
     id 'org.apache.grails.intellij.build.intellij-platform'
-    id 'org.jetbrains.intellij.platform.module'
 }
 
 def shortName = projectDir.name
 
 group = 'org.apache.grails.intellij.compiler'
-base.archivesName = shortName
+// Pinned, not derived, and version dropped: this exact file name appears in the descriptor's
+// compileServer.plugin classpath, so it must not track rootProject.name or the project's depth.
+base.archivesName = "org.apache.grails.intellij.compiler.$shortName"
 
-// Pinned, not derived. IPGP's default is "${rootProject.name}.${project.path with ':' -> '.'}",
-// which tracks both the root project name and the project's depth in the tree; that is exactly
-// how the descriptor's classpath attribute went stale before.
-tasks.named('composedJar') { task ->
-    task.archiveBaseName = "org.apache.grails.intellij.compiler.$shortName"
+tasks.named('jar') { task ->
+    task.archiveVersion = ''
 }
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle
index d6ce900..abf811d 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.intellij-lib.gradle
@@ -27,16 +27,26 @@
 // the root would silently rename them. plugin.xml's compileServer.plugin classpath names two
 // of these jars literally, so the name has to be stable and space-free.
 
+// It deliberately does NOT apply org.jetbrains.intellij.platform.module. IPGP treats any project
+// that applies .module and is reachable from the plugin via api/implementation/runtimeOnly as an
+// inferred *content module*, and packages it into lib/modules/. These projects are plain jars that
+// must sit in lib/: the compileServer.plugin classpath resolves them there by file name, and a jar
+// in lib/modules/ with no <content><module> entry is never loaded at all.
+//
+// That inference is also configuration-order dependent -- it only bites once the plugin project is
+// configured after these -- so it stayed invisible while the plugin lived at the root. Applying only
+// .base (via intellij-platform) gives the platform compile classpath without claiming to be a module.
+
 plugins {
     id 'org.apache.grails.intellij.build.intellij-platform'
-    id 'org.jetbrains.intellij.platform.module'
 }
 
 def shortName = projectDir.name
 
 group = 'org.apache.grails.intellij.lib'
-base.archivesName = shortName
+// The jar task output is what ships; version dropped so the name is stable across releases.
+base.archivesName = "org.apache.grails.intellij.lib.$shortName"
 
-tasks.named('composedJar') { task ->
-    task.archiveBaseName = "org.apache.grails.intellij.lib.$shortName"
+tasks.named('jar') { task ->
+    task.archiveVersion = ''
 }
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
deleted file mode 100644
index 8f29e3d..0000000
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.legacy-layout.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// TRANSITIONAL. Declares the pre-restructure source layout inherited from the JetBrains JPS
-// monorepo (src/, gen/, resources/, test/) so that the convention
-// plugins can be introduced before any files move.
-//
-// Every project applies this while the restructure is in flight; it is deleted once all three
-// tiers have been normalized to src/main/java, src/main/resources and src/test/java. Declaring
-// a srcDir that does not exist is harmless, so one shared block covers all projects even
-// though only the plugin project has gen/ and test/.
-//
-// Do NOT add anything here that is not purely about the old layout.
-
-sourceSets.named('main') { main ->
-    main.java.srcDir 'src'
-    main.java.srcDir 'gen'
-    main.resources.srcDir 'resources'
-}
-
-sourceSets.named('test') { test ->
-    test.java.srcDir 'test'
-}
diff --git a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
index 5e7cca7..b985b76 100644
--- a/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
+++ b/build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle
@@ -26,6 +26,10 @@
 // verifies nothing.
 
 plugins {
+    // base supplies repositories: the RAT plugin resolves its own tool jar
+    // (org.apache.rat:apache-rat) at execution time, and the root project no longer gets
+    // repositories transitively now that it does not apply the java conventions.
+    id 'org.apache.grails.intellij.build.base'
     id 'lifecycle-base'
     id 'org.nosphere.apache.rat'
 }
@@ -60,11 +64,9 @@ tasks.named('rat') { task ->
             // reproducible-build and release-verification output (git-ignored)
             'etc/bin/results/**',
             // generated code (JFlex lexers; headers are injected by the generator config)
-            'gen/**',
             '**/src/main/gen/**',
             // test fixtures: content *is* the test input (mock Grails projects/JARs);
             // adding headers would break parser/position-sensitive tests
-            'testdata/**',
             '**/testdata/**',
             // file templates that materialize inside end-user projects
             '**/resources/fileTemplates/**',
diff --git a/build.gradle b/build.gradle
index 32adff9..4e9e4bb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,81 +13,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import org.jetbrains.intellij.platform.gradle.Constants
-import org.jetbrains.intellij.platform.gradle.TestFrameworkType
 
-// Deliberately no allprojects/subprojects block: every project applies its own convention
-// plugins. Everything here is either content (dependency lists) or root-only wiring.
+// The root project is a pure aggregator: it compiles nothing and produces no artifact. It owns
+// only the two things that must run from the repository root -- the licence audit, whose exclude
+// patterns are anchored at the audited directory, and the cross-project coverage aggregation,
+// which keeps its report at the path CI uploads.
+//
+// Deliberately no allprojects/subprojects block: every project applies its own convention plugins.
 plugins {
-    id 'org.apache.grails.intellij.build.intellij-plugin'
-    id 'org.apache.grails.intellij.build.legacy-layout'
     id 'org.apache.grails.intellij.build.rat'
     id 'org.apache.grails.intellij.build.coverage-aggregation'
 }
 
 dependencies {
-    intellijPlatform {
-        bundledPlugin 'com.intellij.javaee'
-        bundledPlugin 'com.intellij.javaee.el' // no longer transitive in 2026.2
-        bundledPlugin 'com.intellij.persistence'
-        bundledPlugin 'com.intellij.javaee.jpa'
-        bundledPlugin 'com.intellij.jsp'
-        bundledPlugin 'com.intellij.javaee.web'
-        bundledPlugin 'org.intellij.groovy'
-        bundledPlugin 'com.intellij.database'
-        bundledPlugin 'com.intellij.spring'
-        bundledPlugin 'com.intellij.gradle' // split out of org.jetbrains.plugins.gradle in 2026.2
-        bundledPlugin 'org.jetbrains.plugins.gradle'
-        bundledPlugin 'com.intellij.modules.ultimate'
-        bundledPlugin 'com.intellij.microservices.jvm'
-        bundledPlugin 'JavaScript'
-        bundledPlugin 'com.intellij.css'
-
-        // plugin modules. project(path:) is required: a bare project(':x') resolves through the
-        // owner chain to Project.project(String) and returns a Project, not a Dependency.
-        pluginModule project(path: ':pluginModules-copyright')
-        pluginModule project(path: ':pluginModules-coverage')
-        pluginModule project(path: ':pluginModules-hibernate')
-        pluginModule project(path: ':pluginModules-i18n')
-        pluginModule project(path: ':pluginModules-langInjection')
-        pluginModule project(path: ':pluginModules-maven')
-
-        // additional plugins required for testing
-        testBundledPlugin 'com.intellij.hibernate'
-        testBundledPlugin 'HtmlTools'
-        testBundledPlugin 'org.jetbrains.idea.maven'
-        testBundledPlugin 'com.intellij.copyright' // GspCopyrightUpdaterTest
-
-        // .INSTANCE because these are Kotlin objects; see testFramework/build.gradle
-        testFramework TestFrameworkType.Plugin.ExternalSystem.INSTANCE
-        testFramework TestFrameworkType.Plugin.Java.INSTANCE
-        testFramework TestFrameworkType.Plugin.Maven.INSTANCE
-        testFramework TestFrameworkType.Platform.INSTANCE
-    }
-
-    testImplementation 'junit:junit:4.13.2'
-    testImplementation 'org.assertj:assertj-core:4.0.0-M1'
-    testImplementation project(':libs-testFramework')
-    testCompileOnly project(':pluginModules-i18n')
-    testCompileOnly project(':pluginModules-copyright')
-
-    implementation project(':libs-gradle-tooling')
-    implementation project(':libs-grails-rt')
-
-    // JPS/build
-    runtimeOnly project(':compilers-grails-compiler-patch')
-    runtimeOnly project(':compilers-jps-plugin')
-
-    // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
+    // Every project that compiles code contributes to the aggregate coverage report.
+    jacocoAggregation project(':plugin')
     jacocoAggregation project(':pluginModules-copyright')
     jacocoAggregation project(':pluginModules-coverage')
-    jacocoAggregation project(':libs-gradle-tooling')
-    jacocoAggregation project(':compilers-grails-compiler-patch')
-    jacocoAggregation project(':libs-grails-rt')
     jacocoAggregation project(':pluginModules-hibernate')
     jacocoAggregation project(':pluginModules-i18n')
-    jacocoAggregation project(':compilers-jps-plugin')
     jacocoAggregation project(':pluginModules-langInjection')
     jacocoAggregation project(':pluginModules-maven')
+    jacocoAggregation project(':libs-gradle-tooling')
+    jacocoAggregation project(':libs-grails-rt')
     jacocoAggregation project(':libs-testFramework')
+    jacocoAggregation project(':compilers-jps-plugin')
+    jacocoAggregation project(':compilers-grails-compiler-patch')
 }
diff --git a/etc/bin/test-reproducible-build.sh b/etc/bin/test-reproducible-build.sh
index dc8513e..9754a99 100755
--- a/etc/bin/test-reproducible-build.sh
+++ b/etc/bin/test-reproducible-build.sh
@@ -55,7 +55,7 @@ RESULTS="etc/bin/results"
 # line, sorted so the output is order-stable across runs.
 list_artifacts() {
   {
-    find build/distributions -type f -name '*.zip' 2> /dev/null || true
+    find plugin/build/distributions -type f -name '*.zip' 2> /dev/null || true
     find . -type f -path '*/build/libs/*.jar' 2> /dev/null || true
   } | sed 's|^\./||' | sort
 }
diff --git a/etc/bin/verify-reproducible.sh b/etc/bin/verify-reproducible.sh
index 3c45bd4..b5ca796 100755
--- a/etc/bin/verify-reproducible.sh
+++ b/etc/bin/verify-reproducible.sh
@@ -91,9 +91,9 @@ echo "✅ wrapper bootstrapped"
 echo "==> Rebuilding the plugin from source (this downloads the IntelliJ Platform; expect several GB)"
 (cd "${SRC_DIR}" && ./gradlew buildPlugin -Pversion="${VERSION}" --no-daemon)
 
-REBUILT_ZIP="$(find "${SRC_DIR}/build/distributions" -maxdepth 1 -type f -name '*.zip' | head -n 1)"
+REBUILT_ZIP="$(find "${SRC_DIR}/plugin/build/distributions" -maxdepth 1 -type f -name '*.zip' | head -n 1)"
 if [ -z "${REBUILT_ZIP}" ]; then
-  echo "❌ rebuild produced no ZIP in ${SRC_DIR}/build/distributions" >&2
+  echo "❌ rebuild produced no ZIP in ${SRC_DIR}/plugin/build/distributions" >&2
   exit 1
 fi
 echo "✅ rebuilt $(basename "${REBUILT_ZIP}")"
diff --git a/libs/testFramework/build.gradle b/libs/testFramework/build.gradle
index da54ded..5b92b97 100644
--- a/libs/testFramework/build.gradle
+++ b/libs/testFramework/build.gradle
@@ -45,5 +45,5 @@ dependencies {
     compileOnly 'junit:junit:4.13.2'
     compileOnly 'org.assertj:assertj-core:4.0.0-M1'
 
-    compileOnly project(':')
+    compileOnly project(':plugin')
 }
diff --git a/build.gradle b/plugin/build.gradle
similarity index 79%
copy from build.gradle
copy to plugin/build.gradle
index 32adff9..d6869ee 100644
--- a/build.gradle
+++ b/plugin/build.gradle
@@ -20,9 +20,6 @@ import org.jetbrains.intellij.platform.gradle.TestFrameworkType
 // plugins. Everything here is either content (dependency lists) or root-only wiring.
 plugins {
     id 'org.apache.grails.intellij.build.intellij-plugin'
-    id 'org.apache.grails.intellij.build.legacy-layout'
-    id 'org.apache.grails.intellij.build.rat'
-    id 'org.apache.grails.intellij.build.coverage-aggregation'
 }
 
 dependencies {
@@ -78,16 +75,4 @@ dependencies {
     runtimeOnly project(':compilers-grails-compiler-patch')
     runtimeOnly project(':compilers-jps-plugin')
 
-    // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
-    jacocoAggregation project(':pluginModules-copyright')
-    jacocoAggregation project(':pluginModules-coverage')
-    jacocoAggregation project(':libs-gradle-tooling')
-    jacocoAggregation project(':compilers-grails-compiler-patch')
-    jacocoAggregation project(':libs-grails-rt')
-    jacocoAggregation project(':pluginModules-hibernate')
-    jacocoAggregation project(':pluginModules-i18n')
-    jacocoAggregation project(':compilers-jps-plugin')
-    jacocoAggregation project(':pluginModules-langInjection')
-    jacocoAggregation project(':pluginModules-maven')
-    jacocoAggregation project(':libs-testFramework')
 }
diff --git a/gen/org/jetbrains/plugins/grails/GroovyMvcIcons.java b/plugin/src/main/gen/org/jetbrains/plugins/grails/GroovyMvcIcons.java
similarity index 100%
rename from gen/org/jetbrains/plugins/grails/GroovyMvcIcons.java
rename to plugin/src/main/gen/org/jetbrains/plugins/grails/GroovyMvcIcons.java
diff --git a/gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspDirectiveLexer.java b/plugin/src/main/gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspDirectiveLexer.java
similarity index 100%
rename from gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspDirectiveLexer.java
rename to plugin/src/main/gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspDirectiveLexer.java
diff --git a/gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspLexer.java b/plugin/src/main/gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspLexer.java
similarity index 100%
rename from gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspLexer.java
rename to plugin/src/main/gen/org/jetbrains/plugins/grails/lang/gsp/lexer/core/_GspLexer.java
diff --git a/src/org/jetbrains/plugins/grails/GantFileDetector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GantFileDetector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GantFileDetector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GantFileDetector.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsAttachSourcesProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsAttachSourcesProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsAttachSourcesProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsAttachSourcesProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsBundle.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsBundle.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsBundle.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsBundle.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsCompletionContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsCompletionContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsCompletionContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsCompletionContributor.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsConsoleFilterProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsConsoleFilterProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsConsoleFilterProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsConsoleFilterProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsFlashMapContentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsFlashMapContentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsFlashMapContentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsFlashMapContentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsGroovyCodeReferenceContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsGroovyCodeReferenceContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsGroovyCodeReferenceContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsGroovyCodeReferenceContributor.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsGspReferenceContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsGspReferenceContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsGspReferenceContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsGspReferenceContributor.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsHtmlReferenceContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsHtmlReferenceContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsHtmlReferenceContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsHtmlReferenceContributor.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsImplicitUsageProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsImplicitUsageProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsImplicitUsageProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsImplicitUsageProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsPluginFieldCompletionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsPluginFieldCompletionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsPluginFieldCompletionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsPluginFieldCompletionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GrailsStaticFieldCompletionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GrailsStaticFieldCompletionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GrailsStaticFieldCompletionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GrailsStaticFieldCompletionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GsonConstants.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GsonConstants.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GsonConstants.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GsonConstants.java
diff --git a/src/org/jetbrains/plugins/grails/GspCompletionContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GspCompletionContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GspCompletionContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GspCompletionContributor.java
diff --git a/src/org/jetbrains/plugins/grails/GspFindUsagesProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GspFindUsagesProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GspFindUsagesProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GspFindUsagesProvider.java
diff --git a/src/org/jetbrains/plugins/grails/GspTemplateContextType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/GspTemplateContextType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/GspTemplateContextType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/GspTemplateContextType.java
diff --git a/src/org/jetbrains/plugins/grails/TemplateHighlightErrorFilter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/TemplateHighlightErrorFilter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/TemplateHighlightErrorFilter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/TemplateHighlightErrorFilter.java
diff --git a/src/org/jetbrains/plugins/grails/actions/DomainFieldIntention.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/DomainFieldIntention.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/DomainFieldIntention.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/DomainFieldIntention.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GenerateViewIntention.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GenerateViewIntention.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GenerateViewIntention.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GenerateViewIntention.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsActionGroup.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionGroup.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsActionGroup.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionGroup.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsActionUtil.kt
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsConfigureSDKAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsCreateActionByUsageIntention.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsCreateActionByUsageIntention.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsCreateActionByUsageIntention.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsCreateActionByUsageIntention.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsGenerateViewAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGenerateViewAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsGenerateViewAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGenerateViewAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsGoToClassContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGoToClassContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsGoToClassContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGoToClassContributor.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsGotoRelatedProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGotoRelatedProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsGotoRelatedProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGotoRelatedProvider.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsGroovySourceFolderDetector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGroovySourceFolderDetector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsGroovySourceFolderDetector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsGroovySourceFolderDetector.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsRunCommandAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/GrailsWarAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsWarAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/GrailsWarAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/GrailsWarAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/MakeNullableIntention.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/MakeNullableIntention.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/MakeNullableIntention.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/MakeNullableIntention.java
diff --git a/src/org/jetbrains/plugins/grails/actions/MakeUniqueIntention.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/MakeUniqueIntention.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/MakeUniqueIntention.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/MakeUniqueIntention.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsControllerAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsControllerAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsControllerAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsControllerAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsDomainClassAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsDomainClassAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsDomainClassAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsDomainClassAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsFilterAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsFilterAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsFilterAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsFilterAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsInterceptorAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsInterceptorAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsInterceptorAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsInterceptorAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsScriptAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsScriptAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsScriptAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsScriptAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsServiceAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsServiceAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsServiceAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsServiceAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsTagLibAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsTagLibAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsTagLibAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsTagLibAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGrailsXXXAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/NewGspAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGspAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/NewGspAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/NewGspAction.java
diff --git a/src/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/actions/UpdateGrailsSettingsAction.java
diff --git a/src/org/jetbrains/plugins/grails/addins/GrailsIntegrationUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/addins/GrailsIntegrationUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/addins/GrailsIntegrationUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/addins/GrailsIntegrationUtil.java
diff --git a/src/org/jetbrains/plugins/grails/addins/js/CssIntegrationUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/CssIntegrationUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/addins/js/CssIntegrationUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/CssIntegrationUtil.java
diff --git a/src/org/jetbrains/plugins/grails/addins/js/GrailsJavaScriptInjector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/GrailsJavaScriptInjector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/addins/js/GrailsJavaScriptInjector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/GrailsJavaScriptInjector.java
diff --git a/src/org/jetbrains/plugins/grails/addins/js/GspJsInjectionTagBean.java b/plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/GspJsInjectionTagBean.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/addins/js/GspJsInjectionTagBean.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/GspJsInjectionTagBean.java
diff --git a/src/org/jetbrains/plugins/grails/addins/js/JavaScriptIntegrationUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/JavaScriptIntegrationUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/addins/js/JavaScriptIntegrationUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/addins/js/JavaScriptIntegrationUtil.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/ArtefactHandlers.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/GrailsArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/GrailsDisplayableArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/HandlerCache.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/api/IconOwner.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/IconOwner.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/api/IconOwner.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/api/IconOwner.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/ControllerArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/DomainArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/FilterArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/GrailsArtefacts.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/InterceptorArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/ServiceArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/TaglibArtefactHandler.java
diff --git a/src/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/artefact/impl/controllers/functions.kt
diff --git a/src/org/jetbrains/plugins/grails/commands/Grails3CommandProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/commands/Grails3CommandProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/commands/Grails3CommandProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/commands/Grails3CommandProvider.java
diff --git a/src/org/jetbrains/plugins/grails/commands/GrailsCommandCompletionUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/commands/GrailsCommandCompletionUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/commands/GrailsCommandCompletionUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/commands/GrailsCommandCompletionUtil.java
diff --git a/src/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/commands/GrailsCommandProvider.java
diff --git a/src/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/commands/OldGrailsCommandProvider.java
diff --git a/src/org/jetbrains/plugins/grails/config/AbstractConfigSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/AbstractConfigSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/AbstractConfigSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/AbstractConfigSupport.java
diff --git a/src/org/jetbrains/plugins/grails/config/BuildConfigClosureMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/BuildConfigClosureMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/BuildConfigClosureMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/BuildConfigClosureMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/config/BuildConfigMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/BuildConfigMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/BuildConfigMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/BuildConfigMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/config/BuildConfigSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/BuildConfigSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/BuildConfigSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/BuildConfigSupport.java
diff --git a/src/org/jetbrains/plugins/grails/config/ConfigMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/ConfigMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/ConfigMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/ConfigMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/config/ConfigSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/ConfigSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/ConfigSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/ConfigSupport.java
diff --git a/src/org/jetbrains/plugins/grails/config/DataSourcesSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/DataSourcesSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/DataSourcesSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/DataSourcesSupport.java
diff --git a/src/org/jetbrains/plugins/grails/config/GradleParentProjectForm.form b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GradleParentProjectForm.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GradleParentProjectForm.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GradleParentProjectForm.form
diff --git a/src/org/jetbrains/plugins/grails/config/GradleParentProjectForm.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GradleParentProjectForm.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GradleParentProjectForm.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GradleParentProjectForm.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsAbstractConfigSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsAbstractConfigSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsAbstractConfigSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsAbstractConfigSupport.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsConfigUnresolvedFileFilter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsConfigUnresolvedFileFilter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsConfigUnresolvedFileFilter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsConfigUnresolvedFileFilter.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsConfigUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsConfigUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsConfigUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsConfigUtils.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsConstants.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsConstants.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsConstants.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsConstants.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsEnvironmentClosureMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsEnvironmentClosureMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsEnvironmentClosureMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsEnvironmentClosureMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsFramework.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsFramework.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsFramework.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsFramework.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsGradleAwareModuleBuilder.kt
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsIgnoredProvider.kt
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsLibraryPresentationProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsLibraryPresentationProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsLibraryPresentationProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsLibraryPresentationProvider.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsModuleBuilder.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsModuleBuilder.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsModuleBuilder.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsModuleBuilder.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsModuleStructureUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsModuleStructureUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsModuleStructureUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsModuleStructureUtil.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.form b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.form
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsOptionsWizardStep.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsPluginConfigMethodContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPluginConfigMethodContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsPluginConfigMethodContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPluginConfigMethodContributor.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsPluginNameCompletionContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPluginNameCompletionContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsPluginNameCompletionContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPluginNameCompletionContributor.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsPluginWebHelpReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPluginWebHelpReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsPluginWebHelpReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPluginWebHelpReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsProjectStructureDetector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsProjectStructureDetector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsProjectStructureDetector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsProjectStructureDetector.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsPropertiesFileCache.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPropertiesFileCache.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsPropertiesFileCache.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsPropertiesFileCache.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsSettingSynchronizer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsSettingSynchronizer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsSettingSynchronizer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsSettingSynchronizer.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsSettings.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsSettings.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsSettings.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsSettings.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsSettingsService.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsSettingsService.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsSettingsService.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsSettingsService.java
diff --git a/src/org/jetbrains/plugins/grails/config/GrailsStructure.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsStructure.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GrailsStructure.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GrailsStructure.java
diff --git a/src/org/jetbrains/plugins/grails/config/GroovyConfigReader.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/GroovyConfigReader.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/GroovyConfigReader.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/GroovyConfigReader.java
diff --git a/src/org/jetbrains/plugins/grails/config/PrintGrailsSettingsConstants.java b/plugin/src/main/java/org/jetbrains/plugins/grails/config/PrintGrailsSettingsConstants.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/config/PrintGrailsSettingsConstants.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/config/PrintGrailsSettingsConstants.java
diff --git a/src/org/jetbrains/plugins/grails/database/GrailsDataSourceDetector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/database/GrailsDataSourceDetector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/database/GrailsDataSourceDetector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/database/GrailsDataSourceDetector.java
diff --git a/src/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/GenerateTestsAction.java
diff --git a/src/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/GrailsEditorDecorator.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/ViewGrailsEditorToolbarAction.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/selection/GrailsDependencySelectioner.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/selection/GrailsDependencySelectioner.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/selection/GrailsDependencySelectioner.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/selection/GrailsDependencySelectioner.java
diff --git a/src/org/jetbrains/plugins/grails/editor/selection/GrailsGroovyInjectionSelectioner.java b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/selection/GrailsGroovyInjectionSelectioner.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/selection/GrailsGroovyInjectionSelectioner.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/selection/GrailsGroovyInjectionSelectioner.java
diff --git a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGenerateActions.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsGoToArtefactActionBase.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActionBase.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarActions.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarTargetAction.kt
diff --git a/src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/editor/toolbar/GrailsToolbarVfileAction.kt
diff --git a/src/org/jetbrains/plugins/grails/feature/forge/GrailsForgeModuleBuilder.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/forge/GrailsForgeModuleBuilder.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/feature/forge/GrailsForgeModuleBuilder.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/feature/forge/GrailsForgeModuleBuilder.kt
diff --git a/src/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/namedArguments.kt
diff --git a/src/org/jetbrains/plugins/grails/feature/linkable/transform.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/transform.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/feature/linkable/transform.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/feature/linkable/transform.kt
diff --git a/src/org/jetbrains/plugins/grails/fileType/GspFileType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/fileType/GspFileType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/fileType/GspFileType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/fileType/GspFileType.java
diff --git a/src/org/jetbrains/plugins/grails/gorm/GormClassNames.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/GormClassNames.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gorm/GormClassNames.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gorm/GormClassNames.java
diff --git a/src/org/jetbrains/plugins/grails/gorm/gormVersion.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gorm/gormVersion.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gorm/gormVersion.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gorm/gormVersion.kt
diff --git a/src/org/jetbrains/plugins/grails/gradle/GrailsModuleData.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsModuleData.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gradle/GrailsModuleData.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsModuleData.java
diff --git a/src/org/jetbrains/plugins/grails/gradle/GrailsModuleGradleDataService.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsModuleGradleDataService.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gradle/GrailsModuleGradleDataService.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsModuleGradleDataService.java
diff --git a/src/org/jetbrains/plugins/grails/gradle/GrailsProjectResolverExtension.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsProjectResolverExtension.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gradle/GrailsProjectResolverExtension.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsProjectResolverExtension.java
diff --git a/src/org/jetbrains/plugins/grails/gradle/GrailsTaskManagerExtension.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsTaskManagerExtension.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gradle/GrailsTaskManagerExtension.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gradle/GrailsTaskManagerExtension.java
diff --git a/src/org/jetbrains/plugins/grails/gson/GrailsStreamingJsonBuilderContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GrailsStreamingJsonBuilderContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GrailsStreamingJsonBuilderContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GrailsStreamingJsonBuilderContributor.java
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonControllerReferences.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonExpectedPackageProvider.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonImportContributor.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelArgumentsProvider.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonModelFieldArgumentDescriptor.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonPositionManagerDelegate.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonReferenceContributor.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonRenderReferences.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTemplateRendererContributor.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonTransformationSupport.kt
diff --git a/src/org/jetbrains/plugins/grails/gson/GsonUtils.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/gson/GsonUtils.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/gson/GsonUtils.kt
diff --git a/src/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GrailsMethodMayBeStaticFilter.java
diff --git a/src/org/jetbrains/plugins/grails/inspections/GspAndGroovyInspection.java b/plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GspAndGroovyInspection.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/inspections/GspAndGroovyInspection.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GspAndGroovyInspection.java
diff --git a/src/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GspInspectionUtil.java
diff --git a/src/org/jetbrains/plugins/grails/inspections/GspSuppressionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GspSuppressionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/inspections/GspSuppressionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/inspections/GspSuppressionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/DirtyCheckableContributor.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/GormTraitContributor.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/Grails3TraitInjectorContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/Grails3TraitInjectorContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/Grails3TraitInjectorContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/Grails3TraitInjectorContributor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspCssInjector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspCssInjector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspCssInjector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspCssInjector.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspDirectiveKind.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspDirectiveKind.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspDirectiveKind.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspDirectiveKind.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspErrorFilter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspErrorFilter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspErrorFilter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspErrorFilter.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspFileViewProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspFileViewProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspFileViewProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspFileViewProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspFileviewProviderFactory.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspFileviewProviderFactory.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspFileviewProviderFactory.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspFileviewProviderFactory.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspLanguage.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspLanguage.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspLanguage.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspLanguage.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspTagDocumentationProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspTagDocumentationProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspTagDocumentationProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspTagDocumentationProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/GspTagInspection.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspTagInspection.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/GspTagInspection.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/GspTagInspection.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/completion/GspXmlFileNSInfoProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/completion/GspXmlFileNSInfoProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/completion/GspXmlFileNSInfoProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/completion/GspXmlFileNSInfoProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/debug/GspCodeFragmentFactory.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/debug/GspCodeFragmentFactory.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/debug/GspCodeFragmentFactory.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/debug/GspCodeFragmentFactory.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManager.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManager.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManager.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManager.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManagerFactory.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManagerFactory.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManagerFactory.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/debug/GspPositionManagerFactory.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEditorActionsUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEditorActionsUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEditorActionsUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEditorActionsUtil.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEnterHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEnterHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEnterHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspEnterHandler.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspTypedHandler.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspTypedHandler.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspTypedHandler.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/editor/actions/GspTypedHandler.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/folding/GspFoldingBuilder.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/folding/GspFoldingBuilder.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/folding/GspFoldingBuilder.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/folding/GspFoldingBuilder.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/AbstractGspBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/ForeignLanguageBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/ForeignLanguageBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/ForeignLanguageBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/ForeignLanguageBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlockGenerator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlockGenerator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlockGenerator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspBlockGenerator.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingHelper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingHelper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingHelper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingHelper.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingModelBuilder.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingModelBuilder.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingModelBuilder.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspFormattingModelBuilder.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspGroovyBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspGroovyBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspGroovyBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspGroovyBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspHtmlBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspHtmlBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspHtmlBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspHtmlBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspTextSyntheticBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspTextSyntheticBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/GspTextSyntheticBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/GspTextSyntheticBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspIndentProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspIndentProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspIndentProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspIndentProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspSpacingProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspSpacingProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspSpacingProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/processors/GspSpacingProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspCodeStyleSettingsProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspCodeStyleSettingsProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspCodeStyleSettingsProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspCodeStyleSettingsProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspLanguageCodeStyleSettingsProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspLanguageCodeStyleSettingsProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspLanguageCodeStyleSettingsProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/formatter/settings/GspLanguageCodeStyleSettingsProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeIndex.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeIndex.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeIndex.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeIndex.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeInfo.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeInfo.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeInfo.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/gspIndex/GspIncludeInfo.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspCommenter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspCommenter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspCommenter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspCommenter.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlighter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlighter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlighter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlighter.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlightingLexer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlightingLexer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlightingLexer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspDirectiveHighlightingLexer.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighter.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspEditorHighlighterProvider.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspFileBraceMatcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspFileBraceMatcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspFileBraceMatcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspFileBraceMatcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspIndexPatternBuilder.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspIndexPatternBuilder.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspIndexPatternBuilder.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspIndexPatternBuilder.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspPairedBraceMatcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspPairedBraceMatcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspPairedBraceMatcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspPairedBraceMatcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspSyntaxHighlighter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspSyntaxHighlighter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspSyntaxHighlighter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/highlighter/GspSyntaxHighlighter.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/GspDirectiveFlexLexer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/GspDirectiveFlexLexer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/GspDirectiveFlexLexer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/GspDirectiveFlexLexer.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/GspFlexLexer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/GspFlexLexer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/GspFlexLexer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/GspFlexLexer.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/GspTokenTypesEx.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/GspTokenTypesEx.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/GspTokenTypesEx.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/GspTokenTypesEx.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/IGspElementType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/IGspElementType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/IGspElementType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/IGspElementType.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/core/GspTokenTypes.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/core/GspTokenTypes.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/core/GspTokenTypes.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/core/GspTokenTypes.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp.flex b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp.flex
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp.flex
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp.flex
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp_directive.flex b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp_directive.flex
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp_directive.flex
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/lexer/core/gsp_directive.flex
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/GspElementTypes.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/GspElementTypes.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/GspElementTypes.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/GspElementTypes.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/GspGroovyElementTypes.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/GspGroovyElementTypes.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/GspGroovyElementTypes.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/GspGroovyElementTypes.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/GspPsiCreator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/GspPsiCreator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/GspPsiCreator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/GspPsiCreator.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/composite/GspCompositeElementType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/composite/GspCompositeElementType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/composite/GspCompositeElementType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/composite/GspCompositeElementType.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspAwareGroovyParser.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspAwareGroovyParser.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspAwareGroovyParser.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspAwareGroovyParser.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspGroovyParser.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspGroovyParser.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspGroovyParser.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspGroovyParser.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspTemplateStmtParsing.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspTemplateStmtParsing.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspTemplateStmtParsing.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/GspTemplateStmtParsing.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsElementType.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsInGspFileRoot.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsInGspFileRoot.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsInGspFileRoot.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyDeclarationsInGspFileRoot.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyExpressionElementType.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GroovyMapAttributeValueType.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/chameleons/GspGroovyLazyElementType.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/lexer/GspGroovyLexer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/lexer/GspGroovyLexer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/lexer/GspGroovyLexer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/groovy/lexer/GspGroovyLexer.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GrailsTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GrailsTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GrailsTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GrailsTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParser.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParser.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParser.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParser.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParserDefinition.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParserDefinition.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParserDefinition.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/GspParserDefinition.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/chameleons/GspDirectiveElement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/chameleons/GspDirectiveElement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/chameleons/GspDirectiveElement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/chameleons/GspDirectiveElement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/lexer/GspLexer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/lexer/GspLexer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/lexer/GspLexer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/gsp/lexer/GspLexer.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/html/GspHtmlTemplateRootType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/html/GspHtmlTemplateRootType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/html/GspHtmlTemplateRootType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/html/GspHtmlTemplateRootType.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/html/elements/GspXmlDocument.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/html/elements/GspXmlDocument.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/html/elements/GspXmlDocument.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/html/elements/GspXmlDocument.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspGroovyCodeElementType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspGroovyCodeElementType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspGroovyCodeElementType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspGroovyCodeElementType.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspInHtmlElementType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspInHtmlElementType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspInHtmlElementType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspInHtmlElementType.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspTemplateDataElementType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspTemplateDataElementType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspTemplateDataElementType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/parsing/outers/GspTemplateDataElementType.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactory.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactory.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactory.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactory.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactoryImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactoryImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactoryImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiElementFactoryImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/GspPsiUtil.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/GspLazyElement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/GspLazyElement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/GspLazyElement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/GspLazyElement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspClass.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspClass.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspClass.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspClass.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspDeclarationHolder.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspDeclarationHolder.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspDeclarationHolder.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspDeclarationHolder.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspExprInjection.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspExprInjection.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspExprInjection.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspExprInjection.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunBlock.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunBlock.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunBlock.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunBlock.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunMethod.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunMethod.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunMethod.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrGspRunMethod.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrMapAttributeValue.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrMapAttributeValue.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrMapAttributeValue.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GrMapAttributeValue.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspGroovyFile.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspGroovyFile.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspGroovyFile.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspGroovyFile.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterGroovyElement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterGroovyElement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterGroovyElement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterGroovyElement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterHtmlElement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterHtmlElement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterHtmlElement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/GspOuterHtmlElement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/template/GspTemplateStatement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/template/GspTemplateStatement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/template/GspTemplateStatement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/api/template/GspTemplateStatement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspClassImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspClassImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspClassImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspClassImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspDeclarationHolderImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspDeclarationHolderImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspDeclarationHolderImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspDeclarationHolderImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspExprInjectionImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspExprInjectionImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspExprInjectionImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspExprInjectionImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunBlockImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunBlockImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunBlockImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunBlockImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunMethodImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunMethodImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunMethodImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrGspRunMethodImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrMapAttrValueImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrMapAttrValueImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrMapAttrValueImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GrMapAttrValueImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspGroovyFileImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspGroovyFileImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspGroovyFileImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspGroovyFileImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspModelVariableModel.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspModelVariableModel.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspModelVariableModel.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspModelVariableModel.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterGroovyElementImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterGroovyElementImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterGroovyElementImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterGroovyElementImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementManipulator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementManipulator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementManipulator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspOuterHtmlElementManipulator.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspPageSkeleton.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspPageSkeleton.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspPageSkeleton.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/GspPageSkeleton.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/ModelVariableDeclarationSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/ModelVariableDeclarationSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/ModelVariableDeclarationSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/ModelVariableDeclarationSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/template/GspTemplateStatementImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/template/GspTemplateStatementImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/template/GspTemplateStatementImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/groovy/impl/template/GspTemplateStatementImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspDeclarationTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspDeclarationTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspDeclarationTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspDeclarationTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspExpressionTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspExpressionTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspExpressionTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspExpressionTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspFile.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspFile.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspFile.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspFile.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspScriptletTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspScriptletTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspScriptletTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspScriptletTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/GspXmlTagBase.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirective.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirective.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirective.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirective.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttributeValue.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttributeValue.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttributeValue.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/directive/GspDirectiveAttributeValue.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttributeValue.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttributeValue.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttributeValue.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspAttributeValue.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspGrailsTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspTaggedElement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspTaggedElement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspTaggedElement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspTaggedElement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/api/gtag/GspXmlRootTag.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspDeclarationTagImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspDeclarationTagImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspDeclarationTagImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspDeclarationTagImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspExprTagImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspExprTagImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspExprTagImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspExprTagImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspFileImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspFileImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspFileImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspFileImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspScriptletTagImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/GspXmlTagBaseImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeValueImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeValueImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeValueImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveAttributeValueImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/directive/GspDirectiveImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeValueImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeValueImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeValueImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspAttributeValueImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspContentTagDescriptorProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspContentTagDescriptorProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspContentTagDescriptorProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspContentTagDescriptorProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspElementDescriptorBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspElementDescriptorBase.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspElementDescriptorBase.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspElementDescriptorBase.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspGrailsTagImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkElementDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkElementDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkElementDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkElementDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkNamespaceDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkNamespaceDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkNamespaceDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspLinkNamespaceDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspNamespaceDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspNamespaceDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspNamespaceDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspNamespaceDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspPropertyElementDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspPropertyElementDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspPropertyElementDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspPropertyElementDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspSdkElementDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspSdkElementDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspSdkElementDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspSdkElementDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagDescriptorService.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagDescriptorService.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagDescriptorService.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagDescriptorService.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagRuleProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagVariableDeclarationSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagVariableDeclarationSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagVariableDeclarationSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTagVariableDeclarationSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTmplNamespaceDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTmplNamespaceDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTmplNamespaceDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspTmplNamespaceDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/GspXmlRootTagImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/DefaultCodecDirectiveReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/DefaultCodecDirectiveReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/DefaultCodecDirectiveReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/DefaultCodecDirectiveReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/GspCssClassReferenceContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/GspCssClassReferenceContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/GspCssClassReferenceContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/gsp/impl/gtag/reference/contributor/GspCssClassReferenceContributor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspHtmlOuterElement.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspHtmlOuterElement.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspHtmlOuterElement.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspHtmlOuterElement.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspLikeFile.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspLikeFile.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspLikeFile.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/api/GspLikeFile.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlFileImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlFileImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlFileImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlFileImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlOuterElementImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlOuterElementImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlOuterElementImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlOuterElementImpl.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlXmlExtension.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlXmlExtension.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlXmlExtension.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/html/impl/GspHtmlXmlExtension.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/psi/impl/gspImportUtil.kt
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/BuiltInTagReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/BuiltInTagReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/BuiltInTagReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/BuiltInTagReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GrailsTaglibNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GrailsTaglibNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GrailsTaglibNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GrailsTaglibNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/GspTagLibUtil.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/TagLibNamespaceDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/TagLibNamespaceDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/TagLibNamespaceDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/TagLibNamespaceDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/search/GspControllerReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/search/GspControllerReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/search/GspControllerReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/search/GspControllerReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/search/GspPageReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/search/GspPageReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/search/GspPageReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/search/GspPageReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/search/JspPageReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/search/JspPageReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/search/JspPageReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/search/JspPageReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/lang/gsp/util/GspUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/util/GspUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/lang/gsp/util/GspUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/lang/gsp/util/GspUtil.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/DomainClassNodeRenderer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/DomainClassNodeRenderer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/DomainClassNodeRenderer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/DomainClassNodeRenderer.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditor.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditorProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditorProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditorProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/DomainClassesRelationsEditorProvider.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/PerspectiveFileEditorComponent.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/PerspectiveFileEditorComponent.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/PerspectiveFileEditorComponent.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/PerspectiveFileEditorComponent.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.form b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.form
diff --git a/src/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/create/CreateNewRelation.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/create/RelationsCreationsProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/create/RelationsCreationsProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/create/RelationsCreationsProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/create/RelationsCreationsProvider.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/delete/RelationDeleteProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/delete/RelationDeleteProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/delete/RelationDeleteProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/delete/RelationDeleteProvider.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/graph/DataModelAndSelectionModificationTracker.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DataModelAndSelectionModificationTracker.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/graph/DataModelAndSelectionModificationTracker.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DataModelAndSelectionModificationTracker.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassDependencyPresentation.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassDependencyPresentation.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassDependencyPresentation.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassDependencyPresentation.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassNode.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassRelationsInfo.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassRelationsInfo.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassRelationsInfo.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassRelationsInfo.java
diff --git a/src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassesRelationsDataModel.java b/plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassesRelationsDataModel.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/perspectives/graph/DomainClassesRelationsDataModel.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/perspectives/graph/DomainClassesRelationsDataModel.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/AssetScriptInjector.kt
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/assetPipeline/references.kt
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/buildTestData/GrailsBuildTestDataMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/buildTestData/GrailsBuildTestDataMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/buildTestData/GrailsBuildTestDataMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/buildTestData/GrailsBuildTestDataMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceModuleReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceModuleReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceModuleReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceModuleReference.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourceReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesClosureMethodContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesClosureMethodContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesClosureMethodContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesClosureMethodContributor.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesPomDeclarationSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesPomDeclarationSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesPomDeclarationSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesPomDeclarationSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/resources/GrailsResourcesUtil.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableReturnTypeCalculator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableReturnTypeCalculator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableReturnTypeCalculator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableReturnTypeCalculator.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/GrailsSearchableUtil.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableFieldReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableFieldReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableFieldReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableFieldReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableMemberMethodContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableMemberMethodContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableMemberMethodContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/seachable/SearchableMemberMethodContributor.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroAccessControlMethodProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroAccessControlMethodProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroAccessControlMethodProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroAccessControlMethodProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/shiro/GrailsShiroReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/spock/GrailsSpockEnhancer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/spock/GrailsSpockEnhancer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/spock/GrailsSpockEnhancer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/spock/GrailsSpockEnhancer.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowEventDefinitionMethodContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowEventDefinitionMethodContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowEventDefinitionMethodContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowEventDefinitionMethodContributor.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowMethodContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowMethodContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowMethodContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowMethodContributor.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowPomDeclarationSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowPomDeclarationSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowPomDeclarationSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowPomDeclarationSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowStateNameReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowStateNameReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowStateNameReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowStateNameReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/pluginSupport/webflow/WebFlowUtils.java
diff --git a/src/org/jetbrains/plugins/grails/plugins/descriptors.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/descriptors.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/plugins/descriptors.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/plugins/descriptors.kt
diff --git a/src/org/jetbrains/plugins/grails/plugins/impl.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/plugins/impl.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/plugins/impl.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/plugins/impl.kt
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsIconProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsPluginsNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPane.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsProjectViewPanes.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/GrailsTreeStructureProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/NodeWeights.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/NodeWeights.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/NodeWeights.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/NodeWeights.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/api/GrailsSingleNodeProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/api/GrailsViewNodeProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/Grails3NodeProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsAppNodeProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsNodeComparator.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/GrailsViewItems.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/impl/OldGrailsNodeProvider.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/CompactedFqn.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsActionNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsApplicationNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactHandlerNodeBase.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsArtefactPackageNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsControllerNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsFlatPackageNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsNodes.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPackageValue.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPluginNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsPsiDirectoryNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsRegularPackageNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/GrailsRootNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/OldGrailsPluginsNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/OtherGrailsAppSourcesNode.java
diff --git a/src/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/projectView/nodes/TreeNodeUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/CodecNonCodeMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/CodecNonCodeMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/CodecNonCodeMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/CodecNonCodeMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/EnhancesTraitsCollector.kt
diff --git a/src/org/jetbrains/plugins/grails/references/GrailsArtifactNonCodeMemberProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsArtifactNonCodeMemberProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/GrailsArtifactNonCodeMemberProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsArtifactNonCodeMemberProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/references/GrailsArtifactRenameProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsArtifactRenameProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/GrailsArtifactRenameProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsArtifactRenameProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/references/GrailsClosureMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsClosureMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/GrailsClosureMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsClosureMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/GrailsMethodNamedArgumentReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsMethodNamedArgumentReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/GrailsMethodNamedArgumentReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsMethodNamedArgumentReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/GrailsResourceClassMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsResourceClassMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/GrailsResourceClassMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/GrailsResourceClassMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/MemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/MemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/MemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/MemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/TraitInjectorService.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/TraitInjectorService.kt
diff --git a/src/org/jetbrains/plugins/grails/references/bootstrap/GrailsBootStrapMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/bootstrap/GrailsBootStrapMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/bootstrap/GrailsBootStrapMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/bootstrap/GrailsBootStrapMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/buildConfig/BuildConfigFileReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/buildConfig/BuildConfigFileReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/buildConfig/BuildConfigFileReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/buildConfig/BuildConfigFileReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/ArtifactFieldReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ArtifactFieldReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/ArtifactFieldReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ArtifactFieldReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/ArtifactPropertyGetterReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ArtifactPropertyGetterReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/ArtifactPropertyGetterReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ArtifactPropertyGetterReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/CodecReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/CodecReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/CodecReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/CodecReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/ContentTypeReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ContentTypeReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/ContentTypeReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ContentTypeReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ContentTypeReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/ContextPathReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ContextPathReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/ContextPathReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ContextPathReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GrailsEncodingReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsEncodingReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GrailsEncodingReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsEncodingReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GrailsFileReferenceSetBase.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsFileReferenceSetBase.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GrailsFileReferenceSetBase.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsFileReferenceSetBase.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GrailsLayoutFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsLayoutFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GrailsLayoutFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsLayoutFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GrailsRootBasedFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsRootBasedFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GrailsRootBasedFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsRootBasedFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GrailsViewFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsViewFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GrailsViewFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GrailsViewFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GroovyGspAttributeWrapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GroovyGspAttributeWrapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GroovyGspAttributeWrapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GroovyGspAttributeWrapper.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GroovyGspTagWrapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GroovyGspTagWrapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GroovyGspTagWrapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GroovyGspTagWrapper.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GspAttributeWrapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GspAttributeWrapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GspAttributeWrapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GspAttributeWrapper.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/GspTagWrapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GspTagWrapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/GspTagWrapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/GspTagWrapper.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/PluginReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/PluginReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/PluginReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/PluginReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/PluginSupportFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/PluginSupportFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/PluginSupportFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/PluginSupportFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/ResourceDirAttributeFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ResourceDirAttributeFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/ResourceDirAttributeFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/ResourceDirAttributeFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/TemplateFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/TemplateFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/TemplateFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/TemplateFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/WebAppFolderFileReferenceSet.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/WebAppFolderFileReferenceSet.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/WebAppFolderFileReferenceSet.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/WebAppFolderFileReferenceSet.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/XmlGspAttributeWrapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/XmlGspAttributeWrapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/XmlGspAttributeWrapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/XmlGspAttributeWrapper.java
diff --git a/src/org/jetbrains/plugins/grails/references/common/XmlGspTagWrapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/common/XmlGspTagWrapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/common/XmlGspTagWrapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/common/XmlGspTagWrapper.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GormRegexInjector.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GormRegexInjector.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GormRegexInjector.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GormRegexInjector.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintGroupNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintGroupNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintGroupNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintGroupNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsClosureMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsClosureMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsClosureMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsClosureMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsGroupClosureMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsGroupClosureMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsGroupClosureMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsGroupClosureMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsSharedReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsSharedReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsSharedReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsSharedReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsConstraintsUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsValidationMethodsContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsValidationMethodsContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsValidationMethodsContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsValidationMethodsContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/constraints/GrailsValidatorParamTypeEnhancer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsValidatorParamTypeEnhancer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/constraints/GrailsValidatorParamTypeEnhancer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/constraints/GrailsValidatorParamTypeEnhancer.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ActionReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ActionReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ActionReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ActionReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ActionRenameProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ActionRenameProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ActionRenameProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ActionRenameProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodCompletionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodCompletionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodCompletionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodCompletionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerAllowedMethodReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerFieldReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerFieldReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerFieldReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerFieldReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerLayoutReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerLayoutReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerLayoutReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerLayoutReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerMembersProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerMembersProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerMembersProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerMembersProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerRenameProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerRenameProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerRenameProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerRenameProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/ControllerRenderTemplateRefProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerRenderTemplateRefProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/ControllerRenderTemplateRefProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/ControllerRenderTemplateRefProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/GrailsNamespaceReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GrailsNamespaceReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/GrailsNamespaceReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GrailsNamespaceReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/GrailsWithFormatClosureMethodContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GrailsWithFormatClosureMethodContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/GrailsWithFormatClosureMethodContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GrailsWithFormatClosureMethodContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/GspTagMethodComparator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodComparator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/GspTagMethodComparator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodComparator.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/GspTagMethodOverloadResolver.kt
diff --git a/src/org/jetbrains/plugins/grails/references/controller/HttpRequestMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/HttpRequestMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/HttpRequestMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/HttpRequestMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/controller/HttpResponseMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/HttpResponseMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/controller/HttpResponseMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/controller/HttpResponseMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/DomainClassUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainClassUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/DomainClassUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainClassUtils.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/DomainDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/DomainDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/DomainFieldRenameProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainFieldRenameProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/DomainFieldRenameProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainFieldRenameProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/DomainMembersProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainMembersProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/DomainMembersProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainMembersProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/DomainStaticMemberCompletionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainStaticMemberCompletionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/DomainStaticMemberCompletionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/DomainStaticMemberCompletionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormAddToNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAddToNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormAddToNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAddToNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormAstTransformationContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormConstraintMethodProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormConstraintMethodProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormConstraintMethodProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormConstraintMethodProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormDynamicFinderCompletionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormDynamicFinderCompletionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormDynamicFinderCompletionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormDynamicFinderCompletionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormEmbeddedPropertyListReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormEmbeddedPropertyListReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormEmbeddedPropertyListReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormEmbeddedPropertyListReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormFetchModeCompletionProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormFetchModeCompletionProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormFetchModeCompletionProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormFetchModeCompletionProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormFetchModeReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormFetchModeReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormFetchModeReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormFetchModeReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormGetPersistentValueReturnTypeCalculator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormGetPersistentValueReturnTypeCalculator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormGetPersistentValueReturnTypeCalculator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormGetPersistentValueReturnTypeCalculator.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormMappingMethodProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormMappingMethodProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormMappingMethodProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormMappingMethodProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormNamedArgumentReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormNamedArgumentReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormNamedArgumentReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormNamedArgumentReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormPersistentPropertiesNamedArgProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPersistentPropertiesNamedArgProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormPersistentPropertiesNamedArgProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPersistentPropertiesNamedArgProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormPropertiesListReferenceReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertiesListReferenceReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormPropertiesListReferenceReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertiesListReferenceReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormPropertyConstraintReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertyConstraintReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormPropertyConstraintReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertyConstraintReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormPropertyReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertyReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormPropertyReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertyReference.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormPropertyReferenceUnique.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertyReferenceUnique.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormPropertyReferenceUnique.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormPropertyReferenceUnique.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormSortNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormSortNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormSortNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormSortNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormUniqueConstraintReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormUniqueConstraintReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormUniqueConstraintReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormUniqueConstraintReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GormUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GormUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GormUtils.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToReferencesProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToReferencesProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToReferencesProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToReferencesProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToValuesReferencesProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToValuesReferencesProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToValuesReferencesProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GrailsHasManyBelongsToValuesReferencesProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/GrailsSyntheticFieldDeclarationSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GrailsSyntheticFieldDeclarationSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/GrailsSyntheticFieldDeclarationSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/GrailsSyntheticFieldDeclarationSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderImplicitMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderImplicitMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderImplicitMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderImplicitMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaBuilderUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaClosureMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaClosureMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaClosureMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaClosureMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaPropertyReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaPropertyReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaPropertyReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaPropertyReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaReturnTypeCalculator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaReturnTypeCalculator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaReturnTypeCalculator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/criteria/CriteriaReturnTypeCalculator.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaClosureMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaClosureMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaClosureMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaClosureMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaDelegatesToProvider.kt
diff --git a/src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaPropertyNamedArgProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaPropertyNamedArgProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaPropertyNamedArgProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaPropertyNamedArgProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/detachedCriteria/DetachedCriteriaUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryCallTypeCalculator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryCallTypeCalculator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryCallTypeCalculator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryCallTypeCalculator.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryDeclarationSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryDeclarationSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryDeclarationSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/GormNamedQueryDeclarationSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyMembersContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyMembersContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyMembersContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyMembersContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyTypeCalculator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyTypeCalculator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyTypeCalculator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedCriteriaProxyTypeCalculator.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedQueryDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedQueryDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedQueryDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/namedQuery/NamedQueryDescriptor.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormBasicAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormBasicAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormBasicAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormBasicAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormCollectionAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormCollectionAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormCollectionAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormCollectionAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormEmbeddedAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormEmbeddedAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormEmbeddedAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormEmbeddedAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormEntity.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormEntity.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormEntity.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormEntity.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormIdAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormIdAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormIdAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormIdAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistenceMapping.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistenceMapping.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistenceMapping.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistenceMapping.java
diff --git a/src/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistentAttribute.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistentAttribute.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistentAttribute.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/domain/persistent/GormPersistentAttribute.java
diff --git a/src/org/jetbrains/plugins/grails/references/filter/FilterClosureMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/FilterClosureMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/filter/FilterClosureMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/FilterClosureMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/filter/FilterMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/FilterMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/filter/FilterMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/FilterMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/filter/FilterReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/FilterReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/filter/FilterReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/FilterReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/filter/GrailsFilterClosureMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/GrailsFilterClosureMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/filter/GrailsFilterClosureMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/GrailsFilterClosureMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/filter/GrailsFilterUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/GrailsFilterUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/filter/GrailsFilterUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/filter/GrailsFilterUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/jobs/JobClosureMethodProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/jobs/JobClosureMethodProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/jobs/JobClosureMethodProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/jobs/JobClosureMethodProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/jobs/JobsMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/jobs/JobsMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/jobs/JobsMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/jobs/JobsMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/other/GrailsTemplateNamedArgumentReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/other/GrailsTemplateNamedArgumentReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/other/GrailsTemplateNamedArgumentReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/other/GrailsTemplateNamedArgumentReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginClassMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginClassMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginClassMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginClassMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginExcludeReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginExcludeReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginExcludeReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginExcludeReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginListReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginListReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginListReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginListReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginParameterEnhancer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginParameterEnhancer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginParameterEnhancer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/pluginClass/GrailsPluginParameterEnhancer.java
diff --git a/src/org/jetbrains/plugins/grails/references/providers/GspImportListReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/providers/GspImportListReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/providers/GspImportListReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/providers/GspImportListReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/CodecAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/CodecAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/CodecAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/CodecAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspActionAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspActionAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspActionAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspActionAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspApplyLayoutNameAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspApplyLayoutNameAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspApplyLayoutNameAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspApplyLayoutNameAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspContextPathAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspContextPathAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspContextPathAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspContextPathAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspControllerAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspControllerAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspControllerAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspControllerAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspFieldValueTagSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspFieldValueTagSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspFieldValueTagSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspFieldValueTagSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspIncludeViewAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspIncludeViewAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspIncludeViewAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspIncludeViewAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspLayoutReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspLayoutReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspLayoutReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspLayoutReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspMappingAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspMappingAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspMappingAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspMappingAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspMetaTagSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspMetaTagSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspMetaTagSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspMetaTagSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspPluginAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspPluginAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspPluginAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspPluginAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceModuleAttrSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceModuleAttrSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceModuleAttrSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceModuleAttrSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceRequireTagModulesAttrSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceRequireTagModulesAttrSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceRequireTagModulesAttrSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceRequireTagModulesAttrSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceUriAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceUriAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceUriAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspRResourceUriAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspResourceContextPathAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspResourceContextPathAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspResourceContextPathAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspResourceContextPathAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspResourceDirAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspResourceDirAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspResourceDirAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspResourceDirAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspResourceFileAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspResourceFileAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspResourceFileAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspResourceFileAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspSelectTagSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspSelectTagSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspSelectTagSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspSelectTagSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspSrcJsAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspSrcJsAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspSrcJsAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspSrcJsAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspTagSupportGspReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspTagSupportGspReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspTagSupportGspReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspTagSupportGspReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspTemplateAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspTemplateAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspTemplateAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspTemplateAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/GspUpdateAttributeSupport.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspUpdateAttributeSupport.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/GspUpdateAttributeSupport.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/GspUpdateAttributeSupport.java
diff --git a/src/org/jetbrains/plugins/grails/references/tagSupport/TagAttributeReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/TagAttributeReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/tagSupport/TagAttributeReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/tagSupport/TagAttributeReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/taglib/TaglibClosureParamEnhancer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/taglib/TaglibClosureParamEnhancer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/taglib/TaglibClosureParamEnhancer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/taglib/TaglibClosureParamEnhancer.java
diff --git a/src/org/jetbrains/plugins/grails/references/taglib/TaglibMembersProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/taglib/TaglibMembersProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/taglib/TaglibMembersProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/taglib/TaglibMembersProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingConstraintClosureMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingConstraintClosureMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingConstraintClosureMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingConstraintClosureMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingMemberProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingMemberProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingMemberProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingMemberProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingNamedArgumentProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingNamedArgumentProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingNamedArgumentProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingNamedArgumentProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUnresolvedHighlightingFilter.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUnresolvedHighlightingFilter.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUnresolvedHighlightingFilter.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUnresolvedHighlightingFilter.java
diff --git a/src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/urlMappings/UrlMappingUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/util/CodecUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/util/CodecUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/util/CodecUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/util/CodecUtil.java
diff --git a/src/org/jetbrains/plugins/grails/references/util/PsiFieldReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/references/util/PsiFieldReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/references/util/PsiFieldReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/references/util/PsiFieldReference.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsAppEngineDebuggerRunner.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsAppEngineDebuggerRunner.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsAppEngineDebuggerRunner.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsAppEngineDebuggerRunner.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsCommandExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsCommandExecutor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsCommandExecutor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsCommandExecutor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsCommandExecutorUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsCommandExecutorUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsCommandExecutorUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsCommandExecutorUtil.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsCommandLineExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsCommandLineExecutor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsCommandLineExecutor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsCommandLineExecutor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsConfigurationFactory.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConfigurationFactory.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsConfigurationFactory.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConfigurationFactory.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsConsole.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsole.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsConsole.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsole.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsConsoleState.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsDebuggerRunner.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsInstallationExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsInstallationExecutor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsInstallationExecutor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsInstallationExecutor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsRerunFailedTestsAction.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRerunFailedTestsAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsRerunFailedTestsAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRerunFailedTestsAction.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsRunConfiguration.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunConfiguration.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsRunConfiguration.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunConfiguration.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationExtension.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationExtension.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationExtension.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationExtension.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationType.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationType.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationType.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsRunConfigurationType.java
diff --git a/src/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/GrailsScriptRunConfigurationProducer.java
diff --git a/src/org/jetbrains/plugins/grails/runner/LocatableRunConfigurationWithCommonParameters.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/LocatableRunConfigurationWithCommonParameters.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/LocatableRunConfigurationWithCommonParameters.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/LocatableRunConfigurationWithCommonParameters.java
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/BaseGrailsCommandLineState.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3GradleCommandExecutor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3InstallationCommandExecutor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/Grails3TestCommandLineState.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsCommandLineState.java
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/GrailsInstallationCommandExecutor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsInstallationCommandExecutor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/GrailsInstallationCommandExecutor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsInstallationCommandExecutor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsRunAppCommandLineState.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestAppCommandLineState.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/impl/GrailsTestCommandLineState.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestCommandLineState.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/impl/GrailsTestCommandLineState.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/impl/GrailsTestCommandLineState.java
diff --git a/src/org/jetbrains/plugins/grails/runner/setup.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/setup.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/setup.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/setup.kt
diff --git a/src/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsApplicationCombobox.java
diff --git a/src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.form b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.form
diff --git a/src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditor.java
diff --git a/src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorListener.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorListener.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorListener.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorListener.java
diff --git a/src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorWithListener.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorWithListener.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorWithListener.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/ui/GrailsRunConfigurationEditorWithListener.java
diff --git a/src/org/jetbrains/plugins/grails/runner/util/GrailsExecutionUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/runner/util/GrailsExecutionUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/runner/util/GrailsExecutionUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/runner/util/GrailsExecutionUtils.java
diff --git a/src/org/jetbrains/plugins/grails/sdk/GrailsSDK.java b/plugin/src/main/java/org/jetbrains/plugins/grails/sdk/GrailsSDK.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/sdk/GrailsSDK.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/sdk/GrailsSDK.java
diff --git a/src/org/jetbrains/plugins/grails/sdk/GrailsSDKManager.java b/plugin/src/main/java/org/jetbrains/plugins/grails/sdk/GrailsSDKManager.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/sdk/GrailsSDKManager.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/sdk/GrailsSDKManager.java
diff --git a/src/org/jetbrains/plugins/grails/service/GrailsBackgroundService.java b/plugin/src/main/java/org/jetbrains/plugins/grails/service/GrailsBackgroundService.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/service/GrailsBackgroundService.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/service/GrailsBackgroundService.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrLightVariableRenameProcessor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrLightVariableRenameProcessor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrLightVariableRenameProcessor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrLightVariableRenameProcessor.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsCustomSpringComponent.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsCustomSpringComponent.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsCustomSpringComponent.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsCustomSpringComponent.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsInjectedBeanCompletionContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsInjectedBeanCompletionContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsInjectedBeanCompletionContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsInjectedBeanCompletionContributor.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsResourceBeanExtractor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsResourceBeanExtractor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsResourceBeanExtractor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsResourceBeanExtractor.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsResourcesGroovyMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsResourcesGroovyMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsResourcesGroovyMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsResourcesGroovyMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringBeanDiscoverer.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsSpringFacetProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringFacetProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsSpringFacetProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringFacetProvider.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsSpringMethodReferenceProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringMethodReferenceProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsSpringMethodReferenceProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringMethodReferenceProvider.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringSemContributor.kt
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsSpringStereotypeAliasingPsiTargetMapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringStereotypeAliasingPsiTargetMapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsSpringStereotypeAliasingPsiTargetMapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsSpringStereotypeAliasingPsiTargetMapper.java
diff --git a/src/org/jetbrains/plugins/grails/spring/GrailsWebApplicationContextMembersContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsWebApplicationContextMembersContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/GrailsWebApplicationContextMembersContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/GrailsWebApplicationContextMembersContributor.java
diff --git a/src/org/jetbrains/plugins/grails/spring/InjectedSpringBeanProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/InjectedSpringBeanProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/InjectedSpringBeanProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/InjectedSpringBeanProvider.java
diff --git a/src/org/jetbrains/plugins/grails/spring/SpringBeanReferenceSearcher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/SpringBeanReferenceSearcher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/SpringBeanReferenceSearcher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/SpringBeanReferenceSearcher.java
diff --git a/src/org/jetbrains/plugins/grails/spring/SpringGrailsTargetMapper.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/SpringGrailsTargetMapper.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/SpringGrailsTargetMapper.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/SpringGrailsTargetMapper.java
diff --git a/src/org/jetbrains/plugins/grails/spring/SpringInjectionAnnotator.java b/plugin/src/main/java/org/jetbrains/plugins/grails/spring/SpringInjectionAnnotator.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/spring/SpringInjectionAnnotator.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/spring/SpringInjectionAnnotator.java
diff --git a/src/org/jetbrains/plugins/grails/structure/Grails3Application.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/Grails3Application.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/Grails3Application.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplication.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationBase.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsApplicationListener.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationListener.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsApplicationListener.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationListener.java
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsApplicationManager.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationManager.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsApplicationManager.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationManager.java
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsApplicationManagerRefresher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationManagerRefresher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsApplicationManagerRefresher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationManagerRefresher.java
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsApplicationProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsApplicationProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsApplicationProvider.java
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsCommonClassNames.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/GrailsSDKListener.java
diff --git a/src/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/OldGrailsApplication.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails2Application.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationBase.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3ApplicationProvider.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3MultiModuleApplication.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/Grails3SingleModuleApplication.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/GrailsModuleBasedApplication.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/OldGrailsModuleBasedApplication.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/PlainGrailsApplicationProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/PlainGrailsApplicationProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/PlainGrailsApplicationProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/PlainGrailsApplicationProvider.java
diff --git a/src/org/jetbrains/plugins/grails/structure/impl/util.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/util.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/impl/util.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/impl/util.kt
diff --git a/src/org/jetbrains/plugins/grails/structure/sync/GrailsApplicationBackgroundTask.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsApplicationBackgroundTask.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/sync/GrailsApplicationBackgroundTask.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsApplicationBackgroundTask.java
diff --git a/src/org/jetbrains/plugins/grails/structure/sync/GrailsMvcStructureRefresher.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsMvcStructureRefresher.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/sync/GrailsMvcStructureRefresher.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsMvcStructureRefresher.java
diff --git a/src/org/jetbrains/plugins/grails/structure/sync/GrailsRunConfigurationTask.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsRunConfigurationTask.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/sync/GrailsRunConfigurationTask.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsRunConfigurationTask.java
diff --git a/src/org/jetbrains/plugins/grails/structure/sync/GrailsSdkCheckTask.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsSdkCheckTask.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/sync/GrailsSdkCheckTask.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsSdkCheckTask.java
diff --git a/src/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/sync/GrailsStartupActivity.java
diff --git a/src/org/jetbrains/plugins/grails/structure/util.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/structure/util.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/structure/util.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/structure/util.kt
diff --git a/src/org/jetbrains/plugins/grails/tests/GrailsJUnitRecognizer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsJUnitRecognizer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/GrailsJUnitRecognizer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsJUnitRecognizer.java
diff --git a/src/org/jetbrains/plugins/grails/tests/GrailsTestMemberContributor.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestMemberContributor.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/GrailsTestMemberContributor.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestMemberContributor.java
diff --git a/src/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.java
diff --git a/src/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestUtils.kt
diff --git a/src/org/jetbrains/plugins/grails/tests/GrailsTestVariableEnhancer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestVariableEnhancer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/GrailsTestVariableEnhancer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/GrailsTestVariableEnhancer.java
diff --git a/src/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsTestConfigurationProducer.java
diff --git a/src/org/jetbrains/plugins/grails/tests/runner/GrailsUrlProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsUrlProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/runner/GrailsUrlProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/GrailsUrlProvider.java
diff --git a/src/org/jetbrains/plugins/grails/tests/runner/byClass.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byClass.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/runner/byClass.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byClass.kt
diff --git a/src/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/byDirectory.kt
diff --git a/src/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/tests/runner/oldUtils.kt
diff --git a/src/org/jetbrains/plugins/grails/ui/EditorComboBoxWithHistory.java b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/EditorComboBoxWithHistory.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/EditorComboBoxWithHistory.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/EditorComboBoxWithHistory.java
diff --git a/src/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.form b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.form
diff --git a/src/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.java b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsConfigureSDKDialog.java
diff --git a/src/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.form b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.form
diff --git a/src/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.java b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsRunCommandDialog.java
diff --git a/src/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.form b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.form
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.form
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.form
diff --git a/src/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.java b/plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/ui/GrailsSDKHomeForm.java
diff --git a/src/org/jetbrains/plugins/grails/util/DelegateReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/DelegateReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/DelegateReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/DelegateReference.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsArtifact.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifact.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsArtifact.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifact.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsArtifactTransformerUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifactTransformerUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsArtifactTransformerUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsArtifactTransformerUtils.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsCachedValue.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsCachedValue.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsCachedValue.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsCachedValue.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsFacetProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsFacetProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsFacetProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsFacetProvider.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsNameUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsNameUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsNameUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsNameUtils.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsPatterns.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsPatterns.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsPatterns.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsPatterns.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsPluginCondition.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsPluginCondition.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsPluginCondition.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsPluginCondition.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsPsiUtil.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsPsiUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsPsiUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsPsiUtil.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsUtils.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsUtils.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsVersionCondition.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsVersionCondition.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsVersionCondition.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsVersionCondition.java
diff --git a/src/org/jetbrains/plugins/grails/util/GrailsWebFacetProvider.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsWebFacetProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/GrailsWebFacetProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/GrailsWebFacetProvider.java
diff --git a/src/org/jetbrains/plugins/grails/util/ModuleCachedValue.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/ModuleCachedValue.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/ModuleCachedValue.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/ModuleCachedValue.java
diff --git a/src/org/jetbrains/plugins/grails/util/PredefinedTypeReferenceEnhancer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/PredefinedTypeReferenceEnhancer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/PredefinedTypeReferenceEnhancer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/PredefinedTypeReferenceEnhancer.java
diff --git a/src/org/jetbrains/plugins/grails/util/ReferenceGutterIconRenderer.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/ReferenceGutterIconRenderer.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/ReferenceGutterIconRenderer.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/ReferenceGutterIconRenderer.java
diff --git a/src/org/jetbrains/plugins/grails/util/SafeReference.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/SafeReference.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/SafeReference.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/SafeReference.java
diff --git a/src/org/jetbrains/plugins/grails/util/version/Range.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Range.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/version/Range.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Range.java
diff --git a/src/org/jetbrains/plugins/grails/util/version/Version.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Version.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/version/Version.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/version/Version.java
diff --git a/src/org/jetbrains/plugins/grails/util/version/VersionImpl.java b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/VersionImpl.java
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/version/VersionImpl.java
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/version/VersionImpl.java
diff --git a/src/org/jetbrains/plugins/grails/util/version/versions.kt b/plugin/src/main/java/org/jetbrains/plugins/grails/util/version/versions.kt
similarity index 100%
rename from src/org/jetbrains/plugins/grails/util/version/versions.kt
rename to plugin/src/main/java/org/jetbrains/plugins/grails/util/version/versions.kt
diff --git a/src/org/jetbrains/plugins/groovy/dsl/GrailsScriptProvider.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/dsl/GrailsScriptProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/dsl/GrailsScriptProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/dsl/GrailsScriptProvider.java
diff --git a/src/org/jetbrains/plugins/groovy/grails/annotator/GrailsControllerAnnotator.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsControllerAnnotator.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/grails/annotator/GrailsControllerAnnotator.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsControllerAnnotator.java
diff --git a/src/org/jetbrains/plugins/groovy/grails/annotator/GrailsDomainAnnotator.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsDomainAnnotator.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/grails/annotator/GrailsDomainAnnotator.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsDomainAnnotator.java
diff --git a/src/org/jetbrains/plugins/groovy/grails/annotator/GrailsForkSettingsNotificator.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsForkSettingsNotificator.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/grails/annotator/GrailsForkSettingsNotificator.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsForkSettingsNotificator.java
diff --git a/src/org/jetbrains/plugins/groovy/grails/annotator/GrailsSynchronizeStructureAnnotator.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsSynchronizeStructureAnnotator.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/grails/annotator/GrailsSynchronizeStructureAnnotator.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/grails/annotator/GrailsSynchronizeStructureAnnotator.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/ConsoleProcessDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/ConsoleProcessDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/ConsoleProcessDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/ConsoleProcessDescriptor.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/MvcCommand.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcCommand.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/MvcCommand.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcCommand.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureUtil.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureUtil.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/MvcPathMacros.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcPathMacros.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/MvcPathMacros.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcPathMacros.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/MvcProjectStructure.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcProjectStructure.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/MvcProjectStructure.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcProjectStructure.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/MvcWatchedRootProvider.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcWatchedRootProvider.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/MvcWatchedRootProvider.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/MvcWatchedRootProvider.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/AvailablePluginsModel.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/AvailablePluginsModel.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/AvailablePluginsModel.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/AvailablePluginsModel.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/InstallUninstallPluginsDialog.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/InstallUninstallPluginsDialog.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/InstallUninstallPluginsDialog.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/InstallUninstallPluginsDialog.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginColumnInfo.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginColumnInfo.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginColumnInfo.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginColumnInfo.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginDescriptor.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginDescriptor.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginDescriptor.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginDescriptor.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginIsInstalledColumnInfo.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginIsInstalledColumnInfo.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginIsInstalledColumnInfo.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginIsInstalledColumnInfo.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginUtil.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginUtil.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginUtil.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginUtil.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsAction.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.form b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.form
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.form
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.form
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsMain.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsTable.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsTable.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsTable.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/MvcPluginsTable.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/actions/AddCustomPluginAction.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/actions/AddCustomPluginAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/actions/AddCustomPluginAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/actions/AddCustomPluginAction.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/actions/ReloadMvcPluginListAction.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/actions/ReloadMvcPluginListAction.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/actions/ReloadMvcPluginListAction.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/actions/ReloadMvcPluginListAction.java
diff --git a/src/org/jetbrains/plugins/groovy/mvc/plugins/proxySettings.kt b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/proxySettings.kt
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/plugins/proxySettings.kt
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/plugins/proxySettings.kt
diff --git a/src/org/jetbrains/plugins/groovy/mvc/util/MvcTargetDialogCompletionUtils.java b/plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/util/MvcTargetDialogCompletionUtils.java
similarity index 100%
rename from src/org/jetbrains/plugins/groovy/mvc/util/MvcTargetDialogCompletionUtils.java
rename to plugin/src/main/java/org/jetbrains/plugins/groovy/mvc/util/MvcTargetDialogCompletionUtils.java
diff --git a/resources/IdeaPrintProjectSettings.groovy b/plugin/src/main/resources/IdeaPrintProjectSettings.groovy
similarity index 100%
rename from resources/IdeaPrintProjectSettings.groovy
rename to plugin/src/main/resources/IdeaPrintProjectSettings.groovy
diff --git a/resources/META-INF/grails-css-integration.xml b/plugin/src/main/resources/META-INF/grails-css-integration.xml
similarity index 100%
rename from resources/META-INF/grails-css-integration.xml
rename to plugin/src/main/resources/META-INF/grails-css-integration.xml
diff --git a/resources/META-INF/grails-js-integration.xml b/plugin/src/main/resources/META-INF/grails-js-integration.xml
similarity index 100%
rename from resources/META-INF/grails-js-integration.xml
rename to plugin/src/main/resources/META-INF/grails-js-integration.xml
diff --git a/resources/META-INF/grails-method-descriptors.xml b/plugin/src/main/resources/META-INF/grails-method-descriptors.xml
similarity index 100%
rename from resources/META-INF/grails-method-descriptors.xml
rename to plugin/src/main/resources/META-INF/grails-method-descriptors.xml
diff --git a/resources/META-INF/plugin.xml b/plugin/src/main/resources/META-INF/plugin.xml
similarity index 100%
rename from resources/META-INF/plugin.xml
rename to plugin/src/main/resources/META-INF/plugin.xml
diff --git a/resources/META-INF/pluginIcon.svg b/plugin/src/main/resources/META-INF/pluginIcon.svg
similarity index 100%
rename from resources/META-INF/pluginIcon.svg
rename to plugin/src/main/resources/META-INF/pluginIcon.svg
diff --git a/resources/fileTemplates/internal/Groovy Server Page.gsp.ft b/plugin/src/main/resources/fileTemplates/internal/Groovy Server Page.gsp.ft
similarity index 100%
rename from resources/fileTemplates/internal/Groovy Server Page.gsp.ft
rename to plugin/src/main/resources/fileTemplates/internal/Groovy Server Page.gsp.ft
diff --git a/resources/fileTemplates/internal/Groovy Server Page.gsp.html b/plugin/src/main/resources/fileTemplates/internal/Groovy Server Page.gsp.html
similarity index 100%
rename from resources/fileTemplates/internal/Groovy Server Page.gsp.html
rename to plugin/src/main/resources/fileTemplates/internal/Groovy Server Page.gsp.html
diff --git a/resources/icons/action_method.svg b/plugin/src/main/resources/icons/action_method.svg
similarity index 100%
rename from resources/icons/action_method.svg
rename to plugin/src/main/resources/icons/action_method.svg
diff --git a/resources/icons/grails.svg b/plugin/src/main/resources/icons/grails.svg
similarity index 100%
rename from resources/icons/grails.svg
rename to plugin/src/main/resources/icons/grails.svg
diff --git a/resources/icons/grails_13.svg b/plugin/src/main/resources/icons/grails_13.svg
similarity index 100%
rename from resources/icons/grails_13.svg
rename to plugin/src/main/resources/icons/grails_13.svg
diff --git a/resources/icons/grails_13_dark.svg b/plugin/src/main/resources/icons/grails_13_dark.svg
similarity index 100%
rename from resources/icons/grails_13_dark.svg
rename to plugin/src/main/resources/icons/grails_13_dark.svg
diff --git a/resources/icons/grails_app.svg b/plugin/src/main/resources/icons/grails_app.svg
similarity index 100%
rename from resources/icons/grails_app.svg
rename to plugin/src/main/resources/icons/grails_app.svg
diff --git a/resources/icons/grails_sdk.svg b/plugin/src/main/resources/icons/grails_sdk.svg
similarity index 100%
rename from resources/icons/grails_sdk.svg
rename to plugin/src/main/resources/icons/grails_sdk.svg
diff --git a/resources/icons/grails_test.svg b/plugin/src/main/resources/icons/grails_test.svg
similarity index 100%
rename from resources/icons/grails_test.svg
rename to plugin/src/main/resources/icons/grails_test.svg
diff --git a/resources/icons/groovy_mvc_plugin.svg b/plugin/src/main/resources/icons/groovy_mvc_plugin.svg
similarity index 100%
rename from resources/icons/groovy_mvc_plugin.svg
rename to plugin/src/main/resources/icons/groovy_mvc_plugin.svg
diff --git a/resources/icons/gsp_logo.svg b/plugin/src/main/resources/icons/gsp_logo.svg
similarity index 100%
rename from resources/icons/gsp_logo.svg
rename to plugin/src/main/resources/icons/gsp_logo.svg
diff --git a/resources/icons/taglib.svg b/plugin/src/main/resources/icons/taglib.svg
similarity index 100%
rename from resources/icons/taglib.svg
rename to plugin/src/main/resources/icons/taglib.svg
diff --git a/resources/inspectionDescriptions/GspInspection.html b/plugin/src/main/resources/inspectionDescriptions/GspInspection.html
similarity index 100%
rename from resources/inspectionDescriptions/GspInspection.html
rename to plugin/src/main/resources/inspectionDescriptions/GspInspection.html
diff --git a/resources/inspectionDescriptions/metaInformation.json b/plugin/src/main/resources/inspectionDescriptions/metaInformation.json
similarity index 100%
rename from resources/inspectionDescriptions/metaInformation.json
rename to plugin/src/main/resources/inspectionDescriptions/metaInformation.json
diff --git a/resources/intentionDescriptions/GenerateViewIntention/after.gsp.template b/plugin/src/main/resources/intentionDescriptions/GenerateViewIntention/after.gsp.template
similarity index 100%
rename from resources/intentionDescriptions/GenerateViewIntention/after.gsp.template
rename to plugin/src/main/resources/intentionDescriptions/GenerateViewIntention/after.gsp.template
diff --git a/resources/intentionDescriptions/GenerateViewIntention/before.groovy.template b/plugin/src/main/resources/intentionDescriptions/GenerateViewIntention/before.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/GenerateViewIntention/before.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/GenerateViewIntention/before.groovy.template
diff --git a/resources/intentionDescriptions/GenerateViewIntention/description.html b/plugin/src/main/resources/intentionDescriptions/GenerateViewIntention/description.html
similarity index 100%
rename from resources/intentionDescriptions/GenerateViewIntention/description.html
rename to plugin/src/main/resources/intentionDescriptions/GenerateViewIntention/description.html
diff --git a/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/after.groovy.template b/plugin/src/main/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/after.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/GrailsCreateActionByUsageIntention/after.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/after.groovy.template
diff --git a/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/before.groovy.template b/plugin/src/main/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/before.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/GrailsCreateActionByUsageIntention/before.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/before.groovy.template
diff --git a/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/description.html b/plugin/src/main/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/description.html
similarity index 100%
rename from resources/intentionDescriptions/GrailsCreateActionByUsageIntention/description.html
rename to plugin/src/main/resources/intentionDescriptions/GrailsCreateActionByUsageIntention/description.html
diff --git a/resources/intentionDescriptions/MakeNullableIntention/after.groovy.template b/plugin/src/main/resources/intentionDescriptions/MakeNullableIntention/after.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/MakeNullableIntention/after.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/MakeNullableIntention/after.groovy.template
diff --git a/resources/intentionDescriptions/MakeNullableIntention/before.groovy.template b/plugin/src/main/resources/intentionDescriptions/MakeNullableIntention/before.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/MakeNullableIntention/before.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/MakeNullableIntention/before.groovy.template
diff --git a/resources/intentionDescriptions/MakeNullableIntention/description.html b/plugin/src/main/resources/intentionDescriptions/MakeNullableIntention/description.html
similarity index 100%
rename from resources/intentionDescriptions/MakeNullableIntention/description.html
rename to plugin/src/main/resources/intentionDescriptions/MakeNullableIntention/description.html
diff --git a/resources/intentionDescriptions/MakeUniqueIntention/after.groovy.template b/plugin/src/main/resources/intentionDescriptions/MakeUniqueIntention/after.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/MakeUniqueIntention/after.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/MakeUniqueIntention/after.groovy.template
diff --git a/resources/intentionDescriptions/MakeUniqueIntention/before.groovy.template b/plugin/src/main/resources/intentionDescriptions/MakeUniqueIntention/before.groovy.template
similarity index 100%
rename from resources/intentionDescriptions/MakeUniqueIntention/before.groovy.template
rename to plugin/src/main/resources/intentionDescriptions/MakeUniqueIntention/before.groovy.template
diff --git a/resources/intentionDescriptions/MakeUniqueIntention/description.html b/plugin/src/main/resources/intentionDescriptions/MakeUniqueIntention/description.html
similarity index 100%
rename from resources/intentionDescriptions/MakeUniqueIntention/description.html
rename to plugin/src/main/resources/intentionDescriptions/MakeUniqueIntention/description.html
diff --git a/resources/liveTemplates/gsp.xml b/plugin/src/main/resources/liveTemplates/gsp.xml
similarity index 100%
rename from resources/liveTemplates/gsp.xml
rename to plugin/src/main/resources/liveTemplates/gsp.xml
diff --git a/resources/messages/GrailsBundle.properties b/plugin/src/main/resources/messages/GrailsBundle.properties
similarity index 100%
rename from resources/messages/GrailsBundle.properties
rename to plugin/src/main/resources/messages/GrailsBundle.properties
diff --git a/resources/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/tld/grails.tld b/plugin/src/main/resources/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/tld/grails.tld
similarity index 100%
rename from resources/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/tld/grails.tld
rename to plugin/src/main/resources/org/jetbrains/plugins/grails/lang/gsp/resolve/taglib/tld/grails.tld
diff --git a/test/com/intellij/groovy/grails/copyright/GspCopyrightUpdaterTest.java b/plugin/src/test/java/com/intellij/groovy/grails/copyright/GspCopyrightUpdaterTest.java
similarity index 100%
rename from test/com/intellij/groovy/grails/copyright/GspCopyrightUpdaterTest.java
rename to plugin/src/test/java/com/intellij/groovy/grails/copyright/GspCopyrightUpdaterTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/DoublePluginTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/DoublePluginTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/DoublePluginTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/DoublePluginTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrEditorActionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrEditorActionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrEditorActionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrEditorActionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsActionsTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsActionsTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsActionsTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsActionsTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsCodecTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsCodecTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsCodecTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsCodecTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsConsoleFilterTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsConsoleFilterTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsConsoleFilterTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsConsoleFilterTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsDoNotHighlightGrailsSpecifiedFieldsAsUnusedTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsDoNotHighlightGrailsSpecifiedFieldsAsUnusedTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsDoNotHighlightGrailsSpecifiedFieldsAsUnusedTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsDoNotHighlightGrailsSpecifiedFieldsAsUnusedTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsGantTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsGantTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsGantTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsGantTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsHighlightingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsHighlightingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsHighlightingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsHighlightingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsLightVariableInfoTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsLightVariableInfoTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsLightVariableInfoTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsLightVariableInfoTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsLinkGeneratorTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsLinkGeneratorTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsLinkGeneratorTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsLinkGeneratorTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsLogVariableTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsLogVariableTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsLogVariableTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsLogVariableTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsMavenImporterTest.kt
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsPageRendererTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsPageRendererTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsPageRendererTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsPageRendererTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsPluginClassTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsPluginClassTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsPluginClassTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsPluginClassTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsProjectStructureTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsProjectStructureTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsProjectStructureTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsProjectStructureTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsResolveInConfigFileTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsResolveInConfigFileTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsResolveInConfigFileTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsResolveInConfigFileTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsResolveTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsResolveTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsResolveTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsResolveTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsRunConfigurationProducerTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsRunConfigurationProducerTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsRunConfigurationProducerTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsRunConfigurationProducerTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GrailsRunTargetCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsRunTargetCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GrailsRunTargetCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GrailsRunTargetCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/GspLiveTemplatesTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GspLiveTemplatesTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/GspLiveTemplatesTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/GspLiveTemplatesTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/JamInGroovyTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/JamInGroovyTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/JamInGroovyTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/JamInGroovyTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtils14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtils14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtils14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtils14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtilsTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtilsTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtilsTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/VersionInDynamicMemberUtilsTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/DomainFieldIntentionsTest.kt
diff --git a/test/org/jetbrains/plugins/groovy/grails/action/GenerateViewByActionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/GenerateViewByActionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/action/GenerateViewByActionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/GenerateViewByActionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateAction2Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateAction2Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateAction2Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateAction2Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateActionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateActionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateActionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/action/GrailsGenerateActionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/ControllerReferenceCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/ControllerReferenceCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/ControllerReferenceCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/ControllerReferenceCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/DomainClassMethodsCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/DomainClassMethodsCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/DomainClassMethodsCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/DomainClassMethodsCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/GrailsCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GrailsCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/GrailsCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GrailsCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/GrailsNamedAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GrailsNamedAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/GrailsNamedAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GrailsNamedAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/GrailsTestAutoCompletionHardReferencesTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GrailsTestAutoCompletionHardReferencesTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/GrailsTestAutoCompletionHardReferencesTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GrailsTestAutoCompletionHardReferencesTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/GspTagReferenceCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GspTagReferenceCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/GspTagReferenceCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/GspTagReferenceCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/ServiceCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/ServiceCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/ServiceCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/ServiceCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/completion/TaglibClassMethodsCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/TaglibClassMethodsCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/completion/TaglibClassMethodsCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/completion/TaglibClassMethodsCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/config/GrailsBuildConfigResolveTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsBuildConfigResolveTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/config/GrailsBuildConfigResolveTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsBuildConfigResolveTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/config/GrailsConfigFileTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConfigFileTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/config/GrailsConfigFileTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConfigFileTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSelectWordTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSelectWordTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSelectWordTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSelectWordTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSlurperTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSlurperTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSlurperTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConfigSlurperTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/config/GrailsConstraintGroupTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConstraintGroupTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/config/GrailsConstraintGroupTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsConstraintGroupTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/config/GrailsPluginDependencyCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsPluginDependencyCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/config/GrailsPluginDependencyCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/config/GrailsPluginDependencyCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormApiFromJavaTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormApiFromJavaTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormApiFromJavaTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormApiFromJavaTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormBinding14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormBinding14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormBinding14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormBinding14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormBindingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormBindingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormBindingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormBindingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormDetachedCriteriaTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormDetachedCriteriaTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormDetachedCriteriaTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormDetachedCriteriaTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormDomainConstructorTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormDomainConstructorTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormDomainConstructorTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormDomainConstructorTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormDomainFinderMethodTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormDomainFinderMethodTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormDomainFinderMethodTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormDomainFinderMethodTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormFetchModeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormFetchModeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormFetchModeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormFetchModeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormFinderMethodsCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormFinderMethodsCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormFinderMethodsCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormFinderMethodsCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormGetPersistentValueReturnTypeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormGetPersistentValueReturnTypeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormGetPersistentValueReturnTypeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormGetPersistentValueReturnTypeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormHasManyRelationTypeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormHasManyRelationTypeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormHasManyRelationTypeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormHasManyRelationTypeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormMappingSortTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormMappingSortTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormMappingSortTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormMappingSortTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormMultiDefinePropertyTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormMultiDefinePropertyTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormMultiDefinePropertyTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormMultiDefinePropertyTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormNamedArgumentForHibernateMappingBuilderTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormNamedArgumentForHibernateMappingBuilderTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormNamedArgumentForHibernateMappingBuilderTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormNamedArgumentForHibernateMappingBuilderTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormPersistentFacetTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormPersistentFacetTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormPersistentFacetTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormPersistentFacetTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormPropertyAccessTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormPropertyAccessTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormPropertyAccessTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormPropertyAccessTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormStandaloneTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormStandaloneTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormStandaloneTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormStandaloneTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormSyntheticFieldTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormSyntheticFieldTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormSyntheticFieldTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormSyntheticFieldTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GormUniqueConstraintValueTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormUniqueConstraintValueTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GormUniqueConstraintValueTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GormUniqueConstraintValueTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsAddToMethodTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsAddToMethodTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsAddToMethodTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsAddToMethodTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilder14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilder14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilder14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilder14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderToManyRelationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderToManyRelationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderToManyRelationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaBuilderToManyRelationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjections14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjections14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjections14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjections14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjectionsTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjectionsTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjectionsTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsCriteriaProjectionsTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainConstraintTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainConstraintTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainConstraintTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainConstraintTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainFinderMethodsTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainFinderMethodsTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainFinderMethodsTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainFinderMethodsTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttribute14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttribute14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttribute14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttribute14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainNamedAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesFromParentTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesFromParentTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesFromParentTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesFromParentTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsDomainPropertiesTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsFindWhereParameterTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsFindWhereParameterTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsFindWhereParameterTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsFindWhereParameterTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsGormGetIdMethodTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsGormGetIdMethodTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsGormGetIdMethodTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsGormGetIdMethodTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsInjectRegexTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsInjectRegexTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsInjectRegexTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsInjectRegexTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQuery14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQuery14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQuery14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQuery14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQueryTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQueryTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQueryTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsNamedQueryTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainPropertyTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainPropertyTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainPropertyTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainPropertyTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsRenameDomainTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrailsTransientPropertyTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsTransientPropertyTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrailsTransientPropertyTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrailsTransientPropertyTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/GrormEmbeddedFieldTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrormEmbeddedFieldTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/GrormEmbeddedFieldTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/GrormEmbeddedFieldTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/domain/NamedArgumentsInFindOrCreateMethodTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/NamedArgumentsInFindOrCreateMethodTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/domain/NamedArgumentsInFindOrCreateMethodTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/domain/NamedArgumentsInFindOrCreateMethodTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gradle/GradleGrailsImportingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gradle/GradleGrailsImportingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gradle/GradleGrailsImportingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gradle/GradleGrailsImportingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/AssetPipelineScriptInjectionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/AssetPipelineScriptInjectionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/AssetPipelineScriptInjectionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/AssetPipelineScriptInjectionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigation2Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigation2Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigation2Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigation2Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspActionToViewNavigationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspAttributeFromJavadocTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspAttributeFromJavadocTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspAttributeFromJavadocTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspAttributeFromJavadocTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspBuiltInTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspBuiltInTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspBuiltInTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspBuiltInTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspCharsetReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCharsetReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspCharsetReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCharsetReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspClearCompletionBehaviourTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspClearCompletionBehaviourTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspClearCompletionBehaviourTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspClearCompletionBehaviourTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionEmptyTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionEmptyTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionEmptyTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionEmptyTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionInNonexistingNamespaceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionInNonexistingNamespaceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionInNonexistingNamespaceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCompletionInNonexistingNamespaceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspContentTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspContentTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspContentTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspContentTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspControllerReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspControllerReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspControllerReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspControllerReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspCreateTemplateQuickFixTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCreateTemplateQuickFixTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspCreateTemplateQuickFixTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCreateTemplateQuickFixTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspCreateVariableQuickFixTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCreateVariableQuickFixTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspCreateVariableQuickFixTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCreateVariableQuickFixTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspCssTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCssTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspCssTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspCssTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspDirectiveDefaultCodecTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspDirectiveDefaultCodecTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspDirectiveDefaultCodecTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspDirectiveDefaultCodecTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspElementManipulatorForOuterLanguageElementTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspElementManipulatorForOuterLanguageElementTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspElementManipulatorForOuterLanguageElementTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspElementManipulatorForOuterLanguageElementTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspFormatterTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspFormatterTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspFormatterTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspFormatterTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspGroovyInspectionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspGroovyInspectionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspGroovyInspectionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspGroovyInspectionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspHighlightingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspHighlightingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspHighlightingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspHighlightingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspHtmlAttributesTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspHtmlAttributesTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspHtmlAttributesTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspHtmlAttributesTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspIDEA68710Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspIDEA68710Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspIDEA68710Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspIDEA68710Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspImportTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspImportTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspImportTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspImportTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspJavascriptTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspJavascriptTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspJavascriptTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspJavascriptTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspJsFormatterTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspJsFormatterTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspJsFormatterTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspJsFormatterTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspJsInjectionTagEpTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspJsInjectionTagEpTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspJsInjectionTagEpTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspJsInjectionTagEpTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspLanguageInjectionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspLanguageInjectionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspLanguageInjectionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspLanguageInjectionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspLayoutTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspLayoutTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspLayoutTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspLayoutTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspNamedMappingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspNamedMappingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspNamedMappingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspNamedMappingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePrefixIsNotIdentifierTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePrefixIsNotIdentifierTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePrefixIsNotIdentifierTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePrefixIsNotIdentifierTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePriorityTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePriorityTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePriorityTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspNamespacePriorityTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspPropertyResolvePriorityTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspPropertyResolvePriorityTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspPropertyResolvePriorityTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspPropertyResolvePriorityTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspResolveIndexesTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspResolveIndexesTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspResolveIndexesTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspResolveIndexesTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspResolveVariablesFromModelTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspResolveVariablesFromModelTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspResolveVariablesFromModelTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspResolveVariablesFromModelTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspSdkTagCompletionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspSdkTagCompletionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspSdkTagCompletionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspSdkTagCompletionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspSelectWordTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspSelectWordTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspSelectWordTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspSelectWordTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspTagDocumentationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTagDocumentationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspTagDocumentationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTagDocumentationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspThisAndSuperReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspThisAndSuperReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspThisAndSuperReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspThisAndSuperReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GspTypingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTypingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GspTypingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GspTypingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/gsp/GstringAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GstringAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/gsp/GstringAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/gsp/GstringAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsGroovyI18nIntentionTest.kt
diff --git a/test/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GrailsI18nInspectionTest.kt
diff --git a/test/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/i18n/GspI18nIntentionTest.kt
diff --git a/test/org/jetbrains/plugins/groovy/grails/inspections/GrailsMethodMayBeStaticInspectionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/inspections/GrailsMethodMayBeStaticInspectionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/inspections/GrailsMethodMayBeStaticInspectionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/inspections/GrailsMethodMayBeStaticInspectionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/parser/GspGroovyParserTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspGroovyParserTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/parser/GspGroovyParserTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspGroovyParserTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/parser/GspHtmlParserTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspHtmlParserTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/parser/GspHtmlParserTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspHtmlParserTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/parser/GspParserTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspParserTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/parser/GspParserTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspParserTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/parser/GspParsingTestCase.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspParsingTestCase.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/parser/GspParsingTestCase.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/parser/GspParsingTestCase.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsBuildTestDataTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsBuildTestDataTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsBuildTestDataTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsBuildTestDataTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsResourcePluginTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsResourcePluginTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsResourcePluginTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsResourcePluginTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsSearchableTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsSearchableTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsSearchableTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsSearchableTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsShiroPluginTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsShiroPluginTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsShiroPluginTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsShiroPluginTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsWebflowTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsWebflowTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsWebflowTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/GrailsWebflowTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/pluginSupport/SpockMethodReturnTypeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/SpockMethodReturnTypeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/pluginSupport/SpockMethodReturnTypeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/pluginSupport/SpockMethodReturnTypeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/GrailsBeanNameConventionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsBeanNameConventionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/GrailsBeanNameConventionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsBeanNameConventionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/GrailsFilterTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsFilterTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/GrailsFilterTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsFilterTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/GrailsJobTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsJobTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/GrailsJobTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsJobTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/GrailsViewReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsViewReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/GrailsViewReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/GrailsViewReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/bootstrap/GrailsBootStrapTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/bootstrap/GrailsBootStrapTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/bootstrap/GrailsBootStrapTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/bootstrap/GrailsBootStrapTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/Grails14MimePluginTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/Grails14MimePluginTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/Grails14MimePluginTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/Grails14MimePluginTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsAction14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsAction14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsAction14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsAction14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsActionWithExtensionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsActionWithExtensionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsActionWithExtensionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsActionWithExtensionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsController14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsController14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsController14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsController14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerActionReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerActionReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerActionReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerActionReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAllowedMethodsTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAllowedMethodsTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAllowedMethodsTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAllowedMethodsTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAmbiguousMethodInspectionTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAmbiguousMethodInspectionTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAmbiguousMethodInspectionTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerAmbiguousMethodInspectionTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerLayoutReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerLayoutReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerLayoutReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerLayoutReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRenameTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRenameTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRenameTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRenameTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRespondMethodTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRespondMethodTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRespondMethodTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsControllerRespondMethodTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpRequestTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpRequestTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpRequestTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpRequestTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpResponseTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpResponseTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpResponseTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsHttpResponseTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsMimePluginTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsMimePluginTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsMimePluginTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsMimePluginTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsNamespaceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsNamespaceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsNamespaceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsNamespaceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsRenderMethodTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsRenderMethodTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsRenderMethodTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/controller/GrailsRenderMethodTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringDSLTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringDSLTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringDSLTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringDSLTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringIntegrationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringIntegrationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringIntegrationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/spring/GrailsSpringIntegrationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTagLibNamedArgumentsTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTagLibNamedArgumentsTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTagLibNamedArgumentsTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTagLibNamedArgumentsTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglib14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglib14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglib14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglib14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglibParamTypeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglibParamTypeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglibParamTypeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GrailsTaglibParamTypeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/taglib/GspCustomTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GspCustomTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/taglib/GspCustomTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GspCustomTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/taglib/GspResolveTagsFromSameNamespaceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GspResolveTagsFromSameNamespaceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/taglib/GspResolveTagsFromSameNamespaceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/taglib/GspResolveTagsFromSameNamespaceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingUnresolvedReferenceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingUnresolvedReferenceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingUnresolvedReferenceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/reference/urlMapping/GrailsUrlMappingUnresolvedReferenceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/AssetPipelineSrcTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/AssetPipelineSrcTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/AssetPipelineSrcTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/AssetPipelineSrcTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspActionAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspActionAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspActionAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspActionAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspCodecAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspCodecAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspCodecAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspCodecAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspControllerAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspControllerAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspControllerAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspControllerAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspFieldTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspFieldTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspFieldTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspFieldTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspIncludeViewAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspIncludeViewAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspIncludeViewAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspIncludeViewAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspJsSrcTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspJsSrcTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspJsSrcTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspJsSrcTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspMetaTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspMetaTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspMetaTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspMetaTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspPluginAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspPluginAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspPluginAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspPluginAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspRenderTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspRenderTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspRenderTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspRenderTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspResourceTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspResourceTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspResourceTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspResourceTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspSelectTagTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspSelectTagTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspSelectTagTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspSelectTagTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tagSupport/GspUpdateAttributeTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspUpdateAttributeTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tagSupport/GspUpdateAttributeTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tagSupport/GspUpdateAttributeTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tests/GrailsMockAnnotationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsMockAnnotationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tests/GrailsMockAnnotationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsMockAnnotationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tests/GrailsTestForAnnotationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestForAnnotationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tests/GrailsTestForAnnotationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestForAnnotationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tests/GrailsTestMixinAnnotationTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestMixinAnnotationTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tests/GrailsTestMixinAnnotationTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestMixinAnnotationTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tests/GrailsTestUtilTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestUtilTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tests/GrailsTestUtilTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsTestUtilTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhance14Test.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhance14Test.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhance14Test.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhance14Test.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhanceTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhanceTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhanceTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/tests/GrailsVariableEnhanceTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/v3/GrailsEnhancesTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/v3/GrailsEnhancesTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/v3/GrailsEnhancesTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/v3/GrailsEnhancesTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/v3/GrailsProjectDescriptor.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/v3/GrailsProjectDescriptor.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/v3/GrailsProjectDescriptor.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/v3/GrailsProjectDescriptor.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/validation/GrailsImportFromTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/validation/GrailsImportFromTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/validation/GrailsImportFromTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/validation/GrailsImportFromTest.java
diff --git a/test/org/jetbrains/plugins/groovy/grails/validation/GrailsValidatedObjectTest.java b/plugin/src/test/java/org/jetbrains/plugins/groovy/grails/validation/GrailsValidatedObjectTest.java
similarity index 100%
rename from test/org/jetbrains/plugins/groovy/grails/validation/GrailsValidatedObjectTest.java
rename to plugin/src/test/java/org/jetbrains/plugins/groovy/grails/validation/GrailsValidatedObjectTest.java
diff --git a/resources/standardDsls/GrailsStandardDSL.gdsl b/plugin/standardDsls/GrailsStandardDSL.gdsl
similarity index 100%
rename from resources/standardDsls/GrailsStandardDSL.gdsl
rename to plugin/standardDsls/GrailsStandardDSL.gdsl
diff --git a/testdata/grails/actions/GspBlockComment.gsp b/plugin/testdata/grails/actions/GspBlockComment.gsp
similarity index 100%
rename from testdata/grails/actions/GspBlockComment.gsp
rename to plugin/testdata/grails/actions/GspBlockComment.gsp
diff --git a/testdata/grails/actions/GspBlockComment_after.gsp b/plugin/testdata/grails/actions/GspBlockComment_after.gsp
similarity index 100%
rename from testdata/grails/actions/GspBlockComment_after.gsp
rename to plugin/testdata/grails/actions/GspBlockComment_after.gsp
diff --git a/testdata/grails/actions/GspLineComment.gsp b/plugin/testdata/grails/actions/GspLineComment.gsp
similarity index 100%
rename from testdata/grails/actions/GspLineComment.gsp
rename to plugin/testdata/grails/actions/GspLineComment.gsp
diff --git a/testdata/grails/actions/GspLineComment_after.gsp b/plugin/testdata/grails/actions/GspLineComment_after.gsp
similarity index 100%
rename from testdata/grails/actions/GspLineComment_after.gsp
rename to plugin/testdata/grails/actions/GspLineComment_after.gsp
diff --git a/testdata/grails/actions/GspLineUncomment.gsp b/plugin/testdata/grails/actions/GspLineUncomment.gsp
similarity index 100%
rename from testdata/grails/actions/GspLineUncomment.gsp
rename to plugin/testdata/grails/actions/GspLineUncomment.gsp
diff --git a/testdata/grails/actions/GspLineUncomment_after.gsp b/plugin/testdata/grails/actions/GspLineUncomment_after.gsp
similarity index 100%
rename from testdata/grails/actions/GspLineUncomment_after.gsp
rename to plugin/testdata/grails/actions/GspLineUncomment_after.gsp
diff --git a/testdata/grails/completion/AllFinderMethodsInDomainClass.groovy b/plugin/testdata/grails/completion/AllFinderMethodsInDomainClass.groovy
similarity index 100%
rename from testdata/grails/completion/AllFinderMethodsInDomainClass.groovy
rename to plugin/testdata/grails/completion/AllFinderMethodsInDomainClass.groovy
diff --git a/testdata/grails/completion/AttributeNameFinishWithEq.gsp b/plugin/testdata/grails/completion/AttributeNameFinishWithEq.gsp
similarity index 100%
rename from testdata/grails/completion/AttributeNameFinishWithEq.gsp
rename to plugin/testdata/grails/completion/AttributeNameFinishWithEq.gsp
diff --git a/testdata/grails/completion/AttributeNameFinishWithEq_after.gsp b/plugin/testdata/grails/completion/AttributeNameFinishWithEq_after.gsp
similarity index 100%
rename from testdata/grails/completion/AttributeNameFinishWithEq_after.gsp
rename to plugin/testdata/grails/completion/AttributeNameFinishWithEq_after.gsp
diff --git a/testdata/grails/completion/ConstrainsPropertyInDomainClass.groovy b/plugin/testdata/grails/completion/ConstrainsPropertyInDomainClass.groovy
similarity index 100%
rename from testdata/grails/completion/ConstrainsPropertyInDomainClass.groovy
rename to plugin/testdata/grails/completion/ConstrainsPropertyInDomainClass.groovy
diff --git a/testdata/grails/completion/ConstrainsPropertyInDomainClassConstraints.groovy b/plugin/testdata/grails/completion/ConstrainsPropertyInDomainClassConstraints.groovy
similarity index 100%
rename from testdata/grails/completion/ConstrainsPropertyInDomainClassConstraints.groovy
rename to plugin/testdata/grails/completion/ConstrainsPropertyInDomainClassConstraints.groovy
diff --git a/testdata/grails/completion/ConstrainsPropertyInDomainClass_after.groovy b/plugin/testdata/grails/completion/ConstrainsPropertyInDomainClass_after.groovy
similarity index 100%
rename from testdata/grails/completion/ConstrainsPropertyInDomainClass_after.groovy
rename to plugin/testdata/grails/completion/ConstrainsPropertyInDomainClass_after.groovy
diff --git a/testdata/grails/completion/DomainClassDynamicMethods.groovy b/plugin/testdata/grails/completion/DomainClassDynamicMethods.groovy
similarity index 100%
rename from testdata/grails/completion/DomainClassDynamicMethods.groovy
rename to plugin/testdata/grails/completion/DomainClassDynamicMethods.groovy
diff --git a/testdata/grails/completion/DomainClassNonStaticMethods.groovy b/plugin/testdata/grails/completion/DomainClassNonStaticMethods.groovy
similarity index 100%
rename from testdata/grails/completion/DomainClassNonStaticMethods.groovy
rename to plugin/testdata/grails/completion/DomainClassNonStaticMethods.groovy
diff --git a/testdata/grails/completion/DomainClassStaticProperties.groovy b/plugin/testdata/grails/completion/DomainClassStaticProperties.groovy
similarity index 100%
rename from testdata/grails/completion/DomainClassStaticProperties.groovy
rename to plugin/testdata/grails/completion/DomainClassStaticProperties.groovy
diff --git a/testdata/grails/completion/DomainClassStaticProperties_after.groovy b/plugin/testdata/grails/completion/DomainClassStaticProperties_after.groovy
similarity index 100%
rename from testdata/grails/completion/DomainClassStaticProperties_after.groovy
rename to plugin/testdata/grails/completion/DomainClassStaticProperties_after.groovy
diff --git a/testdata/grails/completion/FindByMethodInDomainClass.groovy b/plugin/testdata/grails/completion/FindByMethodInDomainClass.groovy
similarity index 100%
rename from testdata/grails/completion/FindByMethodInDomainClass.groovy
rename to plugin/testdata/grails/completion/FindByMethodInDomainClass.groovy
diff --git a/testdata/grails/completion/FindByMethodWithThreeArgsInDomainClass.groovy b/plugin/testdata/grails/completion/FindByMethodWithThreeArgsInDomainClass.groovy
similarity index 100%
rename from testdata/grails/completion/FindByMethodWithThreeArgsInDomainClass.groovy
rename to plugin/testdata/grails/completion/FindByMethodWithThreeArgsInDomainClass.groovy
diff --git a/testdata/grails/completion/FinderMethodsInDomainClass.groovy b/plugin/testdata/grails/completion/FinderMethodsInDomainClass.groovy
similarity index 100%
rename from testdata/grails/completion/FinderMethodsInDomainClass.groovy
rename to plugin/testdata/grails/completion/FinderMethodsInDomainClass.groovy
diff --git a/testdata/grails/completion/FinderMethodsInGsp.groovy b/plugin/testdata/grails/completion/FinderMethodsInGsp.groovy
similarity index 100%
rename from testdata/grails/completion/FinderMethodsInGsp.groovy
rename to plugin/testdata/grails/completion/FinderMethodsInGsp.groovy
diff --git a/testdata/grails/completion/FinderMethodsInGsp.gsp b/plugin/testdata/grails/completion/FinderMethodsInGsp.gsp
similarity index 100%
rename from testdata/grails/completion/FinderMethodsInGsp.gsp
rename to plugin/testdata/grails/completion/FinderMethodsInGsp.gsp
diff --git a/testdata/grails/completion/FindersWithBelongsTo.groovy b/plugin/testdata/grails/completion/FindersWithBelongsTo.groovy
similarity index 100%
rename from testdata/grails/completion/FindersWithBelongsTo.groovy
rename to plugin/testdata/grails/completion/FindersWithBelongsTo.groovy
diff --git a/testdata/grails/completion/FindersWithHasMany.groovy b/plugin/testdata/grails/completion/FindersWithHasMany.groovy
similarity index 100%
rename from testdata/grails/completion/FindersWithHasMany.groovy
rename to plugin/testdata/grails/completion/FindersWithHasMany.groovy
diff --git a/testdata/grails/completion/GspInjectedJavaScript.gsp b/plugin/testdata/grails/completion/GspInjectedJavaScript.gsp
similarity index 100%
rename from testdata/grails/completion/GspInjectedJavaScript.gsp
rename to plugin/testdata/grails/completion/GspInjectedJavaScript.gsp
diff --git a/testdata/grails/completion/GspInjectedJavaScript_after.gsp b/plugin/testdata/grails/completion/GspInjectedJavaScript_after.gsp
similarity index 100%
rename from testdata/grails/completion/GspInjectedJavaScript_after.gsp
rename to plugin/testdata/grails/completion/GspInjectedJavaScript_after.gsp
diff --git a/testdata/grails/completion/JspTagCompletion.gsp b/plugin/testdata/grails/completion/JspTagCompletion.gsp
similarity index 100%
rename from testdata/grails/completion/JspTagCompletion.gsp
rename to plugin/testdata/grails/completion/JspTagCompletion.gsp
diff --git a/testdata/grails/completion/JspTagCompletion_after.gsp b/plugin/testdata/grails/completion/JspTagCompletion_after.gsp
similarity index 100%
rename from testdata/grails/completion/JspTagCompletion_after.gsp
rename to plugin/testdata/grails/completion/JspTagCompletion_after.gsp
diff --git a/testdata/grails/completion/ModifiersInGspGroovyDeclaration.gsp b/plugin/testdata/grails/completion/ModifiersInGspGroovyDeclaration.gsp
similarity index 100%
rename from testdata/grails/completion/ModifiersInGspGroovyDeclaration.gsp
rename to plugin/testdata/grails/completion/ModifiersInGspGroovyDeclaration.gsp
diff --git a/testdata/grails/completion/OverrideStandardTag.gsp b/plugin/testdata/grails/completion/OverrideStandardTag.gsp
similarity index 100%
rename from testdata/grails/completion/OverrideStandardTag.gsp
rename to plugin/testdata/grails/completion/OverrideStandardTag.gsp
diff --git a/testdata/grails/completion/ServiceInjection.groovy b/plugin/testdata/grails/completion/ServiceInjection.groovy
similarity index 100%
rename from testdata/grails/completion/ServiceInjection.groovy
rename to plugin/testdata/grails/completion/ServiceInjection.groovy
diff --git a/testdata/grails/completion/ServiceInjection_after.groovy b/plugin/testdata/grails/completion/ServiceInjection_after.groovy
similarity index 100%
rename from testdata/grails/completion/ServiceInjection_after.groovy
rename to plugin/testdata/grails/completion/ServiceInjection_after.groovy
diff --git a/testdata/grails/completion/UnpackagedDomainClassInGsp.groovy b/plugin/testdata/grails/completion/UnpackagedDomainClassInGsp.groovy
similarity index 100%
rename from testdata/grails/completion/UnpackagedDomainClassInGsp.groovy
rename to plugin/testdata/grails/completion/UnpackagedDomainClassInGsp.groovy
diff --git a/testdata/grails/completion/UnpackagedDomainClassInGsp.gsp b/plugin/testdata/grails/completion/UnpackagedDomainClassInGsp.gsp
similarity index 100%
rename from testdata/grails/completion/UnpackagedDomainClassInGsp.gsp
rename to plugin/testdata/grails/completion/UnpackagedDomainClassInGsp.gsp
diff --git a/testdata/grails/completion/UnpackagedDomainClassInGsp_after.gsp b/plugin/testdata/grails/completion/UnpackagedDomainClassInGsp_after.gsp
similarity index 100%
rename from testdata/grails/completion/UnpackagedDomainClassInGsp_after.gsp
rename to plugin/testdata/grails/completion/UnpackagedDomainClassInGsp_after.gsp
diff --git a/testdata/grails/enterAction/dir.test b/plugin/testdata/grails/enterAction/dir.test
similarity index 100%
rename from testdata/grails/enterAction/dir.test
rename to plugin/testdata/grails/enterAction/dir.test
diff --git a/testdata/grails/enterAction/simple_type.test b/plugin/testdata/grails/enterAction/simple_type.test
similarity index 100%
rename from testdata/grails/enterAction/simple_type.test
rename to plugin/testdata/grails/enterAction/simple_type.test
diff --git a/testdata/grails/fmt.tld b/plugin/testdata/grails/fmt.tld
similarity index 100%
rename from testdata/grails/fmt.tld
rename to plugin/testdata/grails/fmt.tld
diff --git a/testdata/grails/formatter/comments/schulz1.test b/plugin/testdata/grails/formatter/comments/schulz1.test
similarity index 100%
rename from testdata/grails/formatter/comments/schulz1.test
rename to plugin/testdata/grails/formatter/comments/schulz1.test
diff --git a/testdata/grails/formatter/comments/ven1.test b/plugin/testdata/grails/formatter/comments/ven1.test
similarity index 100%
rename from testdata/grails/formatter/comments/ven1.test
rename to plugin/testdata/grails/formatter/comments/ven1.test
diff --git a/testdata/grails/formatter/groovy/groovy.test b/plugin/testdata/grails/formatter/groovy/groovy.test
similarity index 100%
rename from testdata/grails/formatter/groovy/groovy.test
rename to plugin/testdata/grails/formatter/groovy/groovy.test
diff --git a/testdata/grails/formatter/groovy/plain1.test b/plugin/testdata/grails/formatter/groovy/plain1.test
similarity index 100%
rename from testdata/grails/formatter/groovy/plain1.test
rename to plugin/testdata/grails/formatter/groovy/plain1.test
diff --git a/testdata/grails/formatter/groovy/sim1.test b/plugin/testdata/grails/formatter/groovy/sim1.test
similarity index 100%
rename from testdata/grails/formatter/groovy/sim1.test
rename to plugin/testdata/grails/formatter/groovy/sim1.test
diff --git a/testdata/grails/formatter/gsp/EA29587.test b/plugin/testdata/grails/formatter/gsp/EA29587.test
similarity index 100%
rename from testdata/grails/formatter/gsp/EA29587.test
rename to plugin/testdata/grails/formatter/gsp/EA29587.test
diff --git a/testdata/grails/formatter/gsp/gsp1.test b/plugin/testdata/grails/formatter/gsp/gsp1.test
similarity index 100%
rename from testdata/grails/formatter/gsp/gsp1.test
rename to plugin/testdata/grails/formatter/gsp/gsp1.test
diff --git a/testdata/grails/formatter/gsp/gsp2.test b/plugin/testdata/grails/formatter/gsp/gsp2.test
similarity index 100%
rename from testdata/grails/formatter/gsp/gsp2.test
rename to plugin/testdata/grails/formatter/gsp/gsp2.test
diff --git a/testdata/grails/formatter/gsp/gsp3.test b/plugin/testdata/grails/formatter/gsp/gsp3.test
similarity index 100%
rename from testdata/grails/formatter/gsp/gsp3.test
rename to plugin/testdata/grails/formatter/gsp/gsp3.test
diff --git a/testdata/grails/formatter/gsp/gsp8.test b/plugin/testdata/grails/formatter/gsp/gsp8.test
similarity index 100%
rename from testdata/grails/formatter/gsp/gsp8.test
rename to plugin/testdata/grails/formatter/gsp/gsp8.test
diff --git a/testdata/grails/formatter/gsp/gsp9.test b/plugin/testdata/grails/formatter/gsp/gsp9.test
similarity index 100%
rename from testdata/grails/formatter/gsp/gsp9.test
rename to plugin/testdata/grails/formatter/gsp/gsp9.test
diff --git a/testdata/grails/formatter/html/error/giga_werle.test b/plugin/testdata/grails/formatter/html/error/giga_werle.test
similarity index 100%
rename from testdata/grails/formatter/html/error/giga_werle.test
rename to plugin/testdata/grails/formatter/html/error/giga_werle.test
diff --git a/testdata/grails/formatter/html/error/nik1.test b/plugin/testdata/grails/formatter/html/error/nik1.test
similarity index 100%
rename from testdata/grails/formatter/html/error/nik1.test
rename to plugin/testdata/grails/formatter/html/error/nik1.test
diff --git a/testdata/grails/formatter/html/error/show.test b/plugin/testdata/grails/formatter/html/error/show.test
similarity index 100%
rename from testdata/grails/formatter/html/error/show.test
rename to plugin/testdata/grails/formatter/html/error/show.test
diff --git a/testdata/grails/formatter/html/error/werle1.test b/plugin/testdata/grails/formatter/html/error/werle1.test
similarity index 100%
rename from testdata/grails/formatter/html/error/werle1.test
rename to plugin/testdata/grails/formatter/html/error/werle1.test
diff --git a/testdata/grails/formatter/html/error/werle30.test b/plugin/testdata/grails/formatter/html/error/werle30.test
similarity index 100%
rename from testdata/grails/formatter/html/error/werle30.test
rename to plugin/testdata/grails/formatter/html/error/werle30.test
diff --git a/testdata/grails/formatter/html/inner/GRVY_1165.test b/plugin/testdata/grails/formatter/html/inner/GRVY_1165.test
similarity index 100%
rename from testdata/grails/formatter/html/inner/GRVY_1165.test
rename to plugin/testdata/grails/formatter/html/inner/GRVY_1165.test
diff --git a/testdata/grails/formatter/html/inner/GRVY_1165_1.test b/plugin/testdata/grails/formatter/html/inner/GRVY_1165_1.test
similarity index 100%
rename from testdata/grails/formatter/html/inner/GRVY_1165_1.test
rename to plugin/testdata/grails/formatter/html/inner/GRVY_1165_1.test
diff --git a/testdata/grails/formatter/html/inner/GRVY_876.test b/plugin/testdata/grails/formatter/html/inner/GRVY_876.test
similarity index 100%
rename from testdata/grails/formatter/html/inner/GRVY_876.test
rename to plugin/testdata/grails/formatter/html/inner/GRVY_876.test
diff --git a/testdata/grails/formatter/html/inner/inner2.test b/plugin/testdata/grails/formatter/html/inner/inner2.test
similarity index 100%
rename from testdata/grails/formatter/html/inner/inner2.test
rename to plugin/testdata/grails/formatter/html/inner/inner2.test
diff --git a/testdata/grails/formatter/html/inner/inner3.test b/plugin/testdata/grails/formatter/html/inner/inner3.test
similarity index 100%
rename from testdata/grails/formatter/html/inner/inner3.test
rename to plugin/testdata/grails/formatter/html/inner/inner3.test
diff --git a/testdata/grails/formatter/html/megabug/GRVY_1046.test b/plugin/testdata/grails/formatter/html/megabug/GRVY_1046.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/GRVY_1046.test
rename to plugin/testdata/grails/formatter/html/megabug/GRVY_1046.test
diff --git a/testdata/grails/formatter/html/megabug/bigTest.test b/plugin/testdata/grails/formatter/html/megabug/bigTest.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/bigTest.test
rename to plugin/testdata/grails/formatter/html/megabug/bigTest.test
diff --git a/testdata/grails/formatter/html/megabug/megap2.test b/plugin/testdata/grails/formatter/html/megabug/megap2.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/megap2.test
rename to plugin/testdata/grails/formatter/html/megabug/megap2.test
diff --git a/testdata/grails/formatter/html/megabug/mmm3.test b/plugin/testdata/grails/formatter/html/megabug/mmm3.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/mmm3.test
rename to plugin/testdata/grails/formatter/html/megabug/mmm3.test
diff --git a/testdata/grails/formatter/html/megabug/peter.test b/plugin/testdata/grails/formatter/html/megabug/peter.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/peter.test
rename to plugin/testdata/grails/formatter/html/megabug/peter.test
diff --git a/testdata/grails/formatter/html/megabug/peter2.test b/plugin/testdata/grails/formatter/html/megabug/peter2.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/peter2.test
rename to plugin/testdata/grails/formatter/html/megabug/peter2.test
diff --git a/testdata/grails/formatter/html/megabug/peter3.test b/plugin/testdata/grails/formatter/html/megabug/peter3.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/peter3.test
rename to plugin/testdata/grails/formatter/html/megabug/peter3.test
diff --git a/testdata/grails/formatter/html/megabug/range1.test b/plugin/testdata/grails/formatter/html/megabug/range1.test
similarity index 100%
rename from testdata/grails/formatter/html/megabug/range1.test
rename to plugin/testdata/grails/formatter/html/megabug/range1.test
diff --git a/testdata/grails/formatter/html/nested/attr1.test b/plugin/testdata/grails/formatter/html/nested/attr1.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/attr1.test
rename to plugin/testdata/grails/formatter/html/nested/attr1.test
diff --git a/testdata/grails/formatter/html/nested/gsp6.test b/plugin/testdata/grails/formatter/html/nested/gsp6.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/gsp6.test
rename to plugin/testdata/grails/formatter/html/nested/gsp6.test
diff --git a/testdata/grails/formatter/html/nested/gsp7.test b/plugin/testdata/grails/formatter/html/nested/gsp7.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/gsp7.test
rename to plugin/testdata/grails/formatter/html/nested/gsp7.test
diff --git a/testdata/grails/formatter/html/nested/peter7926.test b/plugin/testdata/grails/formatter/html/nested/peter7926.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/peter7926.test
rename to plugin/testdata/grails/formatter/html/nested/peter7926.test
diff --git a/testdata/grails/formatter/html/nested/peter_simple.test b/plugin/testdata/grails/formatter/html/nested/peter_simple.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/peter_simple.test
rename to plugin/testdata/grails/formatter/html/nested/peter_simple.test
diff --git a/testdata/grails/formatter/html/nested/range.test b/plugin/testdata/grails/formatter/html/nested/range.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/range.test
rename to plugin/testdata/grails/formatter/html/nested/range.test
diff --git a/testdata/grails/formatter/html/nested/range2.test b/plugin/testdata/grails/formatter/html/nested/range2.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/range2.test
rename to plugin/testdata/grails/formatter/html/nested/range2.test
diff --git a/testdata/grails/formatter/html/nested/range4.test b/plugin/testdata/grails/formatter/html/nested/range4.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/range4.test
rename to plugin/testdata/grails/formatter/html/nested/range4.test
diff --git a/testdata/grails/formatter/html/nested/range5.test b/plugin/testdata/grails/formatter/html/nested/range5.test
similarity index 100%
rename from testdata/grails/formatter/html/nested/range5.test
rename to plugin/testdata/grails/formatter/html/nested/range5.test
diff --git a/testdata/grails/formatter/html/simple/GRVY_1146.test b/plugin/testdata/grails/formatter/html/simple/GRVY_1146.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/GRVY_1146.test
rename to plugin/testdata/grails/formatter/html/simple/GRVY_1146.test
diff --git a/testdata/grails/formatter/html/simple/attributeSpaces.test b/plugin/testdata/grails/formatter/html/simple/attributeSpaces.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/attributeSpaces.test
rename to plugin/testdata/grails/formatter/html/simple/attributeSpaces.test
diff --git a/testdata/grails/formatter/html/simple/gsp4.test b/plugin/testdata/grails/formatter/html/simple/gsp4.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/gsp4.test
rename to plugin/testdata/grails/formatter/html/simple/gsp4.test
diff --git a/testdata/grails/formatter/html/simple/gsp8.test b/plugin/testdata/grails/formatter/html/simple/gsp8.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/gsp8.test
rename to plugin/testdata/grails/formatter/html/simple/gsp8.test
diff --git a/testdata/grails/formatter/html/simple/htm1.test b/plugin/testdata/grails/formatter/html/simple/htm1.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/htm1.test
rename to plugin/testdata/grails/formatter/html/simple/htm1.test
diff --git a/testdata/grails/formatter/html/simple/htm2.test b/plugin/testdata/grails/formatter/html/simple/htm2.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/htm2.test
rename to plugin/testdata/grails/formatter/html/simple/htm2.test
diff --git a/testdata/grails/formatter/html/simple/range3.test b/plugin/testdata/grails/formatter/html/simple/range3.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/range3.test
rename to plugin/testdata/grails/formatter/html/simple/range3.test
diff --git a/testdata/grails/formatter/html/simple/sim2.test b/plugin/testdata/grails/formatter/html/simple/sim2.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/sim2.test
rename to plugin/testdata/grails/formatter/html/simple/sim2.test
diff --git a/testdata/grails/formatter/html/simple/tagNameSpaces.test b/plugin/testdata/grails/formatter/html/simple/tagNameSpaces.test
similarity index 100%
rename from testdata/grails/formatter/html/simple/tagNameSpaces.test
rename to plugin/testdata/grails/formatter/html/simple/tagNameSpaces.test
diff --git a/testdata/grails/formatter/html/trash/bug1.test b/plugin/testdata/grails/formatter/html/trash/bug1.test
similarity index 100%
rename from testdata/grails/formatter/html/trash/bug1.test
rename to plugin/testdata/grails/formatter/html/trash/bug1.test
diff --git a/testdata/grails/formatter/html/trash/bug2.test b/plugin/testdata/grails/formatter/html/trash/bug2.test
similarity index 100%
rename from testdata/grails/formatter/html/trash/bug2.test
rename to plugin/testdata/grails/formatter/html/trash/bug2.test
diff --git a/testdata/grails/formatter/html/trash/bug3.test b/plugin/testdata/grails/formatter/html/trash/bug3.test
similarity index 100%
rename from testdata/grails/formatter/html/trash/bug3.test
rename to plugin/testdata/grails/formatter/html/trash/bug3.test
diff --git a/testdata/grails/formatter/html/trash/bug4.test b/plugin/testdata/grails/formatter/html/trash/bug4.test
similarity index 100%
rename from testdata/grails/formatter/html/trash/bug4.test
rename to plugin/testdata/grails/formatter/html/trash/bug4.test
diff --git a/testdata/grails/formatter/html/trash/bug5.test b/plugin/testdata/grails/formatter/html/trash/bug5.test
similarity index 100%
rename from testdata/grails/formatter/html/trash/bug5.test
rename to plugin/testdata/grails/formatter/html/trash/bug5.test
diff --git a/testdata/grails/formatter/html/trash/trash1.test b/plugin/testdata/grails/formatter/html/trash/trash1.test
similarity index 100%
rename from testdata/grails/formatter/html/trash/trash1.test
rename to plugin/testdata/grails/formatter/html/trash/trash1.test
diff --git a/testdata/grails/formatter/hyphenInTagName.test b/plugin/testdata/grails/formatter/hyphenInTagName.test
similarity index 100%
rename from testdata/grails/formatter/hyphenInTagName.test
rename to plugin/testdata/grails/formatter/hyphenInTagName.test
diff --git a/testdata/grails/formatter/js/t1.test b/plugin/testdata/grails/formatter/js/t1.test
similarity index 100%
rename from testdata/grails/formatter/js/t1.test
rename to plugin/testdata/grails/formatter/js/t1.test
diff --git a/testdata/grails/formatter/js/t2.test b/plugin/testdata/grails/formatter/js/t2.test
similarity index 100%
rename from testdata/grails/formatter/js/t2.test
rename to plugin/testdata/grails/formatter/js/t2.test
diff --git a/testdata/grails/formatter/spacing/groovy/gr1.test b/plugin/testdata/grails/formatter/spacing/groovy/gr1.test
similarity index 100%
rename from testdata/grails/formatter/spacing/groovy/gr1.test
rename to plugin/testdata/grails/formatter/spacing/groovy/gr1.test
diff --git a/testdata/grails/formatter/spacing/groovy/gr2.test b/plugin/testdata/grails/formatter/spacing/groovy/gr2.test
similarity index 100%
rename from testdata/grails/formatter/spacing/groovy/gr2.test
rename to plugin/testdata/grails/formatter/spacing/groovy/gr2.test
diff --git a/testdata/grails/formatter/spacing/htm3.test b/plugin/testdata/grails/formatter/spacing/htm3.test
similarity index 100%
rename from testdata/grails/formatter/spacing/htm3.test
rename to plugin/testdata/grails/formatter/spacing/htm3.test
diff --git a/testdata/grails/formatter/spacing/spac2.test b/plugin/testdata/grails/formatter/spacing/spac2.test
similarity index 100%
rename from testdata/grails/formatter/spacing/spac2.test
rename to plugin/testdata/grails/formatter/spacing/spac2.test
diff --git a/testdata/grails/formatter/spacing/spac3.test b/plugin/testdata/grails/formatter/spacing/spac3.test
similarity index 100%
rename from testdata/grails/formatter/spacing/spac3.test
rename to plugin/testdata/grails/formatter/spacing/spac3.test
diff --git a/testdata/grails/formatter/spacing/tags2.test b/plugin/testdata/grails/formatter/spacing/tags2.test
similarity index 100%
rename from testdata/grails/formatter/spacing/tags2.test
rename to plugin/testdata/grails/formatter/spacing/tags2.test
diff --git a/testdata/grails/gsp/TestController.groovy b/plugin/testdata/grails/gsp/TestController.groovy
similarity index 100%
rename from testdata/grails/gsp/TestController.groovy
rename to plugin/testdata/grails/gsp/TestController.groovy
diff --git a/testdata/grails/highlighting/DomainFindersWithInheritedProperties.groovy b/plugin/testdata/grails/highlighting/DomainFindersWithInheritedProperties.groovy
similarity index 100%
rename from testdata/grails/highlighting/DomainFindersWithInheritedProperties.groovy
rename to plugin/testdata/grails/highlighting/DomainFindersWithInheritedProperties.groovy
diff --git a/testdata/grails/highlighting/FileReferenceWithGspInjections.gsp b/plugin/testdata/grails/highlighting/FileReferenceWithGspInjections.gsp
similarity index 100%
rename from testdata/grails/highlighting/FileReferenceWithGspInjections.gsp
rename to plugin/testdata/grails/highlighting/FileReferenceWithGspInjections.gsp
diff --git a/testdata/grails/highlighting/FinderMethods.groovy b/plugin/testdata/grails/highlighting/FinderMethods.groovy
similarity index 100%
rename from testdata/grails/highlighting/FinderMethods.groovy
rename to plugin/testdata/grails/highlighting/FinderMethods.groovy
diff --git a/testdata/grails/highlighting/HasManyBelongsTo.groovy b/plugin/testdata/grails/highlighting/HasManyBelongsTo.groovy
similarity index 100%
rename from testdata/grails/highlighting/HasManyBelongsTo.groovy
rename to plugin/testdata/grails/highlighting/HasManyBelongsTo.groovy
diff --git a/testdata/grails/highlighting/JavascriptWithGspInjections.gsp b/plugin/testdata/grails/highlighting/JavascriptWithGspInjections.gsp
similarity index 100%
rename from testdata/grails/highlighting/JavascriptWithGspInjections.gsp
rename to plugin/testdata/grails/highlighting/JavascriptWithGspInjections.gsp
diff --git a/testdata/grails/highlighting/JspTagInGsp.gsp b/plugin/testdata/grails/highlighting/JspTagInGsp.gsp
similarity index 100%
rename from testdata/grails/highlighting/JspTagInGsp.gsp
rename to plugin/testdata/grails/highlighting/JspTagInGsp.gsp
diff --git a/testdata/grails/highlighting/MappedBy.groovy b/plugin/testdata/grails/highlighting/MappedBy.groovy
similarity index 100%
rename from testdata/grails/highlighting/MappedBy.groovy
rename to plugin/testdata/grails/highlighting/MappedBy.groovy
diff --git a/testdata/grails/highlighting/MappedByErrors1.groovy b/plugin/testdata/grails/highlighting/MappedByErrors1.groovy
similarity index 100%
rename from testdata/grails/highlighting/MappedByErrors1.groovy
rename to plugin/testdata/grails/highlighting/MappedByErrors1.groovy
diff --git a/testdata/grails/highlighting/Properties.groovy b/plugin/testdata/grails/highlighting/Properties.groovy
similarity index 100%
rename from testdata/grails/highlighting/Properties.groovy
rename to plugin/testdata/grails/highlighting/Properties.groovy
diff --git a/testdata/grails/highlighting/UnknownArgsAttribute.gsp b/plugin/testdata/grails/highlighting/UnknownArgsAttribute.gsp
similarity index 100%
rename from testdata/grails/highlighting/UnknownArgsAttribute.gsp
rename to plugin/testdata/grails/highlighting/UnknownArgsAttribute.gsp
diff --git a/testdata/grails/oldCompletion/controllers/contr1Controller.test b/plugin/testdata/grails/oldCompletion/controllers/contr1Controller.test
similarity index 100%
rename from testdata/grails/oldCompletion/controllers/contr1Controller.test
rename to plugin/testdata/grails/oldCompletion/controllers/contr1Controller.test
diff --git a/testdata/grails/oldCompletion/controllers/contr2Controller.test b/plugin/testdata/grails/oldCompletion/controllers/contr2Controller.test
similarity index 100%
rename from testdata/grails/oldCompletion/controllers/contr2Controller.test
rename to plugin/testdata/grails/oldCompletion/controllers/contr2Controller.test
diff --git a/testdata/grails/oldCompletion/controllers/contr3Controller.test b/plugin/testdata/grails/oldCompletion/controllers/contr3Controller.test
similarity index 100%
rename from testdata/grails/oldCompletion/controllers/contr3Controller.test
rename to plugin/testdata/grails/oldCompletion/controllers/contr3Controller.test
diff --git a/testdata/grails/oldCompletion/domain/dyn1.test b/plugin/testdata/grails/oldCompletion/domain/dyn1.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/dyn1.test
rename to plugin/testdata/grails/oldCompletion/domain/dyn1.test
diff --git a/testdata/grails/oldCompletion/domain/finder10.test b/plugin/testdata/grails/oldCompletion/domain/finder10.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder10.test
rename to plugin/testdata/grails/oldCompletion/domain/finder10.test
diff --git a/testdata/grails/oldCompletion/domain/finder11.test b/plugin/testdata/grails/oldCompletion/domain/finder11.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder11.test
rename to plugin/testdata/grails/oldCompletion/domain/finder11.test
diff --git a/testdata/grails/oldCompletion/domain/finder2.test b/plugin/testdata/grails/oldCompletion/domain/finder2.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder2.test
rename to plugin/testdata/grails/oldCompletion/domain/finder2.test
diff --git a/testdata/grails/oldCompletion/domain/finder3.test b/plugin/testdata/grails/oldCompletion/domain/finder3.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder3.test
rename to plugin/testdata/grails/oldCompletion/domain/finder3.test
diff --git a/testdata/grails/oldCompletion/domain/finder4.test b/plugin/testdata/grails/oldCompletion/domain/finder4.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder4.test
rename to plugin/testdata/grails/oldCompletion/domain/finder4.test
diff --git a/testdata/grails/oldCompletion/domain/finder5.test b/plugin/testdata/grails/oldCompletion/domain/finder5.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder5.test
rename to plugin/testdata/grails/oldCompletion/domain/finder5.test
diff --git a/testdata/grails/oldCompletion/domain/finder6.test b/plugin/testdata/grails/oldCompletion/domain/finder6.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder6.test
rename to plugin/testdata/grails/oldCompletion/domain/finder6.test
diff --git a/testdata/grails/oldCompletion/domain/finder7.test b/plugin/testdata/grails/oldCompletion/domain/finder7.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder7.test
rename to plugin/testdata/grails/oldCompletion/domain/finder7.test
diff --git a/testdata/grails/oldCompletion/domain/finder8.test b/plugin/testdata/grails/oldCompletion/domain/finder8.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder8.test
rename to plugin/testdata/grails/oldCompletion/domain/finder8.test
diff --git a/testdata/grails/oldCompletion/domain/finder_full.test b/plugin/testdata/grails/oldCompletion/domain/finder_full.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/finder_full.test
rename to plugin/testdata/grails/oldCompletion/domain/finder_full.test
diff --git a/testdata/grails/oldCompletion/domain/list_order.test b/plugin/testdata/grails/oldCompletion/domain/list_order.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/list_order.test
rename to plugin/testdata/grails/oldCompletion/domain/list_order.test
diff --git a/testdata/grails/oldCompletion/domain/not_ref.test b/plugin/testdata/grails/oldCompletion/domain/not_ref.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/not_ref.test
rename to plugin/testdata/grails/oldCompletion/domain/not_ref.test
diff --git a/testdata/grails/oldCompletion/domain/qulaified.test b/plugin/testdata/grails/oldCompletion/domain/qulaified.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/qulaified.test
rename to plugin/testdata/grails/oldCompletion/domain/qulaified.test
diff --git a/testdata/grails/oldCompletion/domain/static.test b/plugin/testdata/grails/oldCompletion/domain/static.test
similarity index 100%
rename from testdata/grails/oldCompletion/domain/static.test
rename to plugin/testdata/grails/oldCompletion/domain/static.test
diff --git a/testdata/grails/oldCompletion/gsp/attr1.test b/plugin/testdata/grails/oldCompletion/gsp/attr1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/attr1.test
rename to plugin/testdata/grails/oldCompletion/gsp/attr1.test
diff --git a/testdata/grails/oldCompletion/gsp/custom1.test b/plugin/testdata/grails/oldCompletion/gsp/custom1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/custom1.test
rename to plugin/testdata/grails/oldCompletion/gsp/custom1.test
diff --git a/testdata/grails/oldCompletion/gsp/customNamespacePrefix.test b/plugin/testdata/grails/oldCompletion/gsp/customNamespacePrefix.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/customNamespacePrefix.test
rename to plugin/testdata/grails/oldCompletion/gsp/customNamespacePrefix.test
diff --git a/testdata/grails/oldCompletion/gsp/dirname.test b/plugin/testdata/grails/oldCompletion/gsp/dirname.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/dirname.test
rename to plugin/testdata/grails/oldCompletion/gsp/dirname.test
diff --git a/testdata/grails/oldCompletion/gsp/endTagTest1.test b/plugin/testdata/grails/oldCompletion/gsp/endTagTest1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/endTagTest1.test
rename to plugin/testdata/grails/oldCompletion/gsp/endTagTest1.test
diff --git a/testdata/grails/oldCompletion/gsp/endTagTest2.test b/plugin/testdata/grails/oldCompletion/gsp/endTagTest2.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/endTagTest2.test
rename to plugin/testdata/grails/oldCompletion/gsp/endTagTest2.test
diff --git a/testdata/grails/oldCompletion/gsp/endTagTest3.test b/plugin/testdata/grails/oldCompletion/gsp/endTagTest3.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/endTagTest3.test
rename to plugin/testdata/grails/oldCompletion/gsp/endTagTest3.test
diff --git a/testdata/grails/oldCompletion/gsp/endTagTest4.test b/plugin/testdata/grails/oldCompletion/gsp/endTagTest4.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/endTagTest4.test
rename to plugin/testdata/grails/oldCompletion/gsp/endTagTest4.test
diff --git a/testdata/grails/oldCompletion/gsp/endTagTest5.test b/plugin/testdata/grails/oldCompletion/gsp/endTagTest5.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/endTagTest5.test
rename to plugin/testdata/grails/oldCompletion/gsp/endTagTest5.test
diff --git a/testdata/grails/oldCompletion/gsp/excludeNotATagGroovy.test b/plugin/testdata/grails/oldCompletion/gsp/excludeNotATagGroovy.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/excludeNotATagGroovy.test
rename to plugin/testdata/grails/oldCompletion/gsp/excludeNotATagGroovy.test
diff --git a/testdata/grails/oldCompletion/gsp/excludeNotATagHTML.test b/plugin/testdata/grails/oldCompletion/gsp/excludeNotATagHTML.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/excludeNotATagHTML.test
rename to plugin/testdata/grails/oldCompletion/gsp/excludeNotATagHTML.test
diff --git a/testdata/grails/oldCompletion/gsp/g1.test b/plugin/testdata/grails/oldCompletion/gsp/g1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/g1.test
rename to plugin/testdata/grails/oldCompletion/gsp/g1.test
diff --git a/testdata/grails/oldCompletion/gsp/get1.test b/plugin/testdata/grails/oldCompletion/gsp/get1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/get1.test
rename to plugin/testdata/grails/oldCompletion/gsp/get1.test
diff --git a/testdata/grails/oldCompletion/gsp/groo1.test b/plugin/testdata/grails/oldCompletion/gsp/groo1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/groo1.test
rename to plugin/testdata/grails/oldCompletion/gsp/groo1.test
diff --git a/testdata/grails/oldCompletion/gsp/groo2.test b/plugin/testdata/grails/oldCompletion/gsp/groo2.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/groo2.test
rename to plugin/testdata/grails/oldCompletion/gsp/groo2.test
diff --git a/testdata/grails/oldCompletion/gsp/groo3.test b/plugin/testdata/grails/oldCompletion/gsp/groo3.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/groo3.test
rename to plugin/testdata/grails/oldCompletion/gsp/groo3.test
diff --git a/testdata/grails/oldCompletion/gsp/groo4.test b/plugin/testdata/grails/oldCompletion/gsp/groo4.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/groo4.test
rename to plugin/testdata/grails/oldCompletion/gsp/groo4.test
diff --git a/testdata/grails/oldCompletion/gsp/groo5.test b/plugin/testdata/grails/oldCompletion/gsp/groo5.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/groo5.test
rename to plugin/testdata/grails/oldCompletion/gsp/groo5.test
diff --git a/testdata/grails/oldCompletion/gsp/html1.test b/plugin/testdata/grails/oldCompletion/gsp/html1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/html1.test
rename to plugin/testdata/grails/oldCompletion/gsp/html1.test
diff --git a/testdata/grails/oldCompletion/gsp/htmlWithDoctype.test b/plugin/testdata/grails/oldCompletion/gsp/htmlWithDoctype.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/htmlWithDoctype.test
rename to plugin/testdata/grails/oldCompletion/gsp/htmlWithDoctype.test
diff --git a/testdata/grails/oldCompletion/gsp/link.test b/plugin/testdata/grails/oldCompletion/gsp/link.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/link.test
rename to plugin/testdata/grails/oldCompletion/gsp/link.test
diff --git a/testdata/grails/oldCompletion/gsp/link2.test b/plugin/testdata/grails/oldCompletion/gsp/link2.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/link2.test
rename to plugin/testdata/grails/oldCompletion/gsp/link2.test
diff --git a/testdata/grails/oldCompletion/gsp/my1.test b/plugin/testdata/grails/oldCompletion/gsp/my1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/my1.test
rename to plugin/testdata/grails/oldCompletion/gsp/my1.test
diff --git a/testdata/grails/oldCompletion/gsp/nonStatic.test b/plugin/testdata/grails/oldCompletion/gsp/nonStatic.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/nonStatic.test
rename to plugin/testdata/grails/oldCompletion/gsp/nonStatic.test
diff --git a/testdata/grails/oldCompletion/gsp/packageTagLib1.test b/plugin/testdata/grails/oldCompletion/gsp/packageTagLib1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/packageTagLib1.test
rename to plugin/testdata/grails/oldCompletion/gsp/packageTagLib1.test
diff --git a/testdata/grails/oldCompletion/gsp/packageTagLib2.test b/plugin/testdata/grails/oldCompletion/gsp/packageTagLib2.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/packageTagLib2.test
rename to plugin/testdata/grails/oldCompletion/gsp/packageTagLib2.test
diff --git a/testdata/grails/oldCompletion/gsp/tail1.test b/plugin/testdata/grails/oldCompletion/gsp/tail1.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/tail1.test
rename to plugin/testdata/grails/oldCompletion/gsp/tail1.test
diff --git a/testdata/grails/oldCompletion/gsp/tail2.test b/plugin/testdata/grails/oldCompletion/gsp/tail2.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/tail2.test
rename to plugin/testdata/grails/oldCompletion/gsp/tail2.test
diff --git a/testdata/grails/oldCompletion/gsp/tail3.test b/plugin/testdata/grails/oldCompletion/gsp/tail3.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/tail3.test
rename to plugin/testdata/grails/oldCompletion/gsp/tail3.test
diff --git a/testdata/grails/oldCompletion/gsp/tail4.test b/plugin/testdata/grails/oldCompletion/gsp/tail4.test
similarity index 100%
rename from testdata/grails/oldCompletion/gsp/tail4.test
rename to plugin/testdata/grails/oldCompletion/gsp/tail4.test
diff --git a/testdata/grails/parser/gsp/dir/dir1.test b/plugin/testdata/grails/parser/gsp/dir/dir1.test
similarity index 100%
rename from testdata/grails/parser/gsp/dir/dir1.test
rename to plugin/testdata/grails/parser/gsp/dir/dir1.test
diff --git a/testdata/grails/parser/gsp/hyphenInTagName.test b/plugin/testdata/grails/parser/gsp/hyphenInTagName.test
similarity index 100%
rename from testdata/grails/parser/gsp/hyphenInTagName.test
rename to plugin/testdata/grails/parser/gsp/hyphenInTagName.test
diff --git a/testdata/grails/parser/gsp/inject/GRVY_943.test b/plugin/testdata/grails/parser/gsp/inject/GRVY_943.test
similarity index 100%
rename from testdata/grails/parser/gsp/inject/GRVY_943.test
rename to plugin/testdata/grails/parser/gsp/inject/GRVY_943.test
diff --git a/testdata/grails/parser/gsp/inject/escaped1.test b/plugin/testdata/grails/parser/gsp/inject/escaped1.test
similarity index 100%
rename from testdata/grails/parser/gsp/inject/escaped1.test
rename to plugin/testdata/grails/parser/gsp/inject/escaped1.test
diff --git a/testdata/grails/parser/gsp/simple/bubug1.test b/plugin/testdata/grails/parser/gsp/simple/bubug1.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/bubug1.test
rename to plugin/testdata/grails/parser/gsp/simple/bubug1.test
diff --git a/testdata/grails/parser/gsp/simple/clos1.test b/plugin/testdata/grails/parser/gsp/simple/clos1.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/clos1.test
rename to plugin/testdata/grails/parser/gsp/simple/clos1.test
diff --git a/testdata/grails/parser/gsp/simple/clos2.test b/plugin/testdata/grails/parser/gsp/simple/clos2.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/clos2.test
rename to plugin/testdata/grails/parser/gsp/simple/clos2.test
diff --git a/testdata/grails/parser/gsp/simple/common.test b/plugin/testdata/grails/parser/gsp/simple/common.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/common.test
rename to plugin/testdata/grails/parser/gsp/simple/common.test
diff --git a/testdata/grails/parser/gsp/simple/form1.test b/plugin/testdata/grails/parser/gsp/simple/form1.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/form1.test
rename to plugin/testdata/grails/parser/gsp/simple/form1.test
diff --git a/testdata/grails/parser/gsp/simple/megap1.test b/plugin/testdata/grails/parser/gsp/simple/megap1.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/megap1.test
rename to plugin/testdata/grails/parser/gsp/simple/megap1.test
diff --git a/testdata/grails/parser/gsp/simple/mmm1.test b/plugin/testdata/grails/parser/gsp/simple/mmm1.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/mmm1.test
rename to plugin/testdata/grails/parser/gsp/simple/mmm1.test
diff --git a/testdata/grails/parser/gsp/simple/peter2.test b/plugin/testdata/grails/parser/gsp/simple/peter2.test
similarity index 100%
rename from testdata/grails/parser/gsp/simple/peter2.test
rename to plugin/testdata/grails/parser/gsp/simple/peter2.test
diff --git a/testdata/grails/parser/gsp/tags/act1.test b/plugin/testdata/grails/parser/gsp/tags/act1.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/act1.test
rename to plugin/testdata/grails/parser/gsp/tags/act1.test
diff --git a/testdata/grails/parser/gsp/tags/act2.test b/plugin/testdata/grails/parser/gsp/tags/act2.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/act2.test
rename to plugin/testdata/grails/parser/gsp/tags/act2.test
diff --git a/testdata/grails/parser/gsp/tags/gps9.test b/plugin/testdata/grails/parser/gsp/tags/gps9.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/gps9.test
rename to plugin/testdata/grails/parser/gsp/tags/gps9.test
diff --git a/testdata/grails/parser/gsp/tags/orph1.test b/plugin/testdata/grails/parser/gsp/tags/orph1.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/orph1.test
rename to plugin/testdata/grails/parser/gsp/tags/orph1.test
diff --git a/testdata/grails/parser/gsp/tags/tag3.test b/plugin/testdata/grails/parser/gsp/tags/tag3.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/tag3.test
rename to plugin/testdata/grails/parser/gsp/tags/tag3.test
diff --git a/testdata/grails/parser/gsp/tags/tagWithSlash.test b/plugin/testdata/grails/parser/gsp/tags/tagWithSlash.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/tagWithSlash.test
rename to plugin/testdata/grails/parser/gsp/tags/tagWithSlash.test
diff --git a/testdata/grails/parser/gsp/tags/uglyAttributeName.test b/plugin/testdata/grails/parser/gsp/tags/uglyAttributeName.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/uglyAttributeName.test
rename to plugin/testdata/grails/parser/gsp/tags/uglyAttributeName.test
diff --git a/testdata/grails/parser/gsp/tags/unendedAttrList.test b/plugin/testdata/grails/parser/gsp/tags/unendedAttrList.test
similarity index 100%
rename from testdata/grails/parser/gsp/tags/unendedAttrList.test
rename to plugin/testdata/grails/parser/gsp/tags/unendedAttrList.test
diff --git a/testdata/grails/parser/gspGroovy/comments/com.test b/plugin/testdata/grails/parser/gspGroovy/comments/com.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/comments/com.test
rename to plugin/testdata/grails/parser/gspGroovy/comments/com.test
diff --git a/testdata/grails/parser/gspGroovy/comments/comm1.test b/plugin/testdata/grails/parser/gspGroovy/comments/comm1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/comments/comm1.test
rename to plugin/testdata/grails/parser/gspGroovy/comments/comm1.test
diff --git a/testdata/grails/parser/gspGroovy/comments/commm2.test b/plugin/testdata/grails/parser/gspGroovy/comments/commm2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/comments/commm2.test
rename to plugin/testdata/grails/parser/gspGroovy/comments/commm2.test
diff --git a/testdata/grails/parser/gspGroovy/control/common.test b/plugin/testdata/grails/parser/gspGroovy/control/common.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/common.test
rename to plugin/testdata/grails/parser/gspGroovy/control/common.test
diff --git a/testdata/grails/parser/gspGroovy/control/for1.test b/plugin/testdata/grails/parser/gspGroovy/control/for1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/for1.test
rename to plugin/testdata/grails/parser/gspGroovy/control/for1.test
diff --git a/testdata/grails/parser/gspGroovy/control/for2.test b/plugin/testdata/grails/parser/gspGroovy/control/for2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/for2.test
rename to plugin/testdata/grails/parser/gspGroovy/control/for2.test
diff --git a/testdata/grails/parser/gspGroovy/control/foreach1.test b/plugin/testdata/grails/parser/gspGroovy/control/foreach1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/foreach1.test
rename to plugin/testdata/grails/parser/gspGroovy/control/foreach1.test
diff --git a/testdata/grails/parser/gspGroovy/control/if1.test b/plugin/testdata/grails/parser/gspGroovy/control/if1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/if1.test
rename to plugin/testdata/grails/parser/gspGroovy/control/if1.test
diff --git a/testdata/grails/parser/gspGroovy/control/if2.test b/plugin/testdata/grails/parser/gspGroovy/control/if2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/if2.test
rename to plugin/testdata/grails/parser/gspGroovy/control/if2.test
diff --git a/testdata/grails/parser/gspGroovy/control/swit1.test b/plugin/testdata/grails/parser/gspGroovy/control/swit1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/swit1.test
rename to plugin/testdata/grails/parser/gspGroovy/control/swit1.test
diff --git a/testdata/grails/parser/gspGroovy/control/swit2.test b/plugin/testdata/grails/parser/gspGroovy/control/swit2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/control/swit2.test
rename to plugin/testdata/grails/parser/gspGroovy/control/swit2.test
diff --git a/testdata/grails/parser/gspGroovy/custom/tag1.test b/plugin/testdata/grails/parser/gspGroovy/custom/tag1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/custom/tag1.test
rename to plugin/testdata/grails/parser/gspGroovy/custom/tag1.test
diff --git a/testdata/grails/parser/gspGroovy/custom/tag2.test b/plugin/testdata/grails/parser/gspGroovy/custom/tag2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/custom/tag2.test
rename to plugin/testdata/grails/parser/gspGroovy/custom/tag2.test
diff --git a/testdata/grails/parser/gspGroovy/custom/tag3.test b/plugin/testdata/grails/parser/gspGroovy/custom/tag3.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/custom/tag3.test
rename to plugin/testdata/grails/parser/gspGroovy/custom/tag3.test
diff --git a/testdata/grails/parser/gspGroovy/declarations/dec1.test b/plugin/testdata/grails/parser/gspGroovy/declarations/dec1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/declarations/dec1.test
rename to plugin/testdata/grails/parser/gspGroovy/declarations/dec1.test
diff --git a/testdata/grails/parser/gspGroovy/declarations/dec2.test b/plugin/testdata/grails/parser/gspGroovy/declarations/dec2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/declarations/dec2.test
rename to plugin/testdata/grails/parser/gspGroovy/declarations/dec2.test
diff --git a/testdata/grails/parser/gspGroovy/declarations/dec3.test b/plugin/testdata/grails/parser/gspGroovy/declarations/dec3.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/declarations/dec3.test
rename to plugin/testdata/grails/parser/gspGroovy/declarations/dec3.test
diff --git a/testdata/grails/parser/gspGroovy/declarations/dec4.test b/plugin/testdata/grails/parser/gspGroovy/declarations/dec4.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/declarations/dec4.test
rename to plugin/testdata/grails/parser/gspGroovy/declarations/dec4.test
diff --git a/testdata/grails/parser/gspGroovy/direct/dir1.test b/plugin/testdata/grails/parser/gspGroovy/direct/dir1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/direct/dir1.test
rename to plugin/testdata/grails/parser/gspGroovy/direct/dir1.test
diff --git a/testdata/grails/parser/gspGroovy/errors/err1.test b/plugin/testdata/grails/parser/gspGroovy/errors/err1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/errors/err1.test
rename to plugin/testdata/grails/parser/gspGroovy/errors/err1.test
diff --git a/testdata/grails/parser/gspGroovy/errors/err2.test b/plugin/testdata/grails/parser/gspGroovy/errors/err2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/errors/err2.test
rename to plugin/testdata/grails/parser/gspGroovy/errors/err2.test
diff --git a/testdata/grails/parser/gspGroovy/errors/err3.test b/plugin/testdata/grails/parser/gspGroovy/errors/err3.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/errors/err3.test
rename to plugin/testdata/grails/parser/gspGroovy/errors/err3.test
diff --git a/testdata/grails/parser/gspGroovy/errors/err4.test b/plugin/testdata/grails/parser/gspGroovy/errors/err4.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/errors/err4.test
rename to plugin/testdata/grails/parser/gspGroovy/errors/err4.test
diff --git a/testdata/grails/parser/gspGroovy/simple/clos1.test b/plugin/testdata/grails/parser/gspGroovy/simple/clos1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/simple/clos1.test
rename to plugin/testdata/grails/parser/gspGroovy/simple/clos1.test
diff --git a/testdata/grails/parser/gspGroovy/simple/inj1.test b/plugin/testdata/grails/parser/gspGroovy/simple/inj1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/simple/inj1.test
rename to plugin/testdata/grails/parser/gspGroovy/simple/inj1.test
diff --git a/testdata/grails/parser/gspGroovy/simple/inj2.test b/plugin/testdata/grails/parser/gspGroovy/simple/inj2.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/simple/inj2.test
rename to plugin/testdata/grails/parser/gspGroovy/simple/inj2.test
diff --git a/testdata/grails/parser/gspGroovy/simple/nl.test b/plugin/testdata/grails/parser/gspGroovy/simple/nl.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/simple/nl.test
rename to plugin/testdata/grails/parser/gspGroovy/simple/nl.test
diff --git a/testdata/grails/parser/gspGroovy/simple/stat1.test b/plugin/testdata/grails/parser/gspGroovy/simple/stat1.test
similarity index 100%
rename from testdata/grails/parser/gspGroovy/simple/stat1.test
rename to plugin/testdata/grails/parser/gspGroovy/simple/stat1.test
diff --git a/testdata/grails/parser/gspHtml/afterWhitespace.test b/plugin/testdata/grails/parser/gspHtml/afterWhitespace.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/afterWhitespace.test
rename to plugin/testdata/grails/parser/gspHtml/afterWhitespace.test
diff --git a/testdata/grails/parser/gspHtml/alone.test b/plugin/testdata/grails/parser/gspHtml/alone.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/alone.test
rename to plugin/testdata/grails/parser/gspHtml/alone.test
diff --git a/testdata/grails/parser/gspHtml/common.test b/plugin/testdata/grails/parser/gspHtml/common.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/common.test
rename to plugin/testdata/grails/parser/gspHtml/common.test
diff --git a/testdata/grails/parser/gspHtml/html1.test b/plugin/testdata/grails/parser/gspHtml/html1.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/html1.test
rename to plugin/testdata/grails/parser/gspHtml/html1.test
diff --git a/testdata/grails/parser/gspHtml/inject1.test b/plugin/testdata/grails/parser/gspHtml/inject1.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/inject1.test
rename to plugin/testdata/grails/parser/gspHtml/inject1.test
diff --git a/testdata/grails/parser/gspHtml/mmm2.test b/plugin/testdata/grails/parser/gspHtml/mmm2.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/mmm2.test
rename to plugin/testdata/grails/parser/gspHtml/mmm2.test
diff --git a/testdata/grails/parser/gspHtml/mmm5.test b/plugin/testdata/grails/parser/gspHtml/mmm5.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/mmm5.test
rename to plugin/testdata/grails/parser/gspHtml/mmm5.test
diff --git a/testdata/grails/parser/gspHtml/peter1.test b/plugin/testdata/grails/parser/gspHtml/peter1.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/peter1.test
rename to plugin/testdata/grails/parser/gspHtml/peter1.test
diff --git a/testdata/grails/parser/gspHtml/peter2.test b/plugin/testdata/grails/parser/gspHtml/peter2.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/peter2.test
rename to plugin/testdata/grails/parser/gspHtml/peter2.test
diff --git a/testdata/grails/parser/gspHtml/range_parse.test b/plugin/testdata/grails/parser/gspHtml/range_parse.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/range_parse.test
rename to plugin/testdata/grails/parser/gspHtml/range_parse.test
diff --git a/testdata/grails/parser/gspHtml/werle.test b/plugin/testdata/grails/parser/gspHtml/werle.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/werle.test
rename to plugin/testdata/grails/parser/gspHtml/werle.test
diff --git a/testdata/grails/parser/gspHtml/werle28.test b/plugin/testdata/grails/parser/gspHtml/werle28.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/werle28.test
rename to plugin/testdata/grails/parser/gspHtml/werle28.test
diff --git a/testdata/grails/parser/gspHtml/werle29.test b/plugin/testdata/grails/parser/gspHtml/werle29.test
similarity index 100%
rename from testdata/grails/parser/gspHtml/werle29.test
rename to plugin/testdata/grails/parser/gspHtml/werle29.test
diff --git a/testdata/grails/pluginList/plugins-list-grailsCore.xml b/plugin/testdata/grails/pluginList/plugins-list-grailsCore.xml
similarity index 100%
rename from testdata/grails/pluginList/plugins-list-grailsCore.xml
rename to plugin/testdata/grails/pluginList/plugins-list-grailsCore.xml
diff --git a/testdata/grails/resolve/ResolvePropertiesFromBelongsTo.groovy b/plugin/testdata/grails/resolve/ResolvePropertiesFromBelongsTo.groovy
similarity index 100%
rename from testdata/grails/resolve/ResolvePropertiesFromBelongsTo.groovy
rename to plugin/testdata/grails/resolve/ResolvePropertiesFromBelongsTo.groovy
diff --git a/testdata/grails/resolve/ResolvePropertiesFromBelongsTo2.groovy b/plugin/testdata/grails/resolve/ResolvePropertiesFromBelongsTo2.groovy
similarity index 100%
rename from testdata/grails/resolve/ResolvePropertiesFromBelongsTo2.groovy
rename to plugin/testdata/grails/resolve/ResolvePropertiesFromBelongsTo2.groovy
diff --git a/testdata/grails/resolve/ResolvePropertiesFromHasMany.groovy b/plugin/testdata/grails/resolve/ResolvePropertiesFromHasMany.groovy
similarity index 100%
rename from testdata/grails/resolve/ResolvePropertiesFromHasMany.groovy
rename to plugin/testdata/grails/resolve/ResolvePropertiesFromHasMany.groovy
diff --git a/testdata/grails/resolve/ResolvePropertyFromMappedBy.groovy b/plugin/testdata/grails/resolve/ResolvePropertyFromMappedBy.groovy
similarity index 100%
rename from testdata/grails/resolve/ResolvePropertyFromMappedBy.groovy
rename to plugin/testdata/grails/resolve/ResolvePropertyFromMappedBy.groovy
diff --git a/testdata/grails/resolve/StaticsOk.groovy b/plugin/testdata/grails/resolve/StaticsOk.groovy
similarity index 100%
rename from testdata/grails/resolve/StaticsOk.groovy
rename to plugin/testdata/grails/resolve/StaticsOk.groovy
diff --git a/testdata/grails/shiro/AccessControlBuilder.groovy b/plugin/testdata/grails/shiro/AccessControlBuilder.groovy
similarity index 100%
rename from testdata/grails/shiro/AccessControlBuilder.groovy
rename to plugin/testdata/grails/shiro/AccessControlBuilder.groovy
diff --git a/testdata/mockDomainDir/grails-app/domain/Product.groovy b/plugin/testdata/mockDomainDir/grails-app/domain/Product.groovy
similarity index 100%
rename from testdata/mockDomainDir/grails-app/domain/Product.groovy
rename to plugin/testdata/mockDomainDir/grails-app/domain/Product.groovy
diff --git a/testdata/mockDomainDir/grails-app/domain/aaa/Book.groovy b/plugin/testdata/mockDomainDir/grails-app/domain/aaa/Book.groovy
similarity index 100%
rename from testdata/mockDomainDir/grails-app/domain/aaa/Book.groovy
rename to plugin/testdata/mockDomainDir/grails-app/domain/aaa/Book.groovy
diff --git a/testdata/mockGrails11/conf/groovy-starter.conf b/plugin/testdata/mockGrails11/conf/groovy-starter.conf
similarity index 100%
rename from testdata/mockGrails11/conf/groovy-starter.conf
rename to plugin/testdata/mockGrails11/conf/groovy-starter.conf
diff --git a/testdata/mockGrails11/conf/webdefault.xml b/plugin/testdata/mockGrails11/conf/webdefault.xml
similarity index 100%
rename from testdata/mockGrails11/conf/webdefault.xml
rename to plugin/testdata/mockGrails11/conf/webdefault.xml
diff --git a/testdata/mockGrails11/dist/grails-bootstrap-1.1.jar b/plugin/testdata/mockGrails11/dist/grails-bootstrap-1.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-bootstrap-1.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-bootstrap-1.1.jar
diff --git a/testdata/mockGrails11/dist/grails-core-1.1.jar b/plugin/testdata/mockGrails11/dist/grails-core-1.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-core-1.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-core-1.1.jar
diff --git a/testdata/mockGrails11/dist/grails-core-1.3.1.jar b/plugin/testdata/mockGrails11/dist/grails-core-1.3.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-core-1.3.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-core-1.3.1.jar
diff --git a/testdata/mockGrails11/dist/grails-gorm-1.3.1.jar b/plugin/testdata/mockGrails11/dist/grails-gorm-1.3.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-gorm-1.3.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-gorm-1.3.1.jar
diff --git a/testdata/mockGrails11/dist/grails-plugin-url-mappings-2.0.4.jar b/plugin/testdata/mockGrails11/dist/grails-plugin-url-mappings-2.0.4.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-plugin-url-mappings-2.0.4.jar
rename to plugin/testdata/mockGrails11/dist/grails-plugin-url-mappings-2.0.4.jar
diff --git a/testdata/mockGrails11/dist/grails-scripts-1.3.7.jar b/plugin/testdata/mockGrails11/dist/grails-scripts-1.3.7.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-scripts-1.3.7.jar
rename to plugin/testdata/mockGrails11/dist/grails-scripts-1.3.7.jar
diff --git a/testdata/mockGrails11/dist/grails-spring-1.3.1.jar b/plugin/testdata/mockGrails11/dist/grails-spring-1.3.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-spring-1.3.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-spring-1.3.1.jar
diff --git a/testdata/mockGrails11/dist/grails-test-1.3.1.jar b/plugin/testdata/mockGrails11/dist/grails-test-1.3.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-test-1.3.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-test-1.3.1.jar
diff --git a/testdata/mockGrails11/dist/grails-web-1.3.1.jar b/plugin/testdata/mockGrails11/dist/grails-web-1.3.1.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-web-1.3.1.jar
rename to plugin/testdata/mockGrails11/dist/grails-web-1.3.1.jar
diff --git a/testdata/mockGrails11/dist/grails-webflow-1.3.7.jar b/plugin/testdata/mockGrails11/dist/grails-webflow-1.3.7.jar
similarity index 100%
rename from testdata/mockGrails11/dist/grails-webflow-1.3.7.jar
rename to plugin/testdata/mockGrails11/dist/grails-webflow-1.3.7.jar
diff --git a/testdata/mockGrails11/lib/groovy-all.jar b/plugin/testdata/mockGrails11/lib/groovy-all.jar
similarity index 100%
rename from testdata/mockGrails11/lib/groovy-all.jar
rename to plugin/testdata/mockGrails11/lib/groovy-all.jar
diff --git a/testdata/mockGrails11/lib/hibernate-core-3.3.1.GA.jar b/plugin/testdata/mockGrails11/lib/hibernate-core-3.3.1.GA.jar
similarity index 100%
rename from testdata/mockGrails11/lib/hibernate-core-3.3.1.GA.jar
rename to plugin/testdata/mockGrails11/lib/hibernate-core-3.3.1.GA.jar
diff --git a/testdata/mockGrails11/lib/junit-4.8.1.jar b/plugin/testdata/mockGrails11/lib/junit-4.8.1.jar
similarity index 100%
rename from testdata/mockGrails11/lib/junit-4.8.1.jar
rename to plugin/testdata/mockGrails11/lib/junit-4.8.1.jar
diff --git a/testdata/mockGrails11/lib/org.springframework.beans-3.0.3.RELEASE.jar b/plugin/testdata/mockGrails11/lib/org.springframework.beans-3.0.3.RELEASE.jar
similarity index 100%
rename from testdata/mockGrails11/lib/org.springframework.beans-3.0.3.RELEASE.jar
rename to plugin/testdata/mockGrails11/lib/org.springframework.beans-3.0.3.RELEASE.jar
diff --git a/testdata/mockGrails11/lib/org.springframework.context-3.0.3.RELEASE.jar b/plugin/testdata/mockGrails11/lib/org.springframework.context-3.0.3.RELEASE.jar
similarity index 100%
rename from testdata/mockGrails11/lib/org.springframework.context-3.0.3.RELEASE.jar
rename to plugin/testdata/mockGrails11/lib/org.springframework.context-3.0.3.RELEASE.jar
diff --git a/testdata/mockGrails11/lib/org.springframework.test-3.0.2.RELEASE.jar b/plugin/testdata/mockGrails11/lib/org.springframework.test-3.0.2.RELEASE.jar
similarity index 100%
rename from testdata/mockGrails11/lib/org.springframework.test-3.0.2.RELEASE.jar
rename to plugin/testdata/mockGrails11/lib/org.springframework.test-3.0.2.RELEASE.jar
diff --git a/testdata/mockGrails11/lib/servlet-api-2.5.jar b/plugin/testdata/mockGrails11/lib/servlet-api-2.5.jar
similarity index 100%
rename from testdata/mockGrails11/lib/servlet-api-2.5.jar
rename to plugin/testdata/mockGrails11/lib/servlet-api-2.5.jar
diff --git a/testdata/mockGrails11/lib/spock-grails-support-0.5-groovy-1.7.jar b/plugin/testdata/mockGrails11/lib/spock-grails-support-0.5-groovy-1.7.jar
similarity index 100%
rename from testdata/mockGrails11/lib/spock-grails-support-0.5-groovy-1.7.jar
rename to plugin/testdata/mockGrails11/lib/spock-grails-support-0.5-groovy-1.7.jar
diff --git a/testdata/mockGrails11/lib/spring-core-3.0.3.RELEASE.jar b/plugin/testdata/mockGrails11/lib/spring-core-3.0.3.RELEASE.jar
similarity index 100%
rename from testdata/mockGrails11/lib/spring-core-3.0.3.RELEASE.jar
rename to plugin/testdata/mockGrails11/lib/spring-core-3.0.3.RELEASE.jar
diff --git a/testdata/mockGrails11/scripts/Bootstrap.groovy b/plugin/testdata/mockGrails11/scripts/Bootstrap.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Bootstrap.groovy
rename to plugin/testdata/mockGrails11/scripts/Bootstrap.groovy
diff --git a/testdata/mockGrails11/scripts/BugReport.groovy b/plugin/testdata/mockGrails11/scripts/BugReport.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/BugReport.groovy
rename to plugin/testdata/mockGrails11/scripts/BugReport.groovy
diff --git a/testdata/mockGrails11/scripts/Clean.groovy b/plugin/testdata/mockGrails11/scripts/Clean.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Clean.groovy
rename to plugin/testdata/mockGrails11/scripts/Clean.groovy
diff --git a/testdata/mockGrails11/scripts/Compile.groovy b/plugin/testdata/mockGrails11/scripts/Compile.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Compile.groovy
rename to plugin/testdata/mockGrails11/scripts/Compile.groovy
diff --git a/testdata/mockGrails11/scripts/Console.groovy b/plugin/testdata/mockGrails11/scripts/Console.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Console.groovy
rename to plugin/testdata/mockGrails11/scripts/Console.groovy
diff --git a/testdata/mockGrails11/scripts/CreateApp_.groovy b/plugin/testdata/mockGrails11/scripts/CreateApp_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateApp_.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateApp_.groovy
diff --git a/testdata/mockGrails11/scripts/CreateController.groovy b/plugin/testdata/mockGrails11/scripts/CreateController.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateController.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateController.groovy
diff --git a/testdata/mockGrails11/scripts/CreateDomainClass.groovy b/plugin/testdata/mockGrails11/scripts/CreateDomainClass.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateDomainClass.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateDomainClass.groovy
diff --git a/testdata/mockGrails11/scripts/CreateFilters.groovy b/plugin/testdata/mockGrails11/scripts/CreateFilters.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateFilters.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateFilters.groovy
diff --git a/testdata/mockGrails11/scripts/CreateIntegrationTest.groovy b/plugin/testdata/mockGrails11/scripts/CreateIntegrationTest.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateIntegrationTest.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateIntegrationTest.groovy
diff --git a/testdata/mockGrails11/scripts/CreatePlugin_.groovy b/plugin/testdata/mockGrails11/scripts/CreatePlugin_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreatePlugin_.groovy
rename to plugin/testdata/mockGrails11/scripts/CreatePlugin_.groovy
diff --git a/testdata/mockGrails11/scripts/CreateScript.groovy b/plugin/testdata/mockGrails11/scripts/CreateScript.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateScript.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateScript.groovy
diff --git a/testdata/mockGrails11/scripts/CreateService.groovy b/plugin/testdata/mockGrails11/scripts/CreateService.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateService.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateService.groovy
diff --git a/testdata/mockGrails11/scripts/CreateTagLib.groovy b/plugin/testdata/mockGrails11/scripts/CreateTagLib.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateTagLib.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateTagLib.groovy
diff --git a/testdata/mockGrails11/scripts/CreateUnitTest.groovy b/plugin/testdata/mockGrails11/scripts/CreateUnitTest.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/CreateUnitTest.groovy
rename to plugin/testdata/mockGrails11/scripts/CreateUnitTest.groovy
diff --git a/testdata/mockGrails11/scripts/Doc.groovy b/plugin/testdata/mockGrails11/scripts/Doc.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Doc.groovy
rename to plugin/testdata/mockGrails11/scripts/Doc.groovy
diff --git a/testdata/mockGrails11/scripts/GenerateAll.groovy b/plugin/testdata/mockGrails11/scripts/GenerateAll.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/GenerateAll.groovy
rename to plugin/testdata/mockGrails11/scripts/GenerateAll.groovy
diff --git a/testdata/mockGrails11/scripts/GenerateController.groovy b/plugin/testdata/mockGrails11/scripts/GenerateController.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/GenerateController.groovy
rename to plugin/testdata/mockGrails11/scripts/GenerateController.groovy
diff --git a/testdata/mockGrails11/scripts/GenerateViews.groovy b/plugin/testdata/mockGrails11/scripts/GenerateViews.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/GenerateViews.groovy
rename to plugin/testdata/mockGrails11/scripts/GenerateViews.groovy
diff --git a/testdata/mockGrails11/scripts/Help_.groovy b/plugin/testdata/mockGrails11/scripts/Help_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Help_.groovy
rename to plugin/testdata/mockGrails11/scripts/Help_.groovy
diff --git a/testdata/mockGrails11/scripts/Init.groovy b/plugin/testdata/mockGrails11/scripts/Init.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Init.groovy
rename to plugin/testdata/mockGrails11/scripts/Init.groovy
diff --git a/testdata/mockGrails11/scripts/InstallPlugin.groovy b/plugin/testdata/mockGrails11/scripts/InstallPlugin.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/InstallPlugin.groovy
rename to plugin/testdata/mockGrails11/scripts/InstallPlugin.groovy
diff --git a/testdata/mockGrails11/scripts/InstallTemplates.groovy b/plugin/testdata/mockGrails11/scripts/InstallTemplates.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/InstallTemplates.groovy
rename to plugin/testdata/mockGrails11/scripts/InstallTemplates.groovy
diff --git a/testdata/mockGrails11/scripts/ListPlugins_.groovy b/plugin/testdata/mockGrails11/scripts/ListPlugins_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/ListPlugins_.groovy
rename to plugin/testdata/mockGrails11/scripts/ListPlugins_.groovy
diff --git a/testdata/mockGrails11/scripts/Package.groovy b/plugin/testdata/mockGrails11/scripts/Package.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Package.groovy
rename to plugin/testdata/mockGrails11/scripts/Package.groovy
diff --git a/testdata/mockGrails11/scripts/PackagePlugin_.groovy b/plugin/testdata/mockGrails11/scripts/PackagePlugin_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/PackagePlugin_.groovy
rename to plugin/testdata/mockGrails11/scripts/PackagePlugin_.groovy
diff --git a/testdata/mockGrails11/scripts/PluginInfo_.groovy b/plugin/testdata/mockGrails11/scripts/PluginInfo_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/PluginInfo_.groovy
rename to plugin/testdata/mockGrails11/scripts/PluginInfo_.groovy
diff --git a/testdata/mockGrails11/scripts/ReleasePlugin.groovy b/plugin/testdata/mockGrails11/scripts/ReleasePlugin.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/ReleasePlugin.groovy
rename to plugin/testdata/mockGrails11/scripts/ReleasePlugin.groovy
diff --git a/testdata/mockGrails11/scripts/RunApp.groovy b/plugin/testdata/mockGrails11/scripts/RunApp.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/RunApp.groovy
rename to plugin/testdata/mockGrails11/scripts/RunApp.groovy
diff --git a/testdata/mockGrails11/scripts/RunWar.groovy b/plugin/testdata/mockGrails11/scripts/RunWar.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/RunWar.groovy
rename to plugin/testdata/mockGrails11/scripts/RunWar.groovy
diff --git a/testdata/mockGrails11/scripts/SchemaExport.groovy b/plugin/testdata/mockGrails11/scripts/SchemaExport.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/SchemaExport.groovy
rename to plugin/testdata/mockGrails11/scripts/SchemaExport.groovy
diff --git a/testdata/mockGrails11/scripts/SetProxy_.groovy b/plugin/testdata/mockGrails11/scripts/SetProxy_.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/SetProxy_.groovy
rename to plugin/testdata/mockGrails11/scripts/SetProxy_.groovy
diff --git a/testdata/mockGrails11/scripts/SetVersion.groovy b/plugin/testdata/mockGrails11/scripts/SetVersion.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/SetVersion.groovy
rename to plugin/testdata/mockGrails11/scripts/SetVersion.groovy
diff --git a/testdata/mockGrails11/scripts/Shell.groovy b/plugin/testdata/mockGrails11/scripts/Shell.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Shell.groovy
rename to plugin/testdata/mockGrails11/scripts/Shell.groovy
diff --git a/testdata/mockGrails11/scripts/Stats.groovy b/plugin/testdata/mockGrails11/scripts/Stats.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Stats.groovy
rename to plugin/testdata/mockGrails11/scripts/Stats.groovy
diff --git a/testdata/mockGrails11/scripts/TestApp.groovy b/plugin/testdata/mockGrails11/scripts/TestApp.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/TestApp.groovy
rename to plugin/testdata/mockGrails11/scripts/TestApp.groovy
diff --git a/testdata/mockGrails11/scripts/UninstallPlugin.groovy b/plugin/testdata/mockGrails11/scripts/UninstallPlugin.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/UninstallPlugin.groovy
rename to plugin/testdata/mockGrails11/scripts/UninstallPlugin.groovy
diff --git a/testdata/mockGrails11/scripts/Upgrade.groovy b/plugin/testdata/mockGrails11/scripts/Upgrade.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/Upgrade.groovy
rename to plugin/testdata/mockGrails11/scripts/Upgrade.groovy
diff --git a/testdata/mockGrails11/scripts/War.groovy b/plugin/testdata/mockGrails11/scripts/War.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/War.groovy
rename to plugin/testdata/mockGrails11/scripts/War.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsArgParsing.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsArgParsing.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsArgParsing.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsArgParsing.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsBootstrap.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsBootstrap.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsBootstrap.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsBootstrap.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsClasspath.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsClasspath.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsClasspath.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsClasspath.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsClean.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsClean.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsClean.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsClean.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsCompile.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsCompile.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsCompile.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsCompile.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsCreateArtifacts.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsCreateArtifacts.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsCreateArtifacts.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsCreateArtifacts.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsCreateProject.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsCreateProject.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsCreateProject.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsCreateProject.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsDocs.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsDocs.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsDocs.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsDocs.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsEvents.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsEvents.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsEvents.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsEvents.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsGenerate.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsGenerate.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsGenerate.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsGenerate.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsInit.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsInit.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsInit.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsInit.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsPackage.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsPackage.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsPackage.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsPackage.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsPluginDev.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsPluginDev.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsPluginDev.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsPluginDev.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsPlugins.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsPlugins.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsPlugins.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsPlugins.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsProxy.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsProxy.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsProxy.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsProxy.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsRun.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsRun.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsRun.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsRun.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsSettings.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsSettings.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsSettings.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsSettings.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsTest.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsTest.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsTest.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsTest.groovy
diff --git a/testdata/mockGrails11/scripts/_GrailsWar.groovy b/plugin/testdata/mockGrails11/scripts/_GrailsWar.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_GrailsWar.groovy
rename to plugin/testdata/mockGrails11/scripts/_GrailsWar.groovy
diff --git a/testdata/mockGrails11/scripts/_PackagePlugins.groovy b/plugin/testdata/mockGrails11/scripts/_PackagePlugins.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_PackagePlugins.groovy
rename to plugin/testdata/mockGrails11/scripts/_PackagePlugins.groovy
diff --git a/testdata/mockGrails11/scripts/_PluginDependencies.groovy b/plugin/testdata/mockGrails11/scripts/_PluginDependencies.groovy
similarity index 100%
rename from testdata/mockGrails11/scripts/_PluginDependencies.groovy
rename to plugin/testdata/mockGrails11/scripts/_PluginDependencies.groovy
diff --git a/testdata/mockGrails11/scripts/log4j.properties b/plugin/testdata/mockGrails11/scripts/log4j.properties
similarity index 100%
rename from testdata/mockGrails11/scripts/log4j.properties
rename to plugin/testdata/mockGrails11/scripts/log4j.properties
diff --git a/testdata/mockGrails14/grails-bootstrap-2.0.0.M2.jar b/plugin/testdata/mockGrails14/grails-bootstrap-2.0.0.M2.jar
similarity index 100%
rename from testdata/mockGrails14/grails-bootstrap-2.0.0.M2.jar
rename to plugin/testdata/mockGrails14/grails-bootstrap-2.0.0.M2.jar
diff --git a/testdata/mockGrails14/grails-core-2.0.0.M1.jar b/plugin/testdata/mockGrails14/grails-core-2.0.0.M1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-core-2.0.0.M1.jar
rename to plugin/testdata/mockGrails14/grails-core-2.0.0.M1.jar
diff --git a/testdata/mockGrails14/grails-datastore-gorm-1.0.0.RC1.jar b/plugin/testdata/mockGrails14/grails-datastore-gorm-1.0.0.RC1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-datastore-gorm-1.0.0.RC1.jar
rename to plugin/testdata/mockGrails14/grails-datastore-gorm-1.0.0.RC1.jar
diff --git a/testdata/mockGrails14/grails-hibernate-2.0.0.M1.jar b/plugin/testdata/mockGrails14/grails-hibernate-2.0.0.M1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-hibernate-2.0.0.M1.jar
rename to plugin/testdata/mockGrails14/grails-hibernate-2.0.0.M1.jar
diff --git a/testdata/mockGrails14/grails-plugin-controllers-1.4.0.M1.jar b/plugin/testdata/mockGrails14/grails-plugin-controllers-1.4.0.M1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-controllers-1.4.0.M1.jar
rename to plugin/testdata/mockGrails14/grails-plugin-controllers-1.4.0.M1.jar
diff --git a/testdata/mockGrails14/grails-plugin-converters-2.2.1.jar b/plugin/testdata/mockGrails14/grails-plugin-converters-2.2.1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-converters-2.2.1.jar
rename to plugin/testdata/mockGrails14/grails-plugin-converters-2.2.1.jar
diff --git a/testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1-sources.jar b/plugin/testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1-sources.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1-sources.jar
rename to plugin/testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1-sources.jar
diff --git a/testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar b/plugin/testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar
rename to plugin/testdata/mockGrails14/grails-plugin-gsp-2.0.0.RC1.jar
diff --git a/testdata/mockGrails14/grails-plugin-mimetypes-1.4.0.M1.jar b/plugin/testdata/mockGrails14/grails-plugin-mimetypes-1.4.0.M1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-mimetypes-1.4.0.M1.jar
rename to plugin/testdata/mockGrails14/grails-plugin-mimetypes-1.4.0.M1.jar
diff --git a/testdata/mockGrails14/grails-plugin-rest-2.3.0.jar b/plugin/testdata/mockGrails14/grails-plugin-rest-2.3.0.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-rest-2.3.0.jar
rename to plugin/testdata/mockGrails14/grails-plugin-rest-2.3.0.jar
diff --git a/testdata/mockGrails14/grails-plugin-servlets-2.2.1.jar b/plugin/testdata/mockGrails14/grails-plugin-servlets-2.2.1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-servlets-2.2.1.jar
rename to plugin/testdata/mockGrails14/grails-plugin-servlets-2.2.1.jar
diff --git a/testdata/mockGrails14/grails-plugin-testing-1.4.0.M1.jar b/plugin/testdata/mockGrails14/grails-plugin-testing-1.4.0.M1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-plugin-testing-1.4.0.M1.jar
rename to plugin/testdata/mockGrails14/grails-plugin-testing-1.4.0.M1.jar
diff --git a/testdata/mockGrails14/grails-spring-2.2.3.jar b/plugin/testdata/mockGrails14/grails-spring-2.2.3.jar
similarity index 100%
rename from testdata/mockGrails14/grails-spring-2.2.3.jar
rename to plugin/testdata/mockGrails14/grails-spring-2.2.3.jar
diff --git a/testdata/mockGrails14/grails-test-2.0.0.M2.jar b/plugin/testdata/mockGrails14/grails-test-2.0.0.M2.jar
similarity index 100%
rename from testdata/mockGrails14/grails-test-2.0.0.M2.jar
rename to plugin/testdata/mockGrails14/grails-test-2.0.0.M2.jar
diff --git a/testdata/mockGrails14/grails-web-2.0.0.M1.jar b/plugin/testdata/mockGrails14/grails-web-2.0.0.M1.jar
similarity index 100%
rename from testdata/mockGrails14/grails-web-2.0.0.M1.jar
rename to plugin/testdata/mockGrails14/grails-web-2.0.0.M1.jar
diff --git a/testdata/mockGrails14/spring-test-3.1.0.M2.jar b/plugin/testdata/mockGrails14/spring-test-3.1.0.M2.jar
similarity index 100%
rename from testdata/mockGrails14/spring-test-3.1.0.M2.jar
rename to plugin/testdata/mockGrails14/spring-test-3.1.0.M2.jar
diff --git a/testdata/mockTagLib/grails-app/taglib/MyTagLib.groovy b/plugin/testdata/mockTagLib/grails-app/taglib/MyTagLib.groovy
similarity index 100%
rename from testdata/mockTagLib/grails-app/taglib/MyTagLib.groovy
rename to plugin/testdata/mockTagLib/grails-app/taglib/MyTagLib.groovy
diff --git a/testdata/mockTagLib/grails-app/taglib/NonStaticTagLib.groovy b/plugin/testdata/mockTagLib/grails-app/taglib/NonStaticTagLib.groovy
similarity index 100%
rename from testdata/mockTagLib/grails-app/taglib/NonStaticTagLib.groovy
rename to plugin/testdata/mockTagLib/grails-app/taglib/NonStaticTagLib.groovy
diff --git a/testdata/mockTagLib/grails-app/taglib/UnnamedTagLib.groovy b/plugin/testdata/mockTagLib/grails-app/taglib/UnnamedTagLib.groovy
similarity index 100%
rename from testdata/mockTagLib/grails-app/taglib/UnnamedTagLib.groovy
rename to plugin/testdata/mockTagLib/grails-app/taglib/UnnamedTagLib.groovy
diff --git a/testdata/mockTagLib/grails-app/taglib/apackage/PppTagLib.groovy b/plugin/testdata/mockTagLib/grails-app/taglib/apackage/PppTagLib.groovy
similarity index 100%
rename from testdata/mockTagLib/grails-app/taglib/apackage/PppTagLib.groovy
rename to plugin/testdata/mockTagLib/grails-app/taglib/apackage/PppTagLib.groovy
diff --git a/pluginModules/copyright/build.gradle b/pluginModules/copyright/build.gradle
index f2ddad7..e98600d 100644
--- a/pluginModules/copyright/build.gradle
+++ b/pluginModules/copyright/build.gradle
@@ -24,5 +24,5 @@ dependencies {
         bundledPlugin 'com.intellij.copyright'
     }
 
-    compileOnly project(':')
+    compileOnly project(':plugin')
 }
diff --git a/pluginModules/coverage/build.gradle b/pluginModules/coverage/build.gradle
index b1782ca..9339856 100644
--- a/pluginModules/coverage/build.gradle
+++ b/pluginModules/coverage/build.gradle
@@ -23,5 +23,5 @@ dependencies {
         bundledPlugin 'Coverage'
     }
 
-    compileOnly project(':')
+    compileOnly project(':plugin')
 }
diff --git a/pluginModules/hibernate/build.gradle b/pluginModules/hibernate/build.gradle
index e3f3956..9b48ed3 100644
--- a/pluginModules/hibernate/build.gradle
+++ b/pluginModules/hibernate/build.gradle
@@ -22,5 +22,5 @@ dependencies {
         bundledPlugin 'com.intellij.hibernate'
     }
 
-    compileOnly project(':')
+    compileOnly project(':plugin')
 }
diff --git a/pluginModules/i18n/build.gradle b/pluginModules/i18n/build.gradle
index 01868ba..bd92b79 100644
--- a/pluginModules/i18n/build.gradle
+++ b/pluginModules/i18n/build.gradle
@@ -23,5 +23,5 @@ dependencies {
         bundledPlugin 'com.intellij.java-i18n'
     }
 
-    compileOnly project(':')
+    compileOnly project(':plugin')
 }
diff --git a/pluginModules/maven/build.gradle b/pluginModules/maven/build.gradle
index 02d7032..7ab37e9 100644
--- a/pluginModules/maven/build.gradle
+++ b/pluginModules/maven/build.gradle
@@ -26,5 +26,5 @@ dependencies {
     }
 
     compileOnly project(':libs-grails-rt')
-    compileOnly project(':')
+    compileOnly project(':plugin')
 }
diff --git a/settings.gradle b/settings.gradle
index 9bc9b90..e8bb747 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,6 +19,8 @@ pluginManagement {
 
 rootProject.name = 'grails-intellij-plugin'
 
+include 'plugin'
+
 // Project names carry their subpath so every project path is unique and readable, and so
 // IPGP's path-derived defaults never see a nested path. Directories are remapped below.
 ['copyright', 'coverage', 'hibernate', 'i18n', 'langInjection', 'maven'].each { name ->


(grails-intellij-plugin) 31/35: Update the build-hygiene tooling and docs for the restructure

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit c315ab149a4cd1f99185229e5af119e86e402679
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 15:40:58 2026 -0400

    Update the build-hygiene tooling and docs for the restructure
    
    The build-hygiene work predates the composed-build restructure, so several of
    the paths it documents and inspects had gone stale. Audited every file those
    commits introduced.
    
    Docs, all verified against the tree afterwards:
    
      - AGENTS.md's "Project Structure" described the pre-restructure layout: the
        root project as the plugin, non-standard source dirs, and
        compatibilityResources/, which no longer exists anywhere else in the repo.
        Replaced with the tier directories, their Gradle project names, and the
        standard source layout, including why testdata/ sits next to the tests.
      - The plugin ZIP path in README.md and INSTALL, and the test-results path in
        AGENTS.md, are under plugin/ now. RAT and coverage aggregation genuinely do
        stay on the root project, so those paths are left alone.
      - RAT excludes, the reproducible convention plugin, and the bundledPlugin
        list moved out of build.gradle.kts into build-logic and plugin/build.gradle.
    
    etc/bin/verify-distributions.sh picked the composed plugin jar with
    `grep '<dir>/lib/*.jar' | head -n 1`. That resolves correctly today only
    because "apache-grails-intellij-plugin-262.0.0.jar" happens to sort before the
    renamed "org.apache.grails.intellij.*" lib jars beside it -- an accident of
    naming for a check that asserts LICENSE/NOTICE for ASF policy. It now matches
    <plugin-dir>/lib/<plugin-dir>-<version>.jar and fails unless exactly one
    matches; tested against the built ZIP and against a ZIP with no composed jar.
    Written without mapfile, since these scripts declare #!/usr/bin/env bash and a
    release manager on stock macOS gets bash 3.2.
    
    Checked and found already correct: the coverage.yml and rat.yml report paths
    (both root-owned, confirmed by running the tasks), gradle.yml and release.yml
    (already repathed to plugin/build), ossIndexAudit (resolves across all renamed
    projects), dependabot, codecov, .asf.yaml, .gitattributes and gradle.properties.
    
    MIGRATION-PLAN.md is left untouched: its "Current state (surveyed 2026-07-06)"
    section is a dated snapshot of the import, and rewriting it would destroy the
    record of where this started.
---
 AGENTS.md                       | 52 ++++++++++++++++++++++++-----------------
 INSTALL                         |  2 +-
 README.md                       |  2 +-
 RELEASE.md                      | 12 ++++++----
 etc/bin/verify-distributions.sh | 20 ++++++++++++----
 5 files changed, 55 insertions(+), 33 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 3a7c44f..b9b2bbd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -46,12 +46,12 @@ limitations under the License.
    fails during `./gradlew test`, Gradle silently runs the **stale previous bytecode**.
    Always grep the output for `error:` before believing pass/fail results or probe output.
 2. **Collect failure lists from a full run only.** A `--tests`-filtered run **wipes**
-   `build/test-results/test/`, destroying the results of the previous full run.
+   `plugin/build/test-results/test/`, destroying the results of the previous full run.
 3. **Every code change must include tests.** Review the existing tests for the affected
    area before making changes, and keep every test that covers a modified class in sync
    with the new behavior. Run all affected tests and ensure they pass before committing.
 4. **Apache license header required** on all new source files (enforced by `./gradlew rat`).
-   Every RAT exclude in `build.gradle.kts` must carry an inline justification.
+   Every RAT exclude in the `rat` convention plugin must carry an inline justification.
    - **New files use the ASF header** (verbatim from the `HEADER` file: *"Licensed to the
      Apache Software Foundation (ASF) under one or more contributor license agreements…"*),
      rendered as a `/* … */` block for Java/Kotlin or `<!-- … -->` for XML. **Do NOT** copy
@@ -91,26 +91,35 @@ limitations under the License.
 
 ## Project Structure
 
-The root project is the plugin itself; sources live in non-standard directories
-(`src/`, `gen/` for generated JFlex lexers, `test/`, `resources/`,
-`compatibilityResources/`, `testdata/`).
-
-| Module | Description |
-|--------|-------------|
-| **root** | Main plugin: GSP language, Grails project support, run configs |
-| `copyright`, `coverage`, `hibernate`, `i18n`, `langInjection`, `maven` | Optional plugin modules (`pluginModule` deps) |
-| `gradle-tooling` | Gradle tooling API model builders |
-| `grails-rt` | Runtime injected into user apps (Java 8) |
-| `grails-compiler-patch`, `jps-plugin` | JPS build integration |
-| `testFramework` | Shared test infrastructure (`GrailsTestCase`, `GroovyProjectDescriptors`, `TestLibrary`) |
-
-Special packaging: `resources/standardDsls/` is excluded from normal resources and copied
-into `<plugin>/lib/standardDsls/` by a `PrepareSandboxTask` customization.
+A composed build: `build-logic` is an included build holding every convention plugin, and the
+subprojects sit in tier directories. Project names carry the subpath, so
+`pluginModules/hibernate` is the Gradle project `:pluginModules-hibernate`. The root project
+is a pure aggregator — it owns only RAT and coverage aggregation, no sources.
+
+| Path | Gradle project | Description |
+|------|----------------|-------------|
+| `plugin/` | `:plugin` | Main plugin: GSP language, Grails project support, run configs |
+| `pluginModules/{copyright,coverage,hibernate,i18n,langInjection,maven}/` | `:pluginModules-*` | Optional IntelliJ content modules (`pluginModule` deps) |
+| `libs/gradle-tooling/` | `:libs-gradle-tooling` | Gradle tooling API model builders |
+| `libs/grails-rt/` | `:libs-grails-rt` | Runtime injected into user apps (Java 8) |
+| `libs/testFramework/` | `:libs-testFramework` | Shared test infrastructure (`GrailsTestCase`, `GroovyProjectDescriptors`, `TestLibrary`) |
+| `compilers/{grails-compiler-patch,jps-plugin}/` | `:compilers-*` | JPS build integration |
+| `build-logic/` | included build | Convention plugins, ids `org.apache.grails.intellij.build.*` |
+
+`plugin/` uses the standard source layout: `src/main/java` (Java, plus the few remaining
+Kotlin files), `src/main/gen` for generated JFlex lexers, `src/main/resources`, and
+`src/test/java`. `plugin/testdata/` sits next to the tests because Gradle sets
+`Test.workingDir` to the project directory, which is how `GrailsTestUtil.getTestRootPath`
+resolves it.
+
+Special packaging: `plugin/standardDsls/` sits outside the resource roots and is copied to
+`<plugin>/lib/standardDsls/` as loose files by a `PrepareSandboxTask` customization in the
+`intellij-plugin` convention plugin.
 
 ## Running & Debugging Tests
 
-- Full suite: `./gradlew test` — ~3.5 min, ~970 tests.
-- Failure details live in `build/test-results/test/TEST-<fqcn>.xml`; the `<system-out>`
+- Full suite: `./gradlew test` — ~4 min, 987 tests across 186 classes.
+- Failure details live in `plugin/build/test-results/test/TEST-<fqcn>.xml`; the `<system-out>`
   CDATA holds logged output. The giant module-list line and
   `InstanceNotOverridable`/SLF4J warnings are noise — ignore them.
 - **Probe technique** that works well here: add `System.out.println("### TAG ...")`
@@ -129,7 +138,8 @@ into `<plugin>/lib/standardDsls/` by a `PrepareSandboxTask` customization.
 - Bundled-plugin dependencies are sensitive to platform version splits — e.g.
   `com.intellij.javaee.el` is no longer transitive and `com.intellij.gradle` was split
   out of `org.jetbrains.plugins.gradle` in 2026.2. When bumping `platformVersion`,
-  expect to adjust the `bundledPlugin(...)` list in `build.gradle.kts`.
+  expect to adjust the `bundledPlugin(...)` list in `plugin/build.gradle` (and in the
+  `pluginModules/*/build.gradle` that declares the affected plugin).
 - Plugin Verifier gates on real incompatibilities only (`COMPATIBILITY_PROBLEMS`,
   `MISSING_DEPENDENCIES`, `INVALID_PLUGIN`); the inherited internal/deprecated API
   usages are a tracked cleanup item, not a release blocker.
@@ -152,7 +162,7 @@ into `<plugin>/lib/standardDsls/` by a `PrepareSandboxTask` customization.
 | Problem | Solution |
 |---------|----------|
 | Test results look wrong / probes silent | Grep build output for `error:` — you may be running stale bytecode |
-| Lost the failure list | Re-run the **full** `./gradlew test`; filtered runs wipe `build/test-results/test/` |
+| Lost the failure list | Re-run the **full** `./gradlew test`; filtered runs wipe `plugin/build/test-results/test/` |
 | `getMockJdk11()` NPE in `tuneFixture` | Heavy fixture — use `moduleBuilder.addJdk(System.getProperty("java.home"))` |
 | "Cannot find IntelliJ IDEA project files" in light tests | Descriptor uses `RepositoryTestLibrary`; switch to `GroovyProjectDescriptors.MOCK_JDK_11` |
 | Test needs Swing/AWT classes | Override `getTestJdk()` in the test to return a real JDK |
diff --git a/INSTALL b/INSTALL
index c7af0f9..fd08079 100644
--- a/INSTALL
+++ b/INSTALL
@@ -66,7 +66,7 @@ Build the plugin distribution ZIP:
 
         ./gradlew buildPlugin
 
-The ZIP is written to `build/distributions/` and can be installed in IntelliJ
+The ZIP is written to `plugin/build/distributions/` and can be installed in IntelliJ
 IDEA via Settings > Plugins > Install Plugin from Disk.
 
 Note: the first build downloads the IntelliJ Platform (IntelliJ IDEA Ultimate)
diff --git a/README.md b/README.md
index 623dc92..ae0b5a4 100644
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@ to run the `wrapper` task at the version named in `.sdkmanrc` and copy the resul
 ./gradlew buildPlugin
 ```
 
-The plugin ZIP is written to `build/distributions/` and can be installed in IntelliJ IDEA
+The plugin ZIP is written to `plugin/build/distributions/` and can be installed in IntelliJ IDEA
 via **Settings > Plugins > ⚙ > Install Plugin from Disk**.
 
 > The first build downloads the IntelliJ Platform (IntelliJ IDEA Ultimate) and the bundled
diff --git a/RELEASE.md b/RELEASE.md
index c18a513..49509b3 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -86,8 +86,9 @@ The version lives in `gradle.properties` and is overridden per release with
   CI builds match. See [INSTALL](INSTALL) for local build setup.
 - No planning documents that are excluded from the license audit
   (`MIGRATION-PLAN.md`, `IMPROVEMENT-PLAN.md`) should still be present for a final
-  release; they are listed as RAT excludes in [`build.gradle.kts`](build.gradle.kts) and
-  are meant to be removed before the first release.
+  release; they are listed as RAT excludes in
+  [`org.apache.grails.intellij.build.rat.gradle`](build-logic/src/main/groovy/org.apache.grails.intellij.build.rat.gradle)
+  and are meant to be removed before the first release.
 
 ## 2. Cut the release
 
@@ -307,8 +308,9 @@ so aborting before that point leaves no public trace.
 
 ## Verifying a Reproducible Build
 
-Reproducibility is enforced by the `grails-intellij.reproducible` convention plugin in
-[`build-logic`](build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts),
+Reproducibility is enforced by the `org.apache.grails.intellij.build.reproducible` convention
+plugin in
+[`build-logic`](build-logic/src/main/groovy/org.apache.grails.intellij.build.reproducible.gradle),
 applied to the root project and every module. It pins archive entry timestamps
 (`preserveFileTimestamps = false`), file order (`reproducibleFileOrder = true`), and entry
 permissions, which removes the three usual sources of archive non-determinism.
@@ -426,7 +428,7 @@ The tooling that supports this:
 
 | Path | Role |
 | --- | --- |
-| `build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts` | Pins archive timestamps, file order, and permissions across every module. |
+| `build-logic/src/main/groovy/org.apache.grails.intellij.build.reproducible.gradle` | Pins archive timestamps, file order, and permissions across every module. |
 | `etc/bin/test-reproducible-build.sh` | Builds twice on one machine and compares artifact hashes. |
 | `etc/bin/verify-reproducible.sh` | Rebuilds from the staged source distribution and compares to the staged binary. |
 | `etc/bin/Dockerfile` | Linux environment matching CI, for ruling out host-OS differences. |
diff --git a/etc/bin/verify-distributions.sh b/etc/bin/verify-distributions.sh
index a85135c..6bb6d93 100755
--- a/etc/bin/verify-distributions.sh
+++ b/etc/bin/verify-distributions.sh
@@ -143,13 +143,23 @@ echo "✅ source distribution contents verified"
 verify_archive "${BIN_ZIP}"
 echo "==> Checking binary distribution contents"
 # ASF policy: the convenience binary must carry LICENSE and NOTICE. The build puts them in
-# META-INF of the composed plugin jar (see processResources in build.gradle.kts), so check
-# inside that jar rather than at the top level of the ZIP.
-PLUGIN_JAR="$(unzip -Z1 "${BIN_ZIP}" | grep -E '^[^/]+/lib/[^/]+\.jar$' | head -n 1)"
-if [ -z "${PLUGIN_JAR}" ]; then
-  echo "❌ binary distribution: no plugin jar found under <plugin>/lib/ in ${BIN_ZIP}" >&2
+# META-INF of the composed plugin jar (see processResources in the intellij-plugin convention
+# plugin), so check inside that jar rather than at the top level of the ZIP.
+#
+# Identify that jar by name rather than by position: <plugin-dir>/lib/<plugin-dir>-<version>.jar.
+# lib/ also holds the compiler and lib-tier jars, and picking the first entry would silently
+# start inspecting one of those if the naming ever sorted differently.
+PLUGIN_DIR="$(unzip -Z1 "${BIN_ZIP}" | sed -n 's|^\([^/]*\)/.*|\1|p' | sort -u | head -n 1)"
+PLUGIN_JAR_MATCHES="$(unzip -Z1 "${BIN_ZIP}" |
+  grep -E "^${PLUGIN_DIR}/lib/${PLUGIN_DIR}-[^/]+\.jar$" || true)"
+PLUGIN_JAR_COUNT="$(printf '%s' "${PLUGIN_JAR_MATCHES}" | grep -c . || true)"
+if [ "${PLUGIN_JAR_COUNT}" -ne 1 ]; then
+  echo "❌ binary distribution: expected exactly 1 composed plugin jar at" \
+       "${PLUGIN_DIR}/lib/${PLUGIN_DIR}-<version>.jar in ${BIN_ZIP}," \
+       "found ${PLUGIN_JAR_COUNT}: ${PLUGIN_JAR_MATCHES}" >&2
   exit 1
 fi
+PLUGIN_JAR="${PLUGIN_JAR_MATCHES}"
 JAR_TMP="$(mktemp -d)"
 trap 'rm -rf "${GPG_HOME}" "${JAR_TMP}"' EXIT
 unzip -q -o -j "${BIN_ZIP}" "${PLUGIN_JAR}" -d "${JAR_TMP}"


(grails-intellij-plugin) 18/35: Move the six content modules into pluginModules/ with standard source layout

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 33967babe3a45134725e838c2821d7b6b60d6a45
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 11:54:28 2026 -0400

    Move the six content modules into pluginModules/ with standard source layout
    
    Step 4 of the restructure, and the first file relocation. copyright, coverage,
    hibernate, i18n, langInjection and maven move to pluginModules/<name>, with
    src/ -> src/main/java and resources/ -> src/main/resources.
    
    Project names carry their subpath (:pluginModules-hibernate) with directories
    remapped via projectDir, so every project path stays unique and readable and
    IPGP's path-derived naming defaults never see a nested path.
    
    The checkpoint for this step was that the packaged artifact names come out
    UNCHANGED, proving the convention plugins derive them from projectDir.name rather
    than from project.name or project.path. Diffing the ZIP listing against a clean
    pre-move build: identical, and all six content-module descriptors are still at
    their jar roots. Full gate green: 987 tests / 0 failures, RAT clean.
    
    Because the layout is now standard, the six modules drop the transitional
    legacy-layout convention plugin; their build files are down to a plugins block and
    their own dependencies. Kotlin sources in coverage and maven sit under
    src/main/java, which the Kotlin plugin compiles, and where they will land as .java
    when the Kotlin conversion resumes.
    
    Recorded as pure renames -- git reports rename {copyright => pluginModules/copyright}
    at 100% for sources -- so git log --follow and blame survive. Review with
    git -c diff.renameLimit=4000 diff --summary -M; the default rename limit is below
    the file count this restructure will ultimately move.
    
    RAT's exclude list already carried the '*/*/build/**' glob needed for the extra
    directory level, added when the convention plugin was written.
---
 build.gradle                                       | 28 +++++++++++-----------
 {copyright => pluginModules/copyright}/README.md   |  0
 .../copyright}/build.gradle                        |  1 -
 .../copyright/UpdateGspCopyrightsProvider.java     |  0
 .../grails/copyright/UpdateGspFileCopyright.java   |  0
 .../resources/intellij.groovy.grails.copyright.xml |  0
 {coverage => pluginModules/coverage}/README.md     |  0
 {coverage => pluginModules/coverage}/build.gradle  |  1 -
 .../grails/coverage/GrailsCoverageExtension.kt     |  0
 .../resources/intellij.groovy.grails.coverage.xml  |  0
 {hibernate => pluginModules/hibernate}/README.md   |  0
 .../hibernate}/build.gradle                        |  1 -
 .../hibernate/GormHibernateFacetProvider.java      |  0
 .../grails/hibernate/GormSessionFactory.java       |  0
 .../hibernate/GormSessionFactoryContributor.java   |  0
 .../resources/intellij.groovy.grails.hibernate.xml |  0
 {i18n => pluginModules/i18n}/README.md             |  0
 {i18n => pluginModules/i18n}/build.gradle          |  1 -
 .../i18n/GrailsI18nGroovyQuickFixHandler.java      |  0
 .../groovy/grails/i18n/GrailsI18nInspection.java   |  0
 .../i18n/GrailsI18nPropertyReferenceProvider.java  |  0
 .../grails/i18n/GrailsI18nQuickFixHandler.java     |  0
 .../i18n/GrailsI18nReferenceContributor.java       |  0
 .../groovy/grails/i18n/GrailsI18nizeProvider.java  |  0
 .../grails/i18n/GrailsI18nizeQuickFixDialog.java   |  0
 .../groovy/grails/i18n/GspI18nIntention.java       |  0
 .../I18nized GSP Expression with injections.gsp.ft |  0
 ...18nized GSP Expression with injections.gsp.html |  0
 .../code/I18nized GSP Expression.gsp.ft            |  0
 .../code/I18nized GSP Expression.gsp.html          |  0
 .../I18nized Groovy string with injections.gsp.ft  |  0
 ...I18nized Groovy string with injections.gsp.html |  0
 .../code/I18nized Groovy string.gsp.ft             |  0
 .../code/I18nized Groovy string.gsp.html           |  0
 .../InvalidI18nProperty.html                       |  0
 .../inspectionDescriptions/metaInformation.json    |  0
 .../resources/intellij.groovy.grails.i18n.xml      |  0
 .../GspI18nIntention/after.gsp.template            |  0
 .../GspI18nIntention/before.gsp.template           |  0
 .../GspI18nIntention/description.html              |  0
 .../langInjection}/README.md                       |  0
 .../langInjection}/build.gradle                    |  1 -
 .../resources/META-INF/languageInjections.xml      |  0
 .../intellij.groovy.grails.langInjection.xml       |  0
 {maven => pluginModules/maven}/README.md           |  0
 {maven => pluginModules/maven}/build.gradle        |  1 -
 .../grails/maven/GrailsMavenApplication.java       |  0
 .../maven/GrailsMavenApplicationProvider.java      |  0
 .../grails/maven/GrailsMavenDebuggerRunner.java    |  0
 .../maven/GrailsMavenWorkspaceConfigurator.kt      |  0
 .../groovy/grails/maven/MavenCommandExecutor.java  |  0
 .../groovy/grails/maven/MavenNodeProvider.kt       |  0
 .../resources/intellij.groovy.grails.maven.xml     |  0
 settings.gradle                                    | 13 +++++-----
 54 files changed, 21 insertions(+), 26 deletions(-)

diff --git a/build.gradle b/build.gradle
index 27e6cdf..665ee4c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,12 +45,12 @@ dependencies {
 
         // plugin modules. project(path:) is required: a bare project(':x') resolves through the
         // owner chain to Project.project(String) and returns a Project, not a Dependency.
-        pluginModule project(path: ':copyright')
-        pluginModule project(path: ':coverage')
-        pluginModule project(path: ':hibernate')
-        pluginModule project(path: ':i18n')
-        pluginModule project(path: ':langInjection')
-        pluginModule project(path: ':maven')
+        pluginModule project(path: ':pluginModules-copyright')
+        pluginModule project(path: ':pluginModules-coverage')
+        pluginModule project(path: ':pluginModules-hibernate')
+        pluginModule project(path: ':pluginModules-i18n')
+        pluginModule project(path: ':pluginModules-langInjection')
+        pluginModule project(path: ':pluginModules-maven')
 
         // additional plugins required for testing
         testBundledPlugin 'com.intellij.hibernate'
@@ -68,8 +68,8 @@ dependencies {
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.assertj:assertj-core:4.0.0-M1'
     testImplementation project(':testFramework')
-    testCompileOnly project(':i18n')
-    testCompileOnly project(':copyright')
+    testCompileOnly project(':pluginModules-i18n')
+    testCompileOnly project(':pluginModules-copyright')
 
     implementation project(':gradle-tooling')
     implementation project(':grails-rt')
@@ -79,15 +79,15 @@ dependencies {
     runtimeOnly project(':jps-plugin')
 
     // Aggregate JaCoCo coverage from every module into the root testCodeCoverageReport.
-    jacocoAggregation project(':copyright')
-    jacocoAggregation project(':coverage')
+    jacocoAggregation project(':pluginModules-copyright')
+    jacocoAggregation project(':pluginModules-coverage')
     jacocoAggregation project(':gradle-tooling')
     jacocoAggregation project(':grails-compiler-patch')
     jacocoAggregation project(':grails-rt')
-    jacocoAggregation project(':hibernate')
-    jacocoAggregation project(':i18n')
+    jacocoAggregation project(':pluginModules-hibernate')
+    jacocoAggregation project(':pluginModules-i18n')
     jacocoAggregation project(':jps-plugin')
-    jacocoAggregation project(':langInjection')
-    jacocoAggregation project(':maven')
+    jacocoAggregation project(':pluginModules-langInjection')
+    jacocoAggregation project(':pluginModules-maven')
     jacocoAggregation project(':testFramework')
 }
diff --git a/copyright/README.md b/pluginModules/copyright/README.md
similarity index 100%
rename from copyright/README.md
rename to pluginModules/copyright/README.md
diff --git a/copyright/build.gradle b/pluginModules/copyright/build.gradle
similarity index 94%
rename from copyright/build.gradle
rename to pluginModules/copyright/build.gradle
index b00c26a..f2ddad7 100644
--- a/copyright/build.gradle
+++ b/pluginModules/copyright/build.gradle
@@ -15,7 +15,6 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/copyright/src/com/intellij/groovy/grails/copyright/UpdateGspCopyrightsProvider.java b/pluginModules/copyright/src/main/java/com/intellij/groovy/grails/copyright/UpdateGspCopyrightsProvider.java
similarity index 100%
rename from copyright/src/com/intellij/groovy/grails/copyright/UpdateGspCopyrightsProvider.java
rename to pluginModules/copyright/src/main/java/com/intellij/groovy/grails/copyright/UpdateGspCopyrightsProvider.java
diff --git a/copyright/src/com/intellij/groovy/grails/copyright/UpdateGspFileCopyright.java b/pluginModules/copyright/src/main/java/com/intellij/groovy/grails/copyright/UpdateGspFileCopyright.java
similarity index 100%
rename from copyright/src/com/intellij/groovy/grails/copyright/UpdateGspFileCopyright.java
rename to pluginModules/copyright/src/main/java/com/intellij/groovy/grails/copyright/UpdateGspFileCopyright.java
diff --git a/copyright/resources/intellij.groovy.grails.copyright.xml b/pluginModules/copyright/src/main/resources/intellij.groovy.grails.copyright.xml
similarity index 100%
rename from copyright/resources/intellij.groovy.grails.copyright.xml
rename to pluginModules/copyright/src/main/resources/intellij.groovy.grails.copyright.xml
diff --git a/coverage/README.md b/pluginModules/coverage/README.md
similarity index 100%
rename from coverage/README.md
rename to pluginModules/coverage/README.md
diff --git a/coverage/build.gradle b/pluginModules/coverage/build.gradle
similarity index 93%
rename from coverage/build.gradle
rename to pluginModules/coverage/build.gradle
index d9af7b2..b1782ca 100644
--- a/coverage/build.gradle
+++ b/pluginModules/coverage/build.gradle
@@ -16,7 +16,6 @@
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
     id 'org.apache.grails.intellij.build.kotlin'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/coverage/src/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt b/pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt
similarity index 100%
rename from coverage/src/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt
rename to pluginModules/coverage/src/main/java/com/intellij/groovy/grails/coverage/GrailsCoverageExtension.kt
diff --git a/coverage/resources/intellij.groovy.grails.coverage.xml b/pluginModules/coverage/src/main/resources/intellij.groovy.grails.coverage.xml
similarity index 100%
rename from coverage/resources/intellij.groovy.grails.coverage.xml
rename to pluginModules/coverage/src/main/resources/intellij.groovy.grails.coverage.xml
diff --git a/hibernate/README.md b/pluginModules/hibernate/README.md
similarity index 100%
rename from hibernate/README.md
rename to pluginModules/hibernate/README.md
diff --git a/hibernate/build.gradle b/pluginModules/hibernate/build.gradle
similarity index 93%
rename from hibernate/build.gradle
rename to pluginModules/hibernate/build.gradle
index c33812c..e3f3956 100644
--- a/hibernate/build.gradle
+++ b/pluginModules/hibernate/build.gradle
@@ -15,7 +15,6 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/hibernate/src/com/intellij/groovy/grails/hibernate/GormHibernateFacetProvider.java b/pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/GormHibernateFacetProvider.java
similarity index 100%
rename from hibernate/src/com/intellij/groovy/grails/hibernate/GormHibernateFacetProvider.java
rename to pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/GormHibernateFacetProvider.java
diff --git a/hibernate/src/com/intellij/groovy/grails/hibernate/GormSessionFactory.java b/pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/GormSessionFactory.java
similarity index 100%
rename from hibernate/src/com/intellij/groovy/grails/hibernate/GormSessionFactory.java
rename to pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/GormSessionFactory.java
diff --git a/hibernate/src/com/intellij/groovy/grails/hibernate/GormSessionFactoryContributor.java b/pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/GormSessionFactoryContributor.java
similarity index 100%
rename from hibernate/src/com/intellij/groovy/grails/hibernate/GormSessionFactoryContributor.java
rename to pluginModules/hibernate/src/main/java/com/intellij/groovy/grails/hibernate/GormSessionFactoryContributor.java
diff --git a/hibernate/resources/intellij.groovy.grails.hibernate.xml b/pluginModules/hibernate/src/main/resources/intellij.groovy.grails.hibernate.xml
similarity index 100%
rename from hibernate/resources/intellij.groovy.grails.hibernate.xml
rename to pluginModules/hibernate/src/main/resources/intellij.groovy.grails.hibernate.xml
diff --git a/i18n/README.md b/pluginModules/i18n/README.md
similarity index 100%
rename from i18n/README.md
rename to pluginModules/i18n/README.md
diff --git a/i18n/build.gradle b/pluginModules/i18n/build.gradle
similarity index 93%
rename from i18n/build.gradle
rename to pluginModules/i18n/build.gradle
index 5b111d7..01868ba 100644
--- a/i18n/build.gradle
+++ b/pluginModules/i18n/build.gradle
@@ -15,7 +15,6 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nGroovyQuickFixHandler.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nGroovyQuickFixHandler.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nGroovyQuickFixHandler.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nGroovyQuickFixHandler.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nInspection.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nInspection.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nInspection.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nInspection.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nPropertyReferenceProvider.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nPropertyReferenceProvider.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nPropertyReferenceProvider.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nPropertyReferenceProvider.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nQuickFixHandler.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nQuickFixHandler.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nQuickFixHandler.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nQuickFixHandler.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nReferenceContributor.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nReferenceContributor.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nReferenceContributor.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nReferenceContributor.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nizeProvider.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nizeProvider.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nizeProvider.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nizeProvider.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nizeQuickFixDialog.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nizeQuickFixDialog.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GrailsI18nizeQuickFixDialog.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GrailsI18nizeQuickFixDialog.java
diff --git a/i18n/src/com/intellij/groovy/grails/i18n/GspI18nIntention.java b/pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GspI18nIntention.java
similarity index 100%
rename from i18n/src/com/intellij/groovy/grails/i18n/GspI18nIntention.java
rename to pluginModules/i18n/src/main/java/com/intellij/groovy/grails/i18n/GspI18nIntention.java
diff --git a/i18n/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.ft b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.ft
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.ft
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.ft
diff --git a/i18n/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.html b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.html
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.html
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression with injections.gsp.html
diff --git a/i18n/resources/fileTemplates/code/I18nized GSP Expression.gsp.ft b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression.gsp.ft
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized GSP Expression.gsp.ft
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression.gsp.ft
diff --git a/i18n/resources/fileTemplates/code/I18nized GSP Expression.gsp.html b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression.gsp.html
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized GSP Expression.gsp.html
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized GSP Expression.gsp.html
diff --git a/i18n/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.ft b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.ft
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.ft
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.ft
diff --git a/i18n/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.html b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.html
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.html
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string with injections.gsp.html
diff --git a/i18n/resources/fileTemplates/code/I18nized Groovy string.gsp.ft b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string.gsp.ft
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized Groovy string.gsp.ft
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string.gsp.ft
diff --git a/i18n/resources/fileTemplates/code/I18nized Groovy string.gsp.html b/pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string.gsp.html
similarity index 100%
rename from i18n/resources/fileTemplates/code/I18nized Groovy string.gsp.html
rename to pluginModules/i18n/src/main/resources/fileTemplates/code/I18nized Groovy string.gsp.html
diff --git a/i18n/resources/inspectionDescriptions/InvalidI18nProperty.html b/pluginModules/i18n/src/main/resources/inspectionDescriptions/InvalidI18nProperty.html
similarity index 100%
rename from i18n/resources/inspectionDescriptions/InvalidI18nProperty.html
rename to pluginModules/i18n/src/main/resources/inspectionDescriptions/InvalidI18nProperty.html
diff --git a/i18n/resources/inspectionDescriptions/metaInformation.json b/pluginModules/i18n/src/main/resources/inspectionDescriptions/metaInformation.json
similarity index 100%
rename from i18n/resources/inspectionDescriptions/metaInformation.json
rename to pluginModules/i18n/src/main/resources/inspectionDescriptions/metaInformation.json
diff --git a/i18n/resources/intellij.groovy.grails.i18n.xml b/pluginModules/i18n/src/main/resources/intellij.groovy.grails.i18n.xml
similarity index 100%
rename from i18n/resources/intellij.groovy.grails.i18n.xml
rename to pluginModules/i18n/src/main/resources/intellij.groovy.grails.i18n.xml
diff --git a/i18n/resources/intentionDescriptions/GspI18nIntention/after.gsp.template b/pluginModules/i18n/src/main/resources/intentionDescriptions/GspI18nIntention/after.gsp.template
similarity index 100%
rename from i18n/resources/intentionDescriptions/GspI18nIntention/after.gsp.template
rename to pluginModules/i18n/src/main/resources/intentionDescriptions/GspI18nIntention/after.gsp.template
diff --git a/i18n/resources/intentionDescriptions/GspI18nIntention/before.gsp.template b/pluginModules/i18n/src/main/resources/intentionDescriptions/GspI18nIntention/before.gsp.template
similarity index 100%
rename from i18n/resources/intentionDescriptions/GspI18nIntention/before.gsp.template
rename to pluginModules/i18n/src/main/resources/intentionDescriptions/GspI18nIntention/before.gsp.template
diff --git a/i18n/resources/intentionDescriptions/GspI18nIntention/description.html b/pluginModules/i18n/src/main/resources/intentionDescriptions/GspI18nIntention/description.html
similarity index 100%
rename from i18n/resources/intentionDescriptions/GspI18nIntention/description.html
rename to pluginModules/i18n/src/main/resources/intentionDescriptions/GspI18nIntention/description.html
diff --git a/langInjection/README.md b/pluginModules/langInjection/README.md
similarity index 100%
rename from langInjection/README.md
rename to pluginModules/langInjection/README.md
diff --git a/langInjection/build.gradle b/pluginModules/langInjection/build.gradle
similarity index 93%
rename from langInjection/build.gradle
rename to pluginModules/langInjection/build.gradle
index bc3a763..8b589a6 100644
--- a/langInjection/build.gradle
+++ b/pluginModules/langInjection/build.gradle
@@ -15,7 +15,6 @@
  */
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/langInjection/resources/META-INF/languageInjections.xml b/pluginModules/langInjection/src/main/resources/META-INF/languageInjections.xml
similarity index 100%
rename from langInjection/resources/META-INF/languageInjections.xml
rename to pluginModules/langInjection/src/main/resources/META-INF/languageInjections.xml
diff --git a/langInjection/resources/intellij.groovy.grails.langInjection.xml b/pluginModules/langInjection/src/main/resources/intellij.groovy.grails.langInjection.xml
similarity index 100%
rename from langInjection/resources/intellij.groovy.grails.langInjection.xml
rename to pluginModules/langInjection/src/main/resources/intellij.groovy.grails.langInjection.xml
diff --git a/maven/README.md b/pluginModules/maven/README.md
similarity index 100%
rename from maven/README.md
rename to pluginModules/maven/README.md
diff --git a/maven/build.gradle b/pluginModules/maven/build.gradle
similarity index 94%
rename from maven/build.gradle
rename to pluginModules/maven/build.gradle
index 4319350..79197d1 100644
--- a/maven/build.gradle
+++ b/pluginModules/maven/build.gradle
@@ -16,7 +16,6 @@
 plugins {
     id 'org.apache.grails.intellij.build.intellij-module'
     id 'org.apache.grails.intellij.build.kotlin'
-    id 'org.apache.grails.intellij.build.legacy-layout'
 }
 
 dependencies {
diff --git a/maven/src/com/intellij/groovy/grails/maven/GrailsMavenApplication.java b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenApplication.java
similarity index 100%
rename from maven/src/com/intellij/groovy/grails/maven/GrailsMavenApplication.java
rename to pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenApplication.java
diff --git a/maven/src/com/intellij/groovy/grails/maven/GrailsMavenApplicationProvider.java b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenApplicationProvider.java
similarity index 100%
rename from maven/src/com/intellij/groovy/grails/maven/GrailsMavenApplicationProvider.java
rename to pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenApplicationProvider.java
diff --git a/maven/src/com/intellij/groovy/grails/maven/GrailsMavenDebuggerRunner.java b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenDebuggerRunner.java
similarity index 100%
rename from maven/src/com/intellij/groovy/grails/maven/GrailsMavenDebuggerRunner.java
rename to pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenDebuggerRunner.java
diff --git a/maven/src/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt
similarity index 100%
rename from maven/src/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt
rename to pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/GrailsMavenWorkspaceConfigurator.kt
diff --git a/maven/src/com/intellij/groovy/grails/maven/MavenCommandExecutor.java b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenCommandExecutor.java
similarity index 100%
rename from maven/src/com/intellij/groovy/grails/maven/MavenCommandExecutor.java
rename to pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenCommandExecutor.java
diff --git a/maven/src/com/intellij/groovy/grails/maven/MavenNodeProvider.kt b/pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.kt
similarity index 100%
rename from maven/src/com/intellij/groovy/grails/maven/MavenNodeProvider.kt
rename to pluginModules/maven/src/main/java/com/intellij/groovy/grails/maven/MavenNodeProvider.kt
diff --git a/maven/resources/intellij.groovy.grails.maven.xml b/pluginModules/maven/src/main/resources/intellij.groovy.grails.maven.xml
similarity index 100%
rename from maven/resources/intellij.groovy.grails.maven.xml
rename to pluginModules/maven/src/main/resources/intellij.groovy.grails.maven.xml
diff --git a/settings.gradle b/settings.gradle
index f6cfa8b..8537e86 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,14 +19,15 @@ pluginManagement {
 
 rootProject.name = 'grails-intellij-plugin'
 
-include 'copyright'
-include 'coverage'
+// Project names carry their subpath so every project path is unique and readable, and so
+// IPGP's path-derived defaults never see a nested path. Directories are remapped below.
+['copyright', 'coverage', 'hibernate', 'i18n', 'langInjection', 'maven'].each { name ->
+    include "pluginModules-$name"
+    project(":pluginModules-$name").projectDir = file("pluginModules/$name")
+}
+
 include 'gradle-tooling'
 include 'grails-compiler-patch'
 include 'grails-rt'
-include 'hibernate'
-include 'i18n'
 include 'jps-plugin'
-include 'langInjection'
-include 'maven'
 include 'testFramework'
\ No newline at end of file


(grails-intellij-plugin) 33/35: Update the plans to reflect what has shipped

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit e0395ce7f7ce79a173b70dd44adacce06294aa26
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 15:59:06 2026 -0400

    Update the plans to reflect what has shipped
    
    MIGRATION-PLAN.md, 423 -> 222 lines. Phases 1-7 are all done, so their
    step-by-step prose is replaced by a table recording what each one landed and
    where that detail now lives. Also:
    
      - "Current state (surveyed 2026-07-06)" is retitled "State at import" and
        trimmed. It described the pre-restructure build as current, which is why it
        read as stale; as a dated snapshot of what was inherited it is still worth
        keeping.
      - "Open decisions" becomes "Decisions of record". Decisions 3, 4 and 5 were
        settled by what actually got built (no Develocity, workflow artifact rather
        than channel publishing, platform-branch versioning) and now say so.
        Numbering is unchanged because later phases cite these by number.
      - Phase 8 is split into the six items verified locally and the four that need
        a real runner, a tag, or the PMC. The snapshot-publish gating item is
        dropped as moot: decision 4 means CI publishes nothing, so there is no job
        to gate. Worth flagging that no workflow has ever run on a GitHub runner —
        that is the largest untested area left.
      - The suggested commit sequence is removed; it is history now.
      - Phase 3's testdata-JAR question and Phase 7's SBOM stretch goal are pulled
        out as the two genuinely-open items rather than left buried in done phases.
    
    IMPROVEMENT-PLAN.md: the survey said 525 Java / 110 Kotlin, 11 flat submodules,
    platform 2025.3. Updated to the real numbers and layout, with a note on why 19
    Kotlin files remain and that Kotlin therefore cannot be dropped from the build
    yet. The assetPipeline cleanup item is closed. Phase 1's dumb-mode item records
    the two lessons from the IndexNotReadyException fix, since they apply to the
    contributors that have not been swept yet.
    
    MIGRATION-PLAN Phase 9 is untouched — still blocked on the software grant.
---
 IMPROVEMENT-PLAN.md | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/IMPROVEMENT-PLAN.md b/IMPROVEMENT-PLAN.md
index 331368f..211cb0a 100644
--- a/IMPROVEMENT-PLAN.md
+++ b/IMPROVEMENT-PLAN.md
@@ -64,7 +64,7 @@ anything. Concrete checklist:
 
 **0.1b Dormant code inherited from the JetBrains import** — decide revive vs delete for
 each. All of these arrived already-orphaned in `3d4bc40` (the initial import); none was
-broken by later work. They are documented in `grails-rt/README.md` so they are not
+broken by later work. They are documented in `libs/grails-rt/README.md` so they are not
 rediscovered as bugs. Deleting them would shrink `grails-rt` to the two Groovy resources
 that Grails 3+ actually uses.
 - [ ] `GrailsExecutionUtils.addAgentJar` has **no callers**, so the `-javaagent:` path
@@ -79,8 +79,8 @@ that Grails 3+ actually uses.
       `GrailsTaskManagerExtension`, gated on a Gradle task named `grails-test-app` that
       only the old Grails Gradle plugin defines — so it is unreachable on Grails 3+ too.
       Grails 7/8 test-event reporting needs a different mechanism; scope that in 2.6.
-- [ ] Untracked `assetPipeline/` build output remains from a module deleted from
-      `settings.gradle`. Safe to remove.
+- [x] ~~Untracked `assetPipeline/` build output remains from a module deleted from
+      `settings.gradle`.~~ Removed during the restructure.
 
 **0.2 Real-app walkthrough** — generate three apps and open each with the plugin:
 1. Grails **7.0.x** release (via `https://start.grails.org` — forge production
@@ -136,7 +136,16 @@ Walk this checklist per app and record works / broken / missing:
 - [ ] Provide new-UI (20×20) icon variants for the gutter/toolwindow/artefact icons;
       audit `GrailsIconProvider` and `GroovyMvcIcons` (generated) coverage.
 - [ ] Verify dumb-mode safety of the heavier contributors (member contributors and
-      reference providers should be index-tolerant).
+      reference providers should be index-tolerant). **Partly done**: the GSP taglib chain
+      (`GspXmlRootTagImpl` → `GspTagLibUtil` → `GrailsArtifact`) was throwing
+      `IndexNotReadyException` when a GSP was opened before import finished — fixed, with
+      `GspTagLibDumbModeTest` as the regression guard. Two lessons for the rest of the
+      audit: (a) a directory scan being index-free is not sufficient, because resolving a
+      member on a found Groovy class still hits the stub index via supertype resolution;
+      (b) any cache holding a degraded dumb-mode result needs
+      `DumbService.getModificationTracker()` as a dependency, since indexing finishing does
+      not inherently bump `PsiModificationTracker`. The remaining contributors have not
+      been swept.
 - [ ] Encode the supported-platform window in `pluginVerification.ides` (proposal:
       latest two IDE majors for the new plugin; "current major only, best effort" for
       legacy).
@@ -352,7 +361,8 @@ invisible to IntelliJ Community users. Using the Phase 0.3 reference counts:
       submodule (`com.intellij.persistence`, `com.intellij.javaee.jpa`), database
       integration (`com.intellij.database`), JSP-adjacent GSP features
       (`com.intellij.jsp`), JS/CSS injection (already optional).
-- [ ] The existing submodule layout (`hibernate/`, `langInjection/`, `i18n/`, …) is
+- [ ] The existing module layout (`pluginModules/hibernate/`, `pluginModules/langInjection/`,
+      `pluginModules/i18n/`, …) is
       most of the needed seam — the work is breaking compile-time references from
       core packages into Ultimate-only APIs (count known from Phase 0.3).
 - [ ] Decide the Marketplace story: one artifact with optional deps (recommended)


(grails-intellij-plugin) 06/35: Drop grails-core references from the release docs

Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 0e3a95c97d4db8c18aee9dbd84f918696799d62e
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Sun Jul 26 07:21:35 2026 -0400

    Drop grails-core references from the release docs
    
    The release process should stand on its own rather than being described by
    comparison with another repository.
---
 .github/workflows/release-abort.yml | 5 ++---
 RELEASE.md                          | 7 -------
 2 files changed, 2 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/release-abort.yml b/.github/workflows/release-abort.yml
index eadc290..be300fa 100644
--- a/.github/workflows/release-abort.yml
+++ b/.github/workflows/release-abort.yml
@@ -21,9 +21,8 @@
 # Marketplace they are public, and the correct response is a new version rather than an
 # abort. The job hard-fails if it finds the version already promoted.
 #
-# Unlike grails-core's equivalent there is no Nexus staging repository to drop -- this
-# repository publishes no Maven artifacts, only the dist.apache.org distributions and the
-# Marketplace plugin.
+# There is no Nexus staging repository to drop: this repository publishes no Maven
+# artifacts, only the dist.apache.org distributions and the Marketplace plugin.
 name: "Release - Abort Release"
 on:
   workflow_dispatch:
diff --git a/RELEASE.md b/RELEASE.md
index df505c6..20e3ac2 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -300,9 +300,6 @@ Actions tab, giving it the release tag and ticking the confirmation box. It will
 3. Cancel any in-flight `Release` workflow runs.
 4. Delete the GitHub Release and the git tag.
 
-Unlike `grails-core` there is no Nexus staging repository to drop; this repository
-publishes no Maven artifacts.
-
 Afterwards, if a `[VOTE]` thread was already started, reply to it with `[CANCEL][VOTE]`
 and the reason. The workflow prints this reminder at the end.
 
@@ -445,7 +442,3 @@ The tooling that supports this:
 | `etc/bin/verify-reproducible.sh` | Rebuilds from the staged source distribution and compares to the staged binary. |
 | `etc/bin/Dockerfile` | Linux environment matching CI, for ruling out host-OS differences. |
 | `.sdkmanrc` | The pinned JDK and Gradle versions; the single source of truth for both CI and the Dockerfile. |
-
-This mirrors the approach taken in
-[`grails-core`](https://github.com/apache/grails-core/blob/HEAD/RELEASE.md), scaled down
-to this repository's two artifacts.