You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2024/01/02 09:15:32 UTC

(groovy) branch master updated: add doco for regex type checker

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

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 2118aef6c5 add doco for regex type checker
2118aef6c5 is described below

commit 2118aef6c524e6cd195f9e1d02eccc0ec8e0fb8c
Author: Paul King <pa...@asert.com.au>
AuthorDate: Tue Jan 2 19:15:19 2024 +1000

    add doco for regex type checker
---
 subprojects/groovy-binary/src/spec/doc/index.adoc  |   2 +
 .../src/spec/doc/typecheckers.adoc                 | 102 ++++++++++++++++++
 .../src/spec/test/RegexCheckerTest.groovy          | 119 +++++++++++++++++++++
 .../groovy/typecheckers/RegexCheckerTest.groovy    |   2 +-
 4 files changed, 224 insertions(+), 1 deletion(-)

diff --git a/subprojects/groovy-binary/src/spec/doc/index.adoc b/subprojects/groovy-binary/src/spec/doc/index.adoc
index b7b695607d..2ed548a59b 100644
--- a/subprojects/groovy-binary/src/spec/doc/index.adoc
+++ b/subprojects/groovy-binary/src/spec/doc/index.adoc
@@ -111,6 +111,8 @@ include::../../../../../subprojects/groovy-yaml/src/spec/doc/yaml-userguide.adoc
 
 include::../../../../../subprojects/groovy-toml/src/spec/doc/toml-userguide.adoc[leveloffset=+2]
 
+include::../../../../../subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc[leveloffset=+2]
+
 include::../../../../../subprojects/groovy-contracts/src/spec/doc/contracts-userguide.adoc[leveloffset=+2]
 
 === Scripting Ant tasks
diff --git a/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc b/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc
new file mode 100644
index 0000000000..5f3f5e265f
--- /dev/null
+++ b/subprojects/groovy-typecheckers/src/spec/doc/typecheckers.adoc
@@ -0,0 +1,102 @@
+//////////////////////////////////////////
+
+  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
+
+    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.
+
+//////////////////////////////////////////
+ifndef::reldir_typecheckers[]
+:reldir_typecheckers: .
+endif::[]
+
+= Built-in auxiliary type checkers
+
+== Introduction
+
+Groovy's static nature includes an extensible type-checking mechanism.
+This mechanism allows users to selectively strengthen or weaken type checking
+as needed to cater for scenarios where the standard type checking isn't sufficient.
+
+In addition to allowing you to write your own customer checkers,
+Groovy offers a handful of useful built-in type checkers.
+These provide additional checks for specific scenarios.
+
+In the examples which follow, we'll explicitly show declaring
+the type checker we want to use.
+As usual, Groovy's compiler customization mechanisms would allow you to
+simplify application of such checkers, e.g. make it apply globally
+using a compiler configuration script, as just one example.
+
+== Checking Regex literals
+
+Consider the following code, which contains 3 regular expressions:
+
+[source,groovy]
+----
+include::../test/RegexCheckerTest.groovy[tags=introduction_example,indent=0]
+----
+<1> A constant String regex
+<2> Using the constant regex with the find operator
+<3> Checking the matcher's second group
+<4> A local variable containing a regex pattern
+<5> Using the pattern with grep
+<6> An API call passing a String literal regex
+
+Luckily, we made no errors in our regex definitions, and the code executes successfully.
+
+If however, we did make certain errors in those definitions, then we would expect the potential of runtime errors. Using `RegexChecker`, we can
+find certain kinds of errors during compilation.
+Let's look at some examples.
+
+Suppose at &#x2778; we looked for the third group instead of the second:
+
+[source,groovy]
+----
+include::../test/RegexCheckerTest.groovy[tags=invalid_group_count,indent=0]
+----
+
+We would see the following error at compile-time:
+
+----
+include::../test/RegexCheckerTest.groovy[tags=invalid_group_count_message,indent=0]
+----
+
+Alternatively, suppose at &#x2779; we accidentally left off the closing curly brace:
+
+[source,groovy]
+----
+include::../test/RegexCheckerTest.groovy[tags=unclosed_counted_closure,indent=0]
+----
+
+We would see the following error at compile-time:
+
+----
+include::../test/RegexCheckerTest.groovy[tags=unclosed_counted_closure_message,indent=0]
+----
+
+Alternatively, suppose at &#x277B; we accidentally left off the closing round bracket for the last (day of month) group:
+
+[source,groovy]
+----
+include::../test/RegexCheckerTest.groovy[tags=unclosed_group,indent=0]
+----
+
+We would see the following error at compile-time:
+
+[source]
+----
+include::../test/RegexCheckerTest.groovy[tags=unclosed_group_message,indent=0]
+----
diff --git a/subprojects/groovy-typecheckers/src/spec/test/RegexCheckerTest.groovy b/subprojects/groovy-typecheckers/src/spec/test/RegexCheckerTest.groovy
new file mode 100644
index 0000000000..8cba1189f7
--- /dev/null
+++ b/subprojects/groovy-typecheckers/src/spec/test/RegexCheckerTest.groovy
@@ -0,0 +1,119 @@
+/*
+ *  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
+ *
+ *    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.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+
+class RegexCheckerTest {
+
+    @Test
+    void testIntroduction() {
+        assertScript($/
+        import groovy.transform.TypeChecked
+        import java.util.regex.Pattern
+
+        // tag::introduction_example[]
+        static final String TWO_GROUPS_OF_THREE = /(...)(...)/  // <1>
+
+        @TypeChecked(extensions='groovy.typecheckers.RegexChecker')
+        static main(args) {
+            var m = 'foobar' =~ TWO_GROUPS_OF_THREE  //  <2>
+            assert m.find()
+            assert m.group(1) == 'foo'
+            assert m.group(2) == 'bar'  // <3>
+
+            var pets = ['cat', 'dog', 'goldfish']
+            var shortNamed = ~/\w{3}/  // <4>
+            assert pets.grep(shortNamed) == ['cat', 'dog']  // <5>
+
+            assert Pattern.matches(/(\d{4})-(\d{1,2})-(\d{1,2})/, '2020-12-31')  // <6>
+        }
+        // end::introduction_example[]
+        /$)
+    }
+
+    @Test
+    void testInvalidGroupCount() {
+        def err = shouldFail($/
+        import groovy.transform.TypeChecked
+
+        static final String TWO_GROUPS_OF_THREE = /(...)(...)/
+
+        @TypeChecked(extensions='groovy.typecheckers.RegexChecker')
+        static main(args) {
+            var m = 'foobar' =~ TWO_GROUPS_OF_THREE
+            assert m.find()
+            // tag::invalid_group_count[]
+            assert m.group(3) == 'bar'
+            // end::invalid_group_count[]
+        }
+        /$)
+        def expectedError = '''\
+        # tag::invalid_group_count_message[]
+        [Static type checking] - Invalid group count 3 for regex with 2 groups
+        # end::invalid_group_count_message[]
+        '''
+        assert err.message.contains(expectedError.readLines()[1].trim())
+    }
+
+    @Test
+    void testUnclosedCountedClosure() {
+        def err = shouldFail($/
+        import groovy.transform.TypeChecked
+
+        @TypeChecked(extensions='groovy.typecheckers.RegexChecker')
+        static main(args) {
+            var pets = ['cat', 'dog', 'goldfish']
+            // tag::unclosed_counted_closure[]
+            var shortNamed = ~/\w{3/
+            // end::unclosed_counted_closure[]
+        }
+        /$)
+        def expectedError = '''\
+        # tag::unclosed_counted_closure_message[]
+        [Static type checking] - Bad regex: Unclosed counted closure near index 4
+        # end::unclosed_counted_closure_message[]
+        '''
+        assert err.message.contains(expectedError.readLines()[1].trim())
+    }
+
+    @Test
+    void testUnclosedGroup() {
+        def err = shouldFail($/
+        import groovy.transform.TypeChecked
+        import java.util.regex.Pattern
+
+        @TypeChecked(extensions='groovy.typecheckers.RegexChecker')
+        static main(args) {
+            // tag::unclosed_group[]
+            assert Pattern.matches(/(\d{4})-(\d{1,2})-(\d{1,2}/, '2020-12-31')
+            // end::unclosed_group[]
+        }
+        /$)
+        def expectedError = '''\
+        # tag::unclosed_group_message[]
+        [Static type checking] - Bad regex: Unclosed group near index 26
+        # end::unclosed_group_message[]
+        '''
+        assert err.message.contains(expectedError.readLines()[1].trim())
+    }
+
+}
diff --git a/subprojects/groovy-typecheckers/src/test/groovy/groovy/typecheckers/RegexCheckerTest.groovy b/subprojects/groovy-typecheckers/src/test/groovy/groovy/typecheckers/RegexCheckerTest.groovy
index bb529c5dbe..eb47a7d4a0 100644
--- a/subprojects/groovy-typecheckers/src/test/groovy/groovy/typecheckers/RegexCheckerTest.groovy
+++ b/subprojects/groovy-typecheckers/src/test/groovy/groovy/typecheckers/RegexCheckerTest.groovy
@@ -62,7 +62,7 @@ final class RegexCheckerTest {
 
         assertScript shell, '''
             def pets = ['cat', 'dog', 'goldfish']
-            def oNamed = ~/(.)o(.*)/ // missing closing bracket
+            def oNamed = ~/(.)o(.*)/
             assert pets.grep(oNamed) == ['dog', 'goldfish']
         '''
     }