borinquenkid commented on code in PR #16048:
URL: https://github.com/apache/grails-core/pull/16048#discussion_r3887712962


##########
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GroovyDslConfigurationMetadataParser.groovy:
##########
@@ -0,0 +1,207 @@
+/*
+ *  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.buildsrc
+
+import org.codehaus.groovy.ast.ModuleNode
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.BinaryExpression
+import org.codehaus.groovy.ast.expr.ClassExpression
+import org.codehaus.groovy.ast.expr.ClosureExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.Expression
+import org.codehaus.groovy.ast.expr.ListExpression
+import org.codehaus.groovy.ast.expr.MapExpression
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+import org.codehaus.groovy.ast.expr.PropertyExpression
+import org.codehaus.groovy.ast.expr.TernaryExpression
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.ast.stmt.ExpressionStatement
+import org.codehaus.groovy.ast.stmt.IfStatement
+import org.codehaus.groovy.ast.stmt.Statement
+import org.codehaus.groovy.control.CompilationFailedException
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.syntax.Types
+
+import java.nio.charset.StandardCharsets
+
+/** Extracts configuration metadata from Groovy DSL source without evaluating 
source code. */
+final class GroovyDslConfigurationMetadataParser {
+
+    private GroovyDslConfigurationMetadataParser() {
+    }
+
+    static List<Map<String, Object>> parse(Collection<File> files, Map<String, 
String> rootPrefixes) {
+        List<Map<String, Object>> properties = []
+        files.findAll { File file -> file.isFile() }.sort { File file -> 
file.absolutePath }.each { File file ->
+            parseFile(file, rootPrefixes, properties)
+        }
+        properties.sort { Map<String, Object> property -> property.name as 
String }
+    }
+
+    private static void parseFile(File file, Map<String, String> rootPrefixes,
+                                  List<Map<String, Object>> properties) {
+        ModuleNode module = parseSource(file)
+        module.statementBlock.statements.each { Statement statement ->
+            MethodCallExpression call = methodCall(statement)
+            String root = call?.methodAsString
+            ClosureExpression closure = call == null ? null : 
closureArgument(call)
+            String prefix = root == null ? null : rootPrefixes[root]
+            if (prefix != null && closure != null) {
+                parseStatements(closure.code, prefix, false, properties)
+            }
+        }
+    }
+
+    private static ModuleNode parseSource(File file) {
+        try {
+            SourceUnit source = SourceUnit.create(file.absolutePath, 
file.getText(StandardCharsets.UTF_8.name()))
+            source.parse()
+            source.completePhase()
+            source.nextPhase()
+            source.convert()
+            source.errorCollector.failIfErrors()
+            source.AST
+        } catch (CompilationFailedException exception) {
+            throw new IllegalArgumentException("Failed to parse Groovy DSL 
source '${file.absolutePath}'", exception)
+        }
+    }
+
+    private static void parseStatements(Statement statement, String prefix, 
boolean conditional,
+                                        List<Map<String, Object>> properties) {
+        if (statement instanceof BlockStatement) {
+            statement.statements.each { Statement child -> 
parseStatements(child, prefix, conditional, properties) }
+        } else if (statement instanceof IfStatement) {
+            parseStatements(statement.ifBlock, prefix, true, properties)
+            parseStatements(statement.elseBlock, prefix, true, properties)
+        } else if (statement instanceof ExpressionStatement) {
+            Expression expression = statement.expression
+            if (expression instanceof BinaryExpression && 
expression.operation.type == Types.ASSIGN) {
+                addAssignment(expression, prefix, conditional, properties)
+            } else if (expression instanceof MethodCallExpression) {
+                ClosureExpression closure = closureArgument(expression)
+                String nestedName = expression.methodAsString
+                if (closure != null && nestedName != null) {
+                    parseStatements(closure.code, "${prefix}.${nestedName}", 
conditional, properties)
+                }
+            }
+        }
+    }
+
+    private static void addAssignment(BinaryExpression assignment, String 
prefix, boolean conditional,
+                                      List<Map<String, Object>> properties) {
+        List<String> segments = leftHandPath(assignment.leftExpression)
+        if (segments == null) {
+            return
+        }
+        String name = "${prefix}.${segments.join('.')}"
+        Inference inference = infer(assignment.rightExpression)
+        Map<String, Object> property = [name: name]
+        if (inference.type != null) {
+            property.type = inference.type
+        }
+        if (!conditional && inference.literal) {
+            property.defaultValue = inference.value
+        }
+        properties << property

Review Comment:
   Fixed in dd1ab42936 + 6c3328e3df. Same-name entries are now grouped and 
reconciled before they reach `indexByName`: unconditional entries are checked 
for uniqueness among themselves specifically (order-independent, so a 
conditional entry sitting between two conflicting unconditional assignments 
can't mask the conflict), the surviving unconditional entry's `defaultValue` is 
kept, and the type is unioned across every entry for the name — covers both 
examples above (differing literal types, and an unconditional default 
overridden under `Environment.TEST`). Added specs for both, plus a null-branch 
variant and a "still fails on two genuinely conflicting unconditional 
assignments" case.



-- 
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