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 ❸ 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 ❹ 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 ❻ 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']
'''
}