From e9ececeff0a05b3d99aae5a7945c938dbd8d8432 Mon Sep 17 00:00:00 2001 From: Kristina <57489792+KeyrisXdSnow@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:22:20 +0200 Subject: [PATCH] OD-17115 Fix of DslDoc generation task --- .../ish/docs/DslGroovyRootDocBuilder.groovy | 47 +- .../antlr/override/ArrayGroovyMethods.java | 73 +++ .../groovy/antlr/override/FormatHelper.java | 562 ++++++++++++++++++ .../antlr/override/GroovydocVisitor.java | 394 ++++++++++++ .../apache/groovy/antlr/override/Opcodes.java | 18 + .../apache/groovy/antlr/override/README.md | 21 + .../override/RecordTypeASTTransformation.java | 55 ++ .../antlr/override/SimpleGroovyDoc.java | 17 + 8 files changed, 1177 insertions(+), 10 deletions(-) create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java create mode 100644 buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java diff --git a/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy b/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy index a5f3a601755..7d37f21efe0 100644 --- a/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy +++ b/buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy @@ -18,6 +18,7 @@ package au.com.ish.docs import groovyjarjarantlr.RecognitionException import groovyjarjarantlr.TokenStreamException import groovyjarjarantlr.collections.AST +import org.apache.groovy.antlr.override.GroovydocVisitor import org.codehaus.groovy.antlr.AntlrASTProcessor import org.codehaus.groovy.antlr.SourceBuffer import org.codehaus.groovy.antlr.UnicodeEscapingReader @@ -28,13 +29,13 @@ import org.codehaus.groovy.antlr.java.JavaRecognizer import org.codehaus.groovy.antlr.parser.GroovyLexer import org.codehaus.groovy.antlr.parser.GroovyRecognizer import org.codehaus.groovy.antlr.treewalker.PreOrderTraversal -import org.codehaus.groovy.antlr.treewalker.SourceCodeTraversal import org.codehaus.groovy.antlr.treewalker.Visitor +import org.codehaus.groovy.ast.ModuleNode +import org.codehaus.groovy.control.* import org.codehaus.groovy.groovydoc.GroovyClassDoc import org.codehaus.groovy.groovydoc.GroovyRootDoc import org.codehaus.groovy.runtime.ResourceGroovyMethods import org.codehaus.groovy.tools.groovydoc.LinkArgument -import org.codehaus.groovy.tools.groovydoc.SimpleGroovyClassDocAssembler import org.codehaus.groovy.tools.groovydoc.SimpleGroovyExecutableMemberDoc import org.codehaus.groovy.tools.groovydoc.SimpleGroovyPackageDoc import org.codehaus.groovy.tools.groovydoc.SimpleGroovyRootDoc @@ -74,11 +75,11 @@ class DslGroovyRootDocBuilder { unicodeReader.setLexer(lexer) parser = GroovyRecognizer.make(lexer) } - parser.setSourceBuffer(sourceBuffer) - parser.compilationUnit() - AST ast = parser.getAST() - if (isJava) { + parser.setSourceBuffer(sourceBuffer) + parser.compilationUnit() + AST ast = parser.getAST() + // modify the Java AST into a Groovy AST (just token types) Visitor java2groovyConverter = new Java2GroovyConverter(parser.getTokenNames()) AntlrASTProcessor java2groovyTraverser = new PreOrderTraversal(java2groovyConverter) @@ -89,10 +90,36 @@ class DslGroovyRootDocBuilder { AntlrASTProcessor groovifierTraverser = new PreOrderTraversal(groovifier) groovifierTraverser.process(ast) } - Visitor visitor = new SimpleGroovyClassDocAssembler(packagePath, file, sourceBuffer, links, properties, !isJava) - AntlrASTProcessor traverser = new SourceCodeTraversal(visitor) - traverser.process(ast) - return ((SimpleGroovyClassDocAssembler)visitor).getGroovyClassDocs() + + CompilerConfiguration config = new CompilerConfiguration() + config.getOptimizationOptions().put(CompilerConfiguration.GROOVYDOC, true) + CompilationUnit compUnit = new CompilationUnit(config) + SourceUnit unit = new SourceUnit(file, src, config, null, new ErrorCollector(config)); + compUnit.addSource(unit); + int phase = Phases.CONVERSION; + if (properties.containsKey("phaseOverride")) { + String raw = properties.getProperty("phaseOverride") + try { + phase = Integer.parseInt(raw) + } catch(NumberFormatException ignore) { + raw = raw.toUpperCase(); + switch(raw) { + // some dup here but kept simple since we may swap Phases to an enum + case "CONVERSION": phase = 3; break; + case "SEMANTIC_ANALYSIS": phase = 4; break; + case "CANONICALIZATION": phase = 5; break; + case "INSTRUCTION_SELECTION": phase = 6; break; + case "CLASS_GENERATION": phase = 7; break; + default: + System.err.println("Ignoring unrecognised or unsuitable phase and keeping default"); + } + } + } + compUnit.compile(phase); + ModuleNode root = unit.getAST() + GroovydocVisitor visitor = new GroovydocVisitor(unit, packagePath, links, properties) + root.getClasses().forEach(clazz -> visitor.visitClass(clazz)) + return visitor.getGroovyClassDocs() } protected void setOverview() { diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java new file mode 100644 index 00000000000..3d5a52aa086 --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/ArrayGroovyMethods.java @@ -0,0 +1,73 @@ +/* + * Copyright ish group pty ltd 2024. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + */ + +/* + * 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. + */ + +package org.apache.groovy.antlr.override; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport; +import org.codehaus.groovy.util.ArrayIterator; + +/** + * Defines new groovy methods which appear on arrays inside the Groovy environment. + * Static methods are used with the first parameter being the destination class, + * i.e. public static int[] each(int[] self, Closure closure) + * provides an each({i -> }) method for int[]. + *

+ * NOTE: While this class contains many 'public' static methods, it is + * primarily regarded as an internal class (its internal package name + * suggests this also). We value backwards compatibility of these + * methods when used within Groovy but value less backwards compatibility + * at the Java method call level. I.e. future versions of Groovy may + * remove or move a method call in this file but would normally + * aim to keep the method available from within Groovy. + */ + +// TODO: Remove after update Groovy v4, this part of code is taken from this version + +public class ArrayGroovyMethods extends DefaultGroovyMethodsSupport { + + private ArrayGroovyMethods() { + } + + /** + * Concatenates the string representation of each item in this array, + * with the given String as a separator between each item. + * + *

+     * Serializable[] array = [1,2L,-3G]
+     * assert array.join("+") == "1+2+-3"
+     * 
+ * + * @param self an array of Object + * @param separator a String separator + * @return the joined String + * @since 1.0 + */ + public static String join(Object[] self, String separator) { + return DefaultGroovyMethods.join(new ArrayIterator<>(self), separator); + } +} \ No newline at end of file diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java new file mode 100644 index 00000000000..8e9d443b69b --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/FormatHelper.java @@ -0,0 +1,562 @@ +/* + * Copyright ish group pty ltd 2024. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + */ + +/* + * 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. + */ +package org.apache.groovy.antlr.override; + +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovySystem; +import groovy.lang.MetaClassRegistry; +import groovy.lang.Range; +import groovy.lang.Writable; +import groovy.transform.NamedParam; +import groovy.transform.NamedParams; +import org.apache.groovy.io.StringBuilderWriter; +import org.codehaus.groovy.control.ResolveVisitor; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.NullObject; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; +import org.w3c.dom.Element; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static java.lang.Math.max; +import static java.util.Arrays.stream; + +/** + * Formatting methods + */ + +// TODO: Remove after update Groovy v4, this part of code is taken from this version + +public class FormatHelper { + + private FormatHelper() {} + + private static final String SQ = "'"; + private static final String DQ = "\""; + + private static final Object[] EMPTY_ARGS = {}; + + // heuristic size to pre-allocate stringbuffers for collections of items + private static final int ITEM_ALLOCATE_SIZE = 5; + + public static final MetaClassRegistry metaRegistry = GroovySystem.getMetaClassRegistry(); + private static final String XMLUTIL_CLASS_FULL_NAME = "groovy.xml.XmlUtil"; + private static final String SERIALIZE_METHOD_NAME = "serialize"; + + static final Set DEFAULT_IMPORT_PKGS = new HashSet<>(); + static final Set DEFAULT_IMPORT_CLASSES = new HashSet<>(); + + static { + for (String pkgName : ResolveVisitor.DEFAULT_IMPORTS) { + FormatHelper.DEFAULT_IMPORT_PKGS.add(pkgName.substring(0, pkgName.length() - 1)); + } + FormatHelper.DEFAULT_IMPORT_CLASSES.add("java.math.BigDecimal"); + FormatHelper.DEFAULT_IMPORT_CLASSES.add("java.math.BigInteger"); + } + + public static String toString(Object arguments) { + return format(arguments, false, -1, false); + } + + public static String inspect(Object self) { + return format(self, true); + } + + public static String format(Object arguments, boolean verbose) { + return format(arguments, verbose, -1); + } + + public static String format(Object arguments, boolean inspect, boolean escapeBackslashes) { + return format(arguments, inspect, -1); + } + + public static String format(Object arguments, boolean verbose, int maxSize) { + return format(arguments, verbose, maxSize, false); + } + + public static String format(Object arguments, boolean inspect, boolean escapeBackslashes, int maxSize) { + return format(arguments, inspect, escapeBackslashes, maxSize, false); + } + + /** + * Output the {@code toString} for the argument(s) with various options to configure. + * Configuration options: + *
+     * 
+ *
safe
provides protection if the {@code toString} throws an exception, in which case the exception is swallowed and a dumber default {@code toString} is used
+ *
maxSize
will attempt to truncate the output to fit approx the maxSize number of characters, -1 means don't truncate
+ *
inspect
if false, render a value by its {@code toString}, otherwise use its {@code inspect} value
+ *
escapeBackSlashes
whether characters like tab, newline, etc. are converted to their escaped rendering ('\t', '\n', etc.)
+ *
verbose
shorthand to turn on both {@code inspect} and {@code escapeBackslashes}
+ *
+ *
+ * + * @param options a map of configuration options + * @param arguments the argument(s) to calculate the {@code toString} for + * @return the string rendering of the argument(s) + * @see DefaultGroovyMethods#inspect(Object) + */ + public static String toString(@NamedParams({ + @NamedParam(value = "safe", type = Boolean.class), + @NamedParam(value = "maxSize", type = Integer.class), + @NamedParam(value = "verbose", type = Boolean.class), + @NamedParam(value = "escapeBackslashes", type = Boolean.class), + @NamedParam(value = "inspect", type = Boolean.class) + }) Map options, Object arguments) { + Object safe = options.get("safe"); + if (!(safe instanceof Boolean)) safe = false; + Object maxSize = options.get("maxSize"); + if (!(maxSize instanceof Integer)) maxSize = -1; + Object verbose = options.get("verbose"); + Object inspect = options.get("inspect"); + Object escapeBackslashes = options.get("escapeBackslashes"); + if (!(inspect instanceof Boolean)) inspect = false; + if (!(escapeBackslashes instanceof Boolean)) escapeBackslashes = false; + if (!(verbose instanceof Boolean)) verbose = false; + if (Boolean.TRUE.equals(verbose)) { + inspect = true; + escapeBackslashes = true; + } + return format(arguments, (boolean) inspect, (boolean) escapeBackslashes, (int) maxSize, (boolean) safe); + } + + public static String format(Object arguments, boolean verbose, int maxSize, boolean safe) { + return format(arguments, verbose, verbose, maxSize, safe); + } + + public static String format(Object arguments, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) { + if (arguments == null) { + final NullObject nullObject = NullObject.getNullObject(); + return (String) nullObject.getMetaClass().invokeMethod(nullObject, "toString", EMPTY_ARGS); + } + if (arguments.getClass().isArray()) { + if (arguments instanceof Object[]) { + return toArrayString((Object[]) arguments, inspect, escapeBackslashes, maxSize, safe); + } + if (arguments instanceof char[]) { + return new String((char[]) arguments); + } + // other primitives + return formatCollection(DefaultTypeTransformation.arrayAsCollection(arguments), inspect, escapeBackslashes, maxSize, safe); + } + if (arguments instanceof Range) { + Range range = (Range) arguments; + try { + if (inspect) { + return range.inspect(); + } else { + return range.toString(); + } + } catch (RuntimeException ex) { + if (!safe) throw ex; + return handleFormattingException(arguments, ex); + } catch (Exception ex) { + if (!safe) throw new GroovyRuntimeException(ex); + return handleFormattingException(arguments, ex); + } + } + if (arguments instanceof Collection) { + return formatCollection((Collection) arguments, inspect, escapeBackslashes, maxSize, safe); + } + if (arguments instanceof Map) { + return formatMap((Map) arguments, inspect, escapeBackslashes, maxSize, safe); + } + if (arguments instanceof Element) { + try { + Method serialize = Class.forName(XMLUTIL_CLASS_FULL_NAME).getMethod(SERIALIZE_METHOD_NAME, Element.class); + return (String) serialize.invoke(null, arguments); + } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + if (arguments instanceof CharSequence) { + String arg = escapeBackslashes ? escapeBackslashes(arguments.toString()) : arguments.toString(); + if (arguments instanceof String) { + if (!inspect) return arg; + return !escapeBackslashes && multiline(arg) ? "'''" + arg + "'''" : SQ + arg.replace(SQ, "\\'") + SQ; + } + if (!inspect) return arg; + return !escapeBackslashes && multiline(arg) ? "\"\"\"" + arg + "\"\"\"" : DQ + arg.replace(DQ, "\\\"") + DQ; + } + try { + // TODO: For GROOVY-2599 do we need something like below but it breaks other things +// return (String) invokeMethod(arguments, "toString", EMPTY_ARGS); + return arguments.toString(); + } catch (RuntimeException ex) { + if (!safe) throw ex; + return handleFormattingException(arguments, ex); + } catch (Exception ex) { + if (!safe) throw new GroovyRuntimeException(ex); + return handleFormattingException(arguments, ex); + } + } + + private static boolean multiline(String s) { + return s.contains("\n") || s.contains("\r"); + } + + public static String escapeBackslashes(String orig) { + // must replace backslashes first, as the other replacements add backslashes not to be escaped + return orig + .replace("\\", "\\\\") // backslash + .replace("\n", "\\n") // line feed + .replace("\r", "\\r") // carriage return + .replace("\t", "\\t") // tab + .replace("\f", "\\f"); // form feed + } + + private static String handleFormattingException(Object item, Exception ex) { + + String hash; + try { + hash = Integer.toHexString(item.hashCode()); + } catch (Exception ignored) { + hash = "????"; + } + return "<" + typeName(item) + "@" + hash + ">"; + } + + private static String formatMap(Map map, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) { + if (map.isEmpty()) { + return "[:]"; + } + StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * map.size() * 2); + buffer.append('['); + boolean first = true; + for (Object o : map.entrySet()) { + if (first) { + first = false; + } else { + buffer.append(", "); + } + if (maxSize != -1 && buffer.length() > maxSize) { + buffer.append("..."); + break; + } + Map.Entry entry = (Map.Entry) o; + if (entry.getKey() == map) { + buffer.append("(this Map)"); + } else { + buffer.append(format(entry.getKey(), inspect, escapeBackslashes, sizeLeft(maxSize, buffer), safe)); + } + buffer.append(":"); + if (entry.getValue() == map) { + buffer.append("(this Map)"); + } else { + buffer.append(format(entry.getValue(), inspect, escapeBackslashes, sizeLeft(maxSize, buffer), safe)); + } + } + buffer.append(']'); + return buffer.toString(); + } + + private static int sizeLeft(int maxSize, StringBuilder buffer) { + return maxSize == -1 ? maxSize : max(0, maxSize - buffer.length()); + } + + private static String formatCollection(Collection collection, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) { + StringBuilder buffer = new StringBuilder(ITEM_ALLOCATE_SIZE * collection.size()); + buffer.append('['); + boolean first = true; + for (Object item : collection) { + if (first) { + first = false; + } else { + buffer.append(", "); + } + if (maxSize != -1 && buffer.length() > maxSize) { + buffer.append("..."); + break; + } + if (item == collection) { + buffer.append("(this Collection)"); + } else { + buffer.append(format(item, inspect, escapeBackslashes, sizeLeft(maxSize, buffer), safe)); + } + } + buffer.append(']'); + return buffer.toString(); + } + + /** + * A helper method to format the arguments types as a comma-separated list. + * + * @param arguments the type to process + * @return the string representation of the type + */ + public static String toTypeString(Object[] arguments) { + return toTypeString(arguments, -1); + } + + /** + * A helper method to format the arguments types as a comma-separated list. + * + * @param arguments the type to process + * @param maxSize stop after approximately this many characters and append '...', -1 means don't stop + * @return the string representation of the type + */ + public static String toTypeString(Object[] arguments, int maxSize) { + if (arguments == null) { + return "null"; + } + if (arguments.length == 0) { + return ""; + } + if (maxSize < 0) { + return stream(arguments) + .map(arg -> arg != null ? typeName(arg) : "null") + .collect(java.util.stream.Collectors.joining(", ")); + } + + var plainForm = new StringBuilder(); + var shortForm = new StringBuilder(); + for (int i = 0; i < arguments.length; i += 1) { + String type = arguments[i] != null ? typeName(arguments[i]) : "null"; + + if (plainForm.length() < maxSize) { + if (i > 0) plainForm.append(", "); + plainForm.append(type); + } else if (plainForm.charAt(plainForm.length() - 1) != '.') { + plainForm.append("..."); + } + + if (shortForm.length() < maxSize) { + if (i > 0) shortForm.append(", "); + String[] tokens = type.split("\\."); + for (int j = 0; j < tokens.length - 1; j += 1) { + // GROOVY-11270: reduce "foo.bar.Baz" to "f.b.Baz" + shortForm.appendCodePoint(tokens[j].codePointAt(0)).append('.'); + } + shortForm.append(tokens[tokens.length - 1]); + } else { + shortForm.append("..."); + break; + } + } + + return (plainForm.length() <= maxSize ? plainForm : shortForm).toString(); + } + + /** + * Gets the type name + * + * @param argument the object to find the type for + * @return the type name (slightly pretty format taking into account default imports) + */ + static String typeName(Object argument) { + Class aClass = argument.getClass(); + String pkgName = aClass.getPackage() == null ? "" : aClass.getPackage().getName(); + boolean useShort = DEFAULT_IMPORT_PKGS.contains(pkgName) || DEFAULT_IMPORT_CLASSES.contains(aClass.getName()); + return useShort ? aClass.getSimpleName() : aClass.getName(); + } + + /** + * A helper method to return the string representation of a map with bracket boundaries "[" and "]". + * + * @param arg the map to process + * @return the string representation of the map + */ + public static String toMapString(Map arg) { + return toMapString(arg, -1); + } + + /** + * A helper method to return the string representation of a map with bracket boundaries "[" and "]". + * + * @param arg the map to process + * @param maxSize stop after approximately this many characters and append '...', -1 means don't stop + * @return the string representation of the map + */ + public static String toMapString(Map arg, int maxSize) { + return formatMap(arg, false, false, maxSize, false); + } + + /** + * A helper method to return the string representation of a list with bracket boundaries "[" and "]". + * + * @param arg the collection to process + * @return the string representation of the collection + */ + public static String toListString(Collection arg) { + return toListString(arg, -1); + } + + /** + * A helper method to return the string representation of a list with bracket boundaries "[" and "]". + * + * @param arg the collection to process + * @param maxSize stop after approximately this many characters and append '...' + * @return the string representation of the collection + */ + public static String toListString(Collection arg, int maxSize) { + return toListString(arg, maxSize, false); + } + + /** + * A helper method to return the string representation of a list with bracket boundaries "[" and "]". + * + * @param arg the collection to process + * @param maxSize stop after approximately this many characters and append '...', -1 means don't stop + * @param safe whether to use a default object representation for any item in the collection if an exception occurs when generating its toString + * @return the string representation of the collection + */ + public static String toListString(Collection arg, int maxSize, boolean safe) { + return formatCollection(arg, false, false, maxSize, safe); + } + + /** + * A helper method to return the string representation of an array of objects + * with brace boundaries "[" and "]". + * + * @param arguments the array to process + * @return the string representation of the array + */ + public static String toArrayString(Object[] arguments) { + return toArrayString(arguments, false, false, -1, false); + } + + private static String toArrayString(Object[] array, boolean inspect, boolean escapeBackslashes, int maxSize, boolean safe) { + if (array == null) { + return "null"; + } + boolean first = true; + StringBuilder argBuf = new StringBuilder(array.length); + argBuf.append('['); + + for (Object item : array) { + if (first) { + first = false; + } else { + argBuf.append(", "); + } + if (maxSize != -1 && argBuf.length() > maxSize) { + argBuf.append("..."); + break; + } + if (item == array) { + argBuf.append("(this array)"); + } else { + argBuf.append(format(item, inspect, escapeBackslashes, sizeLeft(maxSize, argBuf), safe)); + } + } + argBuf.append(']'); + return argBuf.toString(); + } + + /** + * A helper method to return the string representation of an array of objects + * with brace boundaries "[" and "]". + * + * @param arguments the array to process + * @param maxSize stop after approximately this many characters and append '...' + * @param safe whether to use a default object representation for any item in the array if an exception occurs when generating its toString + * @return the string representation of the array + */ + public static String toArrayString(Object[] arguments, int maxSize, boolean safe) { + return toArrayString(arguments, false, false, maxSize, safe); + } + + /** + * Writes an object to a Writer using Groovy's default representation for the object. + */ + public static void write(Writer out, Object object) throws IOException { + if (object instanceof String) { + out.write((String) object); + } else if (object instanceof Object[]) { + out.write(toArrayString((Object[]) object)); + } else if (object instanceof Map) { + out.write(toMapString((Map) object)); + } else if (object instanceof Collection) { + out.write(toListString((Collection) object)); + } else if (object instanceof Writable) { + Writable writable = (Writable) object; + writable.writeTo(out); + } else if (object instanceof InputStream || object instanceof Reader) { + // Copy stream to stream + Reader reader; + if (object instanceof InputStream) { + reader = new InputStreamReader((InputStream) object); + } else { + reader = (Reader) object; + } + + try (Reader r = reader) { + char[] chars = new char[8192]; + for (int i; (i = r.read(chars)) != -1; ) { + out.write(chars, 0, i); + } + } + } else { + out.write(toString(object)); + } + } + + /** + * Appends an object to an Appendable using Groovy's default representation for the object. + */ + public static void append(Appendable out, Object object) throws IOException { + if (object instanceof String) { + out.append((String) object); + } else if (object instanceof Object[]) { + out.append(toArrayString((Object[]) object)); + } else if (object instanceof Map) { + out.append(toMapString((Map) object)); + } else if (object instanceof Collection) { + out.append(toListString((Collection) object)); + } else if (object instanceof Writable) { + Writable writable = (Writable) object; + Writer stringWriter = new StringBuilderWriter(); + writable.writeTo(stringWriter); + out.append(stringWriter.toString()); + } else if (object instanceof InputStream || object instanceof Reader) { + // Copy stream to stream + try (Reader reader = + object instanceof InputStream + ? new InputStreamReader((InputStream) object) + : (Reader) object) { + char[] chars = new char[8192]; + for (int i; (i = reader.read(chars)) != -1; ) { + for (int j = 0; j < i; j++) { + out.append(chars[j]); + } + } + } + } else { + out.append(toString(object)); + } + } +} diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java new file mode 100644 index 00000000000..f8d5a0dcaeb --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/GroovydocVisitor.java @@ -0,0 +1,394 @@ +/* + * Copyright ish group pty ltd 2024. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + */ + +/* + * 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. + */ + +// TODO: Remove after update Groovy v4, this part of code is taken from this version +package org.apache.groovy.antlr.override; + +import groovy.lang.groovydoc.Groovydoc; +import org.codehaus.groovy.ast.*; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.control.ResolveVisitor; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.groovydoc.GroovyClassDoc; +import org.codehaus.groovy.groovydoc.GroovyFieldDoc; +import org.codehaus.groovy.groovydoc.GroovyMethodDoc; +import org.codehaus.groovy.tools.groovydoc.*; + +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.apache.groovy.antlr.override.RecordTypeASTTransformation.recordNative; +import static org.codehaus.groovy.transform.trait.Traits.isTrait; + +/** + * A visitor which collects Groovydoc information. + */ +public class GroovydocVisitor extends ClassCodeVisitorSupport { + private final SourceUnit unit; + private final List links; + private String packagePath; + private SimpleGroovyClassDoc currentClassDoc = null; + private Map classDocs = new HashMap<>(); + private final Properties properties; + private static final String FS = "/"; + + public GroovydocVisitor(final SourceUnit unit, String packagePath, List links) { + this(unit, packagePath, links, new Properties()); + } + + public GroovydocVisitor(final SourceUnit unit, String packagePath, List links, Properties properties) { + this.unit = unit; + this.packagePath = packagePath; + this.links = links; + this.properties = properties; + } + + @Override + protected SourceUnit getSourceUnit() { + return unit; + } + + @Override + public void visitClass(ClassNode node) { + final Map aliases = new HashMap<>(); + final List imports = new ArrayList<>(); + for (ImportNode iNode : node.getModule().getImports()) { + String name = iNode.getClassName(); + imports.add(name.replace('.', '/')); + if (iNode.getAlias() != null && !iNode.getAlias().isEmpty()) { + aliases.put(iNode.getAlias(), name.replace('.', '/')); + } + } + for (ImportNode iNode : node.getModule().getStarImports()) { + String name = iNode.getPackageName()+"*"; + imports.add(name.replace('.', '/')); + } + String name = node.getNameWithoutPackage(); + + if (node instanceof InnerClassNode) { + name = name.replace('$', '.'); + } + currentClassDoc = new SimpleGroovyClassDoc(withDefaultImports(imports), aliases, name, links); + if (node.isEnum()) { + currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.ENUM_DEF); + } else if (recordNative(node)) { // node is record + currentClassDoc.setTokenType(org.apache.groovy.antlr.override.SimpleGroovyDoc.RECORD_DEF); + } else if (node.isAnnotationDefinition()) { + currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.ANNOTATION_DEF); + } else if (isTrait(node)) { + currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.TRAIT_DEF); + } else if (node.isInterface()) { + currentClassDoc.setTokenType(org.codehaus.groovy.tools.groovydoc.SimpleGroovyDoc.INTERFACE_DEF); + } + if (node.isScript()) { + if ("false".equals(properties.getProperty("processScripts", "true"))) return; + currentClassDoc.setScript(true); + } + for (ClassNode iface : node.getInterfaces()) { + currentClassDoc.addInterfaceName(makeType(iface)); + } + currentClassDoc.setRawCommentText(getDocContent(node.getGroovydoc())); + currentClassDoc.setNameWithTypeArgs(name + genericTypesAsString(node.getGenericsTypes())); + if (!node.isInterface() && !node.isEnum() && node.getSuperClass() != null) { + String superName = makeType(node.getSuperClass()); + currentClassDoc.setSuperClassName(superName); + String superSimpleName = node.getSuperClass().getNameWithoutPackage(); + if (!classDocs.containsKey(superSimpleName)) { + SimpleGroovyClassDoc superDoc = new SimpleGroovyClassDoc(imports, superName); + superDoc.setFullPathName(superName); + } + } + processModifiers(currentClassDoc, node, node.getModifiers()); + processAnnotations(currentClassDoc, node); + if (Modifier.isAbstract(node.getModifiers())) { + currentClassDoc.setAbstract(true); + } + currentClassDoc.setFullPathName(packagePath + FS + name); + currentClassDoc.setGroovy(true); + classDocs.put(currentClassDoc.getFullPathName(), currentClassDoc); + super.visitClass(node); + SimpleGroovyClassDoc parent = currentClassDoc; + if (currentClassDoc.isClass() && currentClassDoc.constructors().length == 0) { + // add default no-arg constructor, but not for interfaces, traits, enums, or annotation definitions + SimpleGroovyConstructorDoc cons = new SimpleGroovyConstructorDoc(name, currentClassDoc); + cons.setPublic(true); + currentClassDoc.add(cons); + } + Iterator innerClasses = node.getInnerClasses(); + while (innerClasses.hasNext()) { + visitClass(innerClasses.next()); + parent.addNested(currentClassDoc); + currentClassDoc = parent; + } + } + + private List withDefaultImports(List imports) { + imports = imports != null ? imports : new ArrayList<>(); + imports.add(packagePath + "/*"); // everything in this package + for (String pkg : ResolveVisitor.DEFAULT_IMPORTS) { + imports.add(pkg.replace('.', '/') + "*"); + } + return imports; + } + + private static final Pattern JAVADOC_COMMENT_PATTERN = Pattern.compile("(?s)/\\*\\*(.*?)\\*/"); + + private String getDocContent(Groovydoc groovydoc) { + if (groovydoc == null) return ""; + String result = groovydoc.getContent(); + if (result == null) result = ""; + Matcher m = JAVADOC_COMMENT_PATTERN.matcher(result); + if (m.find()) { + result = m.group(1).trim(); + } + return result; + } + + private void processAnnotations(SimpleGroovyProgramElementDoc element, AnnotatedNode node) { + for (AnnotationNode an : node.getAnnotations()) { + String name = an.getClassNode().getName(); + element.addAnnotationRef(new SimpleGroovyAnnotationRef(name, an.getText())); + } + } + + private void processAnnotations(SimpleGroovyParameter param, AnnotatedNode node) { + for (AnnotationNode an : node.getAnnotations()) { + String name = an.getClassNode().getName(); + param.addAnnotationRef(new SimpleGroovyAnnotationRef(name, an.getText())); + } + } + + @Override + public void visitConstructor(ConstructorNode node) { + if (node.isSynthetic()) return; + SimpleGroovyConstructorDoc cons = new SimpleGroovyConstructorDoc(currentClassDoc.simpleTypeName(), currentClassDoc); + setConstructorOrMethodCommon(node, cons); + currentClassDoc.add(cons); + super.visitConstructor(node); + } + + @Override + public void visitMethod(MethodNode node) { + if (currentClassDoc.isEnum() && "$INIT".equals(node.getName())) + return; + if (node.isSynthetic()) return; + if ("false".equals(properties.getProperty("includeMainForScripts", "true")) + && currentClassDoc.isScript() && "main".equals(node.getName()) && node.isStatic() && node.getParameters().length == 1) + return; + + SimpleGroovyMethodDoc meth = new SimpleGroovyMethodDoc(node.getName(), currentClassDoc); + meth.setReturnType(new SimpleGroovyType(makeType(node.getReturnType()))); + setConstructorOrMethodCommon(node, meth); + currentClassDoc.add(meth); + processPropertiesFromGetterSetter(meth); + super.visitMethod(node); + meth.setTypeParameters(genericTypesAsString(node.getGenericsTypes())); + } + + private String genericTypesAsString(GenericsType[] genericsTypes) { + if (genericsTypes == null || genericsTypes.length == 0) + return ""; + return "<" + ArrayGroovyMethods.join(genericsTypes, ", ") + ">"; + } + + private void processPropertiesFromGetterSetter(SimpleGroovyMethodDoc currentMethodDoc) { + String methodName = currentMethodDoc.name(); + int len = methodName.length(); + String prefix; + String propName; + if (len > 3 && methodName.startsWith("get")) { + prefix = "get"; + propName = methodName.substring(3); + } else if (len > 3 && methodName.startsWith("set")) { + prefix = "set"; + propName = methodName.substring(3); + } else if (len > 2 && methodName.startsWith("is")) { + prefix = "is"; + propName = methodName.substring(2); + } else { + // Not a (get/set/is) method that contains a property name + return; + } + + for (GroovyFieldDoc field : currentClassDoc.properties()) { + if (propName.equals(field.name())) return; + } + SimpleGroovyClassDoc classDoc = currentClassDoc; + // TODO: not sure why but groovy.ui.view.BasicContentPane#buildOutputArea classDoc is null + if (classDoc == null) { + return; + } + GroovyMethodDoc[] methods = classDoc.methods(); + + //find expected method name + String expectedMethodName; + if ("set".equals(prefix) && (currentMethodDoc.parameters().length >= 1 && !currentMethodDoc.parameters()[0].typeName().equals("boolean"))) { + expectedMethodName = "get" + propName; + } else if ("get".equals(prefix) && !currentMethodDoc.returnType().typeName().equals("boolean")) { + expectedMethodName = "set" + propName; + } else if ("is".equals(prefix)) { + expectedMethodName = "set" + propName; + } else { + expectedMethodName = "is" + propName; + } + + for (GroovyMethodDoc methodDoc : methods) { + if (methodDoc.name().equals(expectedMethodName)) { + + //extract the field name + String fieldName = propName.substring(0, 1).toLowerCase() + propName.substring(1); + SimpleGroovyFieldDoc currentFieldDoc = new SimpleGroovyFieldDoc(fieldName, classDoc); + + //find the type of the field; if it's a setter, need to get the type of the params + if (expectedMethodName.startsWith("set") && methodDoc.parameters().length >= 1) { + String typeName = methodDoc.parameters()[0].typeName(); + currentFieldDoc.setType(new SimpleGroovyType(typeName)); + } else { + //if it's not setter, get the type info of the return type of the get* method + currentFieldDoc.setType(methodDoc.returnType()); + } + + if (methodDoc.isPublic() && currentMethodDoc.isPublic()) { + classDoc.addProperty(currentFieldDoc); + break; + } + } + } + } + + @Override + public void visitProperty(PropertyNode node) { + String name = node.getName(); + SimpleGroovyFieldDoc fieldDoc = new SimpleGroovyFieldDoc(name, currentClassDoc); + fieldDoc.setType(new SimpleGroovyType(makeType(node.getType()))); + int mods = node.getModifiers(); + if (!hasAnno(node.getField(), "PackageScope")) { + processModifiers(fieldDoc, node.getField(), mods); + Groovydoc groovydoc = node.getGroovydoc(); + fieldDoc.setRawCommentText(groovydoc == null ? "" : getDocContent(groovydoc)); + currentClassDoc.addProperty(fieldDoc); + } + processAnnotations(fieldDoc, node.getField()); + super.visitProperty(node); + } + + private String makeType(ClassNode node) { + final ClassNode cn = node.isArray() ? node.getComponentType() : node; + return cn.getName().replace('.', '/').replace('$', '.') + + genericTypesAsString(cn.getGenericsTypes()) + + (node.isArray() ? "[]" : ""); + } + + @Override + public void visitDeclarationExpression(DeclarationExpression expression) { + if (currentClassDoc.isScript()) { + if (hasAnno(expression, "Field")) { + VariableExpression varx = expression.getVariableExpression(); + SimpleGroovyFieldDoc field = new SimpleGroovyFieldDoc(varx.getName(), currentClassDoc); + field.setType(new SimpleGroovyType(makeType(varx.getType()))); + int mods = varx.getModifiers(); + processModifiers(field, varx, mods); + boolean isProp = (mods & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0; + if (isProp) { + currentClassDoc.addProperty(field); + } else { + currentClassDoc.add(field); + } + } + } + super.visitDeclarationExpression(expression); + } + + private void processModifiers(SimpleGroovyProgramElementDoc element, AnnotatedNode node, int mods) { + if (Modifier.isStatic(mods)) { + element.setStatic(true); + } + if (hasAnno(node, "PackageScope")) { + element.setPackagePrivate(true); + } else { + if (Modifier.isPublic(mods)) { + element.setPublic(true); + } else if (Modifier.isProtected(mods)) { + element.setProtected(true); + } else if (Modifier.isPrivate(mods)) { + element.setPrivate(true); + } else { + element.setPackagePrivate(true); + } + } + if (Modifier.isFinal(mods)) { + element.setFinal(true); + } + } + + @Override + public void visitField(FieldNode node) { + if (node.isSynthetic()) return; + String name = node.getName(); + SimpleGroovyFieldDoc fieldDoc = new SimpleGroovyFieldDoc(name, currentClassDoc); + fieldDoc.setType(new SimpleGroovyType(makeType(node.getType()))); + processModifiers(fieldDoc, node, node.getModifiers()); + processAnnotations(fieldDoc, node); + fieldDoc.setRawCommentText(getDocContent(node.getGroovydoc())); + if (node.isEnum()) { + currentClassDoc.addEnumConstant(fieldDoc); + } else { + currentClassDoc.add(fieldDoc); + } + super.visitField(node); + } + + private void setConstructorOrMethodCommon(MethodNode node, SimpleGroovyExecutableMemberDoc methOrCons) { + methOrCons.setRawCommentText(getDocContent(node.getGroovydoc())); + processModifiers(methOrCons, node, node.getModifiers()); + processAnnotations(methOrCons, node); + if (node.isAbstract()) { + methOrCons.setAbstract(true); + } + for (Parameter param : node.getParameters()) { + SimpleGroovyParameter p = new SimpleGroovyParameter(param.getName()); + p.setType(new SimpleGroovyType(makeType(param.getType()))); + processAnnotations(p, param); + methOrCons.add(p); + } + } + + private boolean hasAnno(AnnotatedNode node, String annoSuffix) { + for (AnnotationNode annotationNode : node.getAnnotations()) { + // check name to cover non/resolved cases + if (annotationNode.getClassNode().getName().endsWith(annoSuffix)) return true; + } + return false; + } + + public Map getGroovyClassDocs() { + return classDocs; + } +} diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java new file mode 100644 index 00000000000..6528e723451 --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/Opcodes.java @@ -0,0 +1,18 @@ +/* + * Copyright ish group pty ltd 2024. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + */ + +package org.apache.groovy.antlr.override; + +// TODO: Remove after update Groovy v4, this part of code is taken from this version + +public class Opcodes { + + public static final int ACC_PUBLIC = 0x0001; // class, field, method; + public static final int ACC_PRIVATE = 0x0002; // class, field, method + public static final int ACC_PROTECTED = 0x0004; // class, field, method +} diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md new file mode 100644 index 00000000000..ba16b94509c --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/README.md @@ -0,0 +1,21 @@ +GroovyDocVisitor is a special class for Parsing Groovy documents that was introduced in version > 4. + +Since this class is not yet in the version used, it was copied into the project and also included manually. + +After updating Groovy > 4, all contents of the package + +`buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override` + +can be removed and standard Document Parsing mechanisms can be used. + +// TODO after Groovy update + +Change class [DslGroovyRootDocBuilder.groovy](..%2F..%2F..%2F..%2F..%2F..%2Fgroovy%2Fau%2Fcom%2Fish%2Fdocs%2FDslGroovyRootDocBuilder.groovy) + +```` +private Map parseGroovy(String src, String packagePath, String file) { + ... +} +```` + +Acourding to https://issues.apache.org/jira/browse/GROOVY-11269 \ No newline at end of file diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java new file mode 100644 index 00000000000..f973dca8aa7 --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/RecordTypeASTTransformation.java @@ -0,0 +1,55 @@ +/* + * Copyright ish group pty ltd 2024. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + */ + +/* + * 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. + */ +package org.apache.groovy.antlr.override; + +import org.apache.groovy.lang.annotation.Incubating; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.transform.GroovyASTTransformation; + +/** + * Handles generation of code for the @RecordType annotation. + */ + +// TODO: Remove after update Groovy v4, this part of code is taken from this version + +@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) +public class RecordTypeASTTransformation { + + private static final String RECORD_CLASS_NAME = "java.lang.Record"; + + /** + * Indicates that the given classnode is a native JVM record class. + * For classes being compiled, this will only be valid after the + * {@code RecordTypeASTTransformation} transform has been invoked. + */ + @Incubating + public static boolean recordNative(final ClassNode node) { + return node.getUnresolvedSuperClass() != null && RECORD_CLASS_NAME.equals(node.getUnresolvedSuperClass().getName()); + } + +} diff --git a/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java new file mode 100644 index 00000000000..b792999d1e6 --- /dev/null +++ b/buildSrc/apidoc/src/main/java/org/apache/groovy/antlr/override/SimpleGroovyDoc.java @@ -0,0 +1,17 @@ +/* + * Copyright ish group pty ltd 2024. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + */ + +package org.apache.groovy.antlr.override; + +// TODO: Remove after update Groovy v4, this part of code is taken from this version + +public class SimpleGroovyDoc { + + public static final int RECORD_DEF = 16; + +}