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

(grails-core) branch chore/automate-project-conventions updated: Address repository convention review feedback

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

jamesfredley pushed a commit to branch chore/automate-project-conventions
in repository https://gitbox.apache.org/repos/asf/grails-core.git


The following commit(s) were added to refs/heads/chore/automate-project-conventions by this push:
     new 8bc7b8fdc1 Address repository convention review feedback
8bc7b8fdc1 is described below

commit 8bc7b8fdc108416b8a1797484e2dd6679b106789
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Mon Jul 20 14:19:46 2026 -0400

    Address repository convention review feedback
    
    Sanitize convention violations only at output boundaries and decode message
    property bundles explicitly as UTF-8. Add TestKit coverage for both behaviors.
    
    Assisted-by: opencode:gpt-5.6-sol
---
 .../buildsrc/RepositoryConventionsTask.groovy      |  9 ++--
 .../buildsrc/RepositoryConventionsTaskSpec.groovy  | 59 +++++++++++++++++++++-
 2 files changed, 62 insertions(+), 6 deletions(-)

diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/RepositoryConventionsTask.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/RepositoryConventionsTask.groovy
index fd6da84452..009d4a3fc9 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/RepositoryConventionsTask.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/RepositoryConventionsTask.groovy
@@ -36,6 +36,7 @@ import org.yaml.snakeyaml.Yaml
 import org.yaml.snakeyaml.constructor.SafeConstructor
 import org.yaml.snakeyaml.error.YAMLException
 
+import java.nio.charset.StandardCharsets
 import java.util.Set
 import java.util.regex.Matcher
 import java.util.regex.Pattern
@@ -141,11 +142,11 @@ abstract class RepositoryConventionsTask extends DefaultTask {
             options.setAllowDuplicateKeys(false)
             document = new Yaml(new SafeConstructor(options)).load(lines.subList(1, end).join('\n'))
         } catch (YAMLException exception) {
-            violations.add(sanitizeViolation("${path}: malformed skill front matter: ${exception.message}".toString()))
+            violations.add("${path}: malformed skill front matter: ${exception.message}".toString())
             return [:]
         }
         if (!(document instanceof Map)) {
-            violations.add(sanitizeViolation("${path}: skill front matter must be a YAML mapping".toString()))
+            violations.add("${path}: skill front matter must be a YAML mapping".toString())
             return [:]
         }
         Map<String, String> values = [:]
@@ -154,7 +155,7 @@ abstract class RepositoryConventionsTask extends DefaultTask {
             if (value instanceof String) {
                 values[key] = (String) value
             } else if (value != null) {
-                violations.add(sanitizeViolation("${path}: skill front matter field '${key}' must be a string".toString()))
+                violations.add("${path}: skill front matter field '${key}' must be a string".toString())
             }
         }
         values
@@ -398,7 +399,7 @@ abstract class RepositoryConventionsTask extends DefaultTask {
         List<PropertiesLine> result = []
         String content = null
         int start = 0
-        file.readLines().eachWithIndex { String line, int index ->
+        file.readLines(StandardCharsets.UTF_8.name()).eachWithIndex { String line, int index ->
             if (content == null) {
                 String trimmed = line.trim()
                 if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) {
diff --git a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/RepositoryConventionsTaskSpec.groovy b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/RepositoryConventionsTaskSpec.groovy
index 24e078876c..2357c8709a 100644
--- a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/RepositoryConventionsTaskSpec.groovy
+++ b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/RepositoryConventionsTaskSpec.groovy
@@ -23,6 +23,7 @@ import org.gradle.testkit.runner.TaskOutcome
 import spock.lang.Specification
 import spock.lang.TempDir
 
+import java.nio.charset.StandardCharsets
 import java.nio.file.Path
 
 class RepositoryConventionsTaskSpec extends Specification {
@@ -362,6 +363,58 @@ injected\\r\\n|forged=two
         !report.contains('\r')
     }
 
+    def "validateRepositoryConventions sanitizes front matter violations once in reports and exceptions"() {
+        given:
+        writeBuild()
+        writeSkillContent('sample', '''---
+name: sample
+description: Test skill
+license: Apache-2.0
+"duplicate|key": one
+"duplicate|key": two
+---
+''')
+        writeAgents('.agents/skills/sample/SKILL.md')
+        writeWorkflow('valid.yml', "uses: actions/checkout@${SHA}")
+
+        when:
+        def result = runAndFail('validateRepositoryConventions')
+        def report = testProjectDir.resolve('build/reports/violations/REPOSITORY_CONVENTIONS.md').toFile().text
+
+        then:
+        result.output.contains('duplicate key duplicate\\|key')
+        !result.output.contains('duplicate key duplicate\\\\|key')
+        report.contains('duplicate key duplicate\\|key')
+        !report.contains('duplicate key duplicate\\\\|key')
+    }
+
+    def "validateRepositoryConventions reads duplicate UTF-8 message keys independently of the Gradle default charset"() {
+        given:
+        writeBuild()
+        writeSkill('sample', 'sample')
+        writeAgents('.agents/skills/sample/SKILL.md')
+        writeWorkflow('valid.yml', "uses: actions/checkout@${SHA}")
+        def build = testProjectDir.resolve('build.gradle').toFile()
+        build << '''
+tasks.register('verifyDefaultEncoding') {
+    doLast {
+        assert java.nio.charset.Charset.defaultCharset() == java.nio.charset.StandardCharsets.ISO_8859_1
+    }
+}
+'''
+        testProjectDir.resolve('gradle.properties').toFile().text = 'org.gradle.jvmargs=-Dfile.encoding=ISO-8859-1\n'
+        def file = testProjectDir.resolve('src/main/resources/messages.properties').toFile()
+        file.parentFile.mkdirs()
+        file.setText('caf\u00e9=one\ncaf\u00e9=two\n', StandardCharsets.UTF_8.name())
+
+        when:
+        def result = runAndFail('verifyDefaultEncoding', 'validateRepositoryConventions')
+
+        then:
+        result.task(':verifyDefaultEncoding').outcome == TaskOutcome.SUCCESS
+        result.output.contains("duplicate message key 'caf\u00e9'")
+    }
+
     def "validateRepositoryConventions requires skill front matter to begin on line one"() {
         given:
         writeBuild()
@@ -570,10 +623,12 @@ ${withLicense ? 'license: Apache-2.0' : ''}
                 .build()
     }
 
-    private def runAndFail(String task) {
+    private def runAndFail(String task, String... additionalArguments) {
+        List<String> arguments = [task, '--stacktrace']
+        arguments.addAll(additionalArguments)
         GradleRunner.create()
                 .withProjectDir(testProjectDir.toFile())
-                .withArguments(task, '--stacktrace')
+                .withArguments(arguments)
                 .withPluginClasspath()
                 .buildAndFail()
     }