Copilot commented on code in PR #16042:
URL: https://github.com/apache/grails-core/pull/16042#discussion_r3673660022


##########
grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy:
##########
@@ -56,140 +58,305 @@ import org.grails.io.support.GrailsResourceUtils
 import org.grails.io.support.UrlResource
 
 /**
- * A global transformation that applies Grails' transformations to classes 
within a Grails project
+ * Global AST transformation that applies Grails compiler injection to Grails 
project sources,
+ * including applications and plugins.
+ *
+ * <p>It identifies Grails artefacts, applies the relevant {@link 
ClassInjector} and
+ * {@link grails.compiler.traits.TraitInjector} implementations, and registers 
artefact handlers
+ * and injector implementations. When compiling a plugin descriptor, it also 
creates or updates
+ * the {@code META-INF/grails-plugin.xml} descriptor and records transformed 
plugin resources.</p>
  *
- * @author Graeme Rocher
  * @since 3.0
  */
-@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+@Slf4j
 @CompileStatic
+@GroovyASTTransformation
 class GlobalGrailsClassInjectorTransformation implements ASTTransformation, 
CompilationUnitAware, TransformWithPriority {
 
+    /**
+     * The system property signalling that a multi-project build compiles each 
project into its own
+     * isolated output directory. When set, the transform must never fall back 
to a shared or guessed
+     * location, which could leak one module's generated metadata into another.
+     */
+    public static final String ISOLATED_BUILD_PROPERTY = 
'grails.isolated.build'
+
+    public static final ClassNode ARTEFACT_CLASS_NODE = new ClassNode(Artefact)
     public static final ClassNode ARTEFACT_HANDLER_CLASS = 
ClassHelper.make('grails.core.ArtefactHandler')
     public static final ClassNode TRAIT_INJECTOR_CLASS = 
ClassHelper.make('grails.compiler.traits.TraitInjector')
 
+    private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher()
+
+    private final LinkedHashSet<String> pendingPluginClassNames = []
+    private final Collection<String> pluginExcludePatterns = []
+
+    CompilationUnit compilationUnit
+
+    /**
+     * Returns the ordering position used to run this transformation relative 
to other global
+     * transformations.
+     *
+     * @return the global Grails transformation order
+     */
     @Override
     int priority() {
-        return GroovyTransformOrder.GLOBAL_GRAILS_TRANSFORM_ORDER
+        GroovyTransformOrder.GLOBAL_GRAILS_TRANSFORM_ORDER
     }
 
+    /**
+     * Applies Grails artefact and class injection to a project source and 
updates the generated
+     * plugin metadata for the compiled classes.
+     *
+     * @param nodes AST nodes supplied by Groovy
+     * @param source the source unit being compiled
+     */
     @Override
     void visit(ASTNode[] nodes, SourceUnit source) {
-
-        ModuleNode ast = source.getAST()
-        List<ClassNode> classes = new ArrayList<>(ast.getClasses())
-
-        URL url = GrailsASTUtils.getSourceUrl(source)
-
-        if (url == null) return
-        if (!GrailsResourceUtils.isProjectSource(new UrlResource(url))) return
-
-        List<ArtefactHandler> artefactHandlers = 
GrailsFactoriesLoader.loadFactories(ArtefactHandler)
-        ClassInjector[] classInjectors = 
GrailsAwareInjectionOperation.getClassInjectors()
-
-        Map<String, List<ClassInjector>> cache = new LinkedHashMap<String, 
List<ClassInjector>>().withDefault { String key ->
-            ArtefactTypeAstTransformation.findInjectors(key, classInjectors)
+        def url = GrailsASTUtils.getSourceUrl(source)
+        if (!shouldVisit(url)) {
+            return
         }
 
-        LinkedHashSet<String> transformedClasses = []
-        String pluginVersion = null
         ClassNode pluginClassNode = null
+        String pluginVersion = null
+        def transformedClassNames = new LinkedHashSet<String>()
         def compilationTargetDirectory = 
resolveCompilationTargetDirectory(source)
         def pluginXmlFile = new File(compilationTargetDirectory, 
'META-INF/grails-plugin.xml')
+        def artefactHandlers = 
GrailsFactoriesLoader.loadFactories(ArtefactHandler)
+        def classInjectorCache = new LinkedHashMap<String, 
List<ClassInjector>>().withDefault { String key ->
+            ArtefactTypeAstTransformation.findInjectors(
+                    key,
+                    GrailsAwareInjectionOperation.classInjectors
+            )
+        }
 
-        for (ClassNode classNode : classes) {
-            def projectName = classNode.getNodeMetaData('projectName')
-            def projectVersion = classNode.getNodeMetaData('projectVersion')
-            if (projectVersion == null) {
-                projectVersion = 
getClass().getPackage().getImplementationVersion()
-            }
-
-            pluginVersion = projectVersion
-
-            def classNodeName = classNode.name
-
-            if (classNodeName.endsWith('GrailsPlugin') && 
!classNode.isAbstract()) {
+        for (def classNode : source.AST.classes.toList()) { // toList() to 
avoid concurrent modification exception
+            def projectName = resolveProjectName(classNode)
+            def projectVersion = resolveProjectVersion(classNode)
+            if (isGrailsPluginDescriptorClass(classNode)) {
                 pluginClassNode = classNode
-
-                if (!classNode.getProperty('version')) {
-                    classNode.addProperty(new PropertyNode('version', 
Modifier.PUBLIC, ClassHelper.make(Object), classNode, new 
ConstantExpression(projectVersion.toString()), null, null))
-                }
-
+                pluginVersion = resolvePluginVersion(classNode, 
projectVersion?.toString())
+                addPluginVersionProperty(classNode, pluginVersion)
                 continue
             }
-
-            if (updateGrailsFactoriesWithType(classNode, 
ARTEFACT_HANDLER_CLASS, compilationTargetDirectory)) {
+            if (updateGrailsFactoriesWithTypes(classNode, 
[ARTEFACT_HANDLER_CLASS, TRAIT_INJECTOR_CLASS], compilationTargetDirectory)) {
                 continue
             }
-            if (updateGrailsFactoriesWithType(classNode, TRAIT_INJECTOR_CLASS, 
compilationTargetDirectory)) {
+            if (!GrailsResourceUtils.isGrailsResource(new UrlResource(url))) {
                 continue
             }
-
-            if (!GrailsResourceUtils.isGrailsResource(new UrlResource(url))) 
continue
-
             if (projectName && projectVersion) {
-                GrailsASTUtils.addAnnotationOrGetExisting(classNode, 
GrailsPlugin, [name: 
GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(projectName.toString()),
 version: projectVersion])
+                addPluginAnnotation(classNode, projectName, projectVersion)
             }
 
-            classNode.getModule().addImport('Autowired', 
ClassHelper.make('org.springframework.beans.factory.annotation.Autowired'))
+            addImport(classNode, 
'org.springframework.beans.factory.annotation.Autowired')
 
-            for (ArtefactHandler handler in artefactHandlers) {
+            for (def handler : artefactHandlers) {
                 if (handler.isArtefact(classNode)) {
                     if (!classNode.getAnnotations(ARTEFACT_CLASS_NODE)) {
-                        transformedClasses.add(classNodeName)
-                        def annotationNode = new AnnotationNode(new 
ClassNode(Artefact))
-                        annotationNode.addMember('value', new 
ConstantExpression(handler.getType()))
-                        classNode.addAnnotation(annotationNode)
-
-                        List<ClassInjector> injectors = cache[handler.type]
-                        for (ClassInjector injector : injectors) {
-                            if (injector instanceof CompilationUnitAware) {
-                                ((CompilationUnitAware) 
injector).compilationUnit = compilationUnit
+                        transformedClassNames.add(classNode.name)
+                        addArtefactAnnotation(classNode, handler.type)
+                        def classInjectors = classInjectorCache[handler.type]
+                        for (def classInjector : classInjectors) {
+                            if (classInjector instanceof CompilationUnitAware) 
{
+                                ((CompilationUnitAware) 
classInjector).compilationUnit = compilationUnit
                             }
                         }
-                        ArtefactTypeAstTransformation.performInjection(source, 
classNode, injectors)
-                        TraitInjectionUtils.processTraitsForNode(source, 
classNode, handler.getType(), compilationUnit)
+                        ArtefactTypeAstTransformation.performInjection(source, 
classNode, classInjectors)
+                        TraitInjectionUtils.processTraitsForNode(source, 
classNode, handler.type, compilationUnit)
                     }
                 }
             }
 
-            if (!transformedClasses.contains(classNodeName)) {
+            if (!transformedClassNames.contains(classNode.name)) {
                 def globalClassInjectors = 
GrailsAwareInjectionOperation.globalClassInjectors
-
-                for (ClassInjector injector in globalClassInjectors) {
-                    injector.performInjection(source, classNode)
+                for (def classInjector : globalClassInjectors) {
+                    classInjector.performInjection(source, classNode)
                 }
             }
         }
 
-        // now create or update grails-plugin.xml
-        // first check if plugin.xml exists
-        pluginXmlFile.parentFile.mkdirs()
+        validatePluginVersionDefined(pluginClassNode, pluginVersion, 
pluginXmlFile, source)
 
-        generatePluginXml(pluginClassNode, pluginVersion, transformedClasses, 
pluginXmlFile)
+        // create or update grails-plugin.xml
+        generatePluginXml(pluginClassNode, pluginVersion, 
transformedClassNames, pluginXmlFile)

Review Comment:
   If `pluginClassNode` is present but `pluginVersion` is null, 
`validatePluginVersionDefined(...)` reports an AST error but 
`generatePluginXml(...)` is still invoked with a null version. That can lead to 
a runtime failure (e.g., writing/updating XML with a null `pluginVersion`) and 
mask the intended compilation error. Make the validation short-circuit the 
visit (e.g., return immediately after reporting the error), or make 
`generatePluginXml` a no-op when `pluginClassNode != null && pluginVersion == 
null`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to