diff --git a/.github/workflows/receive-pr.yml b/.github/workflows/receive-pr.yml index 63e17f40a67..67d695322ed 100644 --- a/.github/workflows/receive-pr.yml +++ b/.github/workflows/receive-pr.yml @@ -17,64 +17,3 @@ jobs: uses: openrewrite/gh-automation/.github/workflows/receive-pr.yml@main with: recipe: 'org.openrewrite.recipes.OpenRewriteBestPracticesSubset' - rewrite_yml: | - --- - type: specs.openrewrite.org/v1beta/recipe - name: org.openrewrite.recipes.OpenRewriteBestPracticesSubset - displayName: OpenRewrite best practices - description: Best practices for OpenRewrite recipe development. - recipeList: - - org.openrewrite.recipes.JavaRecipeBestPracticesSubset - - org.openrewrite.recipes.RecipeTestingBestPracticesSubset - - org.openrewrite.recipes.RecipeNullabilityBestPracticesSubset - #- org.openrewrite.java.OrderImports - - org.openrewrite.java.format.EmptyNewlineAtEndOfFile - - org.openrewrite.staticanalysis.InlineVariable - - org.openrewrite.staticanalysis.MissingOverrideAnnotation - - org.openrewrite.staticanalysis.UseDiamondOperator - --- - type: specs.openrewrite.org/v1beta/recipe - name: org.openrewrite.recipes.JavaRecipeBestPracticesSubset - displayName: Java Recipe best practices - description: Best practices for Java recipe development. - preconditions: - - org.openrewrite.java.search.FindTypes: - fullyQualifiedTypeName: org.openrewrite.Recipe - checkAssignability: true - recipeList: - - org.openrewrite.java.recipes.BlankLinesAroundFieldsWithAnnotations - - org.openrewrite.java.recipes.ExecutionContextParameterName - - org.openrewrite.java.recipes.MissingOptionExample - - org.openrewrite.java.recipes.RecipeEqualsAndHashCodeCallSuper - - org.openrewrite.java.recipes.UseTreeRandomId - - org.openrewrite.staticanalysis.NeedBraces - #- org.openrewrite.staticanalysis.RemoveSystemOutPrintln - --- - type: specs.openrewrite.org/v1beta/recipe - name: org.openrewrite.recipes.RecipeTestingBestPracticesSubset - displayName: Recipe testing best practices - description: Best practices for testing recipes. - preconditions: - - org.openrewrite.java.search.FindTypes: - fullyQualifiedTypeName: org.openrewrite.test.RewriteTest - checkAssignability: true - recipeList: - - org.openrewrite.java.recipes.RewriteTestClassesShouldNotBePublic - #- org.openrewrite.java.recipes.SelectRecipeExamples - - org.openrewrite.java.recipes.SourceSpecTextBlockIndentation - - org.openrewrite.java.testing.cleanup.RemoveTestPrefix - - org.openrewrite.java.testing.cleanup.TestsShouldNotBePublic - - org.openrewrite.staticanalysis.NeedBraces - - org.openrewrite.staticanalysis.RemoveSystemOutPrintln - --- - type: specs.openrewrite.org/v1beta/recipe - name: org.openrewrite.recipes.RecipeNullabilityBestPracticesSubset - displayName: Recipe nullability best practices - description: Use OpenRewrite internal nullability annotations; drop JetBrains annotations; use `package-info.java` instead. - recipeList: - - org.openrewrite.staticanalysis.NullableOnMethodReturnType - - org.openrewrite.java.RemoveAnnotation: - annotationPattern: '@org.jetbrains.annotations.NotNull' - - org.openrewrite.java.RemoveAnnotation: - annotationPattern: '@jakarta.annotation.Nonnull' - #- org.openrewrite.java.jspecify.MigrateToJspecify diff --git a/README.md b/README.md index 6ff6438cf8a..fd3be8c71bc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@

- OpenRewrite + + + + + OpenRewrite Logo + +

diff --git a/doc/logo-oss-dark.svg b/doc/logo-oss-dark.svg new file mode 100644 index 00000000000..d462da46bd9 --- /dev/null +++ b/doc/logo-oss-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/doc/logo-oss-light.svg b/doc/logo-oss-light.svg new file mode 100644 index 00000000000..28c6e76e49e --- /dev/null +++ b/doc/logo-oss-light.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9751024bf3d..046b4f50491 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 +distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index ef8625e288e..cce20ae54f8 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -15,11 +15,7 @@ dependencies { api("org.jspecify:jspecify:latest.release") - // Pinning okhttp while waiting on 5.0.0 - // https://github.com/openrewrite/rewrite/issues/1479 - compileOnly("com.squareup.okhttp3:okhttp:4.9.3") - - implementation("org.apache.commons:commons-compress:latest.release") + implementation("org.apache.commons:commons-lang3:latest.release") implementation("io.micrometer:micrometer-core:1.9.+") implementation("io.github.classgraph:classgraph:latest.release") diff --git a/rewrite-core/src/main/java/org/openrewrite/CursorValidatingExecutionContextView.java b/rewrite-core/src/main/java/org/openrewrite/CursorValidatingExecutionContextView.java index 35fd3616483..cb8be5ce807 100644 --- a/rewrite-core/src/main/java/org/openrewrite/CursorValidatingExecutionContextView.java +++ b/rewrite-core/src/main/java/org/openrewrite/CursorValidatingExecutionContextView.java @@ -15,8 +15,11 @@ */ package org.openrewrite; +import org.jspecify.annotations.Nullable; + public class CursorValidatingExecutionContextView extends DelegatingExecutionContext { private static final String VALIDATE_CURSOR_ACYCLIC = "org.openrewrite.CursorValidatingExecutionContextView.ValidateCursorAcyclic"; + private static final String VALIDATE_CTX_MUTATION = "org.openrewrite.CursorValidatingExecutionContextView.ValidateExecutionContextImmutability"; public CursorValidatingExecutionContextView(ExecutionContext delegate) { super(delegate); @@ -38,4 +41,23 @@ public CursorValidatingExecutionContextView setValidateCursorAcyclic(boolean val putMessage(VALIDATE_CURSOR_ACYCLIC, validateCursorAcyclic); return this; } + + public CursorValidatingExecutionContextView setValidateImmutableExecutionContext(boolean allowExecutionContextMutation) { + putMessage(VALIDATE_CTX_MUTATION, allowExecutionContextMutation); + return this; + } + + @Override + public void putMessage(String key, @Nullable Object value) { + boolean mutationAllowed = !getMessage(VALIDATE_CTX_MUTATION, false) || key.equals(VALIDATE_CURSOR_ACYCLIC) || key.equals(VALIDATE_CTX_MUTATION) + || key.equals(ExecutionContext.CURRENT_CYCLE) || key.equals(ExecutionContext.CURRENT_RECIPE) || key.equals(ExecutionContext.DATA_TABLES) + || key.startsWith("org.openrewrite.maven"); // MavenExecutionContextView stores metrics + assert mutationAllowed + : "Recipe mutated execution context key \"" + key + "\". " + + "Recipes should not mutate the contents of the ExecutionContext as it allows mutable state to leak between " + + "recipes, opening the door for difficult to debug recipe composition errors. " + + "If you need to store state within the execution of a single recipe use Cursor messaging. " + + "If you want to pass state between recipes, use a ScanningRecipe instead."; + super.putMessage(key, value); + } } diff --git a/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java b/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java index bf0aa62a234..05362e7c79b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java @@ -23,6 +23,9 @@ import org.openrewrite.table.SourcesFiles; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; @Value @@ -31,7 +34,9 @@ public class FindSourceFiles extends Recipe { transient SourcesFiles results = new SourcesFiles(this); @Option(displayName = "File pattern", - description = "A glob expression representing a file path to search for (relative to the project root). Blank/null matches all.", + description = "A glob expression representing a file path to search for (relative to the project root). Blank/null matches all." + + "Multiple patterns may be specified, separated by a semicolon `;`. " + + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match.", required = false, example = ".github/workflows/*.yml") @Nullable @@ -44,7 +49,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Find files by source path."; + return "Find files by source path. Paths are always interpreted as relative to the repository root."; } @Override @@ -56,7 +61,7 @@ public TreeVisitor getVisitor() { if (tree instanceof SourceFile) { SourceFile sourceFile = (SourceFile) tree; Path sourcePath = sourceFile.getSourcePath(); - if (StringUtils.isBlank(filePattern) || PathUtils.matchesGlob(sourcePath, normalize(filePattern))) { + if (matches(sourcePath)) { results.insertRow(ctx, new SourcesFiles.Row(sourcePath.toString(), tree.getClass().getSimpleName())); return SearchResult.found(sourceFile); @@ -64,6 +69,22 @@ public TreeVisitor getVisitor() { } return tree; } + + String @Nullable[] filePatterns; + + private boolean matches(Path sourcePath) { + if (filePatterns == null) { + filePatterns = Optional.ofNullable(filePattern) + .map(it -> it.split(";")) + .map(Arrays::stream) + .orElseGet(Stream::empty) + .map(String::trim) + .filter(StringUtils::isNotEmpty) + .map(FindSourceFiles::normalize) + .toArray(String[]::new); + } + return filePatterns.length == 0 || Arrays.stream(filePatterns).anyMatch(pattern -> PathUtils.matchesGlob(sourcePath, pattern)); + } }; } @@ -75,4 +96,5 @@ private static String normalize(String filePattern) { } return filePattern; } + } diff --git a/rewrite-core/src/main/java/org/openrewrite/GitRemote.java b/rewrite-core/src/main/java/org/openrewrite/GitRemote.java index 6816c8e9c8b..fd42a076e68 100644 --- a/rewrite-core/src/main/java/org/openrewrite/GitRemote.java +++ b/rewrite-core/src/main/java/org/openrewrite/GitRemote.java @@ -113,51 +113,64 @@ public Parser() { * @return the clone url */ public URI toUri(GitRemote remote, String protocol) { + return buildUri(remote.service, remote.origin, remote.path, protocol); + } + + /** + * Build a {@link URI} clone url from components, if that protocol is supported (configured) by the matched server + * + * @param service the type of SCM service + * @param origin the origin of the SCM service, any protocol will be stripped (and not used for matching) + * @param path the path to the repository + * @param protocol the protocol to use. Supported protocols: ssh, http, https + * @return the clone URL if it could be created. + * @throws IllegalArgumentException if the protocol is not supported by the server. + */ + public URI buildUri(Service service, String origin, String path, String protocol) { if (!ALLOWED_PROTOCOLS.contains(protocol)) { throw new IllegalArgumentException("Invalid protocol: " + protocol + ". Must be one of: " + ALLOWED_PROTOCOLS); } URI selectedBaseUrl; - - if (remote.service == Service.Unknown) { - if (PORT_PATTERN.matcher(remote.origin).find()) { - throw new IllegalArgumentException("Unable to determine protocol/port combination for an unregistered origin with a port: " + remote.origin); + if (service == Service.Unknown) { + if (PORT_PATTERN.matcher(origin).find()) { + throw new IllegalArgumentException("Unable to determine protocol/port combination for an unregistered origin with a port: " + origin); } - selectedBaseUrl = URI.create(protocol + "://" + stripProtocol(remote.origin)); + selectedBaseUrl = URI.create(protocol + "://" + stripProtocol(origin)); } else { selectedBaseUrl = servers.stream() .filter(server -> server.allOrigins() .stream() - .anyMatch(origin -> origin.equalsIgnoreCase(stripProtocol(remote.origin))) + .anyMatch(o -> o.equalsIgnoreCase(stripProtocol(origin))) ) .flatMap(server -> server.getUris().stream()) - .filter(uri -> uri.getScheme().equals(protocol)) + .filter(uri -> Parser.normalize(uri).getScheme().equals(protocol)) .findFirst() .orElseGet(() -> { - URI normalizedUri = Parser.normalize(remote.origin); + URI normalizedUri = Parser.normalize(origin); if (!normalizedUri.getScheme().equals(protocol)) { - throw new IllegalStateException("No matching server found that supports ssh for origin: " + remote.origin); + throw new IllegalArgumentException("Unable to build clone URL. No matching server found that supports " + protocol + " for origin: " + origin); } return normalizedUri; }); } - String path = remote.path.replaceFirst("^/", ""); + path = path.replaceFirst("^/", ""); boolean ssh = protocol.equals("ssh"); - switch (remote.service) { + switch (service) { case Bitbucket: if (!ssh) { - path = "scm/" + remote.path; + path = "scm/" + path; } break; case AzureDevOps: if (ssh) { - path = "v3/" + remote.path; + path = "v3/" + path; } else { - path = remote.path.replaceFirst("([^/]+)/([^/]+)/(.*)", "$1/$2/_git/$3"); + path = path.replaceFirst("([^/]+)/([^/]+)/(.*)", "$1/$2/_git/$3"); } break; } - if (remote.service != Service.AzureDevOps) { + if (service != Service.AzureDevOps) { path += ".git"; } String maybeSlash = selectedBaseUrl.toString().endsWith("/") ? "" : "/"; @@ -203,11 +216,18 @@ public Parser registerRemote(Service service, String origin) { return this; } + /** + * Find a registered remote server by an origin. + * + * @param origin the origin of the server. Any protocol will be stripped (and not used to match) + * @return The server if found, or an unknown type server with a normalized url/origin if not found. + */ public RemoteServer findRemoteServer(String origin) { - return servers.stream().filter(server -> server.origin.equalsIgnoreCase(origin)) + String strippedOrigin = stripProtocol(origin); + return servers.stream().filter(server -> server.origin.equalsIgnoreCase(strippedOrigin)) .findFirst() .orElseGet(() -> { - URI normalizedUri = normalize(origin); + URI normalizedUri = normalize(strippedOrigin); String normalizedOrigin = normalizedUri.getHost() + maybePort(normalizedUri.getPort(), normalizedUri.getScheme()); return new RemoteServer(Service.Unknown, normalizedOrigin, normalizedUri); }); @@ -281,6 +301,10 @@ private String repositoryPath(RemoteServerMatch match, URI normalizedUri) { private static final Pattern PORT_PATTERN = Pattern.compile(":\\d+(/.+)(/.+)+"); + static URI normalize(URI url) { + return normalize(url.toString()); + } + static URI normalize(String url) { try { URIish uri = new URIish(url); @@ -374,10 +398,11 @@ public Set allOrigins() { Set origins = new LinkedHashSet<>(); origins.add(origin); for (URI uri : uris) { - URI normalized = Parser.normalize(uri.toString()); + URI normalized = Parser.normalize(uri); origins.add(Parser.stripProtocol(normalized.toString())); } return origins; } + } } diff --git a/rewrite-core/src/main/java/org/openrewrite/PathUtils.java b/rewrite-core/src/main/java/org/openrewrite/PathUtils.java index 96213005bf7..56a607d9b4d 100755 --- a/rewrite-core/src/main/java/org/openrewrite/PathUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/PathUtils.java @@ -92,8 +92,10 @@ public static boolean matchesGlob(@Nullable Path path, @Nullable String globPatt } } return false; - } else { // If eitherOrPatterns is empty and excludedPatterns is not - if (!matchesGlob(convertNegationToWildcard(globPattern), relativePath)) { + } else { + // When only a segment of a path is negated the other segments must still match + String wildcard = convertNegationToWildcard(globPattern); + if (!matchesGlob(wildcard, relativePath)) { return false; } for (String excludedPattern : excludedPatterns) { @@ -214,9 +216,9 @@ private static boolean matchesGlob(String pattern, String path) { public static String convertNegationToWildcard(String globPattern) { // Regular expression to match !(...) - String negationPattern = "\\!\\((.*?)\\)"; + String negationPattern = "!\\((.*?)\\)"; // Replace all negation patterns with * - return globPattern.replaceAll(negationPattern, "*"); + return globPattern.replaceAll(negationPattern, "**"); } public static List getExcludedPatterns(String globPattern) { @@ -227,7 +229,7 @@ public static List getExcludedPatterns(String globPattern) { List excludedPatterns = new ArrayList<>(3); // Regular expression to match !(...) - String negationPattern = "\\!\\((.*?)\\)"; + String negationPattern = "!\\((.*?)\\)"; Pattern pattern = Pattern.compile(negationPattern); Matcher matcher = pattern.matcher(globPattern); @@ -251,14 +253,14 @@ public static List getEitherOrPatterns(String globPattern) { List eitherOrPatterns = new ArrayList<>(3); // Regular expression to match {...} - String eitherOrPattern = "\\{(.*?)\\}"; + String eitherOrPattern = "\\{(.*?)}"; Pattern pattern = Pattern.compile(eitherOrPattern); Matcher matcher = pattern.matcher(globPattern); // Find all possible patterns and generate patterns while (matcher.find()) { String eitherOrContent = matcher.group(1); - String[] options = eitherOrContent.split("\\,"); + String[] options = eitherOrContent.split(","); for (String option : options) { eitherOrPatterns.add(globPattern.replace(matcher.group(), option)); } diff --git a/rewrite-core/src/main/java/org/openrewrite/SourceFileWithReferences.java b/rewrite-core/src/main/java/org/openrewrite/SourceFileWithReferences.java index d53fa550e7a..bb1584aaabf 100644 --- a/rewrite-core/src/main/java/org/openrewrite/SourceFileWithReferences.java +++ b/rewrite-core/src/main/java/org/openrewrite/SourceFileWithReferences.java @@ -21,6 +21,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.trait.Reference; +import java.lang.ref.SoftReference; import java.util.*; @Incubating(since = "8.39.0") @@ -28,31 +29,32 @@ public interface SourceFileWithReferences extends SourceFile { References getReferences(); + default SoftReference build(@Nullable SoftReference<@Nullable References> references) { + References cache = references == null ? null : references.get(); + if (cache == null || cache.getSourceFile() != this) { + return new SoftReference<>(References.build(this)); + } + return references; + } + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - @Getter class References { + @Getter(AccessLevel.PRIVATE) private final SourceFile sourceFile; + @Getter private final Set references; public Collection findMatches(Reference.Matcher matcher) { - return findMatchesInternal(matcher, null); - } - - public Collection findMatches(Reference.Matcher matcher, Reference.Kind kind) { - return findMatchesInternal(matcher, kind); - } - - private List findMatchesInternal(Reference.Matcher matcher, Reference.@Nullable Kind kind) { List list = new ArrayList<>(); for (Reference ref : references) { - if ((kind == null || ref.getKind().equals(kind)) && ref.matches(matcher) ) { + if (ref.matches(matcher)) { list.add(ref); } } return list; } - public static References build(SourceFile sourceFile) { + private static References build(SourceFile sourceFile) { Set references = new HashSet<>(); ServiceLoader loader = ServiceLoader.load(Reference.Provider.class); loader.forEach(provider -> { diff --git a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java index 3cabc8c209a..64179d985ab 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java @@ -249,12 +249,23 @@ public Builder scanYamlResources() { */ @SuppressWarnings("unused") public Builder scanJar(Path jar, Collection dependencies, ClassLoader classLoader) { - List list = new ArrayList<>(); + List firstPassLoaderList = new ArrayList<>(); for (Path dep : dependencies) { - ClasspathScanningLoader classpathScanningLoader = new ClasspathScanningLoader(dep, properties, emptyList(), classLoader); - list.add(classpathScanningLoader); + firstPassLoaderList.add(new ClasspathScanningLoader(dep, properties, emptyList(), classLoader)); } - return load(new ClasspathScanningLoader(jar, properties, list, classLoader), list); + + /* + * Second loader creation pass where the firstPassLoaderList is passed as the + * dependencyResourceLoaders list to ensure that we can resolve transitive + * dependencies using the loaders we just created. This is necessary because + * the first pass may have missing recipes since the full list of loaders was + * not provided. + */ + List secondPassLoaderList = new ArrayList<>(); + for (Path dep : dependencies) { + secondPassLoaderList.add(new ClasspathScanningLoader(dep, properties, firstPassLoaderList, classLoader)); + } + return load(new ClasspathScanningLoader(jar, properties, secondPassLoaderList, classLoader), secondPassLoaderList); } @SuppressWarnings("unused") diff --git a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java index 7d1f24d8ad1..1984d383600 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java @@ -200,7 +200,7 @@ private Collection> loadResources(ResourceType resourceType) for (Object resource : yaml.loadAll(yamlSource)) { if (resource instanceof Map) { @SuppressWarnings("unchecked") Map resourceMap = (Map) resource; - if (resourceType.equals(ResourceType.fromSpec((String) resourceMap.get("type")))) { + if (resourceType == ResourceType.fromSpec((String) resourceMap.get("type"))) { resources.add(resourceMap); } } diff --git a/rewrite-core/src/main/java/org/openrewrite/format/LineBreaks.java b/rewrite-core/src/main/java/org/openrewrite/format/LineBreaks.java new file mode 100644 index 00000000000..da2bdfc05ea --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/format/LineBreaks.java @@ -0,0 +1,34 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.format; + +public class LineBreaks { + public static String normalizeNewLines(String text, boolean useCrlf) { + if (!text.contains("\n")) { + return text; + } + StringBuilder normalized = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (useCrlf && c == '\n' && (i == 0 || text.charAt(i - 1) != '\r')) { + normalized.append('\r').append('\n'); + } else if (useCrlf || c != '\r') { + normalized.append(c); + } + } + return normalized.toString(); + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java index a18cc8bf585..5de88dc8c50 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java @@ -261,6 +261,7 @@ public static List insert(@Nullable List ls, @Nullable T t, int index) /** * For backwards compatibility; prefer {@link #map(List, Function)}. */ + @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable List map(@Nullable List ls, UnaryOperator<@Nullable T> map) { return map(ls, (Function) map); } diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/NameCaseConvention.java b/rewrite-core/src/main/java/org/openrewrite/internal/NameCaseConvention.java index 74639b39b89..038a065c074 100755 --- a/rewrite-core/src/main/java/org/openrewrite/internal/NameCaseConvention.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/NameCaseConvention.java @@ -96,6 +96,9 @@ public boolean matches(String str) { } public static boolean matches(NameCaseConvention convention, String str) { + if (str.isEmpty()) { + return false; + } switch (convention) { case LOWER_CAMEL: if (!Character.isLowerCase(str.charAt(0)) && str.charAt(0) != '$') { diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/ReflectionUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/ReflectionUtils.java index 8cda946a88d..b207a7232e9 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/ReflectionUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/ReflectionUtils.java @@ -36,7 +36,18 @@ public class ReflectionUtils { * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods * from Java 8 based interfaces, allowing for fast iteration. */ - private static final Map, Method[]> declaredMethodsCache = new ConcurrentHashMap<>(256); + private static final Map, Method[]> DECLARED_METHODS_CACHE = new ConcurrentHashMap<>(256); + + public static boolean isClassAvailable(String fullyQualifiedClassName) { + try { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = contextClassLoader == null ? ReflectionUtils.class.getClassLoader() : contextClassLoader; + Class.forName(fullyQualifiedClassName, false, classLoader); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } public static @Nullable Method findMethod(Class clazz, String name, Class... paramTypes) { Class searchType = clazz; @@ -54,7 +65,7 @@ public class ReflectionUtils { } private static Method[] getDeclaredMethods(Class clazz) { - Method[] result = declaredMethodsCache.get(clazz); + Method[] result = DECLARED_METHODS_CACHE.get(clazz); if (result == null) { try { Method[] declaredMethods = clazz.getDeclaredMethods(); @@ -70,7 +81,7 @@ private static Method[] getDeclaredMethods(Class clazz) { } else { result = declaredMethods; } - declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result)); + DECLARED_METHODS_CACHE.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result)); } catch (Throwable ex) { throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index 7cafdeb341a..e06c1d58281 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -692,7 +692,13 @@ public static int indexOfNextNonWhitespace(int cursor, String source) { continue; } else if (length > cursor + 1) { char next = source.charAt(cursor + 1); - if (current == '/' && next == '/') { + if (inMultiLineComment) { + if (current == '*' && next == '/') { + inMultiLineComment = false; + cursor++; + continue; + } + } else if (current == '/' && next == '/') { inSingleLineComment = true; cursor++; continue; @@ -700,10 +706,6 @@ public static int indexOfNextNonWhitespace(int cursor, String source) { inMultiLineComment = true; cursor++; continue; - } else if (current == '*' && next == '/') { - inMultiLineComment = false; - cursor++; - continue; } } if (!inMultiLineComment && !Character.isWhitespace(current)) { @@ -720,4 +722,14 @@ public static String formatUriForPropertiesFile(String uri) { public static boolean hasLineBreak(@Nullable String s) { return s != null && LINE_BREAK.matcher(s).find(); } + + public static boolean containsWhitespace(String s) { + for (int i = 0; i < s.length(); ++i) { + if (Character.isWhitespace(s.charAt(i))) { + return true; + } + } + + return false; + } } diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java index 23de1da8810..6e553fdac4b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java @@ -34,6 +34,7 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.UUID; import java.util.regex.Pattern; @@ -92,6 +93,9 @@ public InputStream getInputStream(ExecutionContext ctx) { try { Path localArchive = cache.compute(uri, () -> { //noinspection resource + if ("file".equals(uri.getScheme())) { + return Files.newInputStream(Paths.get(uri)); + } HttpSender.Response response = httpSender.send(httpSender.get(uri.toString()).build()); if (response.isSuccessful()) { return response.getBody(); diff --git a/rewrite-core/src/main/java/org/openrewrite/semver/LatestMinor.java b/rewrite-core/src/main/java/org/openrewrite/semver/LatestMinor.java new file mode 100644 index 00000000000..17737b52488 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/semver/LatestMinor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * Licensed 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.openrewrite.semver; + +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Validated; + +@Value +public class LatestMinor implements VersionComparator { + @Nullable + String metadataPattern; + + @Override + public boolean isValid(@Nullable String currentVersion, String version) { + Validated validated = currentVersion == null ? + LatestRelease.buildLatestRelease("latest.release", metadataPattern) : + TildeRange.build("~" + Semver.majorVersion(currentVersion), metadataPattern); + + if (validated.isValid()) { + VersionComparator comparator = validated.getValue(); + if (comparator != null) { + return comparator.isValid(currentVersion, version); + } + } + return false; + } + + @Override + public int compare(@Nullable String currentVersion, String v1, String v2) { + if(currentVersion == null) { + return new LatestRelease(null) + .compare(null, v1, v2); + } + + //noinspection ConstantConditions + return TildeRange.build("~" + Semver.majorVersion(currentVersion) + "." + Semver.minorVersion(currentVersion), metadataPattern) + .getValue() + .compare(currentVersion, v1, v2); + } + + public static Validated build(String toVersion, @Nullable String metadataPattern) { + return "latest.minor".equalsIgnoreCase(toVersion) ? + Validated.valid("latestMinor", new LatestMinor(metadataPattern)) : + Validated.invalid("latestMinor", toVersion, "not latest release"); + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/semver/LatestRelease.java b/rewrite-core/src/main/java/org/openrewrite/semver/LatestRelease.java index 18fc5d4b12e..0b1fa2b0095 100644 --- a/rewrite-core/src/main/java/org/openrewrite/semver/LatestRelease.java +++ b/rewrite-core/src/main/java/org/openrewrite/semver/LatestRelease.java @@ -140,9 +140,8 @@ public int compare(@Nullable String currentVersion, String v1, String v2) { // parts are the same: // // HyphenRange [25-28] should include "28-jre" and "28-android" as possible candidates. - String normalized1 = metadataPattern == null ? nv1 : nv1.replace(metadataPattern, ""); - String normalized2 = metadataPattern == null ? nv2 : nv1.replace(metadataPattern, ""); - + String normalized1 = metadataPattern == null ? nv1 : nv1.replaceAll(metadataPattern, ""); + String normalized2 = metadataPattern == null ? nv2 : nv2.replaceAll(metadataPattern, ""); try { for (int i = 1; i <= Math.max(vp1, vp2); i++) { String v1Part = v1Gav.group(i); @@ -168,7 +167,7 @@ public int compare(@Nullable String currentVersion, String v1, String v2) { } public static Validated buildLatestRelease(String toVersion, @Nullable String metadataPattern) { - return "latest.release".equalsIgnoreCase(toVersion) ? + return "latest.release".equalsIgnoreCase(toVersion) || "latest.major".equalsIgnoreCase(toVersion) ? Validated.valid("latestRelease", new LatestRelease(metadataPattern)) : Validated.invalid("latestRelease", toVersion, "not latest release"); } diff --git a/rewrite-core/src/main/java/org/openrewrite/semver/Semver.java b/rewrite-core/src/main/java/org/openrewrite/semver/Semver.java index d4d6388702a..e47ec996629 100644 --- a/rewrite-core/src/main/java/org/openrewrite/semver/Semver.java +++ b/rewrite-core/src/main/java/org/openrewrite/semver/Semver.java @@ -59,6 +59,7 @@ public static Validated validate(String toVersion, @Nullable ).and(Validated.none() .or(LatestRelease.buildLatestRelease(toVersion, metadataPattern)) .or(LatestIntegration.build(toVersion, metadataPattern)) + .or(LatestMinor.build(toVersion, metadataPattern)) .or(LatestPatch.build(toVersion, metadataPattern)) .or(HyphenRange.build(toVersion, metadataPattern)) .or(XRange.build(toVersion, metadataPattern)) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/Find.java b/rewrite-core/src/main/java/org/openrewrite/text/Find.java index 90450be6d64..24ef0290980 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/Find.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/Find.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.apache.commons.lang3.BooleanUtils; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.binary.Binary; @@ -27,7 +28,6 @@ import org.openrewrite.table.TextMatches; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -85,12 +85,18 @@ public String getDescription() { description = "A glob expression that can be used to constrain which directories or source files should be searched. " + "Multiple patterns may be specified, separated by a semicolon `;`. " + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + - "When not set, all source files are searched. ", + "When not set, all source files are searched.", required = false, example = "**/*.java") @Nullable String filePattern; + @Option(displayName = "Description", + description = "Add the matched value(s) as description on the search result marker. Default `false`.", + required = false) + @Nullable + Boolean description; + @Override public TreeVisitor getVisitor() { @@ -122,46 +128,64 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (!matcher.find()) { return sourceFile; } - matcher.reset(); + + String sourceFilePath = sourceFile.getSourcePath().toString(); + List snippets = new ArrayList<>(); int previousEnd = 0; - while (matcher.find()) { + + int lastNewLineIndex = -1; + int nextNewLineIndex = -1; + boolean isFirstMatch = true; + + do { int matchStart = matcher.start(); snippets.add(snippet(rawText.substring(previousEnd, matchStart))); - snippets.add(SearchResult.found(snippet(rawText.substring(matchStart, matcher.end())))); + String text = rawText.substring(matchStart, matcher.end()); + snippets.add(SearchResult.found(snippet(text), BooleanUtils.isTrue(description) ? text : null)); previousEnd = matcher.end(); - int startLine = Math.max(0, rawText.substring(0, matchStart).lastIndexOf('\n') + 1); + // For the first match, search backwards + if (isFirstMatch) { + lastNewLineIndex = rawText.lastIndexOf('\n', matchStart); + nextNewLineIndex = rawText.indexOf('\n', lastNewLineIndex + 1); + isFirstMatch = false; + } else if (nextNewLineIndex != -1 && nextNewLineIndex < matchStart) { + // Advance lastNewLineIndex while before match start + while (nextNewLineIndex != -1 && nextNewLineIndex < matchStart) { + lastNewLineIndex = nextNewLineIndex; + nextNewLineIndex = rawText.indexOf('\n', lastNewLineIndex + 1); + } + } + + int startLine = lastNewLineIndex + 1; int endLine = rawText.indexOf('\n', matcher.end()); if (endLine == -1) { endLine = rawText.length(); } + //noinspection StringBufferReplaceableByString textMatches.insertRow(ctx, new TextMatches.Row( - sourceFile.getSourcePath().toString(), - rawText.substring(startLine, matcher.start()) + "~~>" + - rawText.substring(matcher.start(), endLine) + sourceFilePath, + new StringBuilder(endLine - startLine + 3) + .append(rawText, startLine, matchStart) + .append("~~>") + .append(rawText, matchStart, endLine) + .toString() )); - } + } while (matcher.find()); snippets.add(snippet(rawText.substring(previousEnd))); return plainText.withText("").withSnippets(snippets); } }; - //noinspection DuplicatedCode if (filePattern != null) { - //noinspection unchecked - TreeVisitor check = Preconditions.or(Arrays.stream(filePattern.split(";")) - .map(FindSourceFiles::new) - .map(Recipe::getVisitor) - .toArray(TreeVisitor[]::new)); - - visitor = Preconditions.check(check, visitor); + visitor = Preconditions.check(new FindSourceFiles(filePattern), visitor); } return visitor; } - private static PlainText.Snippet snippet(String text) { return new PlainText.Snippet(Tree.randomId(), Markers.EMPTY, text); } + } diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 0842da78a57..5a284180a62 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -27,7 +27,6 @@ import org.openrewrite.quark.Quark; import org.openrewrite.remote.Remote; -import java.util.Arrays; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -95,7 +94,7 @@ public String getDescription() { description = "A glob expression that can be used to constrain which directories or source files should be searched. " + "Multiple patterns may be specified, separated by a semicolon `;`. " + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + - "When not set, all source files are searched. ", + "When not set, all source files are searched.", required = false, example = "**/*.java") @Nullable @@ -153,15 +152,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { .withMarkers(sourceFile.getMarkers().add(new AlreadyReplaced(randomId(), find, replace))); } }; - //noinspection DuplicatedCode if (filePattern != null) { - //noinspection unchecked - TreeVisitor check = Preconditions.or(Arrays.stream(filePattern.split(";")) - .map(FindSourceFiles::new) - .map(Recipe::getVisitor) - .toArray(TreeVisitor[]::new)); - - visitor = Preconditions.check(check, visitor); + visitor = Preconditions.check(new FindSourceFiles(filePattern), visitor); } if (Boolean.TRUE.equals(plaintextOnly)) { diff --git a/rewrite-core/src/main/java/org/openrewrite/text/PlainText.java b/rewrite-core/src/main/java/org/openrewrite/text/PlainText.java index 6ccab32adf8..623122d9921 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/PlainText.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/PlainText.java @@ -16,14 +16,17 @@ package org.openrewrite.text; import lombok.*; +import lombok.experimental.NonFinal; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.marker.Markers; +import java.lang.ref.SoftReference; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.function.Predicate; @@ -35,8 +38,8 @@ */ @Value @Builder -@AllArgsConstructor -public class PlainText implements SourceFile, Tree { +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class PlainText implements SourceFileWithReferences, Tree { @Builder.Default @With @@ -79,16 +82,41 @@ public SourceFile withCharset(Charset charset) { @Builder.Default String text = ""; + @Nullable + List snippets; + + @Nullable + @NonFinal + @ToString.Exclude + transient SoftReference references; + + public PlainText(UUID id, Path sourcePath, Markers markers, @Nullable String charsetName, boolean charsetBomMarked, + @Nullable FileAttributes fileAttributes, @Nullable Checksum checksum, String text, @Nullable List snippets) { + this.id = id; + this.sourcePath = sourcePath; + this.markers = markers; + this.charsetName = charsetName; + this.charsetBomMarked = charsetBomMarked; + this.fileAttributes = fileAttributes; + this.checksum = checksum; + this.text = text; + this.snippets = snippets; + } + + @Override + public References getReferences() { + this.references = build(this.references); + return Objects.requireNonNull(this.references.get()); + } + public PlainText withText(String text) { if (!text.equals(this.text)) { return new PlainText(this.id, this.sourcePath, this.markers, this.charsetName, this.charsetBomMarked, - this.fileAttributes, this.checksum, text, this.snippets); + this.fileAttributes, this.checksum, text, this.snippets, this.references); } return this; } - List snippets; - @Override public

boolean isAcceptable(TreeVisitor v, P p) { return v.isAdaptableTo(PlainTextVisitor.class); @@ -123,7 +151,7 @@ public PlainText withSnippets(@Nullable List snippets) { if (this.snippets == snippets) { return this; } - return new PlainText(id, sourcePath, markers, charsetName, charsetBomMarked, fileAttributes, checksum, text, snippets); + return new PlainText(id, sourcePath, markers, charsetName, charsetBomMarked, fileAttributes, checksum, text, snippets, null); } @Value diff --git a/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java b/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java index d7da58e2be6..a1a105e9331 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java @@ -20,7 +20,6 @@ import org.openrewrite.Parser; import org.openrewrite.SourceFile; import org.openrewrite.internal.EncodingDetectingInputStream; -import org.openrewrite.marker.Markers; import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; @@ -34,8 +33,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static org.openrewrite.Tree.randomId; - public class PlainTextParser implements Parser { /** @@ -73,17 +70,13 @@ public Stream parseInputs(Iterable sources, @Nullable Path re try { EncodingDetectingInputStream is = input.getSource(ctx); String sourceStr = is.readFully(); - PlainText plainText = new PlainText( - randomId(), - path, - Markers.EMPTY, - is.getCharset().name(), - is.isCharsetBomMarked(), - input.getFileAttributes(), - null, - sourceStr, - null - ); + PlainText plainText = PlainText.builder() + .sourcePath(path) + .charsetName(is.getCharset().name()) + .charsetBomMarked(is.isCharsetBomMarked()) + .fileAttributes(input.getFileAttributes()) + .text(sourceStr) + .build(); parsingListener.parsed(input, plainText); return plainText; } catch (Throwable t) { diff --git a/rewrite-core/src/main/java/org/openrewrite/trait/Reference.java b/rewrite-core/src/main/java/org/openrewrite/trait/Reference.java index df960a7b412..55cf9f63855 100644 --- a/rewrite-core/src/main/java/org/openrewrite/trait/Reference.java +++ b/rewrite-core/src/main/java/org/openrewrite/trait/Reference.java @@ -17,6 +17,7 @@ import org.openrewrite.*; +import java.util.HashSet; import java.util.Set; @Incubating(since = "8.39.0") @@ -24,7 +25,8 @@ public interface Reference extends Trait { enum Kind { TYPE, - PACKAGE + PACKAGE, + IMAGE } Kind getKind(); @@ -57,6 +59,20 @@ interface Provider { boolean isAcceptable(SourceFile sourceFile); } + abstract class AbstractProvider implements Provider { + protected abstract SimpleTraitMatcher getMatcher(); + + @Override + public Set getReferences(SourceFile sourceFile) { + Set references = new HashSet<>(); + getMatcher().asVisitor(reference -> { + references.add(reference); + return reference.getTree(); + }).visit(sourceFile, 0); + return references; + } + } + interface Matcher { boolean matchesReference(Reference value); diff --git a/rewrite-core/src/test/java/org/openrewrite/ExecutionContextTest.java b/rewrite-core/src/test/java/org/openrewrite/ExecutionContextTest.java index ab9ad0ef731..fa7aaa25d7c 100644 --- a/rewrite-core/src/test/java/org/openrewrite/ExecutionContextTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/ExecutionContextTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextVisitor; @@ -41,7 +42,8 @@ public PlainText visitText(PlainText text, ExecutionContext ctx) { cycles.incrementAndGet(); return text; } - }).withCausesAnotherCycle(true)), + }).withCausesAnotherCycle(true)) + .typeValidationOptions(TypeValidation.all().immutableExecutionContext(false)), text("hello world") ); assertThat(cycles.get()).isEqualTo(2); diff --git a/rewrite-core/src/test/java/org/openrewrite/GitRemoteTest.java b/rewrite-core/src/test/java/org/openrewrite/GitRemoteTest.java index 410eb17df9b..37581c2f934 100644 --- a/rewrite-core/src/test/java/org/openrewrite/GitRemoteTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/GitRemoteTest.java @@ -232,11 +232,21 @@ void shouldNotStripJgit() { assertThat(remote.getPath()).isEqualTo("openrewrite/jgit"); } + @Test + void shouldNotReplaceExistingWellKnownServer(){ + GitRemote.Parser parser = new GitRemote.Parser() + .registerRemote(GitRemote.Service.GitHub, URI.create("https://github.com"), List.of(URI.create("ssh://notgithub.com"))); + + assertThat(parser.findRemoteServer("github.com").getUris()) + .containsExactlyInAnyOrder(URI.create("https://github.com"), URI.create("ssh://git@github.com")); + } + @Test void findRemote() { GitRemote.Parser parser = new GitRemote.Parser() .registerRemote(GitRemote.Service.Bitbucket, URI.create("scm.company.com/stash"), Collections.emptyList()); assertThat(parser.findRemoteServer("github.com").getService()).isEqualTo(GitRemote.Service.GitHub); + assertThat(parser.findRemoteServer("https://github.com").getService()).isEqualTo(GitRemote.Service.GitHub); assertThat(parser.findRemoteServer("gitlab.com").getService()).isEqualTo(GitRemote.Service.GitLab); assertThat(parser.findRemoteServer("bitbucket.org").getService()).isEqualTo(GitRemote.Service.BitbucketCloud); assertThat(parser.findRemoteServer("dev.azure.com").getService()).isEqualTo(GitRemote.Service.AzureDevOps); @@ -265,18 +275,18 @@ void parseOriginCaseInsensitive(String cloneUrl, String expectedOrigin, String e @ParameterizedTest @CsvSource(textBlock = """ - GitHub, GitHub - GITLAB, GitLab - bitbucket, Bitbucket - BitbucketCloud, BitbucketCloud - Bitbucket Cloud, BitbucketCloud - BITBUCKET_CLOUD, BitbucketCloud - AzureDevOps, AzureDevOps - AZURE_DEVOPS, AzureDevOps - Azure DevOps, AzureDevOps - idontknow, Unknown - """) - void findServiceForName(String name, GitRemote.Service service){ + GitHub, GitHub + GITLAB, GitLab + bitbucket, Bitbucket + BitbucketCloud, BitbucketCloud + Bitbucket Cloud, BitbucketCloud + BITBUCKET_CLOUD, BitbucketCloud + AzureDevOps, AzureDevOps + AZURE_DEVOPS, AzureDevOps + Azure DevOps, AzureDevOps + idontknow, Unknown + """) + void findServiceForName(String name, GitRemote.Service service) { assertThat(GitRemote.Service.forName(name)).isEqualTo(service); } diff --git a/rewrite-core/src/test/java/org/openrewrite/internal/ReflectionUtilsTest.java b/rewrite-core/src/test/java/org/openrewrite/internal/ReflectionUtilsTest.java new file mode 100644 index 00000000000..83eb1763c75 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/internal/ReflectionUtilsTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.internal; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ReflectionUtilsTest { + + @Test + void classAvailable() { + boolean result = ReflectionUtils.isClassAvailable("org.openrewrite.internal.ReflectionUtilsTest"); + assertTrue(result); + } + + @Test + void classNotAvailable() { + boolean result = ReflectionUtils.isClassAvailable("org.openrewrite.internal.ReflectionUtilsTest2"); + assertFalse(result); + } + + @Test + void classNotAvailableWhenFQNOmitted() { + boolean result = ReflectionUtils.isClassAvailable("ReflectionUtilsTest"); + assertFalse(result); + } +} diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java index f96eea97f00..e09fc8454f5 100644 --- a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java @@ -21,10 +21,12 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.HttpSenderExecutionContextView; import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.PrintOutputCapture; import org.openrewrite.test.MockHttpSender; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Paths; import java.util.concurrent.*; @@ -40,10 +42,6 @@ class RemoteArchiveTest { void gradleWrapper(String version) throws Exception { URL distributionUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader().getResource("gradle-" + version + "-bin.zip")); ExecutionContext ctx = new InMemoryExecutionContext(); - RemoteExecutionContextView.view(ctx).setArtifactCache(new LocalRemoteArtifactCache( - Paths.get(System.getProperty("user.home") + "/.rewrite/remote/gradleWrapper"))); - HttpSenderExecutionContextView.view(ctx) - .setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream)); RemoteArchive remoteArchive = Remote .builder( @@ -58,10 +56,9 @@ void gradleWrapper(String version) throws Exception { @Test void gradleWrapperDownloadFails() throws Exception { - URL distributionUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader().getResource("gradle-7.4.2-bin.zip")); + URL distributionUrl = new URL("http://example"); ExecutionContext ctx = new InMemoryExecutionContext(); - RemoteExecutionContextView.view(ctx).setArtifactCache(new LocalRemoteArtifactCache( - Paths.get(System.getProperty("user.home") + "/.rewrite/remote/gradleWrapperDownloadFails"))); + HttpSenderExecutionContextView.view(ctx) .setLargeFileHttpSender(new MockHttpSender(408)); @@ -116,6 +113,21 @@ void gradleWrapperConcurrent(String version) throws Exception { executorService.shutdown(); } + @Test + void printingRemoteArchive() throws URISyntaxException { + URL zipUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader().getResource("zipfile.zip")); + + RemoteArchive remoteArchive = Remote + .builder( + Paths.get("content.txt"), + zipUrl.toURI() + ) + .build("content.txt"); + + String printed = remoteArchive.printAll(new PrintOutputCapture<>(0, PrintOutputCapture.MarkerPrinter.DEFAULT)); + assertThat(printed).isEqualTo("this is a zipped file"); + } + private Long getInputStreamSize(InputStream is) { BlackHoleOutputStream out = new BlackHoleOutputStream(); try { diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java index 6e335a1eee3..de1b23476c8 100644 --- a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java @@ -55,10 +55,8 @@ void gradleWrapperProperties() throws Exception { @Test void gradleWrapperDownloadFails() throws Exception { - URL distributionUrl = requireNonNull(RemoteFileTest.class.getClassLoader().getResource("gradle-wrapper.properties")); + URL distributionUrl = new URL("http://example.com"); ExecutionContext ctx = new InMemoryExecutionContext(); - RemoteExecutionContextView.view(ctx).setArtifactCache(new LocalRemoteArtifactCache( - Paths.get(System.getProperty("user.home") + "/.rewrite/remote/gradleWrapperDownloadFails"))); HttpSenderExecutionContextView.view(ctx) .setLargeFileHttpSender(new MockHttpSender(408)); diff --git a/rewrite-core/src/test/java/org/openrewrite/semver/LatestMinorTest.java b/rewrite-core/src/test/java/org/openrewrite/semver/LatestMinorTest.java new file mode 100644 index 00000000000..ef551e1d882 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/semver/LatestMinorTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.semver; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LatestMinorTest { + private final LatestMinor latestMinor = new LatestMinor(null); + + @Test + void isValidWhenCurrentIsNull() { + assertThat(latestMinor.isValid(null, "1.0.0")).isTrue(); + } + @Test + void isValid() { + assertThat(latestMinor.isValid("1.0.0", "1.0.0")).isTrue(); + assertThat(latestMinor.isValid("1.0.0", "1.0.0.1")).isTrue(); + assertThat(latestMinor.isValid("1.0.0", "1.0.1")).isTrue(); + assertThat(latestMinor.isValid("1.0", "1.0.1")).isTrue(); + assertThat(latestMinor.isValid("1.0.0", "1.1.0")).isTrue(); + assertThat(latestMinor.isValid("1.0.0", "2.0.0")).isFalse(); + } +} diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java index 0bfae8cadbb..1e628c1b681 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java @@ -29,7 +29,7 @@ class FindTest implements RewriteTest { @Test void dataTable() { rewriteRun( - spec -> spec.recipe(new Find("text", null, null, null, null, null)) + spec -> spec.recipe(new Find("text", null, null, null, null, null, null)) .dataTable(TextMatches.Row.class, rows -> { assertThat(rows).hasSize(1); assertThat(rows.get(0).getMatch()).isEqualTo("This is ~~>text."); @@ -53,7 +53,7 @@ void dataTable() { @Test void regex() { rewriteRun( - spec -> spec.recipe(new Find("[T\\s]", true, true, null, null, null)), + spec -> spec.recipe(new Find("[T\\s]", true, true, null, null, null, null)), text( """ This is\ttext. @@ -68,7 +68,7 @@ void regex() { @Test void plainText() { rewriteRun( - spec -> spec.recipe(new Find("\\s", null, null, null, null, null)), + spec -> spec.recipe(new Find("\\s", null, null, null, null, null, null)), text( """ This i\\s text. @@ -83,7 +83,7 @@ void plainText() { @Test void caseInsensitive() { rewriteRun( - spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**;**/baz/**")), + spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**;**/baz/**", null)), dir("foo", text( """ @@ -111,4 +111,136 @@ void caseInsensitive() { ) ); } + + @Test + void regexBasicMultiLine() { + rewriteRun( + spec -> spec.recipe(new Find("[T\\s]", true, true, true, null, null, null)), + text( + """ + This is\ttext. + This is\ttext. + """, + """ + ~~>This~~> is~~>\ttext.~~> + ~~>This~~> is~~>\ttext. + """ + ) + ); + } + + @Test + void regexWithoutMultilineAndDotall() { + rewriteRun( + spec -> spec.recipe(new Find("^This.*below\\.$", true, true, false, false, null, null)), + text( + """ + This is text. + This is a line below. + This is a line above. + This is text. + This is a line below. + """ + ) + ); + } + + @Test + void regexMatchingWhitespaceWithoutMultilineWithDotall() { + rewriteRun( + spec -> spec.recipe(new Find("One.Two$", true, true, false, true, null, null)), + //language=csv + text( // the `.` above matches the space character on the same line + """ + Zero + One Two + Three + """ + ) + ); + } + + @Test + void regexWithoutMultilineAndWithDotAll() { + rewriteRun( + spec -> spec.recipe(new Find("^This.*below\\.$", true, true, false, true, null, null)), + text( + """ + This is text. + This is a line below. + This is a line above. + This is text. + This is a line below. + """, + """ + ~~>This is text. + This is a line below. + This is a line above. + This is text. + This is a line below. + """ + ) + ); + } + + @Test + void regexWithMultilineAndWithoutDotall() { + rewriteRun( + spec -> spec.recipe(new Find("^This.*below\\.$", true, true, true, false, null, null)), + text( + """ + This is text. + This is a line below. + This is a line above. + This is text. + This is a line below. + """, + """ + This is text. + ~~>This is a line below. + This is a line above. + This is text. + ~~>This is a line below. + """ + ) + ); + } + + @Test + void regexWithBothMultilineAndDotAll() { + rewriteRun( + spec -> spec.recipe(new Find("^This.*below\\.$", true, true, true, true, null, null)), + text( + """ + The first line. + This is a line below. + This is a line above. + This is text. + This is a line below. + """, + """ + The first line. + ~~>This is a line below. + This is a line above. + This is text. + This is a line below. + """ + ) + ); + } + + @Test + void description() { + rewriteRun( + spec -> spec.recipe(new Find("text", null, null, null, null, null, true)), + text( + """ + This is text. + """, + """ + This is ~~(text)~~>text. + """ + ) + ); + } } diff --git a/rewrite-core/src/test/resources/zipfile.zip b/rewrite-core/src/test/resources/zipfile.zip new file mode 100644 index 00000000000..028830c420e Binary files /dev/null and b/rewrite-core/src/test/resources/zipfile.zip differ diff --git a/rewrite-gradle/build.gradle.kts b/rewrite-gradle/build.gradle.kts index 9f18146b3b0..9df2c9c9477 100644 --- a/rewrite-gradle/build.gradle.kts +++ b/rewrite-gradle/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { testImplementation(project(":rewrite-test")) { // because gradle-api fatjars this implementation already exclude("ch.qos.logback", "logback-classic") + exclude("org.slf4j", "slf4j-nop") } testImplementation("org.openrewrite.gradle.tooling:model:$latest") diff --git a/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy b/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy index e3283fd5733..ee780d3d430 100644 --- a/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy +++ b/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy @@ -78,6 +78,7 @@ interface DependencyHandlerSpec extends DependencyHandler { Dependency earlib(Object... dependencyNotation) Dependency earlib(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) + @Override void constraints(Action configureAction) } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java index 990a0606d65..59084bd7cec 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java @@ -34,6 +34,7 @@ import java.util.*; +import static java.util.Collections.*; import static java.util.Objects.requireNonNull; @Value @@ -171,14 +172,14 @@ private boolean usesType(SourceFile sourceFile, ExecutionContext ctx) { return tree; } SourceFile sourceFile = (SourceFile) tree; - sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> - sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet -> { - if (usesType(sourceFile, ctx)) { - acc.usingType = true; - } - Set configurations = acc.configurationsByProject.computeIfAbsent(javaProject, ignored -> new HashSet<>()); - configurations.add("main".equals(sourceSet.getName()) ? "implementation" : sourceSet.getName() + "Implementation"); - })); + sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> { + if (usesType(sourceFile, ctx)) { + acc.usingType = true; + } + Set configurations = acc.configurationsByProject.computeIfAbsent(javaProject, ignored -> new HashSet<>()); + sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet -> + configurations.add("main".equals(sourceSet.getName()) ? "implementation" : sourceSet.getName() + "Implementation")); + }); return tree; } }; @@ -216,7 +217,12 @@ public TreeVisitor getVisitor(Scanned acc) { GradleProject gp = maybeGp.get(); - Set resolvedConfigurations = StringUtils.isBlank(configuration) ? acc.configurationsByProject.get(jp) : new HashSet<>(Collections.singletonList(configuration)); + Set resolvedConfigurations = StringUtils.isBlank(configuration) ? + acc.configurationsByProject.getOrDefault(jp, new HashSet<>()) : + new HashSet<>(singletonList(configuration)); + if (resolvedConfigurations.isEmpty()) { + resolvedConfigurations.add("implementation"); + } Set tmpConfigurations = new HashSet<>(resolvedConfigurations); for (String tmpConfiguration : tmpConfigurations) { GradleDependencyConfiguration gdc = gp.getConfiguration(tmpConfiguration); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java index 61659730458..64e58977947 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java @@ -290,22 +290,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } } else { Space originalPrefix = addDependencyInvocation.getPrefix(); - if (currentStatement instanceof J.VariableDeclarations) { - J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) currentStatement; - if (variableDeclarations.getTypeExpression() != null) { - addDependencyInvocation = addDependencyInvocation.withPrefix(variableDeclarations.getTypeExpression().getPrefix()); - } - } else { - addDependencyInvocation = addDependencyInvocation.withPrefix(currentStatement.getPrefix()); - } + addDependencyInvocation = addDependencyInvocation.withPrefix(currentStatement.getPrefix()); if (addDependencyInvocation.getSimpleName().equals(beforeDependency.getSimpleName())) { - if (currentStatement instanceof J.VariableDeclarations) { - J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) currentStatement; - if (variableDeclarations.getTypeExpression() != null && !variableDeclarations.getTypeExpression().getPrefix().equals(originalPrefix)) { - statements.set(i, variableDeclarations.withTypeExpression(variableDeclarations.getTypeExpression().withPrefix(originalPrefix))); - } - } else if (!currentStatement.getPrefix().equals(originalPrefix)) { + if (!currentStatement.getPrefix().equals(originalPrefix)) { statements.set(i, currentStatement.withPrefix(originalPrefix)); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 0ff50885dee..9312fa96c3b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -180,14 +180,16 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .groupId(oldGroupId) + .artifactId(oldArtifactId); if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry) { + if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry || depArgs.get(0) instanceof G.MapLiteral) { m = updateDependency(m, ctx); } else if (depArgs.get(0) instanceof J.MethodInvocation && (((J.MethodInvocation) depArgs.get(0)).getSimpleName().equals("platform") || @@ -204,7 +206,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { Dependency original = DependencyStringNotationConverter.parse(gav); - if (original != null && depMatcher.matches(original.getGroupId(), original.getArtifactId())) { + if (original != null) { Dependency updated = original; if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { updated = updated.withGroupId(newGroupId); @@ -238,7 +240,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte J.Literal literal = (J.Literal) strings.get(0); Dependency original = DependencyStringNotationConverter.parse((String) requireNonNull(literal.getValue())); - if (original != null && depMatcher.matches(original.getGroupId(), original.getArtifactId())) { + if (original != null) { Dependency updated = original; if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { updated = updated.withGroupId(newGroupId); @@ -352,6 +354,92 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte return arg; })); } + } else if (m.getArguments().get(0) instanceof G.MapLiteral) { + G.MapLiteral map = (G.MapLiteral) depArgs.get(0); + G.MapEntry groupEntry = null; + G.MapEntry artifactEntry = null; + G.MapEntry versionEntry = null; + String groupId = null; + String artifactId = null; + String version = null; + + for (G.MapEntry arg : map.getElements()) { + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + switch (keyValue) { + case "group": + groupEntry = arg; + groupId = valueValue; + break; + case "name": + artifactEntry = arg; + artifactId = valueValue; + break; + case "version": + versionEntry = arg; + version = valueValue; + break; + } + } + if (groupId == null || artifactId == null) { + return m; + } + if (!depMatcher.matches(groupId, artifactId)) { + return m; + } + String updatedGroupId = groupId; + if (!StringUtils.isBlank(newGroupId) && !updatedGroupId.equals(newGroupId)) { + updatedGroupId = newGroupId; + } + String updatedArtifactId = artifactId; + if (!StringUtils.isBlank(newArtifactId) && !updatedArtifactId.equals(newArtifactId)) { + updatedArtifactId = newArtifactId; + } + String updatedVersion = version; + if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(version) || Boolean.TRUE.equals(overrideManagedVersion))) { + String resolvedVersion; + try { + resolvedVersion = new DependencyVersionSelector(null, gradleProject, null) + .select(new GroupArtifact(updatedGroupId, updatedArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); + } catch (MavenDownloadingException e) { + return e.warn(m); + } + if (resolvedVersion != null && !resolvedVersion.equals(updatedVersion)) { + updatedVersion = resolvedVersion; + } + } + + if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || updatedVersion != null && !updatedVersion.equals(version)) { + G.MapEntry finalGroup = groupEntry; + String finalGroupIdValue = updatedGroupId; + G.MapEntry finalArtifact = artifactEntry; + String finalArtifactIdValue = updatedArtifactId; + G.MapEntry finalVersion = versionEntry; + String finalVersionValue = updatedVersion; + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { + if (e == finalGroup) { + return finalGroup.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalGroup.getValue(), finalGroupIdValue)); + } + if (e == finalArtifact) { + return finalArtifact.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalArtifact.getValue(), finalArtifactIdValue)); + } + if (e == finalVersion) { + return finalVersion.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalVersion.getValue(), finalVersionValue)); + } + return e; + })); + })); + } } return m; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index ba98f950ca9..02e028fd305 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -25,21 +25,18 @@ import org.openrewrite.gradle.util.ChangeStringLiteral; import org.openrewrite.gradle.util.Dependency; import org.openrewrite.gradle.util.DependencyStringNotationConverter; -import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.maven.tree.GroupArtifact; -import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; import static java.util.Objects.requireNonNull; @@ -90,38 +87,42 @@ public Validated validate() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { + return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - final Map updatedDependencies = new HashMap<>(); + GradleProject gradleProject; @Override - public G visitCompilationUnit(G.CompilationUnit compilationUnit, ExecutionContext ctx) { - G.CompilationUnit cu = (G.CompilationUnit) super.visitCompilationUnit(compilationUnit, ctx); - if(cu != compilationUnit) { - cu = cu.withMarkers(cu.getMarkers().withMarkers(ListUtils.map(cu.getMarkers().getMarkers(), m -> { - if (m instanceof GradleProject) { - return updateModel((GradleProject) m, updatedDependencies); - } - return m; - }))); + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); + if (!maybeGp.isPresent()) { + return cu; + } + + gradleProject = maybeGp.get(); + + G.CompilationUnit g = super.visitCompilationUnit(cu, ctx); + if (g != cu) { + g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject))); } - return cu; + return g; } @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .configuration(configuration) + .groupId(groupId) + .artifactId(artifactId); - if (!((gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m)) && (StringUtils.isBlank(configuration) || m.getSimpleName().equals(configuration)))) { + if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry) { + if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry || depArgs.get(0) instanceof G.MapLiteral) { m = updateDependency(m); } else if (depArgs.get(0) instanceof J.MethodInvocation && (((J.MethodInvocation) depArgs.get(0)).getSimpleName().equals("platform") || @@ -138,11 +139,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { Dependency dependency = DependencyStringNotationConverter.parse(gav); - if (dependency != null && !newArtifactId.equals(dependency.getArtifactId()) && - ((dependency.getVersion() == null && depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) || - (dependency.getVersion() != null && depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())))) { + if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { Dependency newDependency = dependency.withArtifactId(newArtifactId); - updatedDependencies.put(dependency.getGav().asGroupArtifact(), newDependency.getGav().asGroupArtifact()); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); } } @@ -151,10 +149,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { Dependency dependency = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); - if (dependency != null && !newArtifactId.equals(dependency.getArtifactId()) && - depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { Dependency newDependency = dependency.withArtifactId(newArtifactId); - updatedDependencies.put(dependency.getGav().asGroupArtifact(), newDependency.getGav().asGroupArtifact()); String replacement = newDependency.toStringNotation(); m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { G.GString gString = (G.GString) arg; @@ -166,7 +162,6 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { G.MapEntry artifactEntry = null; String groupId = null; String artifactId = null; - String version = null; String versionStringDelimiter = "'"; for (Expression e : depArgs) { @@ -192,13 +187,9 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { } artifactEntry = arg; artifactId = valueValue; - } else if ("version".equals(keyValue)) { - version = valueValue; } } - if (groupId == null || artifactId == null || - (version == null && !depMatcher.matches(groupId, artifactId)) || - (version != null && !depMatcher.matches(groupId, artifactId, version))) { + if (groupId == null || artifactId == null) { return m; } String delimiter = versionStringDelimiter; @@ -211,35 +202,86 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { } return arg; })); + } else if (depArgs.get(0) instanceof G.MapLiteral) { + G.MapLiteral map = (G.MapLiteral) depArgs.get(0); + G.MapEntry artifactEntry = null; + String groupId = null; + String artifactId = null; + + String versionStringDelimiter = "'"; + for (G.MapEntry arg : map.getElements()) { + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + if ("group".equals(keyValue)) { + groupId = valueValue; + } else if ("name".equals(keyValue) && !newArtifactId.equals(valueValue)) { + if (value.getValueSource() != null) { + versionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); + } + artifactEntry = arg; + artifactId = valueValue; + } + } + if (groupId == null || artifactId == null) { + return m; + } + String delimiter = versionStringDelimiter; + G.MapEntry finalArtifact = artifactEntry; + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { + if (e == finalArtifact) { + return finalArtifact.withValue(((J.Literal) finalArtifact.getValue()) + .withValue(newArtifactId) + .withValueSource(delimiter + newArtifactId + delimiter)); + } + return e; + })); + })); } return m; } - }); - } - private GradleProject updateModel(GradleProject gp, Map updatedDependencies) { - Map nameToConfigurations = gp.getNameToConfiguration(); - Map updatedNameToConfigurations = new HashMap<>(); - for (Map.Entry nameToConfiguration : nameToConfigurations.entrySet()) { - String configurationName = nameToConfiguration.getKey(); - GradleDependencyConfiguration configuration = nameToConfiguration.getValue(); - - List newRequested = configuration.getRequested() - .stream() - .map(requested -> requested.withGav(requested.getGav() - .withGroupArtifact(updatedDependencies.getOrDefault(requested.getGav().asGroupArtifact(), requested.getGav().asGroupArtifact())))) - .collect(Collectors.toList()); - - List newResolved = configuration.getResolved().stream() - .map(resolved -> - resolved.withGav(resolved.getGav() - .withGroupArtifact(updatedDependencies.getOrDefault(resolved.getGav().asGroupArtifact(), resolved.getGav().asGroupArtifact())))) - .collect(Collectors.toList()); - - updatedNameToConfigurations.put(configurationName, configuration.withRequested(newRequested).withDirectResolved(newResolved)); - } - - return gp.withNameToConfiguration(updatedNameToConfigurations); + private GradleProject updateGradleModel(GradleProject gp) { + Map nameToConfiguration = gp.getNameToConfiguration(); + Map newNameToConfiguration = new HashMap<>(nameToConfiguration.size()); + boolean anyChanged = false; + for (GradleDependencyConfiguration gdc : nameToConfiguration.values()) { + if (!StringUtils.isBlank(configuration) && configuration.equals(gdc.getName())) { + newNameToConfiguration.put(gdc.getName(), gdc); + continue; + } + + GradleDependencyConfiguration newGdc = gdc; + newGdc = newGdc.withRequested(ListUtils.map(gdc.getRequested(), requested -> { + if (depMatcher.matches(requested.getGroupId(), requested.getArtifactId())) { + return requested.withGav(requested.getGav().withArtifactId(newArtifactId)); + } + return requested; + })); + newGdc = newGdc.withDirectResolved(ListUtils.map(gdc.getDirectResolved(), resolved -> { + if (depMatcher.matches(resolved.getGroupId(), resolved.getArtifactId())) { + return resolved.withGav(resolved.getGav().withArtifactId(newArtifactId)); + } + return resolved; + })); + anyChanged |= newGdc != gdc; + newNameToConfiguration.put(newGdc.getName(), newGdc); + } + if (anyChanged) { + gp = gp.withNameToConfiguration(newNameToConfiguration); + } + return gp; + } + }); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java index c11f7ccd68d..007f0c45ef4 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java @@ -19,21 +19,21 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import org.openrewrite.gradle.marker.GradleDependencyConfiguration; +import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.gradle.util.ChangeStringLiteral; import org.openrewrite.gradle.util.Dependency; import org.openrewrite.gradle.util.DependencyStringNotationConverter; -import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.semver.DependencyMatcher; -import java.util.List; -import java.util.Objects; +import java.util.*; import static java.util.Objects.requireNonNull; @@ -86,16 +86,36 @@ public Validated validate() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { + return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); + + GradleProject gradleProject; @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); + if (!maybeGp.isPresent()) { + return cu; + } + + gradleProject = maybeGp.get(); - if (!((gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m)) && (StringUtils.isBlank(configuration) || m.getSimpleName().equals(configuration)))) { + G.CompilationUnit g = super.visitCompilationUnit(cu, ctx); + if (g != cu) { + g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject))); + } + return g; + } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .configuration(configuration) + .groupId(groupId) + .artifactId(artifactId); + + if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } @@ -104,8 +124,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { Dependency dependency = DependencyStringNotationConverter.parse(gav); - if (dependency != null && dependency.getVersion() != null && !Objects.equals(newClassifier, dependency.getClassifier()) && - depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())) { + if (dependency != null && dependency.getVersion() != null && !Objects.equals(newClassifier, dependency.getClassifier())) { Dependency newDependency = dependency.withClassifier(newClassifier); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); } @@ -157,10 +176,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) } index++; } - if (groupId == null || artifactId == null || - (version == null && !depMatcher.matches(groupId, artifactId)) || - (version != null && !depMatcher.matches(groupId, artifactId, version)) || - Objects.equals(newClassifier, classifier)) { + if (groupId == null || artifactId == null || Objects.equals(newClassifier, classifier)) { return m; } @@ -187,12 +203,118 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) })); } } + } else if (depArgs.get(0) instanceof G.MapLiteral) { + G.MapLiteral map = (G.MapLiteral) depArgs.get(0); + G.MapEntry classifierEntry = null; + String groupId = null; + String artifactId = null; + String classifier = null; + + String groupDelimiter = "'"; + G.MapEntry mapEntry = null; + String classifierStringDelimiter = null; + int index = 0; + for (G.MapEntry arg : map.getElements()) { + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + if ("group".equals(keyValue)) { + groupId = valueValue; + if (value.getValueSource() != null) { + groupDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); + } + } else if ("name".equals(keyValue)) { + if (index > 0 && mapEntry == null) { + mapEntry = arg; + } + artifactId = valueValue; + } else if ("classifier".equals(keyValue)) { + if (value.getValueSource() != null) { + classifierStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); + } + classifierEntry = arg; + classifier = valueValue; + } + index++; + } + if (groupId == null || artifactId == null || Objects.equals(newClassifier, classifier)) { + return m; + } + if (classifier == null) { + String delimiter = groupDelimiter; + G.MapEntry finalMapEntry = mapEntry; + J.Literal keyLiteral = new J.Literal(Tree.randomId(), mapEntry == null ? Space.EMPTY : mapEntry.getKey().getPrefix(), Markers.EMPTY, "classifier", "classifier", null, JavaType.Primitive.String); + J.Literal valueLiteral = new J.Literal(Tree.randomId(), mapEntry == null ? Space.EMPTY : mapEntry.getValue().getPrefix(), Markers.EMPTY, newClassifier, delimiter + newClassifier + delimiter, null, JavaType.Primitive.String); + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.concat(mapLiteral.getElements(), new G.MapEntry(Tree.randomId(), finalMapEntry == null ? Space.EMPTY : finalMapEntry.getPrefix(), Markers.EMPTY, JRightPadded.build(keyLiteral), valueLiteral, null))); + })); + } else { + G.MapEntry finalClassifier = classifierEntry; + if (newClassifier == null) { + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> e == finalClassifier ? null : e)); + })); + } else { + String delimiter = classifierStringDelimiter; // `classifierStringDelimiter` cannot be null + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { + if (e == finalClassifier) { + return finalClassifier.withValue(((J.Literal) finalClassifier.getValue()) + .withValue(newClassifier) + .withValueSource(delimiter + newClassifier + delimiter)); + } + return e; + })); + })); + } + } } return m; } + + private GradleProject updateGradleModel(GradleProject gp) { + Map nameToConfiguration = gp.getNameToConfiguration(); + Map newNameToConfiguration = new HashMap<>(nameToConfiguration.size()); + boolean anyChanged = false; + for (GradleDependencyConfiguration gdc : nameToConfiguration.values()) { + if (!StringUtils.isBlank(configuration) && !configuration.equals(gdc.getName())) { + newNameToConfiguration.put(gdc.getName(), gdc); + continue; + } + + GradleDependencyConfiguration newGdc = gdc; + newGdc = newGdc.withRequested(ListUtils.map(gdc.getRequested(), requested -> { + if (depMatcher.matches(requested.getGroupId(), requested.getArtifactId()) && !Objects.equals(requested.getClassifier(), newClassifier)) { + return requested.withClassifier(newClassifier); + } + return requested; + })); + newGdc = newGdc.withDirectResolved(ListUtils.map(gdc.getDirectResolved(), resolved -> { + if (depMatcher.matches(resolved.getGroupId(), resolved.getArtifactId()) && !Objects.equals(resolved.getClassifier(), newClassifier)) { + return resolved.withClassifier(newClassifier); + } + return resolved; + })); + anyChanged |= newGdc != gdc; + newNameToConfiguration.put(newGdc.getName(), newGdc); + } + if (anyChanged) { + gp = gp.withNameToConfiguration(newNameToConfiguration); + } + return gp; + } }); } - } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java index dd2e0275e5c..ad6747d3a41 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java @@ -22,9 +22,8 @@ import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.gradle.util.Dependency; import org.openrewrite.gradle.util.DependencyStringNotationConverter; -import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; -import org.openrewrite.internal.StringUtils; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @@ -86,16 +85,18 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { + return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { + // Still need to be able to change the configuration for project dependencies which are not yet supported by the `GradleDependency.Matcher` final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .configuration(configuration); - if (!((gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m)) && (StringUtils.isBlank(configuration) || m.getSimpleName().equals(configuration)))) { + if (!(gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m))) { return m; } @@ -127,19 +128,65 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) if (dependency == null || !dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { return m; } - } else if (args.get(0) instanceof G.MapEntry && args.size() >= 2) { - Expression groupValue = ((G.MapEntry) args.get(0)).getValue(); - Expression artifactValue = ((G.MapEntry) args.get(1)).getValue(); - if (!(groupValue instanceof J.Literal) || !(artifactValue instanceof J.Literal)) { + } else if (args.get(0) instanceof G.MapEntry) { + if (args.size() < 2) { return m; } - J.Literal groupLiteral = (J.Literal) groupValue; - J.Literal artifactLiteral = (J.Literal) artifactValue; - if (!(groupLiteral.getValue() instanceof String) || !(artifactLiteral.getValue() instanceof String)) { + + String groupId = null; + String artifactId = null; + for (Expression e : args) { + if (!(e instanceof G.MapEntry)) { + continue; + } + G.MapEntry arg = (G.MapEntry) e; + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + if ("group".equals(keyValue)) { + groupId = valueValue; + } else if ("name".equals(keyValue)) { + artifactId = valueValue; + } + } + + if (artifactId == null || !dependencyMatcher.matches(groupId, artifactId)) { return m; } + } else if (args.get(0) instanceof G.MapLiteral) { + if (args.size() < 2) { + return m; + } + + G.MapLiteral map = (G.MapLiteral) args.get(0); + String groupId = null; + String artifactId = null; + for (G.MapEntry arg : map.getElements()) { + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + if ("group".equals(keyValue)) { + groupId = valueValue; + } else if ("name".equals(keyValue)) { + artifactId = valueValue; + } + } - if (!dependencyMatcher.matches((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())) { + if (artifactId == null || !dependencyMatcher.matches(groupId, artifactId)) { return m; } } else if (args.get(0) instanceof J.MethodInvocation) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java index a6a617f627b..dc103c00f75 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java @@ -23,19 +23,15 @@ import org.openrewrite.gradle.util.ChangeStringLiteral; import org.openrewrite.gradle.util.Dependency; import org.openrewrite.gradle.util.DependencyStringNotationConverter; -import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.semver.DependencyMatcher; import java.util.List; -import static java.util.Objects.requireNonNull; - @Value @EqualsAndHashCode(callSuper = false) public class ChangeDependencyExtension extends Recipe { @@ -83,17 +79,17 @@ public Validated validate() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { - final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - + return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .configuration(configuration) + .groupId(groupId) + .artifactId(artifactId); - if (!((gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m)) && (StringUtils.isBlank(configuration) || m.getSimpleName().equals(configuration)))) { + if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } @@ -102,9 +98,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { Dependency dependency = DependencyStringNotationConverter.parse(gav); - if (dependency != null && !newExtension.equals(dependency.getExt()) && - ((dependency.getVersion() == null && depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) || - (dependency.getVersion() != null && depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())))) { + if (dependency != null && !newExtension.equals(dependency.getExt())) { Dependency newDependency = dependency.withExt(newExtension); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); } @@ -113,7 +107,6 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) G.MapEntry extensionEntry = null; String groupId = null; String artifactId = null; - String version = null; String extension = null; String extensionStringDelimiter = "'"; @@ -136,8 +129,6 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) groupId = valueValue; } else if ("name".equals(keyValue)) { artifactId = valueValue; - } else if ("version".equals(keyValue)) { - version = valueValue; } else if ("ext".equals(keyValue) && !newExtension.equals(valueValue)) { if (value.getValueSource() != null) { extensionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); @@ -146,10 +137,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) extension = valueValue; } } - if (groupId == null || artifactId == null || - (version == null && !depMatcher.matches(groupId, artifactId)) || - (version != null && !depMatcher.matches(groupId, artifactId, version)) || - extension == null) { + if (groupId == null || artifactId == null || extension == null) { return m; } String delimiter = extensionStringDelimiter; @@ -162,6 +150,53 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) } return arg; })); + } else if (depArgs.get(0) instanceof G.MapLiteral) { + G.MapLiteral map = (G.MapLiteral) depArgs.get(0); + G.MapEntry extensionEntry = null; + String groupId = null; + String artifactId = null; + String extension = null; + + String extensionStringDelimiter = "'"; + for (G.MapEntry arg : map.getElements()) { + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + if ("group".equals(keyValue)) { + groupId = valueValue; + } else if ("name".equals(keyValue)) { + artifactId = valueValue; + } else if ("ext".equals(keyValue) && !newExtension.equals(valueValue)) { + if (value.getValueSource() != null) { + extensionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); + } + extensionEntry = arg; + extension = valueValue; + } + } + if (groupId == null || artifactId == null || extension == null) { + return m; + } + String delimiter = extensionStringDelimiter; + G.MapEntry finalExtension = extensionEntry; + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { + if (e == finalExtension) { + return finalExtension.withValue(((J.Literal) finalExtension.getValue()) + .withValue(newExtension) + .withValueSource(delimiter + newExtension + delimiter)); + } + return e; + })); + })); } return m; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index 550b71f1492..ade503f67d1 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -25,21 +25,18 @@ import org.openrewrite.gradle.util.ChangeStringLiteral; import org.openrewrite.gradle.util.Dependency; import org.openrewrite.gradle.util.DependencyStringNotationConverter; -import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.maven.tree.GroupArtifact; -import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; import static java.util.Objects.requireNonNull; @@ -90,38 +87,42 @@ public Validated validate() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { + return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - final Map updatedDependencies = new HashMap<>(); + GradleProject gradleProject; @Override - public G visitCompilationUnit(G.CompilationUnit compilationUnit, ExecutionContext ctx) { - G.CompilationUnit cu = (G.CompilationUnit) super.visitCompilationUnit(compilationUnit, ctx); - if(cu != compilationUnit) { - cu = cu.withMarkers(cu.getMarkers().withMarkers(ListUtils.map(cu.getMarkers().getMarkers(), m -> { - if (m instanceof GradleProject) { - return updateModel((GradleProject) m, updatedDependencies); - } - return m; - }))); + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); + if (!maybeGp.isPresent()) { + return cu; + } + + gradleProject = maybeGp.get(); + + G.CompilationUnit g = super.visitCompilationUnit(cu, ctx); + if (g != cu) { + g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject))); } - return cu; + return g; } @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .configuration(configuration) + .groupId(groupId) + .artifactId(artifactId); - if (!((gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m)) && (StringUtils.isBlank(configuration) || m.getSimpleName().equals(configuration)))) { + if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry) { + if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry || depArgs.get(0) instanceof G.MapLiteral) { m = updateDependency(m); } else if (depArgs.get(0) instanceof J.MethodInvocation && (((J.MethodInvocation) depArgs.get(0)).getSimpleName().equals("platform") || @@ -138,11 +139,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { Dependency dependency = DependencyStringNotationConverter.parse(gav); - if (dependency != null && !newGroupId.equals(dependency.getGroupId()) && - ((dependency.getVersion() == null && depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) || - (dependency.getVersion() != null && depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())))) { + if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { Dependency newDependency = dependency.withGroupId(newGroupId); - updatedDependencies.put(dependency.getGav().asGroupArtifact(), newDependency.getGav().asGroupArtifact()); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); } } @@ -151,10 +149,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { Dependency dependency = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); - if (dependency != null && !newGroupId.equals(dependency.getGroupId()) && - depMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { Dependency newDependency = dependency.withGroupId(newGroupId); - updatedDependencies.put(dependency.getGav().asGroupArtifact(), newDependency.getGav().asGroupArtifact()); String replacement = newDependency.toStringNotation(); m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { G.GString gString = (G.GString) arg; @@ -166,7 +162,6 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { G.MapEntry groupEntry = null; String groupId = null; String artifactId = null; - String version = null; String versionStringDelimiter = "'"; for (Expression e : depArgs) { @@ -192,13 +187,9 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { groupId = valueValue; } else if ("name".equals(keyValue)) { artifactId = valueValue; - } else if ("version".equals(keyValue)) { - version = valueValue; } } - if (groupId == null || artifactId == null || - (version == null && !depMatcher.matches(groupId, artifactId)) || - (version != null && !depMatcher.matches(groupId, artifactId, version))) { + if (groupId == null || artifactId == null) { return m; } String delimiter = versionStringDelimiter; @@ -211,35 +202,86 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { } return arg; })); + } else if (depArgs.get(0) instanceof G.MapLiteral) { + G.MapLiteral map = (G.MapLiteral) depArgs.get(0); + G.MapEntry groupEntry = null; + String groupId = null; + String artifactId = null; + + String versionStringDelimiter = "'"; + for (G.MapEntry arg : map.getElements()) { + if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { + continue; + } + J.Literal key = (J.Literal) arg.getKey(); + J.Literal value = (J.Literal) arg.getValue(); + if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { + continue; + } + String keyValue = (String) key.getValue(); + String valueValue = (String) value.getValue(); + if ("group".equals(keyValue) && !newGroupId.equals(valueValue)) { + if (value.getValueSource() != null) { + versionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); + } + groupEntry = arg; + groupId = valueValue; + } else if ("name".equals(keyValue)) { + artifactId = valueValue; + } + } + if (groupId == null || artifactId == null) { + return m; + } + String delimiter = versionStringDelimiter; + G.MapEntry finalGroup = groupEntry; + m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { + if (e == finalGroup) { + return finalGroup.withValue(((J.Literal) finalGroup.getValue()) + .withValue(newGroupId) + .withValueSource(delimiter + newGroupId + delimiter)); + } + return e; + })); + })); } return m; } - }); - } - private GradleProject updateModel(GradleProject gp, Map updatedDependencies) { - Map nameToConfigurations = gp.getNameToConfiguration(); - Map updatedNameToConfigurations = new HashMap<>(); - for (Map.Entry nameToConfiguration : nameToConfigurations.entrySet()) { - String configurationName = nameToConfiguration.getKey(); - GradleDependencyConfiguration configuration = nameToConfiguration.getValue(); - - List newRequested = configuration.getRequested() - .stream() - .map(requested -> requested.withGav(requested.getGav() - .withGroupArtifact(updatedDependencies.getOrDefault(requested.getGav().asGroupArtifact(), requested.getGav().asGroupArtifact())))) - .collect(Collectors.toList()); - - List newResolved = configuration.getResolved().stream() - .map(resolved -> - resolved.withGav(resolved.getGav() - .withGroupArtifact(updatedDependencies.getOrDefault(resolved.getGav().asGroupArtifact(), resolved.getGav().asGroupArtifact())))) - .collect(Collectors.toList()); - - updatedNameToConfigurations.put(configurationName, configuration.withRequested(newRequested).withDirectResolved(newResolved)); - } - - return gp.withNameToConfiguration(updatedNameToConfigurations); + private GradleProject updateGradleModel(GradleProject gp) { + Map nameToConfiguration = gp.getNameToConfiguration(); + Map newNameToConfiguration = new HashMap<>(nameToConfiguration.size()); + boolean anyChanged = false; + for (GradleDependencyConfiguration gdc : nameToConfiguration.values()) { + if (!StringUtils.isBlank(configuration) && configuration.equals(gdc.getName())) { + newNameToConfiguration.put(gdc.getName(), gdc); + continue; + } + + GradleDependencyConfiguration newGdc = gdc; + newGdc = newGdc.withRequested(ListUtils.map(gdc.getRequested(), requested -> { + if (depMatcher.matches(requested.getGroupId(), requested.getArtifactId())) { + return requested.withGav(requested.getGav().withGroupId(newGroupId)); + } + return requested; + })); + newGdc = newGdc.withDirectResolved(ListUtils.map(gdc.getDirectResolved(), resolved -> { + if (depMatcher.matches(resolved.getGroupId(), resolved.getArtifactId())) { + return resolved.withGav(resolved.getGav().withGroupId(newGroupId)); + } + return resolved; + })); + anyChanged |= newGdc != gdc; + newNameToConfiguration.put(newGdc.getName(), newGdc); + } + if (anyChanged) { + gp = gp.withNameToConfiguration(newNameToConfiguration); + } + return gp; + } + }); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java index 984523ff5d8..301fb375987 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java @@ -25,7 +25,6 @@ import org.openrewrite.groovy.GroovyVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -53,15 +52,13 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - if (!(gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m))) { + if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } m = forBasicString(m); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java index b3c17260f90..17f8b300a05 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java @@ -23,7 +23,6 @@ import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyVisitor; import org.openrewrite.groovy.tree.G; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -50,15 +49,13 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - if (!(gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m))) { + if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { return m; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyVersionSelector.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyVersionSelector.java index 143c12ea29a..f33fe49a3e4 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyVersionSelector.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyVersionSelector.java @@ -185,7 +185,9 @@ private List determineRepos(@Nullable String configuration) { if (gradleSettings != null) { return gradleSettings.getBuildscript().getMavenRepositories(); } - Objects.requireNonNull(gradleProject); + if (gradleProject == null) { + throw new IllegalStateException("Gradle project must be set to determine repositories."); // Caught by caller + } return "classpath".equals(configuration) ? gradleProject.getBuildscript().getMavenRepositories() : gradleProject.getMavenRepositories(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/EnableDevelocityBuildCache.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/EnableDevelocityBuildCache.java new file mode 100644 index 00000000000..f15ab6ac8cc --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/EnableDevelocityBuildCache.java @@ -0,0 +1,118 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.groovy.GroovyIsoVisitor; +import org.openrewrite.groovy.tree.G; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.tree.J; + +import java.util.concurrent.atomic.AtomicBoolean; + +@Value +@EqualsAndHashCode(callSuper = false) +public class EnableDevelocityBuildCache extends Recipe { + + @Override + public String getDisplayName() { + return "Enable Develocity build cache"; + } + + @Override + public String getDescription() { + return "Adds `buildCache` configuration to `develocity` where not yet present."; + } + + @Option(displayName = "Enable remote build cache", + description = "Value for `//develocity/buildCache/remote/enabled`.", + example = "true", + required = false) + @Nullable + String remoteEnabled; + + @Option(displayName = "Enable remote build cache push", + description = "Value for `//develocity/buildCache/remote/storeEnabled`.", + example = "System.getenv(\"CI\") != null", + required = false) + @Nullable + String remotePushEnabled; + + @Override + public Validated validate(ExecutionContext ctx) { + return super.validate(ctx) + .and(Validated.notBlank("remoteEnabled", remoteEnabled) + .or(Validated.notBlank("remotePushEnabled", remotePushEnabled))); + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new IsSettingsGradle<>(), new GroovyIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + if ("develocity".equals(method.getSimpleName()) && !hasBuildCache(method)) { + J.MethodInvocation buildCache = createBuildCache(); + return maybeAutoFormat(method, method.withArguments(ListUtils.mapFirst(method.getArguments(), arg -> { + if (arg instanceof J.Lambda) { + J.Lambda lambda = (J.Lambda) arg; + J.Block block = (J.Block) lambda.getBody(); + return lambda.withBody(block.withStatements(ListUtils.concat(block.getStatements(), buildCache))); + } + return arg; + })), ctx); + } + return method; + } + + private boolean hasBuildCache(J.MethodInvocation m) { + return new GroovyIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicBoolean atomicBoolean) { + if ("buildCache".equals(method.getSimpleName())) { + atomicBoolean.set(true); + return method; + } + return super.visitMethodInvocation(method, atomicBoolean); + } + }.reduce(m, new AtomicBoolean(false), getCursor().getParentTreeCursor()).get(); + } + }); + } + + private J.MethodInvocation createBuildCache() { + String conf = "buildCache {\n" + + " remote(develocity.buildCache) {\n"; + if (!StringUtils.isBlank(remoteEnabled)) { + conf += " enabled = " + remoteEnabled + "\n"; + } + if (!StringUtils.isBlank(remotePushEnabled)) { + conf += " push = " + remotePushEnabled + "\n"; + } + conf += " }" + + "}"; + return (J.MethodInvocation) GradleParser.builder().build() + .parse(conf) + .map(G.CompilationUnit.class::cast) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")) + .getStatements() + .get(0); + } +} diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java index f38e853fbe6..b05ac29701b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java @@ -15,7 +15,6 @@ */ package org.openrewrite.gradle; -import io.micrometer.core.instrument.util.StringUtils; import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; @@ -23,17 +22,14 @@ import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; -import org.openrewrite.gradle.util.Dependency; -import org.openrewrite.gradle.util.DependencyStringNotationConverter; -import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.tree.Expression; +import org.openrewrite.internal.StringUtils; import org.openrewrite.java.tree.J; import org.openrewrite.semver.DependencyMatcher; -import java.util.List; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -77,55 +73,35 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { - final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - final DependencyMatcher dependencyMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); + return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { + final GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + .configuration(configuration) + .groupId(groupId) + .artifactId(artifactId); + final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); + + GradleProject gradleProject; @Override - public J visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { - G.CompilationUnit g = (G.CompilationUnit) super.visitCompilationUnit(cu, ctx); - if (g == cu) { + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); + if (!maybeGp.isPresent()) { return cu; } - Optional maybeGp = g.getMarkers().findFirst(GradleProject.class); - if (!maybeGp.isPresent()) { - // Allow modification of freestanding scripts which do not carry a GradleProject marker - return g; - } + gradleProject = maybeGp.get(); - GradleProject gp = maybeGp.get(); - Map nameToConfiguration = gp.getNameToConfiguration(); - boolean anyChanged = false; - for (GradleDependencyConfiguration gdc : nameToConfiguration.values()) { - GradleDependencyConfiguration newGdc = gdc.withRequested(ListUtils.map(gdc.getRequested(), requested -> { - if (requested.getGroupId() != null && dependencyMatcher.matches(requested.getGroupId(), requested.getArtifactId())) { - return null; - } - return requested; - })); - newGdc = newGdc.withDirectResolved(ListUtils.map(newGdc.getDirectResolved(), resolved -> { - if (dependencyMatcher.matches(resolved.getGroupId(), resolved.getArtifactId())) { - return null; - } - return resolved; - })); - nameToConfiguration.put(newGdc.getName(), newGdc); - anyChanged |= newGdc != gdc; - } - - if (!anyChanged) { - // instance was changed, but no marker update is needed - return g; + G.CompilationUnit g = super.visitCompilationUnit(cu, ctx); + if (g != cu) { + g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject))); } - - return g.withMarkers(g.getMarkers().setByType(gp.withNameToConfiguration(nameToConfiguration))); + return g; } @Override - public @Nullable J visitReturn(J.Return return_, ExecutionContext ctx) { - boolean dependencyInvocation = return_.getExpression() instanceof J.MethodInvocation && dependencyDsl.matches((J.MethodInvocation) return_.getExpression()); - J.Return r = (J.Return) super.visitReturn(return_, ctx); + public J.@Nullable Return visitReturn(J.Return return_, ExecutionContext ctx) { + boolean dependencyInvocation = return_.getExpression() instanceof J.MethodInvocation && gradleDependencyMatcher.get(return_.getExpression(), getCursor()).isPresent(); + J.Return r = super.visitReturn(return_, ctx); if (dependencyInvocation && r.getExpression() == null) { //noinspection DataFlowIssue return null; @@ -134,86 +110,46 @@ public J visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { } @Override - public @Nullable J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - - if ((gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m)) && (StringUtils.isEmpty(configuration) || configuration.equals(m.getSimpleName()))) { - Expression firstArgument = m.getArguments().get(0); - if (firstArgument instanceof J.Literal || firstArgument instanceof G.GString || firstArgument instanceof G.MapEntry) { - //noinspection DataFlowIssue - return maybeRemoveDependency(m); - } else if (firstArgument instanceof J.MethodInvocation && - (((J.MethodInvocation) firstArgument).getSimpleName().equals("platform") || - ((J.MethodInvocation) firstArgument).getSimpleName().equals("enforcedPlatform"))) { - J after = maybeRemoveDependency((J.MethodInvocation) firstArgument); - if (after == null) { - //noinspection DataFlowIssue - return null; - } - } + public J.@Nullable MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + + if (gradleDependencyMatcher.get(getCursor()).isPresent()) { + return null; } return m; } - private @Nullable J maybeRemoveDependency(J.MethodInvocation m) { - if (m.getArguments().get(0) instanceof G.GString) { - G.GString gString = (G.GString) m.getArguments().get(0); - List strings = gString.getStrings(); - if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof G.GString.Value)) { - return m; - } - J.Literal groupArtifact = (J.Literal) strings.get(0); - if (!(groupArtifact.getValue() instanceof String)) { - return m; - } - Dependency dependency = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); - if (dependency != null && dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { - return null; - } - } else if (m.getArguments().get(0) instanceof J.Literal) { - Object value = ((J.Literal) m.getArguments().get(0)).getValue(); - if(!(value instanceof String)) { - return null; - } - Dependency dependency = DependencyStringNotationConverter.parse((String) value); - if (dependency != null && dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { - return null; + private GradleProject updateGradleModel(GradleProject gp) { + Map nameToConfiguration = gp.getNameToConfiguration(); + Map newNameToConfiguration = new HashMap<>(nameToConfiguration.size()); + boolean anyChanged = false; + for (GradleDependencyConfiguration gdc : nameToConfiguration.values()) { + if (!StringUtils.isBlank(configuration) && configuration.equals(gdc.getName())) { + newNameToConfiguration.put(gdc.getName(), gdc); + continue; } - } else if (m.getArguments().get(0) instanceof G.MapEntry) { - String groupId = null; - String artifactId = null; - for (Expression e : m.getArguments()) { - if (!(e instanceof G.MapEntry)) { - continue; - } - G.MapEntry arg = (G.MapEntry) e; - if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { - continue; - } - J.Literal key = (J.Literal) arg.getKey(); - J.Literal value = (J.Literal) arg.getValue(); - if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { - continue; + GradleDependencyConfiguration newGdc = gdc; + newGdc = newGdc.withRequested(ListUtils.map(gdc.getRequested(), requested -> { + if (depMatcher.matches(requested.getGroupId(), requested.getArtifactId())) { + return null; } - String keyValue = (String) key.getValue(); - String valueValue = (String) value.getValue(); - if ("group".equals(keyValue)) { - groupId = valueValue; - } else if ("name".equals(keyValue)) { - artifactId = valueValue; + return requested; + })); + newGdc = newGdc.withDirectResolved(ListUtils.map(gdc.getDirectResolved(), resolved -> { + if (depMatcher.matches(resolved.getGroupId(), resolved.getArtifactId())) { + return null; } - } - - if (groupId != null && artifactId != null && dependencyMatcher.matches(groupId, artifactId)) { - return null; - } + return resolved; + })); + anyChanged |= newGdc != gdc; + newNameToConfiguration.put(newGdc.getName(), newGdc); } - - return m; + if (anyChanged) { + gp = gp.withNameToConfiguration(newNameToConfiguration); + } + return gp; } }); } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java new file mode 100644 index 00000000000..a2cd1dab3b3 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java @@ -0,0 +1,316 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.gradle.marker.GradleDependencyConfiguration; +import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.gradle.trait.GradleDependency; +import org.openrewrite.gradle.util.ChangeStringLiteral; +import org.openrewrite.gradle.util.Dependency; +import org.openrewrite.gradle.util.DependencyStringNotationConverter; +import org.openrewrite.groovy.GroovyIsoVisitor; +import org.openrewrite.groovy.tree.G; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.internal.MavenPomDownloader; +import org.openrewrite.maven.tree.GroupArtifactVersion; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.semver.ExactVersion; +import org.openrewrite.semver.LatestIntegration; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; + +import java.util.*; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveRedundantDependencyVersions extends Recipe { + @Option(displayName = "Group", + description = "Group glob expression pattern used to match dependencies that should be managed." + + "Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`.", + example = "com.google.*", + required = false) + @Nullable + String groupPattern; + + @Option(displayName = "Artifact", + description = "Artifact glob expression pattern used to match dependencies that should be managed." + + "Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`.", + example = "guava*", + required = false) + @Nullable + String artifactPattern; + + @Option(displayName = "Only if managed version is ...", + description = "Only remove the explicit version if the managed version has the specified comparative relationship to the explicit version. " + + "For example, `gte` will only remove the explicit version if the managed version is the same or newer. " + + "Default `eq`.", + valid = {"any", "eq", "lt", "lte", "gt", "gte"}, + required = false) + @Nullable + Comparator onlyIfManagedVersionIs; + + @Option(displayName = "Except", + description = "Accepts a list of GAVs. Dependencies matching a GAV will be ignored by this recipe." + + " GAV versions are ignored if provided.", + example = "com.jcraft:jsch", + required = false) + @Nullable + List except; + + public enum Comparator { + ANY, + EQ, + LT, + LTE, + GT, + GTE + } + + @Override + public String getDisplayName() { + return "Remove redundant explicit dependency versions"; + } + + @Override + public String getDescription() { + return "Remove explicitly-specified dependency versions that are managed by a Gradle `platform`/`enforcedPlatform`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + new IsBuildGradle<>(), + new GroovyIsoVisitor() { + GradleProject gp; + final Map> platforms = new HashMap<>(); + + @Override + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); + if (!maybeGp.isPresent()) { + return cu; + } + + gp = maybeGp.get(); + new GroovyIsoVisitor() { + final MethodMatcher platformMatcher = new MethodMatcher("org.gradle.api.artifacts.dsl.DependencyHandler platform(..)"); + final MethodMatcher enforcedPlatformMatcher = new MethodMatcher("org.gradle.api.artifacts.dsl.DependencyHandler enforcedPlatform(..)"); + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + if (!platformMatcher.matches(m) && !enforcedPlatformMatcher.matches(m)) { + return m; + } + + if (m.getArguments().get(0) instanceof J.Literal) { + J.Literal l = (J.Literal) m.getArguments().get(0); + if (l.getType() != JavaType.Primitive.String) { + return m; + } + + Dependency dependency = DependencyStringNotationConverter.parse((String) l.getValue()); + MavenPomDownloader mpd = new MavenPomDownloader(ctx); + try { + ResolvedPom platformPom = mpd.download(new GroupArtifactVersion(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()), null, null, gp.getMavenRepositories()) + .resolve(Collections.emptyList(), mpd, ctx); + platforms.computeIfAbsent(getCursor().getParent(1).firstEnclosing(J.MethodInvocation.class).getSimpleName(), k -> new ArrayList<>()).add(platformPom); + } catch (MavenDownloadingException e) { + return m; + } + } else if (m.getArguments().get(0) instanceof G.MapEntry) { + String groupId = null; + String artifactId = null; + String version = null; + + for (Expression arg : m.getArguments()) { + if (!(arg instanceof G.MapEntry)) { + continue; + } + + G.MapEntry entry = (G.MapEntry) arg; + if (!(entry.getKey() instanceof J.Literal) || !(entry.getValue() instanceof J.Literal)) { + continue; + } + + J.Literal key = (J.Literal) entry.getKey(); + J.Literal value = (J.Literal) entry.getValue(); + if (key.getType() != JavaType.Primitive.String || value.getType() != JavaType.Primitive.String) { + continue; + } + + switch ((String) key.getValue()) { + case "group": + groupId = (String) value.getValue(); + break; + case "name": + artifactId = (String) value.getValue(); + break; + case "version": + version = (String) value.getValue(); + break; + } + } + + if (groupId == null || artifactId == null || version == null) { + return m; + } + + MavenPomDownloader mpd = new MavenPomDownloader(ctx); + try { + ResolvedPom platformPom = mpd.download(new GroupArtifactVersion(groupId, artifactId, version), null, null, gp.getMavenRepositories()) + .resolve(Collections.emptyList(), mpd, ctx); + platforms.computeIfAbsent(getCursor().getParent(1).firstEnclosing(J.MethodInvocation.class).getSimpleName(), k -> new ArrayList<>()).add(platformPom); + } catch (MavenDownloadingException e) { + return m; + } + } + return m; + } + }.visit(cu, ctx); + + return super.visitCompilationUnit(cu, ctx); + } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + + Optional maybeGradleDependency = new GradleDependency.Matcher() + .groupId(groupPattern) + .artifactId(artifactPattern) + .get(getCursor()); + if (!maybeGradleDependency.isPresent()) { + return m; + } + + GradleDependency gradleDependency = maybeGradleDependency.get(); + ResolvedDependency d = gradleDependency.getResolvedDependency(); + if (StringUtils.isBlank(d.getVersion())) { + return m; + } + + if (platforms.containsKey(m.getSimpleName())) { + for (ResolvedPom platform : platforms.get(m.getSimpleName())) { + String managedVersion = platform.getManagedVersion(d.getGroupId(), d.getArtifactId(), null, d.getRequested().getClassifier()); + if (matchesComparator(managedVersion, d.getVersion())) { + return maybeRemoveVersion(m); + } + } + } + GradleDependencyConfiguration gdc = gp.getConfiguration(m.getSimpleName()); + if (gdc != null) { + for (GradleDependencyConfiguration configuration : gdc.allExtendsFrom()) { + if (platforms.containsKey(configuration.getName())) { + for (ResolvedPom platform : platforms.get(configuration.getName())) { + String managedVersion = platform.getManagedVersion(d.getGroupId(), d.getArtifactId(), null, d.getRequested().getClassifier()); + if (matchesComparator(managedVersion, d.getVersion())) { + return maybeRemoveVersion(m); + } + } + } + } + } + + return m; + } + + private J.MethodInvocation maybeRemoveVersion(J.MethodInvocation m) { + if (m.getArguments().get(0) instanceof J.Literal) { + J.Literal l = (J.Literal) m.getArguments().get(0); + if (l.getType() != JavaType.Primitive.String) { + return m; + } + + Dependency dep = DependencyStringNotationConverter.parse((String) l.getValue()) + .withVersion(null); + if (dep.getClassifier() != null || dep.getExt() != null) { + return m; + } + + return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue(l, dep.toStringNotation()))); + } else if (m.getArguments().get(0) instanceof G.MapLiteral) { + return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), entry -> { + if (entry.getKey() instanceof J.Literal && + "version".equals(((J.Literal) entry.getKey()).getValue())) { + return null; + } + return entry; + })); + })); + } else if (m.getArguments().get(0) instanceof G.MapEntry) { + return m.withArguments(ListUtils.map(m.getArguments(), arg -> { + G.MapEntry entry = (G.MapEntry) arg; + if (entry.getKey() instanceof J.Literal && + "version".equals(((J.Literal) entry.getKey()).getValue())) { + return null; + } + return entry; + })); + } + return m; + } + } + ); + } + + private Comparator determineComparator() { + if (onlyIfManagedVersionIs != null) { + return onlyIfManagedVersionIs; + } + return Comparator.EQ; + } + + private boolean matchesComparator(@Nullable String managedVersion, String requestedVersion) { + Comparator comparator = determineComparator(); + if (managedVersion == null) { + return false; + } + if (comparator == Comparator.ANY) { + return true; + } + if (!isExact(managedVersion)) { + return false; + } + int comparison = new LatestIntegration(null) + .compare(null, managedVersion, requestedVersion); + if (comparison < 0) { + return comparator == Comparator.LT || comparator == Comparator.LTE; + } else if (comparison > 0) { + return comparator == Comparator.GT || comparator == Comparator.GTE; + } else { + return comparator == Comparator.EQ || comparator == Comparator.LTE || comparator == Comparator.GTE; + } + } + + private boolean isExact(String managedVersion) { + Validated maybeVersionComparator = Semver.validate(managedVersion, null); + return maybeVersionComparator.isValid() && maybeVersionComparator.getValue() instanceof ExactVersion; + } +} diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index d008c4ae3f1..d8c32b7cd84 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -32,7 +32,6 @@ import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; @@ -140,8 +139,6 @@ public DependencyVersionState getInitialValue(ExecutionContext ctx) { return new DependencyVersionState(); } - private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); - @Override public TreeVisitor getScanner(DependencyVersionState acc) { @@ -163,7 +160,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - if (gradleDependencyMatcher.get(getCursor()).isPresent() || DEPENDENCY_DSL_MATCHER.matches(m)) { + if (gradleDependencyMatcher.get(getCursor()).isPresent()) { if (m.getArguments().get(0) instanceof G.MapEntry) { String groupId = null; String artifactId = null; @@ -374,15 +371,10 @@ public J postVisit(J tree, ExecutionContext ctx) { @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if ("constraints".equals(method.getSimpleName()) || "project".equals(method.getSimpleName())) { - // don't mess with anything inside a constraints block, leave that to UpgradeTransitiveDependency version recipe - // `project` dependencies should also be skipped - return method; - } J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - if (gradleDependencyMatcher.get(getCursor()).isPresent() || DEPENDENCY_DSL_MATCHER.matches(m)) { + if (gradleDependencyMatcher.get(getCursor()).isPresent()) { List depArgs = m.getArguments(); if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry) { m = updateDependency(m, ctx); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java index 3acdda2ff7e..21d5df8b380 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java @@ -55,6 +55,15 @@ public class AddBuildPlugin extends Recipe { @Nullable Boolean apply; + @Option(displayName = "Accept transitive", + description = "Some plugins apply other plugins. When this is set to true no plugin declaration will be added if the plugin is already applied transitively. " + + "When this is set to false the plugin will be added explicitly even if it is already applied transitively. " + + "Defaults to `true`.", + valid = {"true", "false"}, + required = false) + @Nullable + Boolean acceptTransitive; + @Override public String getDisplayName() { return "Add Gradle plugin"; @@ -78,7 +87,7 @@ public Validated validate() { public TreeVisitor getVisitor() { return Preconditions.check( new FindGradleProject(FindGradleProject.SearchCriteria.Marker), - new AddPluginVisitor(pluginId, version, versionPattern, apply) + new AddPluginVisitor(pluginId, version, versionPattern, apply, acceptTransitive) ); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java index 27c40fa6301..11f2cb4d944 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java @@ -92,7 +92,7 @@ public class AddDevelocityGradlePlugin extends Recipe { @Nullable Boolean uploadInBackground; - @Option(displayName = "Publish Criteria", + @Option(displayName = "Publish criteria", description = "When set to `Always` the plugin will publish build scans of every single build. " + "When set to `Failure` the plugin will only publish build scans when the build fails. " + "When omitted scans will be published only when the `--scan` option is passed to the build.", @@ -210,7 +210,7 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon } private G.CompilationUnit withPlugin(G.CompilationUnit cu, String pluginId, String newVersion, VersionComparator versionComparator, ExecutionContext ctx) { - cu = (G.CompilationUnit) new AddPluginVisitor(pluginId, newVersion, null, null) + cu = (G.CompilationUnit) new AddPluginVisitor(pluginId, newVersion, null, null, false) .visitNonNull(cu, ctx); cu = (G.CompilationUnit) new UpgradePluginVersion(pluginId, newVersion, null).getVisitor() .visitNonNull(cu, ctx); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java index 1a22bc3d5f9..f7ca2de95c9 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static java.util.Collections.singletonList; @@ -58,6 +59,9 @@ public class AddPluginVisitor extends GroovyIsoVisitor { @Nullable Boolean apply; + @Nullable + Boolean acceptTransitive; + private static @Nullable Comment getLicenseHeader(G.CompilationUnit cu) { if (!cu.getStatements().isEmpty()) { Statement firstStatement = cu.getStatements().get(0); @@ -95,124 +99,133 @@ private static G.CompilationUnit removeLicenseHeader(G.CompilationUnit cu) { @Override public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { - if (FindPlugins.find(cu, pluginId).isEmpty()) { - String version; - if (newVersion == null) { - // We have been requested to add a versionless plugin - version = null; - } else { - Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); - Optional maybeGs = cu.getMarkers().findFirst(GradleSettings.class); - if (!maybeGp.isPresent() && !maybeGs.isPresent()) { - return cu; - } + if (!FindPlugins.find(cu, pluginId).isEmpty() && (acceptTransitive == null || acceptTransitive)) { + return cu; + } - try { - version = new DependencyVersionSelector(null, maybeGp.orElse(null), maybeGs.orElse(null)).select(new GroupArtifact(pluginId, pluginId + ".gradle.plugin"), "classpath", newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(cu); - } + String version; + if (newVersion == null) { + // We have been requested to add a versionless plugin + version = null; + } else { + Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); + Optional maybeGs = cu.getMarkers().findFirst(GradleSettings.class); + if (!maybeGp.isPresent() && !maybeGs.isPresent()) { + return cu; } - AtomicInteger singleQuote = new AtomicInteger(); - AtomicInteger doubleQuote = new AtomicInteger(); - new GroovyIsoVisitor() { - final MethodMatcher pluginIdMatcher = new MethodMatcher("PluginSpec id(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer integer) { - J.MethodInvocation m = super.visitMethodInvocation(method, integer); - if (pluginIdMatcher.matches(m)) { - if (m.getArguments().get(0) instanceof J.Literal) { - J.Literal l = (J.Literal) m.getArguments().get(0); - assert l.getValueSource() != null; - if (l.getValueSource().startsWith("'")) { - singleQuote.incrementAndGet(); - } else { - doubleQuote.incrementAndGet(); - } + try { + version = new DependencyVersionSelector(null, maybeGp.orElse(null), maybeGs.orElse(null)).select(new GroupArtifact(pluginId, pluginId + ".gradle.plugin"), "classpath", newVersion, versionPattern, ctx); + } catch (MavenDownloadingException e) { + return e.warn(cu); + } + } + + AtomicInteger singleQuote = new AtomicInteger(); + AtomicInteger doubleQuote = new AtomicInteger(); + AtomicBoolean pluginAlreadyApplied = new AtomicBoolean(); + new GroovyIsoVisitor() { + final MethodMatcher pluginIdMatcher = new MethodMatcher("PluginSpec id(..)"); + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer integer) { + J.MethodInvocation m = super.visitMethodInvocation(method, integer); + if (pluginIdMatcher.matches(m)) { + if (m.getArguments().get(0) instanceof J.Literal) { + J.Literal l = (J.Literal) m.getArguments().get(0); + if (pluginId.equals(l.getValue())) { + pluginAlreadyApplied.set(true); + } + assert l.getValueSource() != null; + if (l.getValueSource().startsWith("'")) { + singleQuote.incrementAndGet(); + } else { + doubleQuote.incrementAndGet(); } } - return m; } - }.visitCompilationUnit(cu, 0); - - String delimiter = singleQuote.get() < doubleQuote.get() ? "\"" : "'"; - String source = "plugins {\n" + - " id " + delimiter + pluginId + delimiter + (version != null ? " version " + delimiter + version + delimiter : "") + (version != null && Boolean.FALSE.equals(apply) ? " apply " + apply : "") + "\n" + - "}"; - Statement statement = GradleParser.builder().build() - .parseInputs(singletonList(Parser.Input.fromString(source)), null, ctx) - .findFirst() - .map(parsed -> { - if (parsed instanceof ParseError) { - throw ((ParseError) parsed).toException(); - } - return ((G.CompilationUnit) parsed); - }) - .map(parsed -> parsed.getStatements().get(0)) - .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")); - - if (FindMethods.find(cu, "RewriteGradleProject plugins(..)").isEmpty() && FindMethods.find(cu, "RewriteSettings plugins(..)").isEmpty()) { - if (cu.getSourcePath().endsWith(Paths.get("settings.gradle")) && - !cu.getStatements().isEmpty() && - cu.getStatements().get(0) instanceof J.MethodInvocation && - ((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) { - return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), 1)); - } else { - int insertAtIdx = 0; - for (int i = 0; i < cu.getStatements().size(); i++) { - Statement existingStatement = cu.getStatements().get(i); - if (existingStatement instanceof J.MethodInvocation && ((J.MethodInvocation) existingStatement).getSimpleName().equals("buildscript")) { - insertAtIdx = i + 1; - break; - } + return m; + } + }.visitCompilationUnit(cu, 0); + + if (pluginAlreadyApplied.get()) { + return cu; + } + + String delimiter = singleQuote.get() < doubleQuote.get() ? "\"" : "'"; + String source = "plugins {\n" + + " id " + delimiter + pluginId + delimiter + (version != null ? " version " + delimiter + version + delimiter : "") + (version != null && Boolean.FALSE.equals(apply) ? " apply " + apply : "") + "\n" + + "}"; + Statement statement = GradleParser.builder().build() + .parseInputs(singletonList(Parser.Input.fromString(source)), null, ctx) + .findFirst() + .map(parsed -> { + if (parsed instanceof ParseError) { + throw ((ParseError) parsed).toException(); } - if (insertAtIdx == 0) { - Comment licenseHeader = getLicenseHeader(cu); - if (licenseHeader != null) { - cu = removeLicenseHeader(cu); - statement = statement.withComments(Collections.singletonList(licenseHeader)); - } - Space leadingSpace = Space.firstPrefix(cu.getStatements()); - return cu.withStatements(ListUtils.insert( - Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), - autoFormat(statement, ctx, getCursor()), - insertAtIdx)); - } else { - return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), insertAtIdx)); + return ((G.CompilationUnit) parsed); + }) + .map(parsed -> parsed.getStatements().get(0)) + .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")); + + if (FindMethods.find(cu, "RewriteGradleProject plugins(..)").isEmpty() && FindMethods.find(cu, "RewriteSettings plugins(..)").isEmpty()) { + if (cu.getSourcePath().endsWith(Paths.get("settings.gradle")) && + !cu.getStatements().isEmpty() && + cu.getStatements().get(0) instanceof J.MethodInvocation && + ((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) { + return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), 1)); + } else { + int insertAtIdx = 0; + for (int i = 0; i < cu.getStatements().size(); i++) { + Statement existingStatement = cu.getStatements().get(i); + if (existingStatement instanceof J.MethodInvocation && ((J.MethodInvocation) existingStatement).getSimpleName().equals("buildscript")) { + insertAtIdx = i + 1; + break; } } - } else { - MethodMatcher buildPluginsMatcher = new MethodMatcher("RewriteGradleProject plugins(groovy.lang.Closure)"); - MethodMatcher settingsPluginsMatcher = new MethodMatcher("RewriteSettings plugins(groovy.lang.Closure)"); - J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); - return cu.withStatements(ListUtils.map(cu.getStatements(), stat -> { - if (stat instanceof J.MethodInvocation) { - J.MethodInvocation m = (J.MethodInvocation) stat; - if (buildPluginsMatcher.matches(m) || settingsPluginsMatcher.matches(m)) { - m = m.withArguments(ListUtils.map(m.getArguments(), a -> { - if (a instanceof J.Lambda) { - J.Lambda l = (J.Lambda) a; - J.Block b = (J.Block) l.getBody(); - List pluginStatements = b.getStatements(); - if (!pluginStatements.isEmpty() && pluginStatements.get(pluginStatements.size() - 1) instanceof J.Return) { - Statement last = pluginStatements.remove(pluginStatements.size() - 1); - Expression lastExpr = requireNonNull(((J.Return) last).getExpression()); - pluginStatements.add(lastExpr.withPrefix(last.getPrefix())); - } - pluginStatements.add(pluginDef); - return l.withBody(autoFormat(b.withStatements(pluginStatements), ctx, getCursor())); - } - return a; - })); - return m; - } + if (insertAtIdx == 0) { + Comment licenseHeader = getLicenseHeader(cu); + if (licenseHeader != null) { + cu = removeLicenseHeader(cu); + statement = statement.withComments(Collections.singletonList(licenseHeader)); } - return stat; - })); + Space leadingSpace = Space.firstPrefix(cu.getStatements()); + return cu.withStatements(ListUtils.insert( + Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), + autoFormat(statement, ctx, getCursor()), + insertAtIdx)); + } else { + return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), insertAtIdx)); + } } + } else { + MethodMatcher buildPluginsMatcher = new MethodMatcher("RewriteGradleProject plugins(groovy.lang.Closure)"); + MethodMatcher settingsPluginsMatcher = new MethodMatcher("RewriteSettings plugins(groovy.lang.Closure)"); + J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); + return cu.withStatements(ListUtils.map(cu.getStatements(), stat -> { + if (stat instanceof J.MethodInvocation) { + J.MethodInvocation m = (J.MethodInvocation) stat; + if (buildPluginsMatcher.matches(m) || settingsPluginsMatcher.matches(m)) { + m = m.withArguments(ListUtils.map(m.getArguments(), a -> { + if (a instanceof J.Lambda) { + J.Lambda l = (J.Lambda) a; + J.Block b = (J.Block) l.getBody(); + List pluginStatements = b.getStatements(); + if (!pluginStatements.isEmpty() && pluginStatements.get(pluginStatements.size() - 1) instanceof J.Return) { + Statement last = pluginStatements.remove(pluginStatements.size() - 1); + Expression lastExpr = requireNonNull(((J.Return) last).getExpression()); + pluginStatements.add(lastExpr.withPrefix(last.getPrefix())); + } + pluginStatements.add(pluginDef); + return l.withBody(autoFormat(b.withStatements(pluginStatements), ctx, getCursor())); + } + return a; + })); + return m; + } + } + return stat; + })); } - return super.visitCompilationUnit(cu, ctx); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java index 9abdd8f6a84..55b29c0cabc 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java @@ -57,6 +57,15 @@ public class AddSettingsPlugin extends Recipe { @Nullable Boolean apply; + @Option(displayName = "Accept transitive", + description = "Some plugins apply other plugins. When this is set to true no plugin declaration will be added if the plugin is already applied transitively. " + + "When this is set to false the plugin will be added explicitly even if it is already applied transitively. " + + "Defaults to `true`.", + valid = {"true", "false"}, + required = false) + @Nullable + Boolean acceptTransitive; + @Override public String getDisplayName() { return "Add Gradle settings plugin"; @@ -80,7 +89,7 @@ public Validated validate() { public TreeVisitor getVisitor() { return Preconditions.check( new IsSettingsGradle<>(), - new AddPluginVisitor(pluginId, StringUtils.isBlank(version) ? "latest.release" : version, versionPattern, apply) + new AddPluginVisitor(pluginId, StringUtils.isBlank(version) ? "latest.release" : version, versionPattern, apply, acceptTransitive) ); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveDevelocityConfiguration.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveDevelocityConfiguration.java new file mode 100644 index 00000000000..356c2dfab43 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveDevelocityConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle.plugins; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.gradle.IsBuildGradle; +import org.openrewrite.gradle.IsSettingsGradle; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; + +import static org.openrewrite.Preconditions.or; + +public class RemoveDevelocityConfiguration extends Recipe { + @Override + public String getDisplayName() { + return "Remove Develocity configuration"; + } + + @Override + public String getDescription() { + return "Remove Develocity configuration from a Gradle build."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + or(new IsBuildGradle<>(), new IsSettingsGradle<>()), + new JavaIsoVisitor() { + @Override + public J.@Nullable MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + if ("develocity".equals(method.getSimpleName()) || + "gradleEnterprise".equals(method.getSimpleName())) { + return null; + } + return super.visitMethodInvocation(method, ctx); + } + } + ); + } +} diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java index 1f20c8e92e2..e4a806cdd25 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java @@ -24,10 +24,13 @@ import org.openrewrite.gradle.util.DependencyStringNotationConverter; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.trait.Trait; import java.util.List; @@ -43,6 +46,8 @@ public class GradleDependency implements Trait { ResolvedDependency resolvedDependency; public static class Matcher extends GradleTraitMatcher { + private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); + @Nullable protected String configuration; @@ -77,13 +82,14 @@ public Matcher artifactId(@Nullable String artifactId) { return null; } - GradleProject gradleProject = getGradleProject(cursor); - if (gradleProject == null) { + if (withinDependencyConstraintsBlock(cursor)) { + // A dependency constraint is different from an actual dependency return null; } + GradleProject gradleProject = getGradleProject(cursor); GradleDependencyConfiguration gdc = getConfiguration(gradleProject, methodInvocation); - if (gdc == null) { + if (gdc == null && !(DEPENDENCY_DSL_MATCHER.matches(methodInvocation) && !"project".equals(methodInvocation.getSimpleName()))) { return null; } @@ -95,49 +101,77 @@ public Matcher artifactId(@Nullable String artifactId) { Expression argument = methodInvocation.getArguments().get(0); if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral) { dependency = parseDependency(methodInvocation.getArguments()); - } else if (argument instanceof J.MethodInvocation && - (((J.MethodInvocation) argument).getSimpleName().equals("platform") || - ((J.MethodInvocation) argument).getSimpleName().equals("enforcedPlatform"))) { - dependency = parseDependency(((J.MethodInvocation) argument).getArguments()); + } else if (argument instanceof J.MethodInvocation) { + if (((J.MethodInvocation) argument).getSimpleName().equals("platform") || + ((J.MethodInvocation) argument).getSimpleName().equals("enforcedPlatform")) { + dependency = parseDependency(((J.MethodInvocation) argument).getArguments()); + } else if (((J.MethodInvocation) argument).getSimpleName().equals("project")) { + // project dependencies are not yet supported + return null; + } } if (dependency == null) { return null; } - if (gdc.isCanBeResolved()) { - for (ResolvedDependency resolvedDependency : gdc.getResolved()) { - if ((groupId == null || matchesGlob(resolvedDependency.getGroupId(), groupId)) && - (artifactId == null || matchesGlob(resolvedDependency.getArtifactId(), artifactId))) { - Dependency req = resolvedDependency.getRequested(); - if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && - req.getArtifactId().equals(dependency.getArtifactId())) { - return new GradleDependency(cursor, resolvedDependency); + if (gdc != null) { + if (gdc.isCanBeResolved()) { + for (ResolvedDependency resolvedDependency : gdc.getResolved()) { + if ((groupId == null || matchesGlob(resolvedDependency.getGroupId(), groupId)) && + (artifactId == null || matchesGlob(resolvedDependency.getArtifactId(), artifactId))) { + Dependency req = resolvedDependency.getRequested(); + if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && + req.getArtifactId().equals(dependency.getArtifactId())) { + return new GradleDependency(cursor, resolvedDependency); + } } } - } - } else { - for (GradleDependencyConfiguration transitiveConfiguration : gradleProject.configurationsExtendingFrom(gdc, true)) { - if (transitiveConfiguration.isCanBeResolved()) { - for (ResolvedDependency resolvedDependency : transitiveConfiguration.getResolved()) { - if ((groupId == null || matchesGlob(resolvedDependency.getGroupId(), groupId)) && - (artifactId == null || matchesGlob(resolvedDependency.getArtifactId(), artifactId))) { - Dependency req = resolvedDependency.getRequested(); - if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && - req.getArtifactId().equals(dependency.getArtifactId())) { - return new GradleDependency(cursor, resolvedDependency); + } else { + for (GradleDependencyConfiguration transitiveConfiguration : gradleProject.configurationsExtendingFrom(gdc, true)) { + if (transitiveConfiguration.isCanBeResolved()) { + for (ResolvedDependency resolvedDependency : transitiveConfiguration.getResolved()) { + if ((groupId == null || matchesGlob(resolvedDependency.getGroupId(), groupId)) && + (artifactId == null || matchesGlob(resolvedDependency.getArtifactId(), artifactId))) { + Dependency req = resolvedDependency.getRequested(); + if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && + req.getArtifactId().equals(dependency.getArtifactId())) { + return new GradleDependency(cursor, resolvedDependency); + } } } } } } } + + if ((groupId == null || matchesGlob(dependency.getGroupId(), groupId)) && + (artifactId == null || matchesGlob(dependency.getArtifactId(), artifactId))) { + // Couldn't find the actual resolved dependency, return a virtualized one instead + ResolvedDependency resolvedDependency = ResolvedDependency.builder() + .depth(-1) + .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) + .classifier(dependency.getClassifier()) + .type(dependency.getExt()) + .requested(Dependency.builder() + .scope(methodInvocation.getSimpleName()) + .type(dependency.getExt()) + .gav(new GroupArtifactVersion(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())) + .classifier(dependency.getClassifier()) + .build()) + .build(); + return new GradleDependency(cursor, resolvedDependency); + } } return null; } - private static @Nullable GradleDependencyConfiguration getConfiguration(GradleProject gradleProject, J.MethodInvocation methodInvocation) { + private static @Nullable GradleDependencyConfiguration getConfiguration(@Nullable GradleProject gradleProject, J.MethodInvocation methodInvocation) { + if (gradleProject == null) { + return null; + } + String methodName = methodInvocation.getSimpleName(); if (methodName.equals("classpath")) { return gradleProject.getBuildscript().getConfiguration(methodName); @@ -146,12 +180,12 @@ public Matcher artifactId(@Nullable String artifactId) { } } - private boolean withinDependenciesBlock(Cursor cursor) { + private boolean withinBlock(Cursor cursor, String name) { Cursor parentCursor = cursor.getParent(); while (parentCursor != null) { if (parentCursor.getValue() instanceof J.MethodInvocation) { J.MethodInvocation m = parentCursor.getValue(); - if (m.getSimpleName().equals("dependencies")) { + if (m.getSimpleName().equals(name)) { return true; } } @@ -161,6 +195,14 @@ private boolean withinDependenciesBlock(Cursor cursor) { return false; } + private boolean withinDependenciesBlock(Cursor cursor) { + return withinBlock(cursor, "dependencies"); + } + + private boolean withinDependencyConstraintsBlock(Cursor cursor) { + return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor); + } + private org.openrewrite.gradle.util.@Nullable Dependency parseDependency(List arguments) { Expression argument = arguments.get(0); if (argument instanceof J.Literal) { diff --git a/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-8.yml b/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-8.yml index 9babdf64095..097b7a669da 100644 --- a/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-8.yml +++ b/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-8.yml @@ -24,6 +24,9 @@ recipeList: version: 8.x addIfMissing: false + # https://docs.gradle.org/8.11.1/userguide/upgrading_version_8.html#test_framework_implementation_dependencies + - org.openrewrite.gradle.AddJUnitPlatformLauncher + # https://github.com/gradle/gradle/blob/v7.6.4/subprojects/core/src/main/java/org/gradle/api/internal/FeaturePreviews.java # https://github.com/gradle/gradle/blob/v8.10.1/subprojects/core/src/main/java/org/gradle/api/internal/FeaturePreviews.java - org.openrewrite.gradle.RemoveEnableFeaturePreview: @@ -32,3 +35,16 @@ recipeList: previewFeatureName: VERSION_ORDERING_V2 - org.openrewrite.gradle.RemoveEnableFeaturePreview: previewFeatureName: VERSION_CATALOGS +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.gradle.AddJUnitPlatformLauncher +displayName: Add JUnit Platform Launcher +description: Add the JUnit Platform Launcher to the buildscript dependencies. +recipeList: + - org.openrewrite.gradle.AddDependency: + groupId: org.junit.platform + artifactId: junit-platform-launcher + version: 1.x + acceptTransitive: true + configuration: testRuntimeOnly + onlyIfUsing: org.junit.jupiter.api.Test diff --git a/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml b/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml index 7240bdf3672..6fdf529b07e 100644 --- a/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml +++ b/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml @@ -42,3 +42,18 @@ recipeList: key: org.gradle.parallel value: true filePattern: gradle.properties +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.gradle.plugins.RemoveDevelocity +displayName: Remove Develocity +description: Remove the Develocity plugin and configuration from the Gradle build and settings files. +recipeList: + - org.openrewrite.gradle.plugins.RemoveBuildPlugin: + pluginId: com.gradle.develocity + - org.openrewrite.gradle.plugins.RemoveSettingsPlugin: + pluginId: com.gradle.develocity + - org.openrewrite.gradle.plugins.RemoveBuildPlugin: + pluginId: com.gradle.enterprise + - org.openrewrite.gradle.plugins.RemoveSettingsPlugin: + pluginId: com.gradle.enterprise + - org.openrewrite.gradle.plugins.RemoveDevelocityConfiguration \ No newline at end of file diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java index 6330f931018..cb1da2ffe4a 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java @@ -1317,9 +1317,6 @@ void addUnconditionally() { rewriteRun( spec -> spec.recipe(addDependency("org.apache.logging.log4j:log4j-core:2.22.1")), mavenProject("project", - srcMainJava( - java(usingGuavaIntMath) - ), buildGradle(""" plugins { id "java-library" diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddJUnitPlatformLauncherTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddJUnitPlatformLauncherTest.java new file mode 100644 index 00000000000..0c120dc8bce --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddJUnitPlatformLauncherTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.openrewrite.gradle.Assertions.buildGradle; +import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; +import static org.openrewrite.java.Assertions.*; + +class AddJUnitPlatformLauncherTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .beforeRecipe(withToolingApi()) + .parser(JavaParser.fromJavaVersion().classpath("junit-jupiter-api")) + .recipeFromResources("org.openrewrite.gradle.AddJUnitPlatformLauncher"); + } + + @DocumentExample + @Test + void addJUnitPlatformLauncher() { + rewriteRun( + mavenProject("project", + srcTestJava( + java( + """ + import org.junit.jupiter.api.Test; + public class A { + @Test + void foo() { + } + } + """ + ) + ), + buildGradle( + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + """, + spec -> spec.after(buildGradle -> { + assertThat(buildGradle).contains("testRuntimeOnly \"org.junit.platform:junit-platform-launcher:"); + return buildGradle; + }) + ) + ) + ); + } +} diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/EnableDevelocityBuildCacheTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/EnableDevelocityBuildCacheTest.java new file mode 100644 index 00000000000..6e725db524f --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/EnableDevelocityBuildCacheTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.gradle.Assertions.settingsGradle; + +class EnableDevelocityBuildCacheTest implements RewriteTest { + + @Test + @DocumentExample + void addBuildCacheRemoteConfig() { + rewriteRun(spec -> spec.recipe(new EnableDevelocityBuildCache("true", "System.getenv(\"CI\") != null")), + settingsGradle( + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + buildCache { + remote(develocity.buildCache) { + enabled = true + push = System.getenv("CI") != null + } + } + } + """ + ) + ); + } + + @Test + void addBuildCacheRemoteConfigExtendedConfig() { + rewriteRun(spec -> spec.recipe(new EnableDevelocityBuildCache("true", "System.getenv(\"CI\") != null")), + settingsGradle( + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + buildScan { + uploadInBackground = true + } + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + buildScan { + uploadInBackground = true + } + buildCache { + remote(develocity.buildCache) { + enabled = true + push = System.getenv("CI") != null + } + } + } + """ + ) + ); + } + + @Test + void addBuildCacheRemoteConfigWithOnlyPush() { + rewriteRun(spec -> spec.recipe(new EnableDevelocityBuildCache(null, "true")), + settingsGradle( + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + buildCache { + remote(develocity.buildCache) { + push = true + } + } + } + """ + ) + ); + } + + @Test + void shouldNotModifyBuildCacheConfig() { + rewriteRun(spec -> spec.recipe(new EnableDevelocityBuildCache(null, "#{isTrue(env['CI'])}")), + settingsGradle( + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://dev.example.com/' + buildCache { + remote(develocity.buildCache) { + push = false + } + } + } + """ + ) + ); + } +} diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/RemoveRedundantDependencyVersionsTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/RemoveRedundantDependencyVersionsTest.java new file mode 100644 index 00000000000..379b0381291 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/RemoveRedundantDependencyVersionsTest.java @@ -0,0 +1,310 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.gradle.Assertions.buildGradle; +import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; + +class RemoveRedundantDependencyVersionsTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.beforeRecipe(withToolingApi()) + .recipe(new RemoveRedundantDependencyVersions(null, null, null, null)); + } + + @DocumentExample + @Test + void literal() { + rewriteRun( + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3:3.14.0") + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3") + } + """ + ) + ); + } + + @Test + void mapEntry() { + rewriteRun( + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation(group: "org.apache.commons", name: "commons-lang3", version: "3.14.0") + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation(group: "org.apache.commons", name: "commons-lang3") + } + """ + ) + ); + } + + @Test + void mapLiteral() { + rewriteRun( + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation([group: "org.apache.commons", name: "commons-lang3", version: "3.14.0"]) + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation([group: "org.apache.commons", name: "commons-lang3"]) + } + """ + ) + ); + } + + @Test + void enforcedPlatform() { + rewriteRun( + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3:3.14.0") + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3") + } + """ + ) + ); + } + + @Test + void platformUsingMapEntry() { + rewriteRun( + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(enforcedPlatform(group: "org.springframework.boot", name: "spring-boot-dependencies", version: "3.3.3")) + implementation("org.apache.commons:commons-lang3:3.14.0") + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(enforcedPlatform(group: "org.springframework.boot", name: "spring-boot-dependencies", version: "3.3.3")) + implementation("org.apache.commons:commons-lang3") + } + """ + ) + ); + } + + @Test + void freestandingScript() { + rewriteRun( + buildGradle( + """ + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3:3.14.0") + } + """, + """ + repositories { + mavenCentral() + } + + dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3") + } + """, + spec -> spec.path("dependencies.gradle") + ), + buildGradle( + """ + plugins { + id("java") + } + + apply from: 'dependencies.gradle' + """ + ) + ); + } + + @Test + void transitiveConfiguration() { + rewriteRun( + buildGradle( + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + dependencies { + api(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3:3.14.0") + } + """, + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + dependencies { + api(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + implementation("org.apache.commons:commons-lang3") + } + """ + ) + ); + } + + @Test + void unmanagedDependency() { + rewriteRun( + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation("org.apache.commons:commons-lang3:3.14.0") + + testImplementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.3")) + } + """ + ) + ); + } +} diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java index 5e9ecf85f34..2626825c559 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java @@ -44,18 +44,18 @@ public void defaults(RecipeSpec spec) { @DocumentExample @Test - void guava() { + void guavaCompileOnly() { rewriteRun( buildGradle( """ plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { compileOnly 'com.google.guava:guava:29.0-jre' runtimeOnly ('com.google.guava:guava:29.0-jre') @@ -65,11 +65,11 @@ void guava() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { compileOnly 'com.google.guava:guava:30.1.1-jre' runtimeOnly ('com.google.guava:guava:30.1.1-jre') @@ -78,21 +78,77 @@ void guava() { spec -> spec.afterRecipe(after -> { Optional maybeGp = after.getMarkers().findFirst(GradleProject.class); assertThat(maybeGp).isPresent(); - GradleProject gp = maybeGp.get(); - GradleDependencyConfiguration compileClasspath = gp.getConfiguration("compileClasspath"); + GradleDependencyConfiguration compileClasspath = maybeGp.get().getConfiguration("compileClasspath"); assertThat(compileClasspath).isNotNull(); - assertThat( - compileClasspath.getRequested().stream() - .filter(dep -> "com.google.guava".equals(dep.getGroupId()) && "guava".equals(dep.getArtifactId()) && "30.1.1-jre".equals(dep.getVersion())) - .findAny()) + assertThat(compileClasspath.getRequested()) .as("GradleProject requested dependencies should have been updated with the new version of guava") - .isPresent(); - assertThat( - compileClasspath.getResolved().stream() - .filter(dep -> "com.google.guava".equals(dep.getGroupId()) && "guava".equals(dep.getArtifactId()) && "30.1.1-jre".equals(dep.getVersion())) - .findAny()) - .as("GradleProject requested dependencies should have been updated with the new version of guava") - .isPresent(); + .anySatisfy(dep -> { + assertThat(dep.getGroupId()).isEqualTo("com.google.guava"); + assertThat(dep.getArtifactId()).isEqualTo("guava"); + assertThat(dep.getVersion()).isEqualTo("30.1.1-jre"); + }); + assertThat(compileClasspath.getResolved()) + .as("GradleProject resolved dependencies should have been updated with the new version of guava") + .anySatisfy(dep -> { + assertThat(dep.getGroupId()).isEqualTo("com.google.guava"); + assertThat(dep.getArtifactId()).isEqualTo("guava"); + assertThat(dep.getVersion()).isEqualTo("30.1.1-jre"); + }); + }) + ) + ); + } + + @Test + void mockitoTestImplementation() { + rewriteRun(recipeSpec -> { + recipeSpec.beforeRecipe(withToolingApi()) + .recipe(new UpgradeDependencyVersion("org.mockito", "*", "4.11.0", null)); + }, + buildGradle( + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + testImplementation("org.mockito:mockito-junit-jupiter:3.12.4") + } + """, + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + testImplementation("org.mockito:mockito-junit-jupiter:4.11.0") + } + """, + spec -> spec.afterRecipe(after -> { + Optional maybeGp = after.getMarkers().findFirst(GradleProject.class); + assertThat(maybeGp).isPresent(); + GradleDependencyConfiguration testCompileClasspath = maybeGp.get().getConfiguration("testCompileClasspath"); + assertThat(testCompileClasspath).isNotNull(); + assertThat(testCompileClasspath.getRequested()) + .as("GradleProject requested dependencies should have been updated with the new version of mockito") + .anySatisfy(dep -> { + assertThat(dep.getGroupId()).isEqualTo("org.mockito"); + assertThat(dep.getArtifactId()).isEqualTo("mockito-junit-jupiter"); + }); + assertThat(testCompileClasspath.getResolved()) + .as("GradleProject resolved dependencies should have been updated with the new version of mockito") + .anySatisfy(dep -> { + assertThat(dep.getGroupId()).isEqualTo("org.mockito"); + assertThat(dep.getArtifactId()).isEqualTo("mockito-junit-jupiter"); + }); }) ) ); @@ -106,13 +162,13 @@ void updateVersionInVariable() { plugins { id 'java-library' } - + def guavaVersion = '29.0-jre' def otherVersion = "latest.release" repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") implementation "com.fasterxml.jackson.core:jackson-databind:$otherVersion" @@ -122,13 +178,13 @@ void updateVersionInVariable() { plugins { id 'java-library' } - + def guavaVersion = '30.1.1-jre' def otherVersion = "latest.release" repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") implementation "com.fasterxml.jackson.core:jackson-databind:$otherVersion" @@ -146,7 +202,7 @@ void deeplyNestedProjectDependency() { plugins { id 'java-library' } - + dependencies { implementation project(":foo:bar:baz:qux:quux") } @@ -178,11 +234,11 @@ void varargsDependency() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation( 'com.google.guava:guava-gwt:29.0-jre', @@ -193,11 +249,11 @@ void varargsDependency() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation( 'com.google.guava:guava-gwt:29.0-jre', @@ -215,12 +271,12 @@ void mapNotationVariable() { plugins { id 'java-library' } - + def guavaVersion = '29.0-jre' repositories { mavenCentral() } - + dependencies { implementation group: "com.google.guava", name: "guava", version: guavaVersion } @@ -229,12 +285,12 @@ void mapNotationVariable() { plugins { id 'java-library' } - + def guavaVersion = '30.1.1-jre' repositories { mavenCentral() } - + dependencies { implementation group: "com.google.guava", name: "guava", version: guavaVersion } @@ -251,11 +307,11 @@ void mapNotationLiteral() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation (group: "com.google.guava", name: "guava", version: '29.0-jre') } @@ -264,11 +320,11 @@ void mapNotationLiteral() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation (group: "com.google.guava", name: "guava", version: '30.1.1-jre') } @@ -286,11 +342,11 @@ void worksWithPlatform() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation platform("com.google.guava:guava:29.0-jre") } @@ -299,11 +355,11 @@ implementation platform("com.google.guava:guava:29.0-jre") plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation platform("com.google.guava:guava:30.1.1-jre") } @@ -328,19 +384,19 @@ void upgradesVariablesDefinedInExtraProperties() { classpath("com.google.guava:guava:${guavaVersion}") } } - + plugins { id "java" } - + repositories { mavenCentral() } - + ext { guavaVersion2 = "29.0-jre" } - + dependencies { implementation "com.google.guava:guava:${guavaVersion2}" } @@ -357,19 +413,19 @@ void upgradesVariablesDefinedInExtraProperties() { classpath("com.google.guava:guava:${guavaVersion}") } } - + plugins { id "java" } - + repositories { mavenCentral() } - + ext { guavaVersion2 = "30.1.1-jre" } - + dependencies { implementation "com.google.guava:guava:${guavaVersion2}" } @@ -387,15 +443,15 @@ void matchesGlobs() { plugins { id "java" } - + repositories { mavenCentral() } - + ext { guavaVersion = "29.0-jre" } - + def guavaVersion2 = "29.0-jre" dependencies { implementation("com.google.guava:guava:29.0-jre") @@ -408,15 +464,15 @@ void matchesGlobs() { plugins { id "java" } - + repositories { mavenCentral() } - + ext { guavaVersion = "30.1.1-jre" } - + def guavaVersion2 = "30.1.1-jre" dependencies { implementation("com.google.guava:guava:30.1.1-jre") @@ -438,11 +494,11 @@ void defaultsToLatestRelease() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation 'com.google.guava:guava:29.0-jre' } @@ -488,11 +544,11 @@ void versionInPropertiesFile() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") } @@ -515,11 +571,11 @@ void versionInPropertiesFileNotUpdatedIfNoDependencyVariable() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:30.1.1-jre") } @@ -554,11 +610,11 @@ void versionInParentAndMultiModulePropertiesFiles() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") } @@ -570,11 +626,11 @@ void versionInParentAndMultiModulePropertiesFiles() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") } @@ -601,18 +657,18 @@ void versionInParentSubprojectDefinitionWithPropertiesFiles() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + subprojects { repositories { mavenCentral() } - + apply plugin: "java-library" - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") } @@ -631,7 +687,7 @@ void versionInParentSubprojectDefinitionWithPropertiesFiles() { plugins { id 'java-library' } - + repositories { mavenCentral() } @@ -659,11 +715,11 @@ void versionOnlyInMultiModuleChildPropertiesFiles() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation ("com.google.guava:guava:$guavaVersion") } @@ -694,7 +750,7 @@ void mapNotationVariableInPropertiesFile() { repositories { mavenCentral() } - + dependencies { implementation group: "com.google.guava", name: "guava", version: guavaVersion } @@ -720,11 +776,11 @@ void mapNotationGStringVariableInPropertiesFile() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation group: "com.google.guava", name: "guava", version: "${guavaVersion}" } @@ -753,11 +809,11 @@ void globVersionsInPropertiesFileWithMultipleVersionsOnlyUpdatesCorrectProperty( plugins { id 'java' } - + repositories { mavenCentral() } - + dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}") implementation("org.springframework.security:spring-security-oauth2-core:${springSecurityVersion}") @@ -783,11 +839,11 @@ void disallowDowngrade() { plugins { id 'java' } - + repositories { mavenCentral() } - + dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}") implementation("org.springframework.security:spring-security-oauth2-core:${springSecurityVersion}") @@ -806,11 +862,11 @@ void dontDowngradeWhenExactVersion() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation 'com.google.guava:guava:29.0-jre' } @@ -828,11 +884,11 @@ void leaveConstraintsAlone() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { constraints { implementation("com.google.guava:guava:28.0-jre") @@ -854,11 +910,11 @@ void unknownConfiguration() { id 'java' id "org.hidetake.swagger.generator" version "2.18.2" } - + repositories { mavenCentral() } - + dependencies { swaggerCodegen "org.openapitools:openapi-generator-cli:5.2.0" } @@ -868,11 +924,11 @@ void unknownConfiguration() { id 'java' id "org.hidetake.swagger.generator" version "2.18.2" } - + repositories { mavenCentral() } - + dependencies { swaggerCodegen "org.openapitools:openapi-generator-cli:5.2.1" } @@ -890,11 +946,11 @@ void noActionForNonStringLiterals() { plugins { id 'java' } - + repositories { mavenCentral() } - + dependencies { implementation(gradleApi()) jar { @@ -908,7 +964,7 @@ void noActionForNonStringLiterals() { @Test @Issue("https://github.com/openrewrite/rewrite-java-dependencies/pull/106") - void isAcceptable(){ + void isAcceptable() { // Mimic org.openrewrite.java.dependencies.UpgradeTransitiveDependencyVersion#getVisitor UpgradeDependencyVersion guava = new UpgradeDependencyVersion("com.google.guava", "guava", "30.x", "-jre"); TreeVisitor visitor = guava.getVisitor(); @@ -930,11 +986,11 @@ void exactVersionWithExactPattern() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation('com.google.guava:guava:29.0-jre') } @@ -943,11 +999,11 @@ void exactVersionWithExactPattern() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation('com.google.guava:guava:32.1.1-jre') } @@ -960,17 +1016,17 @@ void exactVersionWithExactPattern() { @Issue("https://github.com/openrewrite/rewrite/issues/4333") void exactVersionWithRegexPattern() { rewriteRun( - spec -> spec.recipe(new UpgradeDependencyVersion("com.google.guava", "guava", "32.1.1", ".*droid")), + spec -> spec.recipe(new UpgradeDependencyVersion("com.google.guava", "guava", "32.1.1", "-.*?droid")), buildGradle( """ plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation('com.google.guava:guava:29.0-android') } @@ -979,11 +1035,11 @@ void exactVersionWithRegexPattern() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation('com.google.guava:guava:32.1.1-android') } @@ -1001,11 +1057,11 @@ void upgradesDependencyVersionDefinedInJvmTestSuite() { id "java-library" id 'jvm-test-suite' } - + repositories { mavenCentral() } - + testing { suites { test { @@ -1021,11 +1077,11 @@ void upgradesDependencyVersionDefinedInJvmTestSuite() { id "java-library" id 'jvm-test-suite' } - + repositories { mavenCentral() } - + testing { suites { test { @@ -1091,11 +1147,11 @@ void issue4655() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + version='ORC-246-1-SNAPSHOT' dependencies { implementation "com.veon.eurasia.oraculum:jira-api:$version" diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java index 252765fc17f..90868e8e2a6 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java @@ -35,7 +35,7 @@ public void defaults(RecipeSpec spec) { @Test void addPluginWithoutVersionToNewBlock() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("java-library", null, null, null)), + spec -> spec.recipe(new AddBuildPlugin("java-library", null, null, null, null)), buildGradle( "", """ @@ -50,7 +50,7 @@ void addPluginWithoutVersionToNewBlock() { @Test void addPluginWithoutVersionToExistingBlock() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("java-library", null, null, null)), + spec -> spec.recipe(new AddBuildPlugin("java-library", null, null, null, null)), buildGradle( """ plugins { @@ -70,7 +70,7 @@ void addPluginWithoutVersionToExistingBlock() { @Test void addThirdPartyPluginWithoutVersion() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("org.openrewrite.rewrite", null, null, null)), + spec -> spec.recipe(new AddBuildPlugin("org.openrewrite.rewrite", null, null, null, null)), buildGradle( """ plugins { @@ -90,7 +90,7 @@ void addThirdPartyPluginWithoutVersion() { @Test void addPluginWithVersionToNewBlock() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null)), + spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null, null)), buildGradle( "", """ @@ -105,7 +105,7 @@ void addPluginWithVersionToNewBlock() { @Test void addPluginWithVersionToExistingBlock() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null)), + spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null, null)), buildGradle( """ plugins { @@ -125,20 +125,18 @@ void addPluginWithVersionToExistingBlock() { @Test void addPluginAfterBuildscriptBlock() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null)), + spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null, null)), buildGradle( """ import java.util.List - buildscript { } """, """ import java.util.List - buildscript { } - + plugins { id 'com.jfrog.bintray' version '1.0' } @@ -151,7 +149,7 @@ void addPluginAfterBuildscriptBlock() { void addPluginToNewBlockWithLicenseHeader() { rewriteRun( spec -> { - spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null)); + spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null, null)); spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))); }, buildGradle( @@ -159,7 +157,7 @@ void addPluginToNewBlockWithLicenseHeader() { /* * License header */ - + dependencies { } """, """ @@ -182,7 +180,7 @@ void addPluginToNewBlockWithLicenseHeader() { void addPluginToNewBlockWithLicenseHeaderAndComment() { rewriteRun( spec -> { - spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null)); + spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null, null)); spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))); }, buildGradle( @@ -190,7 +188,6 @@ void addPluginToNewBlockWithLicenseHeaderAndComment() { /* * License header */ - // Comment dependencies { } @@ -198,7 +195,6 @@ void addPluginToNewBlockWithLicenseHeaderAndComment() { /* * License header */ - plugins { id 'com.jfrog.bintray' version '1.0' } @@ -215,7 +211,7 @@ void addPluginToNewBlockWithLicenseHeaderAndComment() { void addPluginToNewBlockWithOnlyLicenseHeader() { rewriteRun( spec -> { - spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null)); + spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, null, null)); spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))); }, buildGradle( @@ -239,7 +235,7 @@ void addPluginToNewBlockWithOnlyLicenseHeader() { @Test void addPluginApplyFalse() { rewriteRun( - spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, false)), + spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null, false, null)), buildGradle( """ plugins { diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddSettingsPluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddSettingsPluginTest.java index 9a838dddd52..4700abd60ab 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddSettingsPluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddSettingsPluginTest.java @@ -34,7 +34,7 @@ class AddSettingsPluginTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.beforeRecipe(withToolingApi()) - .recipe(new AddSettingsPlugin("com.gradle.enterprise", "3.11.x", null, null)); + .recipe(new AddSettingsPlugin("com.gradle.enterprise", "3.11.x", null, null, null)); } @Test @@ -80,7 +80,7 @@ void addPluginToExistingBlock() { """ plugins { } - + rootProject.name = 'my-project' """, interpolateResolvedVersion( @@ -88,7 +88,7 @@ void addPluginToExistingBlock() { plugins { id 'com.gradle.enterprise' version '%s' } - + rootProject.name = 'my-project' """ ) @@ -132,7 +132,7 @@ void addPluginWithPluginManagementBlock() { void addPluginApplyFalse() { rewriteRun( spec -> spec.beforeRecipe(withToolingApi()) - .recipe(new AddSettingsPlugin("com.gradle.enterprise", "3.11.x", null, false)), + .recipe(new AddSettingsPlugin("com.gradle.enterprise", "3.11.x", null, false, null)), settingsGradle( "", interpolateResolvedVersion( diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/RemoveDevelocityTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/RemoveDevelocityTest.java new file mode 100644 index 00000000000..3c26e264f79 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/RemoveDevelocityTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.gradle.plugins; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.gradle.Assertions.settingsGradle; +import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; + +class RemoveDevelocityTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .beforeRecipe(withToolingApi()) + .recipeFromResource("/META-INF/rewrite/gradle.yml", "org.openrewrite.gradle.plugins.RemoveDevelocity"); + } + + @Test + @DocumentExample + void removeGradleEnterprise() { + rewriteRun( + settingsGradle( + """ + plugins { + id 'com.gradle.enterprise' version '3.16' + } + gradleEnterprise { + server = 'https://ge.sam.com/' + allowUntrustedServer = true + buildScan { + publishAlways() + uploadInBackground = true + capture { + taskInputFiles = true + } + } + buildCache { + remote(gradleEnterprise.buildCache) { + enabled = true + push = System.getenv("CI") != null + } + } + } + """, + "" + ) + ); + } + + @Test + void removeDevelocity() { + rewriteRun( + settingsGradle( + """ + plugins { + id 'com.gradle.develocity' version '3.17.6' + } + develocity { + server = 'https://ge.sam.com/' + allowUntrustedServer = true + buildScan { + uploadInBackground = true + capture { + fileFingerprints = true + } + } + buildCache { + remote(develocity.buildCache) { + enabled = true + push = System.getenv("CI") != null + } + } + } + """, + "" + ) + ); + } +} diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 47b65310df4..9d8cdc534bf 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -16,6 +16,8 @@ package org.openrewrite.groovy; import groovy.lang.GroovySystem; +import groovy.transform.Generated; +import groovy.transform.Immutable; import groovyjarjarasm.asm.Opcodes; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -36,12 +38,16 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.ImplicitReturn; +import org.openrewrite.java.marker.OmitParentheses; import org.openrewrite.java.marker.Semicolon; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.Statement; import org.openrewrite.marker.Markers; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.nio.charset.Charset; import java.nio.file.Path; @@ -51,11 +57,11 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; import static org.openrewrite.Tree.randomId; import static org.openrewrite.internal.StringUtils.indexOfNextNonWhitespace; import static org.openrewrite.java.tree.Space.EMPTY; @@ -130,9 +136,8 @@ public G.CompilationUnit visit(SourceUnit unit, ModuleNode ast) throws GroovyPar JRightPadded pkg = null; if (ast.getPackage() != null) { prefix = whitespace(); - cursor += "package".length(); - pkg = JRightPadded.build(new J.Package(randomId(), EMPTY, Markers.EMPTY, - typeTree(null), emptyList())); + skip("package"); + pkg = maybeSemicolon(new J.Package(randomId(), EMPTY, Markers.EMPTY, typeTree(null), emptyList())); } for (ImportNode anImport : ast.getImports()) { @@ -149,10 +154,8 @@ public G.CompilationUnit visit(SourceUnit unit, ModuleNode ast) throws GroovyPar } for (ClassNode aClass : ast.getClasses()) { - if (aClass.getSuperClass() == null || - !("groovy.lang.Script".equals(aClass.getSuperClass().getName()) || - "RewriteGradleProject".equals(aClass.getSuperClass().getName()) || - "RewriteSettings".equals(aClass.getSuperClass().getName()))) { + // skip over the synthetic script class + if (!aClass.getName().equals(ast.getMainClassName()) || !aClass.getName().endsWith("doesntmatter")) { sortedByPosition.computeIfAbsent(pos(aClass), i -> new ArrayList<>()).add(aClass); } } @@ -204,7 +207,7 @@ public G.CompilationUnit visit(SourceUnit unit, ModuleNode ast) throws GroovyPar null, pkg, statements, - format(source.substring(cursor)) + format(source, cursor, source.length()) ); } @@ -218,32 +221,23 @@ private class RewriteGroovyClassVisitor extends ClassCodeVisitorSupport { @Override public void visitClass(ClassNode clazz) { Space fmt = whitespace(); - List leadingAnnotations; - if (clazz.getAnnotations().isEmpty()) { - leadingAnnotations = emptyList(); - } else { - leadingAnnotations = new ArrayList<>(clazz.getAnnotations().size()); - for (AnnotationNode annotation : clazz.getAnnotations()) { - visitAnnotation(annotation); - leadingAnnotations.add(pollQueue()); - } - } + List leadingAnnotations = visitAndGetAnnotations(clazz); List modifiers = visitModifiers(clazz.getModifiers()); Space kindPrefix = whitespace(); J.ClassDeclaration.Kind.Type kindType = null; if (source.startsWith("class", cursor)) { kindType = J.ClassDeclaration.Kind.Type.Class; - cursor += "class".length(); + skip("class"); } else if (source.startsWith("interface", cursor)) { kindType = J.ClassDeclaration.Kind.Type.Interface; - cursor += "interface".length(); + skip("interface"); } else if (source.startsWith("@interface", cursor)) { kindType = J.ClassDeclaration.Kind.Type.Annotation; - cursor += "@interface".length(); + skip("@interface"); } else if (source.startsWith("enum", cursor)) { kindType = J.ClassDeclaration.Kind.Type.Enum; - cursor += "enum".length(); + skip("enum"); } assert kindType != null; J.ClassDeclaration.Kind kind = new J.ClassDeclaration.Kind(randomId(), kindPrefix, Markers.EMPTY, emptyList(), kindType); @@ -256,8 +250,15 @@ public void visitClass(ClassNode clazz) { } JLeftPadded extendings = null; - if (clazz.getSuperClass().getLineNumber() >= 0) { - extendings = padLeft(sourceBefore("extends"), visitTypeTree(clazz.getSuperClass())); + if (kindType == J.ClassDeclaration.Kind.Type.Class) { + int saveCursor = cursor; + Space extendsPrefix = whitespace(); + if (source.startsWith("extends", cursor)) { + skip("extends"); + extendings = padLeft(extendsPrefix, visitTypeTree(clazz.getSuperClass())); + } else { + cursor = saveCursor; + } } JContainer implementings = null; @@ -273,7 +274,7 @@ public void visitClass(ClassNode clazz) { for (int i = 0; i < interfaces.length; i++) { ClassNode anInterface = interfaces[i]; // Any annotation @interface is listed as extending java.lang.annotation.Annotation, although it doesn't appear in source - if (kindType == J.ClassDeclaration.Kind.Type.Annotation && "java.lang.annotation.Annotation".equals(anInterface.getName())) { + if (kindType == J.ClassDeclaration.Kind.Type.Annotation && Annotation.class.getName().equals(anInterface.getName())) { continue; } implTypes.add(JRightPadded.build(visitTypeTree(anInterface)) @@ -310,18 +311,18 @@ J.Block visitClassBlock(ClassNode clazz) { org.codehaus.groovy.ast.stmt.Statement statement = ((BlockStatement) method.getCode()).getStatements().get(0); sortedByPosition.computeIfAbsent(pos(statement), i -> new ArrayList<>()).add(statement); } - } else { + } else if (method.getAnnotations(new ClassNode(Generated.class)).isEmpty()) { sortedByPosition.computeIfAbsent(pos(method), i -> new ArrayList<>()).add(method); } } for (org.codehaus.groovy.ast.stmt.Statement objectInitializer : clazz.getObjectInitializerStatements()) { - if(!(objectInitializer instanceof BlockStatement)) { + if (!(objectInitializer instanceof BlockStatement)) { continue; } // A class initializer BlockStatement will be wrapped in an otherwise empty BlockStatement with the same source positions // No idea why, except speculation that it is for consistency with static intiializers, but we can skip the wrapper and just visit its contents BlockStatement s = (BlockStatement) objectInitializer; - if(s.getStatements().size() == 1 && pos(s).equals(pos(s.getStatements().get(0)))) { + if (s.getStatements().size() == 1 && pos(s).equals(pos(s.getStatements().get(0)))) { s = (BlockStatement) s.getStatements().get(0); } sortedByPosition.computeIfAbsent(pos(s), i -> new ArrayList<>()) @@ -369,11 +370,11 @@ class A { JRightPadded.build(false), sortedByPosition.values().stream() .flatMap(asts -> asts.stream() + // anonymous classes will be visited as part of visiting the ConstructorCallExpression + .filter(ast -> !(ast instanceof InnerClassNode && ((InnerClassNode) ast).isAnonymous())) .map(ast -> { if (ast instanceof FieldNode) { visitField((FieldNode) ast); - } else if (ast instanceof ConstructorNode) { - visitConstructor((ConstructorNode) ast); } else if (ast instanceof MethodNode) { visitMethod((MethodNode) ast); } else if (ast instanceof ClassNode) { @@ -384,7 +385,7 @@ class A { Statement stat = pollQueue(); return maybeSemicolon(stat); })) - .collect(Collectors.toList()), + .collect(toList()), sourceBefore("}")); } @@ -412,13 +413,7 @@ private void visitEnumField(@SuppressWarnings("unused") FieldNode fieldNode) { private void visitVariableField(FieldNode field) { RewriteGroovyVisitor visitor = new RewriteGroovyVisitor(field, this); - List annotations = field.getAnnotations().stream() - .map(a -> { - visitAnnotation(a); - return (J.Annotation) pollQueue(); - }) - .collect(Collectors.toList()); - + List annotations = visitAndGetAnnotations(field); List modifiers = visitModifiers(field.getModifiers()); TypeTree typeExpr = visitTypeTree(field.getOriginType()); @@ -459,42 +454,48 @@ private void visitVariableField(FieldNode field) { @Override protected void visitAnnotation(AnnotationNode annotation) { RewriteGroovyVisitor bodyVisitor = new RewriteGroovyVisitor(annotation, this); - String lastArgKey = annotation.getMembers().keySet().stream().reduce("", (k1, k2) -> k2); Space prefix = sourceBefore("@"); NameTree annotationType = visitTypeTree(annotation.getClassNode()); JContainer arguments = null; if (!annotation.getMembers().isEmpty()) { - // This doesn't handle the case where an annotation has empty arguments like @Foo(), but that is rare arguments = JContainer.build( sourceBefore("("), annotation.getMembers().entrySet().stream() + // Non-value implicit properties should not be represented in our LST. + .filter(it -> sourceStartsWith(it.getKey()) || "value".equals(it.getKey())) .map(arg -> { - Space argPrefix; - if ("value".equals(arg.getKey())) { - // Determine whether the value is implicit or explicit - int saveCursor = cursor; - argPrefix = whitespace(); - if (!source.startsWith("value", cursor)) { - return new JRightPadded( - ((Expression) bodyVisitor.visit(arg.getValue())).withPrefix(argPrefix), - arg.getKey().equals(lastArgKey) ? sourceBefore(")") : sourceBefore(","), - Markers.EMPTY); - } - cursor = saveCursor; + boolean isImplicitValue = "value".equals(arg.getKey()) && !sourceStartsWith("value"); + Space argPrefix = isImplicitValue ? whitespace() : sourceBefore(arg.getKey()); + Space isSign = isImplicitValue ? null : sourceBefore("="); + Expression expression; + if (arg.getValue() instanceof AnnotationConstantExpression) { + visitAnnotation((AnnotationNode) ((AnnotationConstantExpression) arg.getValue()).getValue()); + expression = (J.Annotation) queue.poll(); + } else { + expression = bodyVisitor.visit(arg.getValue()); } - argPrefix = sourceBefore(arg.getKey()); - J.Identifier argName = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), arg.getKey(), null, null); - J.Assignment assign = new J.Assignment(randomId(), argPrefix, Markers.EMPTY, - argName, padLeft(sourceBefore("="), bodyVisitor.visit(arg.getValue())), - null); - return JRightPadded.build((Expression) assign) + Expression element = isImplicitValue ? expression + : (new J.Assignment(randomId(), argPrefix, Markers.EMPTY, + new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), arg.getKey(), null, null), + padLeft(isSign, expression), null)); + return JRightPadded.build(element) .withAfter(arg.getKey().equals(lastArgKey) ? sourceBefore(")") : sourceBefore(",")); }) - .collect(Collectors.toList()), + .collect(toList()), Markers.EMPTY ); + // Rare scenario where annotation does only have non-value implicit properties + if (arguments.getElements().isEmpty()) { + arguments = null; + } + } else if (sourceStartsWith("(")) { + // Annotation with empty arguments like @Foo() + arguments = JContainer.build(sourceBefore("("), + singletonList(JRightPadded.build(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY))), + Markers.EMPTY); } + queue.add(new J.Annotation(randomId(), prefix, Markers.EMPTY, annotationType, arguments)); } @@ -502,23 +503,46 @@ argName, padLeft(sourceBefore("="), bodyVisitor.visit(arg.getValue())), public void visitMethod(MethodNode method) { Space fmt = whitespace(); - List annotations = method.getAnnotations().stream() - .map(a -> { - visitAnnotation(a); - return (J.Annotation) pollQueue(); - }) - .collect(Collectors.toList()); - + List annotations = visitAndGetAnnotations(method); List modifiers = visitModifiers(method.getModifiers()); + boolean isConstructorOfInnerNonStaticClass = false; + RedundantDef redundantDef = getRedundantDefMarker(method); + J.TypeParameters typeParameters = null; + if (method.getGenericsTypes() != null) { + Space prefix = sourceBefore("<"); + GenericsType[] genericsTypes = method.getGenericsTypes(); + List> typeParametersList = new ArrayList<>(genericsTypes.length); + for (int i = 0; i < genericsTypes.length; i++) { + typeParametersList.add(JRightPadded.build(visitTypeParameter(genericsTypes[i])) + .withAfter(i < genericsTypes.length - 1 ? sourceBefore(",") : sourceBefore(">"))); + } + typeParameters = new J.TypeParameters(randomId(), prefix, Markers.EMPTY, emptyList(), typeParametersList); + } + TypeTree returnType = method instanceof ConstructorNode ? null : visitTypeTree(method.getReturnType()); - TypeTree returnType = visitTypeTree(method.getReturnType()); - - // Method name might be in quotes Space namePrefix = whitespace(); String methodName; - if (source.startsWith(method.getName(), cursor)) { + if (method instanceof ConstructorNode) { + /* + To support Java syntax for non-static inner classes, the groovy compiler uses an extra parameter with a reference to its parent class under the hood: + class A { class A { + class B { class B { + String s String s + B(String s) { => B(A $p$, String s) { + => new Object().this$0 = $p$ + this.s = s => this.s = s + } } + } } + } + In our LST, we don't need this internal logic, so we'll skip the first param + first two statements (ConstructorCallExpression and BlockStatement)} + See also: https://groovy-lang.org/differences.html#_creating_instances_of_non_static_inner_classes + */ + isConstructorOfInnerNonStaticClass = method.getDeclaringClass() instanceof InnerClassNode && (method.getDeclaringClass().getModifiers() & Modifier.STATIC) == 0; + methodName = method.getDeclaringClass().getName().replaceFirst(".*\\$", ""); + } else if (source.startsWith(method.getName(), cursor)) { methodName = method.getName(); } else { + // Method name might be in quotes char openingQuote = source.charAt(cursor); methodName = openingQuote + method.getName() + openingQuote; } @@ -536,15 +560,10 @@ public void visitMethod(MethodNode method) { Space beforeParen = sourceBefore("("); List> params = new ArrayList<>(method.getParameters().length); Parameter[] unparsedParams = method.getParameters(); - for (int i = 0; i < unparsedParams.length; i++) { + for (int i = (isConstructorOfInnerNonStaticClass ? 1 : 0); i < unparsedParams.length; i++) { Parameter param = unparsedParams[i]; - List paramAnnotations = param.getAnnotations().stream() - .map(a -> { - visitAnnotation(a); - return (J.Annotation) pollQueue(); - }) - .collect(Collectors.toList()); + List paramAnnotations = visitAndGetAnnotations(param); TypeTree paramType; if (param.isDynamicTyped()) { @@ -552,12 +571,20 @@ public void visitMethod(MethodNode method) { } else { paramType = visitTypeTree(param.getOriginType()); } + + Space varargs = null; + if (paramType instanceof J.ArrayType && sourceStartsWith("...")) { + int varargStart = indexOfNextNonWhitespace(cursor, source); + varargs = format(source, cursor, varargStart); + cursor = varargStart + 3; + } + JRightPadded paramName = JRightPadded.build( new J.VariableDeclarations.NamedVariable(randomId(), EMPTY, Markers.EMPTY, new J.Identifier(randomId(), whitespace(), Markers.EMPTY, emptyList(), param.getName(), null, null), emptyList(), null, null) ); - cursor += param.getName().length(); + skip(param.getName()); org.codehaus.groovy.ast.expr.Expression defaultValue = param.getInitialExpression(); if (defaultValue != null) { @@ -571,11 +598,11 @@ public void visitMethod(MethodNode method) { params.add(JRightPadded.build((Statement) new J.VariableDeclarations(randomId(), EMPTY, Markers.EMPTY, paramAnnotations, emptyList(), paramType, - null, emptyList(), + varargs, emptyList(), singletonList(paramName))).withAfter(rightPad)); } - if (unparsedParams.length == 0) { + if (unparsedParams.length == 0 || (isConstructorOfInnerNonStaticClass && unparsedParams.length == 1)) { params.add(JRightPadded.build(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY))); } @@ -585,14 +612,23 @@ null, emptyList(), Markers.EMPTY ); - J.Block body = method.getCode() == null ? null : - bodyVisitor.visit(method.getCode()); + J.Block body = null; + if (method.getCode() != null) { + ASTNode code = isConstructorOfInnerNonStaticClass ? + new BlockStatement( + ((BlockStatement) method.getCode()).getStatements().subList(2, ((BlockStatement) method.getCode()).getStatements().size()), + ((BlockStatement) method.getCode()).getVariableScope() + ) + : method.getCode(); + body = bodyVisitor.visit(code); + } queue.add(new J.MethodDeclaration( - randomId(), fmt, Markers.EMPTY, + randomId(), fmt, + redundantDef == null ? Markers.EMPTY : Markers.EMPTY.add(redundantDef), annotations, modifiers, - null, + typeParameters, returnType, new J.MethodDeclaration.IdentifierWithAnnotations(name, emptyList()), JContainer.build(beforeParen, params, Markers.EMPTY), @@ -603,107 +639,44 @@ null, emptyList(), )); } - @Override - public void visitConstructor(ConstructorNode ctor) { - Space fmt = whitespace(); - - List annotations = ctor.getAnnotations().stream() - .map(a -> { - visitAnnotation(a); - return (J.Annotation) pollQueue(); - }) - .collect(Collectors.toList()); - - List modifiers = visitModifiers(ctor.getModifiers()); - - // Constructor name might be in quotes - Space namePrefix = whitespace(); - String ctorName; - if (source.startsWith(ctor.getDeclaringClass().getName(), cursor)) { - ctorName = ctor.getDeclaringClass().getName(); - } else { - char openingQuote = source.charAt(cursor); - ctorName = openingQuote + ctor.getName() + openingQuote; + /** + * Methods can be declared with "def" AND a return type (or constructors with "def"). + * In these cases the "def" is semantically meaningless but needs to be preserved for source code accuracy. + * If there is both a def and a return type, this method returns a RedundantDef object and advances the cursor + * position past the "def" keyword, leaving the return type to be parsed as normal. + */ + private @Nullable RedundantDef getRedundantDefMarker(MethodNode method) { + int saveCursor = cursor; + Space defPrefix = whitespace(); + if (source.startsWith("def", cursor) && (method.getReturnType().isRedirectNode() || !Object.class.getName().equals(method.getReturnType().getName()))) { + skip("def"); + return new RedundantDef(randomId(), defPrefix); } - cursor += ctorName.length(); - J.Identifier name = new J.Identifier(randomId(), - namePrefix, - Markers.EMPTY, - emptyList(), - ctorName, - null, null); - - RewriteGroovyVisitor bodyVisitor = new RewriteGroovyVisitor(ctor, this); - // Parameter has no visit implementation, so we've got to do this by hand - Space beforeParen = sourceBefore("("); - List> params = new ArrayList<>(ctor.getParameters().length); - Parameter[] unparsedParams = ctor.getParameters(); - for (int i = 0; i < unparsedParams.length; i++) { - Parameter param = unparsedParams[i]; + cursor = saveCursor; + return null; + } - List paramAnnotations = param.getAnnotations().stream() - .map(a -> { - visitAnnotation(a); - return (J.Annotation) pollQueue(); - }) - .collect(Collectors.toList()); + public List visitAndGetAnnotations(AnnotatedNode node) { + if (node.getAnnotations().isEmpty()) { + return emptyList(); + } - TypeTree paramType; - if (param.isDynamicTyped()) { - paramType = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), "", JavaType.ShallowClass.build("java.lang.Object"), null); - } else { - paramType = visitTypeTree(param.getOriginType()); + List paramAnnotations = new ArrayList<>(node.getAnnotations().size()); + for (AnnotationNode annotationNode : node.getAnnotations()) { + // The groovy compiler can add or remove annotations for AST transformations. + // Because @groovy.transform.Immutable is discarded in favour of other transform annotations, the removed annotation must be parsed by hand. + if (sourceStartsWith("@" + Immutable.class.getSimpleName()) || sourceStartsWith("@" + Immutable.class.getCanonicalName())) { + visitAnnotation(new AnnotationNode(new ClassNode(Immutable.class))); + paramAnnotations.add(pollQueue()); } - JRightPadded paramName = JRightPadded.build( - new J.VariableDeclarations.NamedVariable(randomId(), EMPTY, Markers.EMPTY, - new J.Identifier(randomId(), whitespace(), Markers.EMPTY, emptyList(), param.getName(), null, null), - emptyList(), null, null) - ); - cursor += param.getName().length(); - org.codehaus.groovy.ast.expr.Expression defaultValue = param.getInitialExpression(); - if (defaultValue != null) { - paramName = paramName.withElement(paramName.getElement().getPadding() - .withInitializer(new JLeftPadded<>( - sourceBefore("="), - new RewriteGroovyVisitor(defaultValue, this).visit(defaultValue), - Markers.EMPTY))); + if (appearsInSource(annotationNode)) { + visitAnnotation(annotationNode); + paramAnnotations.add(pollQueue()); } - Space rightPad = sourceBefore(i == unparsedParams.length - 1 ? ")" : ","); - - params.add(JRightPadded.build((Statement) new J.VariableDeclarations(randomId(), EMPTY, - Markers.EMPTY, paramAnnotations, emptyList(), paramType, - null, emptyList(), - singletonList(paramName))).withAfter(rightPad)); - } - - if (unparsedParams.length == 0) { - params.add(JRightPadded.build(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY))); } - - JContainer throws_ = ctor.getExceptions().length == 0 ? null : JContainer.build( - sourceBefore("throws"), - bodyVisitor.visitRightPadded(ctor.getExceptions(), null), - Markers.EMPTY - ); - - J.Block body = ctor.getCode() == null ? null : - bodyVisitor.visit(ctor.getCode()); - - queue.add(new J.MethodDeclaration( - randomId(), fmt, Markers.EMPTY, - annotations, - modifiers, - null, - null, - new J.MethodDeclaration.IdentifierWithAnnotations(name, emptyList()), - JContainer.build(beforeParen, params, Markers.EMPTY), - throws_, - body, - null, - typeMapping.methodType(ctor) - )); + return paramAnnotations; } @SuppressWarnings({"ConstantConditions", "unchecked"}) @@ -733,19 +706,17 @@ private List> visitRightPadded(ASTNode[] nodes, @Nullable St List> ts = new ArrayList<>(nodes.length); for (int i = 0; i < nodes.length; i++) { ASTNode node = nodes[i]; - @SuppressWarnings("unchecked") JRightPadded converted = JRightPadded.build( - node instanceof ClassNode ? (T) visitTypeTree((ClassNode) node) : visit(node)); + @SuppressWarnings("unchecked") + JRightPadded converted = JRightPadded.build(node instanceof ClassNode ? (T) visitTypeTree((ClassNode) node) : visit(node)); if (i == nodes.length - 1) { converted = converted.withAfter(whitespace()); if (',' == source.charAt(cursor)) { // In Groovy trailing "," are allowed - cursor += 1; - converted = converted.withMarkers(Markers.EMPTY.add(new org.openrewrite.java.marker.TrailingComma(randomId(), whitespace()))); + skip(","); + converted = converted.withMarkers(Markers.EMPTY.add(new TrailingComma(randomId(), whitespace()))); } ts.add(converted); - if (afterLast != null && source.startsWith(afterLast, cursor)) { - cursor += afterLast.length(); - } + skip(afterLast); } else { ts.add(converted.withAfter(sourceBefore(","))); } @@ -797,74 +768,71 @@ public void visitArgumentlistExpression(ArgumentListExpression expression) { int saveCursor = cursor; Space beforeOpenParen = whitespace(); - org.openrewrite.java.marker.OmitParentheses omitParentheses = null; + boolean hasParentheses = true; if (source.charAt(cursor) == '(') { - cursor++; + skip("("); } else { - omitParentheses = new org.openrewrite.java.marker.OmitParentheses(randomId()); + hasParentheses = false; beforeOpenParen = EMPTY; cursor = saveCursor; } List unparsedArgs = expression.getExpressions().stream() - .filter(GroovyParserVisitor::appearsInSource) - .collect(Collectors.toList()); + .filter(GroovyParserVisitor.this::appearsInSource) + .collect(toList()); // If the first parameter to a function is a Map, then groovy allows "named parameters" style invocations, see: // https://docs.groovy-lang.org/latest/html/documentation/#_named_parameters_2 // When named parameters are in use they may appear before, after, or intermixed with any positional arguments if (unparsedArgs.size() > 1 && unparsedArgs.get(0) instanceof MapExpression && (unparsedArgs.get(0).getLastLineNumber() > unparsedArgs.get(1).getLastLineNumber() || - (unparsedArgs.get(0).getLastLineNumber() == unparsedArgs.get(1).getLastLineNumber() && - unparsedArgs.get(0).getLastColumnNumber() > unparsedArgs.get(1).getLastColumnNumber()))) { + (unparsedArgs.get(0).getLastLineNumber() == unparsedArgs.get(1).getLastLineNumber() && + unparsedArgs.get(0).getLastColumnNumber() > unparsedArgs.get(1).getLastColumnNumber()))) { // Figure out the source-code ordering of the expressions MapExpression namedArgExpressions = (MapExpression) unparsedArgs.get(0); unparsedArgs = - Stream.concat( - namedArgExpressions.getMapEntryExpressions().stream(), - unparsedArgs.subList(1, unparsedArgs.size()).stream()) - .sorted(Comparator.comparing(ASTNode::getLastLineNumber) - .thenComparing(ASTNode::getLastColumnNumber)) - .collect(Collectors.toList()); + ListUtils.concatAll(unparsedArgs.subList(1, unparsedArgs.size()), namedArgExpressions.getMapEntryExpressions()).stream() + .sorted(Comparator.comparing(ASTNode::getLastLineNumber).thenComparing(ASTNode::getLastColumnNumber)) + .collect(toList()); } else if (!unparsedArgs.isEmpty() && unparsedArgs.get(0) instanceof MapExpression) { // The map literal may or may not be wrapped in "[]" // If it is wrapped in "[]" then this isn't a named arguments situation, and we should not lift the parameters out of the enclosing MapExpression saveCursor = cursor; whitespace(); - boolean isOpeningBracketPresent = '[' == source.charAt(cursor); cursor = saveCursor; - if (!isOpeningBracketPresent) { + if ('[' != source.charAt(cursor)) { // Bring named parameters out of their containing MapExpression so that they can be parsed correctly MapExpression namedArgExpressions = (MapExpression) unparsedArgs.get(0); unparsedArgs = Stream.concat( namedArgExpressions.getMapEntryExpressions().stream(), unparsedArgs.subList(1, unparsedArgs.size()).stream()) - .collect(Collectors.toList()); + .collect(toList()); } } if (unparsedArgs.isEmpty()) { args.add(JRightPadded.build((Expression) new J.Empty(randomId(), whitespace(), Markers.EMPTY)) - .withAfter(omitParentheses == null ? sourceBefore(")") : EMPTY)); + .withAfter(hasParentheses ? sourceBefore(")") : EMPTY)); } else { + boolean lastArgumentsAreAllClosures = endsWithClosures(expression.getExpressions()); for (int i = 0; i < unparsedArgs.size(); i++) { org.codehaus.groovy.ast.expr.Expression rawArg = unparsedArgs.get(i); Expression arg = visit(rawArg); - if (omitParentheses != null) { - arg = arg.withMarkers(arg.getMarkers().add(omitParentheses)); + if (!hasParentheses) { + arg = arg.withMarkers(arg.getMarkers().add(new OmitParentheses(randomId()))); } Space after = EMPTY; if (i == unparsedArgs.size() - 1) { - if (omitParentheses == null) { + if (hasParentheses) { after = sourceBefore(")"); } - } else { + } else if (!(arg instanceof J.Lambda && lastArgumentsAreAllClosures && !hasParentheses)) { after = whitespace(); if (source.charAt(cursor) == ')') { - // the next argument will have an OmitParentheses marker - omitParentheses = new org.openrewrite.java.marker.OmitParentheses(randomId()); + // next argument(s), if they exists, are trailing closures and will have an OmitParentheses marker + hasParentheses = false; } cursor++; } @@ -876,12 +844,36 @@ public void visitArgumentlistExpression(ArgumentListExpression expression) { queue.add(JContainer.build(beforeOpenParen, args, Markers.EMPTY)); } + public boolean endsWithClosures(List list) { + if (!(list.get(list.size() - 1) instanceof ClosureExpression)) { + return false; + } + + boolean foundNonClosure = false; + for (int i = list.size() - 2; i >= 0; i--) { + if (list.get(i) instanceof ClosureExpression) { + if (foundNonClosure) { + return false; + } + } else { + foundNonClosure = true; + } + } + + return true; + } + @Override public void visitClassExpression(ClassExpression clazz) { String unresolvedName = clazz.getType().getUnresolvedName().replace('$', '.'); + Space space = sourceBefore(unresolvedName); + if (source.substring(cursor).startsWith(".class")) { + unresolvedName += ".class"; + skip(".class"); + } queue.add(TypeTree.build(unresolvedName) .withType(typeMapping.type(clazz.getType())) - .withPrefix(sourceBefore(unresolvedName))); + .withPrefix(space)); } @Override @@ -1056,7 +1048,7 @@ public void visitBinaryExpression(BinaryExpression binary) { public void visitBlockStatement(BlockStatement block) { Space fmt = EMPTY; Space staticInitPadding = EMPTY; - boolean isStaticInit = source.substring(indexOfNextNonWhitespace(cursor, source)).startsWith("static"); + boolean isStaticInit = sourceStartsWith("static"); Object parent = nodeCursor.getParentOrThrow().getValue(); if (isStaticInit) { fmt = sourceBefore("static"); @@ -1072,7 +1064,7 @@ public void visitBlockStatement(BlockStatement block) { J expr = visit(statement); if (i == blockStatements.size() - 1 && (expr instanceof Expression)) { if (parent instanceof ClosureExpression || (parent instanceof MethodNode && - !JavaType.Primitive.Void.equals(typeMapping.type(((MethodNode) parent).getReturnType())))) { + JavaType.Primitive.Void != typeMapping.type(((MethodNode) parent).getReturnType()))) { expr = new J.Return(randomId(), expr.getPrefix(), Markers.EMPTY, expr.withPrefix(EMPTY)); expr = expr.withMarkers(expr.getMarkers().add(new ImplicitReturn(randomId()))); @@ -1082,11 +1074,11 @@ public void visitBlockStatement(BlockStatement block) { JRightPadded stat = JRightPadded.build((Statement) expr); int saveCursor = cursor; Space beforeSemicolon = whitespace(); - if (cursor < source.length() && source.charAt(cursor) == ';') { + if (source.charAt(cursor) == ';') { stat = stat .withMarkers(stat.getMarkers().add(new Semicolon(randomId()))) .withAfter(beforeSemicolon); - cursor++; + skip(";"); } else { cursor = saveCursor; } @@ -1111,11 +1103,11 @@ public void visitCatchStatement(CatchStatement node) { Space paramPrefix = whitespace(); // Groovy allows catch variables to omit their type, shorthand for being of type java.lang.Exception // Can't use isSynthetic() here because groovy doesn't record the line number on the Parameter - if ("java.lang.Exception".equals(param.getType().getName()) && + if (Exception.class.getName().equals(param.getType().getName()) && !source.startsWith("Exception", cursor) && !source.startsWith("java.lang.Exception", cursor)) { paramType = new J.Identifier(randomId(), paramPrefix, Markers.EMPTY, emptyList(), "", - JavaType.ShallowClass.build("java.lang.Exception"), null); + JavaType.ShallowClass.build(Exception.class.getName()), null); } else { paramType = visitTypeTree(param.getOriginType()).withPrefix(paramPrefix); } @@ -1127,7 +1119,7 @@ public void visitCatchStatement(CatchStatement node) { ); cursor += param.getName().length(); Space rightPad = whitespace(); - cursor += 1; // skip ) + skip(")"); JRightPadded variable = JRightPadded.build(new J.VariableDeclarations(randomId(), paramType.getPrefix(), Markers.EMPTY, emptyList(), emptyList(), paramType.withPrefix(EMPTY), null, emptyList(), @@ -1159,10 +1151,12 @@ public void visitCaseStatement(CaseStatement statement) { J.Case.Type.Statement, null, JContainer.build(singletonList(JRightPadded.build(visit(statement.getExpression())))), + null, + null, statement.getCode() instanceof EmptyStatement ? JContainer.build(sourceBefore(":"), convertStatements(emptyList()), Markers.EMPTY) : - JContainer.build(sourceBefore(":"), convertStatements(((BlockStatement) statement.getCode()).getStatements()), Markers.EMPTY) - , null) + JContainer.build(sourceBefore(":"), convertStatements(((BlockStatement) statement.getCode()).getStatements()), Markers.EMPTY), + null) ); } @@ -1172,9 +1166,10 @@ private J.Case visitDefaultCaseStatement(BlockStatement statement) { Markers.EMPTY, J.Case.Type.Statement, null, - JContainer.build(singletonList(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)))), - JContainer.build(sourceBefore(":"), - convertStatements(statement.getStatements()), Markers.EMPTY), + JContainer.build(singletonList(JRightPadded.build(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)))), + null, + null, + JContainer.build(sourceBefore(":"), convertStatements(statement.getStatements()), Markers.EMPTY), null ); } @@ -1184,7 +1179,7 @@ public void visitCastExpression(CastExpression cast) { queue.add(insideParentheses(cast, prefix -> { // Might be looking at a Java-style cast "(type)object" or a groovy-style cast "object as type" if (source.charAt(cursor) == '(') { - cursor++; // skip '(' + skip("("); return new J.TypeCast(randomId(), prefix, Markers.EMPTY, new J.ControlParentheses<>(randomId(), EMPTY, Markers.EMPTY, new JRightPadded<>(visitTypeTree(cast.getType()), sourceBefore(")"), Markers.EMPTY) @@ -1275,7 +1270,7 @@ public void visitClosureListExpression(ClosureListExpression closureListExpressi for (int i = 0, expressionsSize = expressions.size(); i < expressionsSize; i++) { results.add(JRightPadded.build(visit(expressions.get(i))).withAfter(whitespace())); if (i < expressionsSize - 1) { - cursor++; // "," + cursor++; // "," or ";" (a for-loop uses a ClosureListExpression) } } queue.add(results); @@ -1299,46 +1294,44 @@ public void visitConstantExpression(ConstantExpression expression) { jType = JavaType.Primitive.Byte; } else if (type == ClassHelper.char_TYPE) { jType = JavaType.Primitive.Char; - } else if (type == ClassHelper.double_TYPE || "java.lang.Double".equals(type.getName())) { + } else if (type == ClassHelper.double_TYPE || Double.class.getName().equals(type.getName())) { jType = JavaType.Primitive.Double; if (expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT") instanceof String) { text = (String) expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT"); } - } else if (type == ClassHelper.float_TYPE || "java.lang.Float".equals(type.getName())) { + } else if (type == ClassHelper.float_TYPE || Float.class.getName().equals(type.getName())) { jType = JavaType.Primitive.Float; if (expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT") instanceof String) { text = (String) expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT"); } - } else if (type == ClassHelper.int_TYPE || "java.lang.Integer".equals(type.getName())) { + } else if (type == ClassHelper.int_TYPE || Integer.class.getName().equals(type.getName())) { jType = JavaType.Primitive.Int; if (expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT") instanceof String) { text = (String) expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT"); } - } else if (type == ClassHelper.long_TYPE || "java.lang.Long".equals(type.getName())) { + } else if (type == ClassHelper.long_TYPE || Long.class.getName().equals(type.getName())) { if (expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT") instanceof String) { text = (String) expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT"); } jType = JavaType.Primitive.Long; - } else if (type == ClassHelper.short_TYPE || "java.lang.Short".equals(type.getName())) { + } else if (type == ClassHelper.short_TYPE || Short.class.getName().equals(type.getName())) { jType = JavaType.Primitive.Short; } else if (type == ClassHelper.STRING_TYPE) { jType = JavaType.Primitive.String; - // String literals value returned by getValue()/getText() has already processed sequences like "\\" -> "\" - int length = sourceLengthOfString(expression); - // this is an attribute selector + // an attribute selector is modeled as a String ConstantExpression if (source.startsWith("@" + value, cursor)) { - length += 1; - } - text = source.substring(cursor, cursor + length); - int delimiterLength = 0; - if (text.startsWith("$/")) { - delimiterLength = 2; - } else if (text.startsWith("\"\"\"") || text.startsWith("'''")) { - delimiterLength = 3; - } else if (text.startsWith("/") || text.startsWith("\"") || text.startsWith("'")) { - delimiterLength = 1; + value = "@" + value; + text = "@" + text; + } else { + String delimiter = getDelimiter(); + if (delimiter != null) { + // handle the `pattern` operator + String endDelimiter = "~/".equals(delimiter) ? "/" : delimiter; + // Get the string literal from the source, so escaping of newlines and the like works out of the box + value = sourceSubstring(cursor + delimiter.length(), endDelimiter); + text = delimiter + value + endDelimiter; + } } - value = text.substring(delimiterLength, text.length() - delimiterLength); } else if (expression.isNullExpression()) { if (source.startsWith("null", cursor)) { text = "null"; @@ -1346,9 +1339,6 @@ public void visitConstantExpression(ConstantExpression expression) { text = ""; } jType = JavaType.Primitive.Null; - } else if (expression instanceof AnnotationConstantExpression) { - classVisitor.visitAnnotation((AnnotationNode) value); - return ((Expression) classVisitor.pollQueue()).withPrefix(fmt); } else { throw new IllegalStateException("Unexpected constant type " + type); } @@ -1373,8 +1363,8 @@ public void visitConstantExpression(ConstantExpression expression) { @Override public void visitConstructorCallExpression(ConstructorCallExpression ctor) { queue.add(insideParentheses(ctor, fmt -> { - cursor += 3; // skip "new" - TypeTree clazz = visitTypeTree(ctor.getType(), ctor.getMetaDataMap().containsKey(StaticTypesMarker.INFERRED_TYPE)); + skip("new"); + TypeTree clazz = visitTypeTree(ctor.getType(), ctor.getNodeMetaData().containsKey(StaticTypesMarker.INFERRED_TYPE)); JContainer args = visit(ctor.getArguments()); J.Block body = null; if (ctor.isUsingAnonymousInnerClass() && ctor.getType() instanceof InnerClassNode) { @@ -1411,12 +1401,15 @@ public void visitNotExpression(NotExpression expression) { @Override public void visitDeclarationExpression(DeclarationExpression expression) { + Space prefix = whitespace(); + Optional multiVariable = maybeMultiVariable(); + List modifiers = getModifiers(expression.getVariableExpression()); TypeTree typeExpr = visitVariableExpressionType(expression.getVariableExpression()); J.VariableDeclarations.NamedVariable namedVariable; if (expression.isMultipleAssignmentDeclaration()) { // def (a, b) = [1, 2] - throw new UnsupportedOperationException("FIXME"); + throw new UnsupportedOperationException("Parsing multiple assignment (e.g.: def (a, b) = [1, 2]) is not implemented"); } else { J.Identifier name = visit(expression.getVariableExpression()); namedVariable = new J.VariableDeclarations.NamedVariable( @@ -1438,19 +1431,60 @@ public void visitDeclarationExpression(DeclarationExpression expression) { J.VariableDeclarations variableDeclarations = new J.VariableDeclarations( randomId(), - EMPTY, + prefix, Markers.EMPTY, emptyList(), - emptyList(), + modifiers, typeExpr, null, emptyList(), singletonList(JRightPadded.build(namedVariable)) ); + if (multiVariable.isPresent()) { + variableDeclarations = variableDeclarations.withMarkers(variableDeclarations.getMarkers().add(multiVariable.get())); + } queue.add(variableDeclarations); } + private Optional maybeMultiVariable() { + int saveCursor = cursor; + Space commaPrefix = whitespace(); + if (source.startsWith(",", cursor)) { + skip(","); + return Optional.of(new MultiVariable(randomId(), commaPrefix)); + } + cursor = saveCursor; + return Optional.empty(); + } + + private List getModifiers(Variable variable) { + String varName = variable.getName(); + List modifiers = new ArrayList<>(); + int saveCursor = cursor; + Space prefix = whitespace(); + Set possibleModifiers = new LinkedHashSet<>(modifierNameToType.keySet()); + String currentModifier = possibleModifiers.stream().filter(modifierName -> source.startsWith(modifierName, cursor)) + .findFirst() + .orElse(null); + while (currentModifier != null) { + possibleModifiers.remove(currentModifier); + modifiers.add(new J.Modifier(randomId(), prefix, Markers.EMPTY, currentModifier, modifierNameToType.get(currentModifier), emptyList())); + skip(currentModifier); + saveCursor = cursor; + prefix = whitespace(); + currentModifier = possibleModifiers.stream() + .filter(modifierName -> + // Try to avoid confusing a variable name with an incidentally similar modifier keyword + (varName.length() < modifierName.length() || !source.startsWith(varName, cursor)) && + source.startsWith(modifierName, cursor)) + .findFirst() + .orElse(null); + } + cursor = saveCursor; + return modifiers; + } + @Override public void visitEmptyExpression(EmptyExpression expression) { queue.add(new J.Empty(randomId(), EMPTY, Markers.EMPTY)); @@ -1493,7 +1527,7 @@ public void visitForLoop(ForStatement forLoop) { List> update = controls.get(2).getElement() instanceof List ? (List>) controls.get(2).getElement() : singletonList((JRightPadded) controls.get(2)); - cursor++; // skip ')' + skip(")"); return new J.ForLoop(randomId(), prefix, Markers.EMPTY, new J.ForLoop.Control(randomId(), controlFmt, @@ -1502,8 +1536,8 @@ public void visitForLoop(ForStatement forLoop) { } else { Parameter param = forLoop.getVariable(); Space paramFmt = whitespace(); - TypeTree paramType = param.getOriginType().getColumnNumber() >= 0 ? - visitTypeTree(param.getOriginType()) : null; + List modifiers = getModifiers(param); + TypeTree paramType = param.getOriginType().getColumnNumber() >= 0 ? visitTypeTree(param.getOriginType()) : null; JRightPadded paramName = JRightPadded.build( new J.VariableDeclarations.NamedVariable(randomId(), whitespace(), Markers.EMPTY, new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), param.getName(), null, null), @@ -1513,14 +1547,14 @@ public void visitForLoop(ForStatement forLoop) { Space rightPad = whitespace(); Markers forEachMarkers = Markers.EMPTY; if (source.charAt(cursor) == ':') { - cursor++; // Skip ":" + skip(":"); } else { - cursor += 2; // Skip "in" + skip("in"); forEachMarkers = forEachMarkers.add(new InStyleForEachLoop(randomId())); } JRightPadded variable = JRightPadded.build(new J.VariableDeclarations(randomId(), paramFmt, - Markers.EMPTY, emptyList(), emptyList(), paramType, null, emptyList(), + Markers.EMPTY, emptyList(), modifiers, paramType, null, emptyList(), singletonList(paramName)) ).withAfter(rightPad); @@ -1549,17 +1583,8 @@ public void visitIfElse(IfStatement ifElse) { @Override public void visitGStringExpression(GStringExpression gstring) { Space fmt = whitespace(); - String delimiter; - if (source.startsWith("\"\"\"", cursor)) { - delimiter = "\"\"\""; - } else if (source.startsWith("/", cursor)) { - delimiter = "/"; - } else if (source.startsWith("$/", cursor)) { - delimiter = "$/"; - } else { - delimiter = "\""; - } - cursor += delimiter.length(); + String delimiter = getDelimiter(); + skip(delimiter); // Opening delim for GString NavigableMap sortedByPosition = new TreeMap<>(); for (org.codehaus.groovy.ast.expr.ConstantExpression e : gstring.getStrings()) { @@ -1577,56 +1602,51 @@ public void visitGStringExpression(GStringExpression gstring) { for (int i = 0; i < rawExprs.size(); i++) { org.codehaus.groovy.ast.expr.Expression e = rawExprs.get(i); if (source.charAt(cursor) == '$') { - cursor++; + skip("$"); boolean inCurlies = source.charAt(cursor) == '{'; if (inCurlies) { - cursor++; + skip("{"); } else { columnOffset--; } - strings.add(new G.GString.Value(randomId(), Markers.EMPTY, visit(e), inCurlies ? sourceBefore("}") : Space.EMPTY, inCurlies)); + strings.add(new G.GString.Value(randomId(), Markers.EMPTY, visit(e), inCurlies ? sourceBefore("}") : EMPTY, inCurlies)); if (!inCurlies) { columnOffset++; } } else if (e instanceof ConstantExpression) { - ConstantExpression cs = (ConstantExpression) e; - // The sub-strings within a GString have no delimiters of their own, confusing visitConstantExpression() - // ConstantExpression.getValue() cannot be trusted for strings as its values don't match source code because sequences like "\\" have already been replaced with a single "\" - // Use the AST element's line/column positions to figure out its extent, but those numbers need tweaks to be correct - int length = lengthAccordingToAst(cs); - if (i == 0 || i == rawExprs.size() - 1) { - // The first and last constants within a GString have line/column position which incorrectly include the GString's delimiters - length -= delimiter.length(); - } - // The line/column numbers incorrectly indicate that the following expression's opening "$" is part of this expression - if (i < rawExprs.size() - 1) { - length--; + // Get the string literal from the source, so escaping of newlines and the like works out of the box + String value = sourceSubstring(cursor, ("~/".equals(delimiter) ? "/" : delimiter)); + // There could be a closer GString before the end of the closing delimiter, so shorten the string if needs be + int indexNextSign = source.indexOf("$", cursor); + if (indexNextSign != -1 && indexNextSign < (cursor + value.length())) { + value = source.substring(cursor, indexNextSign); } - String value = source.substring(cursor, cursor + length); strings.add(new J.Literal(randomId(), EMPTY, Markers.EMPTY, value, value, null, JavaType.Primitive.String)); - cursor += value.length(); + skip(value); } else { // Everything should be handled already by the other two code paths, but just in case strings.add(visit(e)); } } - queue.add(new G.GString(randomId(), fmt, Markers.EMPTY, delimiter, strings, - typeMapping.type(gstring.getType()))); - cursor += delimiter.length(); + queue.add(new G.GString(randomId(), fmt, Markers.EMPTY, delimiter, strings, typeMapping.type(gstring.getType()))); + skip(delimiter); // Closing delim for GString } @Override public void visitListExpression(ListExpression list) { - if (list.getExpressions().isEmpty()) { - queue.add(new G.ListLiteral(randomId(), sourceBefore("["), Markers.EMPTY, - JContainer.build(singletonList(new JRightPadded<>(new J.Empty(randomId(), EMPTY, Markers.EMPTY), sourceBefore("]"), Markers.EMPTY))), - typeMapping.type(list.getType()))); - } else { - queue.add(new G.ListLiteral(randomId(), sourceBefore("["), Markers.EMPTY, - JContainer.build(visitRightPadded(list.getExpressions().toArray(new ASTNode[0]), "]")), - typeMapping.type(list.getType()))); - } + queue.add(insideParentheses(list, fmt -> { + skip("["); + if (list.getExpressions().isEmpty()) { + return new G.ListLiteral(randomId(), fmt, Markers.EMPTY, + JContainer.build(singletonList(new JRightPadded<>(new J.Empty(randomId(), EMPTY, Markers.EMPTY), sourceBefore("]"), Markers.EMPTY))), + typeMapping.type(list.getType())); + } else { + return new G.ListLiteral(randomId(), fmt, Markers.EMPTY, + JContainer.build(visitRightPadded(list.getExpressions().toArray(new ASTNode[0]), "]")), + typeMapping.type(list.getType())); + } + })); } @Override @@ -1641,23 +1661,24 @@ public void visitMapEntryExpression(MapEntryExpression expression) { @Override public void visitMapExpression(MapExpression map) { - Space prefix = sourceBefore("["); - JContainer entries; - if (map.getMapEntryExpressions().isEmpty()) { - entries = JContainer.build(Collections.singletonList(JRightPadded.build( - new G.MapEntry(randomId(), whitespace(), Markers.EMPTY, - JRightPadded.build(new J.Empty(randomId(), sourceBefore(":"), Markers.EMPTY)), - new J.Empty(randomId(), sourceBefore("]"), Markers.EMPTY), null)))); - } else { - entries = JContainer.build(visitRightPadded(map.getMapEntryExpressions().toArray(new ASTNode[0]), "]")); - } - queue.add(new G.MapLiteral(randomId(), prefix, Markers.EMPTY, entries, typeMapping.type(map.getType()))); + queue.add(insideParentheses(map, fmt -> { + skip("["); + JContainer entries; + if (map.getMapEntryExpressions().isEmpty()) { + entries = JContainer.build(Collections.singletonList(JRightPadded.build( + new G.MapEntry(randomId(), whitespace(), Markers.EMPTY, + JRightPadded.build(new J.Empty(randomId(), sourceBefore(":"), Markers.EMPTY)), + new J.Empty(randomId(), sourceBefore("]"), Markers.EMPTY), null)))); + } else { + entries = JContainer.build(visitRightPadded(map.getMapEntryExpressions().toArray(new ASTNode[0]), "]")); + } + return new G.MapLiteral(randomId(), fmt, Markers.EMPTY, entries, typeMapping.type(map.getType())); + })); } @Override public void visitMethodCallExpression(MethodCallExpression call) { queue.add(insideParentheses(call, fmt -> { - ImplicitDot implicitDot = null; JRightPadded select = null; if (!call.isImplicitThis()) { @@ -1686,10 +1707,8 @@ public void visitMethodCallExpression(MethodCallExpression call) { Space prefix = whitespace(); if (methodNameExpression.equals(source.substring(cursor, cursor + methodNameExpression.length()))) { - cursor += methodNameExpression.length(); - name = new J.Identifier(randomId(), prefix, Markers.EMPTY, - emptyList(), methodNameExpression, null, null); - + skip(methodNameExpression); + name = new J.Identifier(randomId(), prefix, Markers.EMPTY, emptyList(), methodNameExpression, null, null); } else if (select != null && select.getElement() instanceof J.Identifier) { name = (J.Identifier) select.getElement(); select = null; @@ -1732,23 +1751,7 @@ public void visitMethodCallExpression(MethodCallExpression call) { } } } - // handle the obscure case where there are empty parens ahead of a closure - if (args.getExpressions().size() == 1 && args.getExpressions().get(0) instanceof ClosureExpression) { - int saveCursor = cursor; - Space argPrefix = whitespace(); - if (source.charAt(cursor) == '(') { - cursor += 1; - Space infix = whitespace(); - if (source.charAt(cursor) == ')') { - cursor += 1; - markers = markers.add(new EmptyArgumentListPrecedesArgument(randomId(), argPrefix, infix)); - } else { - cursor = saveCursor; - } - } else { - cursor = saveCursor; - } - } + markers = handlesCaseWhereEmptyParensAheadOfClosure(args, markers); } JContainer args = visit(call.getArguments()); @@ -1796,8 +1799,7 @@ public void visitMethodCallExpression(MethodCallExpression call) { } else { methodType = typeMapping.methodType(methodNode); } - return new J.MethodInvocation(randomId(), fmt, markers, - select, null, name, args, methodType); + return new J.MethodInvocation(randomId(), fmt, markers, select, null, name, args, methodType); })); } @@ -1836,57 +1838,39 @@ public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { } } } - // handle the obscure case where there are empty parens ahead of a closure - if (args.getExpressions().size() == 1 && args.getExpressions().get(0) instanceof ClosureExpression) { - int saveCursor = cursor; - Space prefix = whitespace(); - if (source.charAt(cursor) == '(') { - cursor += 1; - Space infix = whitespace(); - if (source.charAt(cursor) == ')') { - cursor += 1; - markers = markers.add(new EmptyArgumentListPrecedesArgument(randomId(), prefix, infix)); - } else { - cursor = saveCursor; - } - } else { - cursor = saveCursor; - } - } + markers = handlesCaseWhereEmptyParensAheadOfClosure(args, markers); } JContainer args = visit(call.getArguments()); - queue.add(new J.MethodInvocation(randomId(), fmt, markers, - null, null, name, args, methodType)); + queue.add(new J.MethodInvocation(randomId(), fmt, markers, null, null, name, args, methodType)); } @Override public void visitAttributeExpression(AttributeExpression attr) { - Space fmt = whitespace(); - Expression target = visit(attr.getObjectExpression()); - Space beforeDot = attr.isSafe() ? sourceBefore("?.") : - sourceBefore(attr.isSpreadSafe() ? "*." : "."); - J name = visit(attr.getProperty()); - if (name instanceof J.Literal) { - String nameStr = ((J.Literal) name).getValueSource(); - assert nameStr != null; - name = new J.Identifier(randomId(), name.getPrefix(), Markers.EMPTY, emptyList(), nameStr, null, null); - } - if (attr.isSpreadSafe()) { - name = name.withMarkers(name.getMarkers().add(new StarDot(randomId()))); - } - if (attr.isSafe()) { - name = name.withMarkers(name.getMarkers().add(new NullSafe(randomId()))); - } - queue.add(new J.FieldAccess(randomId(), fmt, Markers.EMPTY, target, padLeft(beforeDot, (J.Identifier) name), null)); + queue.add(insideParentheses(attr, fmt -> { + Expression target = visit(attr.getObjectExpression()); + Space beforeDot = attr.isSafe() ? sourceBefore("?.") : sourceBefore(attr.isSpreadSafe() ? "*." : "."); + J name = visit(attr.getProperty()); + if (name instanceof J.Literal) { + String nameStr = ((J.Literal) name).getValueSource(); + assert nameStr != null; + name = new J.Identifier(randomId(), name.getPrefix(), Markers.EMPTY, emptyList(), nameStr, null, null); + } + if (attr.isSpreadSafe()) { + name = name.withMarkers(name.getMarkers().add(new StarDot(randomId()))); + } + if (attr.isSafe()) { + name = name.withMarkers(name.getMarkers().add(new NullSafe(randomId()))); + } + return new J.FieldAccess(randomId(), fmt, Markers.EMPTY, target, padLeft(beforeDot, (J.Identifier) name), null); + })); } @Override public void visitPropertyExpression(PropertyExpression prop) { queue.add(insideParentheses(prop, fmt -> { Expression target = visit(prop.getObjectExpression()); - Space beforeDot = prop.isSpreadSafe() ? sourceBefore("*.") : - sourceBefore(prop.isSafe() ? "?." : "."); + Space beforeDot = prop.isSpreadSafe() ? sourceBefore("*.") : sourceBefore(prop.isSafe() ? "?." : "."); J name = visit(prop.getProperty()); if (name instanceof J.Literal) { J.Literal nameLiteral = ((J.Literal) name); @@ -1945,7 +1929,7 @@ public void visitSwitch(SwitchStatement statement) { randomId(), sourceBefore("{"), Markers.EMPTY, JRightPadded.build(false), ListUtils.concat( - convertAll(statement.getCaseStatements(), t -> Space.EMPTY, t -> Space.EMPTY), + convertAll(statement.getCaseStatements(), t -> EMPTY, t -> EMPTY), statement.getDefaultStatement().isEmpty() ? null : JRightPadded.build(visitDefaultCaseStatement((BlockStatement) statement.getDefaultStatement())) ), sourceBefore("}")))); @@ -1983,11 +1967,11 @@ public void visitTupleExpression(TupleExpression tuple) { int saveCursor = cursor; Space beforeOpenParen = whitespace(); - org.openrewrite.java.marker.OmitParentheses omitParentheses = null; + OmitParentheses omitParentheses = null; if (source.charAt(cursor) == '(') { - cursor++; + skip("("); } else { - omitParentheses = new org.openrewrite.java.marker.OmitParentheses(randomId()); + omitParentheses = new OmitParentheses(randomId()); beforeOpenParen = EMPTY; cursor = saveCursor; } @@ -2011,7 +1995,7 @@ public void visitTupleExpression(TupleExpression tuple) { after = whitespace(); if (source.charAt(cursor) == ')') { // the next argument will have an OmitParentheses marker - omitParentheses = new org.openrewrite.java.marker.OmitParentheses(randomId()); + omitParentheses = new OmitParentheses(randomId()); } cursor++; } @@ -2047,7 +2031,6 @@ public void visitTryCatchFinally(TryCatchStatement node) { //noinspection ConstantConditions queue.add(new J.Try(randomId(), prefix, Markers.EMPTY, resources, body, catches, finally_)); - } @Override @@ -2079,7 +2062,7 @@ public void visitPostfixExpression(PostfixExpression unary) { public void visitPrefixExpression(PrefixExpression unary) { Space fmt = whitespace(); String typeToken = unary.getOperation().getText(); - cursor += typeToken.length(); + skip(typeToken); J.Unary.Type operator = null; switch (typeToken) { @@ -2103,29 +2086,18 @@ public void visitPrefixExpression(PrefixExpression unary) { public TypeTree visitVariableExpressionType(VariableExpression expression) { JavaType type = typeMapping.type(staticType(((org.codehaus.groovy.ast.expr.Expression) expression))); + Space prefix = whitespace(); + String typeName = ""; - if (expression.isDynamicTyped()) { - Space prefix = whitespace(); - String keyword; - if (source.substring(cursor).startsWith("final")) { - keyword = "final"; - } else { - keyword = source.substring(cursor, cursor + 3); - } - cursor += keyword.length(); - return new J.Identifier(randomId(), - prefix, - Markers.EMPTY, - emptyList(), - keyword, - type, null); + if (!expression.isDynamicTyped() && source.startsWith(expression.getOriginType().getUnresolvedName(), cursor)) { + typeName = expression.getOriginType().getUnresolvedName(); + skip(typeName); } - Space prefix = sourceBefore(expression.getOriginType().getUnresolvedName()); J.Identifier ident = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), - expression.getOriginType().getUnresolvedName(), + typeName, type, null); if (expression.getOriginType().getGenericsTypes() != null) { return new J.ParameterizedType(randomId(), prefix, Markers.EMPTY, ident, visitTypeParameterizations( @@ -2136,19 +2108,21 @@ public TypeTree visitVariableExpressionType(VariableExpression expression) { @Override public void visitVariableExpression(VariableExpression expression) { - JavaType type; - if (expression.isDynamicTyped() && expression.getAccessedVariable() != null && expression.getAccessedVariable().getType() != expression.getOriginType()) { - type = typeMapping.type(staticType(expression.getAccessedVariable())); - } else { - type = typeMapping.type(staticType((org.codehaus.groovy.ast.expr.Expression) expression)); - } - queue.add(new J.Identifier(randomId(), - sourceBefore(expression.getName()), - Markers.EMPTY, - emptyList(), - expression.getName(), - type, null) - ); + queue.add(insideParentheses(expression, fmt -> { + JavaType type; + if (expression.isDynamicTyped() && expression.getAccessedVariable() != null && expression.getAccessedVariable().getType() != expression.getOriginType()) { + type = typeMapping.type(staticType(expression.getAccessedVariable())); + } else { + type = typeMapping.type(staticType((org.codehaus.groovy.ast.expr.Expression) expression)); + } + + return new J.Identifier(randomId(), + fmt.withWhitespace(fmt.getWhitespace() + sourceBefore(expression.getName()).getWhitespace()), + Markers.EMPTY, + emptyList(), + expression.getName(), + type, null); + })); } @Override @@ -2200,6 +2174,27 @@ private T pollQueue() { } } + // handle the obscure case where there are empty parens ahead of a closure + private Markers handlesCaseWhereEmptyParensAheadOfClosure(ArgumentListExpression args, Markers markers) { + if (args.getExpressions().size() == 1 && args.getExpressions().get(0) instanceof ClosureExpression) { + int saveCursor = cursor; + Space argPrefix = whitespace(); + if (source.charAt(cursor) == '(') { + skip("("); + Space infix = whitespace(); + if (source.charAt(cursor) == ')') { + skip(")"); + markers = markers.add(new EmptyArgumentListPrecedesArgument(randomId(), argPrefix, infix)); + } else { + cursor = saveCursor; + } + } else { + cursor = saveCursor; + } + } + return markers; + } + private JRightPadded convertTopLevelStatement(SourceUnit unit, ASTNode node) { if (node instanceof ClassNode) { ClassNode classNode = (ClassNode) node; @@ -2320,12 +2315,19 @@ private int positionOfNext(String untilDelim) { return delimIndex > source.length() - untilDelim.length() ? -1 : delimIndex; } + /** + * Get all whitespace characters of the source file between the cursor and the first non-whitespace character. + * The cursor will be moved before first non-whitespace character. + */ private Space whitespace() { String prefix = source.substring(cursor, indexOfNextNonWhitespace(cursor, source)); cursor += prefix.length(); return format(prefix); } + /** + * Move the cursor after the token. + */ private String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions @@ -2362,12 +2364,12 @@ private T typeTree(@Nullable ClassNode classNo fullName += "." + part; Matcher whitespacePrefix = whitespacePrefixPattern.matcher(part); - Space identFmt = whitespacePrefix.matches() ? format(whitespacePrefix.group(0)) : Space.EMPTY; + Space identFmt = whitespacePrefix.matches() ? format(whitespacePrefix.group(0)) : EMPTY; Matcher whitespaceSuffix = whitespaceSuffixPattern.matcher(part); //noinspection ResultOfMethodCallIgnored whitespaceSuffix.matches(); - Space namePrefix = i == parts.length - 1 ? Space.EMPTY : format(whitespaceSuffix.group(1)); + Space namePrefix = i == parts.length - 1 ? EMPTY : format(whitespaceSuffix.group(1)); expr = new J.FieldAccess( randomId(), @@ -2403,7 +2405,7 @@ private TypeTree arrayType(ClassNode classNode) { } Space prefix = whitespace(); TypeTree elemType = typeTree(typeTree); - JLeftPadded dimension = padLeft(sourceBefore("["), sourceBefore("]")); + JLeftPadded dimension = sourceStartsWith("...") ? null : padLeft(sourceBefore("["), sourceBefore("]")); return new J.ArrayType(randomId(), prefix, Markers.EMPTY, count == 1 ? elemType : mapDimensions(elemType, classNode.getComponentType()), null, @@ -2428,100 +2430,115 @@ private TypeTree mapDimensions(TypeTree baseType, ClassNode classNode) { return baseType; } + /** + * Get all characters of the source file between the cursor and the given delimiter. + * The cursor will be moved past the delimiter. + */ private Space sourceBefore(String untilDelim) { int delimIndex = positionOfNext(untilDelim); if (delimIndex < 0) { return EMPTY; // unable to find this delimiter } - String prefix = source.substring(cursor, delimIndex); - cursor += prefix.length() + untilDelim.length(); // advance past the delimiter - return Space.format(prefix); + if (delimIndex == cursor) { + cursor += untilDelim.length(); + return EMPTY; + } + + Space space = format(source, cursor, delimIndex); + cursor = delimIndex + untilDelim.length(); // advance past the delimiter + return space; + } + + /** + * Tests if the source beginning at the current cursor starts with the specified delimiter. + * Whitespace characters are excluded, the cursor will not be moved. + */ + private boolean sourceStartsWith(String delimiter) { + return source.startsWith(delimiter, indexOfNextNonWhitespace(cursor, source)); } /** - * Gets the length in characters of a String literal. - * Attempts to account for a number of different strange compiler behaviors, old bugs, and edge cases. - * cursor is presumed to point at the beginning of the node. + * Returns a string that is a part of this source. The substring begins at the specified beginIndex and extends until delimiter. + * The cursor will not be moved. */ - private int sourceLengthOfString(ConstantExpression expr) { - // ConstantExpression.getValue() already has resolved escaped characters. So "\t" and a literal tab will look the same. - // Since we cannot differentiate between the two, use this alternate method only when an old version of groovy indicates risk - // and the literal doesn't contain any characters which might be from an escape sequence. e.g.: tabs, newlines, carriage returns - String value = (String) expr.getValue(); - if (isOlderThanGroovy3() && value.matches("[^\\t\\r\\n\\\\]*")) { - int delimiterLength = getDelimiterLength(); - return delimiterLength + value.length() + delimiterLength; - } - int lengthAccordingToAst = lengthAccordingToAst(expr); - // subtract any parentheses that were included with lengthAccordingToAst - Integer insideParenthesesLevel = getInsideParenthesesLevel(expr); - if (insideParenthesesLevel != null) { - return lengthAccordingToAst - insideParenthesesLevel * 2; - } - - return lengthAccordingToAst; + private String sourceSubstring(int beginIndex, String untilDelim) { + int endIndex = source.indexOf(untilDelim, Math.max(beginIndex, cursor + untilDelim.length())); + // don't stop if last char is escaped. + while (source.charAt(endIndex - 1) == '\\') { + endIndex = source.indexOf(untilDelim, endIndex + 1); + } + return source.substring(beginIndex, endIndex); } - private static @Nullable Integer getInsideParenthesesLevel(ASTNode node) { + private @Nullable Integer getInsideParenthesesLevel(ASTNode node) { Object rawIpl = node.getNodeMetaData("_INSIDE_PARENTHESES_LEVEL"); if (rawIpl instanceof AtomicInteger) { // On Java 11 and newer _INSIDE_PARENTHESES_LEVEL is an AtomicInteger return ((AtomicInteger) rawIpl).get(); - } else { + } else if (rawIpl instanceof Integer) { // On Java 8 _INSIDE_PARENTHESES_LEVEL is a regular Integer return (Integer) rawIpl; - } - } - - private int getDelimiterLength() { - String maybeDelimiter = source.substring(cursor, Math.min(cursor + 3, source.length())); - int delimiterLength = 0; - if (maybeDelimiter.startsWith("$/")) { - delimiterLength = 2; - } else if (maybeDelimiter.startsWith("\"\"\"") || maybeDelimiter.startsWith("'''")) { - delimiterLength = 3; - } else if (maybeDelimiter.startsWith("/") || maybeDelimiter.startsWith("\"") || maybeDelimiter.startsWith("'")) { - delimiterLength = 1; - } - return delimiterLength; + } else if (node instanceof MethodCallExpression) { + MethodCallExpression expr = (MethodCallExpression) node; + return determineParenthesisLevel(expr.getObjectExpression().getLineNumber(), expr.getLineNumber(), expr.getObjectExpression().getColumnNumber(), expr.getColumnNumber()); + } else if (node instanceof BinaryExpression) { + BinaryExpression expr = (BinaryExpression) node; + return determineParenthesisLevel(expr.getLeftExpression().getLineNumber(), expr.getLineNumber(), expr.getLeftExpression().getColumnNumber(), expr.getColumnNumber()); + } + return null; } /** - * Gets the length according to the Groovy compiler's attestation of starting/ending line and column numbers. - * On older versions of the JDK/Groovy compiler string literals with following whitespace sometimes erroneously include - * the length of the whitespace in the length of the AST node. - * So in this method invocation: - * foo( 'a' ) - * the correct source length for the AST node representing 'a' is 3, but in affected groovy versions the length - * on the node is '4' because it is also counting the trailing whitespace. + * @param childLineNumber the beginning line number of the first sub node + * @param parentLineNumber the beginning line number of the parent node + * @param childColumn the column on the {@code childLineNumber} line where the sub node starts + * @param parentColumn the column on the {@code parentLineNumber} line where the parent node starts + * @return the level of parenthesis parsed from the source */ - private int lengthAccordingToAst(ConstantExpression node) { - if (!appearsInSource(node)) { - return 0; - } - int lineCount = node.getLastLineNumber() - node.getLineNumber(); - if (lineCount == 0) { - return node.getLastColumnNumber() - node.getColumnNumber() + columnOffset; - } - int linesSoFar = 0; - int length = 0; - int finalLineChars = 0; - while (true) { - char c = source.charAt(cursor + length); - if (c == '\n') { - linesSoFar++; + private int determineParenthesisLevel(int childLineNumber, int parentLineNumber, int childColumn, int parentColumn) { + int saveCursor = cursor; + whitespace(); + int untilCursor = cursor; + if (childLineNumber > parentLineNumber) { + for (int i = 0; i < (childLineNumber - parentLineNumber); i++) { + untilCursor = source.indexOf('\n', untilCursor) + 1; // +1; set cursor past `\n` } - if (linesSoFar == lineCount) { - finalLineChars++; + untilCursor += childColumn - 1; // -1; skip previous `\n` + } else { + untilCursor += childColumn - parentColumn; + } + int count = 0; + for (int i = cursor; i < untilCursor; i++) { + if (source.charAt(i) == '(') { + count++; } - - length++; - - if (finalLineChars == node.getLastColumnNumber() + columnOffset) { - return length; + if (source.charAt(i) == ')') { + count--; } } + cursor = saveCursor; + return Math.max(count, 0); + } + + private @Nullable String getDelimiter() { + String maybeDelimiter = source.substring(cursor, Math.min(cursor + 3, source.length())); + if (maybeDelimiter.startsWith("$/")) { + return "$/"; + } else if (maybeDelimiter.startsWith("\"\"\"")) { + return "\"\"\""; + } else if (maybeDelimiter.startsWith("'''")) { + return "'''"; + } else if (maybeDelimiter.startsWith("~/")) { + return "~/"; + } else if (maybeDelimiter.startsWith("/")) { + return "/"; + } else if (maybeDelimiter.startsWith("\"")) { + return "\""; + } else if (maybeDelimiter.startsWith("'")) { + return "'"; + } + return null; } private TypeTree visitTypeTree(ClassNode classNode) { @@ -2533,17 +2550,6 @@ private TypeTree visitTypeTree(ClassNode classNode, boolean inferredType) { if (primitiveType != null) { return new J.Primitive(randomId(), sourceBefore(classNode.getUnresolvedName()), Markers.EMPTY, primitiveType); } - - int saveCursor = cursor; - Space fmt = whitespace(); - if (cursor < source.length() && source.startsWith("def", cursor)) { - cursor += 3; - return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), "def", - JavaType.ShallowClass.build("java.lang.Object"), null); - } else { - cursor = saveCursor; - } - return typeTree(classNode, inferredType); } @@ -2624,7 +2630,8 @@ private String name() { int i = cursor; for (; i < source.length(); i++) { char c = source.charAt(i); - if (!(Character.isJavaIdentifierPart(c) || c == '.' || c == '*')) { + boolean isVarargs = source.length() > (i + 2) && c == '.' && source.charAt(i + 1) == '.' && source.charAt(i + 2) == '.'; + if (!(Character.isJavaIdentifierPart(c) || c == '.' || c == '*') || isVarargs) { break; } } @@ -2645,7 +2652,7 @@ private String name() { Can contain a J.FieldAccess, as in a variable declaration with fully qualified type parameterization: List */ - private JContainer visitTypeParameterizations(GenericsType @Nullable[] genericsTypes) { + private JContainer visitTypeParameterizations(GenericsType @Nullable [] genericsTypes) { Space prefix = sourceBefore("<"); List> parameters; @@ -2662,7 +2669,7 @@ private JContainer visitTypeParameterizations(GenericsType @Nullable Space suffix = whitespace(); parameters.add(JRightPadded.build(param).withAfter(suffix)); if (source.charAt(cursor) == '>') { - cursor++; + skip(">"); break; } cursor++; @@ -2673,15 +2680,10 @@ private JContainer visitTypeParameterizations(GenericsType @Nullable parameters = new ArrayList<>(genericsTypes.length); for (int i = 0; i < genericsTypes.length; i++) { parameters.add(JRightPadded.build(visitTypeParameterization(genericsTypes[i])) - .withAfter( - i < genericsTypes.length - 1 ? - sourceBefore(",") : - sourceBefore(">") - )); + .withAfter(i < genericsTypes.length - 1 ? sourceBefore(",") : sourceBefore(">"))); } } - return JContainer.build(prefix, parameters, Markers.EMPTY); } @@ -2689,10 +2691,19 @@ private Expression visitTypeParameterization(GenericsType genericsType) { int saveCursor = cursor; Space prefix = whitespace(); if (source.charAt(cursor) == '?') { - cursor = saveCursor; - return visitWildcard(genericsType); + skip("?"); + JLeftPadded bound = null; + NameTree boundedType = null; + if (genericsType.getUpperBounds() != null) { + bound = padLeft(sourceBefore("extends"), J.Wildcard.Bound.Extends); + boundedType = visitTypeTree(genericsType.getUpperBounds()[0]); + } else if (genericsType.getLowerBound() != null) { + bound = padLeft(sourceBefore("super"), J.Wildcard.Bound.Super); + boundedType = visitTypeTree(genericsType.getLowerBound()); + } + return new J.Wildcard(randomId(), prefix, Markers.EMPTY, bound, boundedType); } else if (source.charAt(cursor) == '>') { - cursor++; + skip(">"); return new J.Empty(randomId(), prefix, Markers.EMPTY); } cursor = saveCursor; @@ -2708,19 +2719,14 @@ private JContainer visitTypeParameters(GenericsType[] genericsT List> typeParameters = new ArrayList<>(genericsTypes.length); for (int i = 0; i < genericsTypes.length; i++) { typeParameters.add(JRightPadded.build(visitTypeParameter(genericsTypes[i])) - .withAfter( - i < genericsTypes.length - 1 ? - sourceBefore(",") : - sourceBefore(">") - )); + .withAfter(i < genericsTypes.length - 1 ? sourceBefore(",") : sourceBefore(">"))); } return JContainer.build(prefix, typeParameters, Markers.EMPTY); } private J.TypeParameter visitTypeParameter(GenericsType genericType) { Space prefix = whitespace(); - Expression name = typeTree(null) - .withType(typeMapping.type(genericType)); + Expression name = typeTree(null).withType(typeMapping.type(genericType)); JContainer bounds = null; if (genericType.getUpperBounds() != null) { Space boundsPrefix = sourceBefore("extends"); @@ -2728,11 +2734,7 @@ private J.TypeParameter visitTypeParameter(GenericsType genericType) { List> convertedBounds = new ArrayList<>(upperBounds.length); for (int i = 0; i < upperBounds.length; i++) { convertedBounds.add(JRightPadded.build(visitTypeTree(upperBounds[i])) - .withAfter( - i < upperBounds.length - 1 ? - sourceBefore("&") : - EMPTY - )); + .withAfter(i < upperBounds.length - 1 ? sourceBefore("&") : EMPTY)); } bounds = JContainer.build(boundsPrefix, convertedBounds, Markers.EMPTY); } else if (genericType.getLowerBound() != null) { @@ -2746,34 +2748,19 @@ private J.TypeParameter visitTypeParameter(GenericsType genericType) { return new J.TypeParameter(randomId(), prefix, Markers.EMPTY, emptyList(), emptyList(), name, bounds); } - private J.Wildcard visitWildcard(GenericsType genericType) { - Space namePrefix = sourceBefore("?"); - - JLeftPadded bound; - NameTree boundedType; - if (genericType.getUpperBounds() != null) { - bound = padLeft(sourceBefore("extends"), J.Wildcard.Bound.Extends); - boundedType = visitTypeTree(genericType.getUpperBounds()[0]); - } else if (genericType.getLowerBound() != null) { - bound = padLeft(sourceBefore("super"), J.Wildcard.Bound.Super); - boundedType = visitTypeTree(genericType.getLowerBound()); - } else { - bound = null; - boundedType = null; - } - - return new J.Wildcard(randomId(), namePrefix, Markers.EMPTY, bound, boundedType); - } - /** * Sometimes the groovy compiler inserts phantom elements into argument lists and class bodies, - * presumably to pass type information around. These elements do not appear in source code and should not - * be represented in our AST. + * presumably to pass type information around. Other times the groovy compiler adds extra transform annotations. + * These elements do not appear in source code and should not be represented in our LST. * * @param node possible phantom node * @return true if the node reports that it does have a position within the source code */ - private static boolean appearsInSource(ASTNode node) { + private boolean appearsInSource(ASTNode node) { + if (node instanceof AnnotationNode) { + return sourceStartsWith("@" + ((AnnotationNode) node).getClassNode().getUnresolvedName()); + } + return node.getColumnNumber() >= 0 && node.getLineNumber() >= 0 && node.getLastColumnNumber() >= 0 && node.getLastLineNumber() >= 0; } @@ -2809,4 +2796,24 @@ private static ClassNode staticType(Parameter parameter) { return inferred; } } + + private static final Map modifierNameToType; + + static { + modifierNameToType = new LinkedHashMap<>(); + modifierNameToType.put("def", J.Modifier.Type.LanguageExtension); + modifierNameToType.put("var", J.Modifier.Type.LanguageExtension); + modifierNameToType.put("public", J.Modifier.Type.Public); + modifierNameToType.put("protected", J.Modifier.Type.Protected); + modifierNameToType.put("private", J.Modifier.Type.Private); + modifierNameToType.put("abstract", J.Modifier.Type.Abstract); + modifierNameToType.put("static", J.Modifier.Type.Static); + modifierNameToType.put("final", J.Modifier.Type.Final); + modifierNameToType.put("volatile", J.Modifier.Type.Volatile); + modifierNameToType.put("synchronized", J.Modifier.Type.Synchronized); + modifierNameToType.put("transient", J.Modifier.Type.Transient); + modifierNameToType.put("native", J.Modifier.Type.Native); + modifierNameToType.put("default", J.Modifier.Type.Default); + modifierNameToType.put("strictfp", J.Modifier.Type.Strictfp); + } } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java index 223036f95c2..69923fd1a1c 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java @@ -25,6 +25,7 @@ import org.openrewrite.groovy.tree.GRightPadded; import org.openrewrite.groovy.tree.GSpace; import org.openrewrite.java.JavaPrinter; +import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; @@ -56,6 +57,7 @@ public J visitCompilationUnit(G.CompilationUnit cu, PrintOutputCapture

p) { JRightPadded pkg = cu.getPadding().getPackageDeclaration(); if (pkg != null) { visit(pkg.getElement(), p); + visitMarkers(pkg.getMarkers(), p); visitSpace(pkg.getAfter(), Space.Location.PACKAGE_SUFFIX, p); } @@ -258,22 +260,56 @@ public J visitTypeCast(J.TypeCast t, PrintOutputCapture

p) { return t; } + + @Override + public J visitVariableDeclarations(J.VariableDeclarations multiVariable, PrintOutputCapture

p) { + beforeSyntax(multiVariable, Space.Location.VARIABLE_DECLARATIONS_PREFIX, p); + visitSpace(Space.EMPTY, Space.Location.ANNOTATIONS, p); + visit(multiVariable.getLeadingAnnotations(), p); + multiVariable.getMarkers().findFirst(RedundantDef.class).ifPresent(def -> { + visitSpace(def.getPrefix(), Space.Location.LANGUAGE_EXTENSION, p); + p.append("def"); + }); + for (J.Modifier m : multiVariable.getModifiers()) { + visitModifier(m, p); + } + multiVariable.getMarkers().findFirst(MultiVariable.class).ifPresent(multiVar -> { + visitSpace(multiVar.getPrefix(), Space.Location.NAMED_VARIABLE_SUFFIX, p); + p.append(","); + }); + visit(multiVariable.getTypeExpression(), p); + // For backwards compatibility. + for (JLeftPadded dim : multiVariable.getDimensionsBeforeName()) { + visitSpace(dim.getBefore(), Space.Location.DIMENSION_PREFIX, p); + p.append('['); + visitSpace(dim.getElement(), Space.Location.DIMENSION, p); + p.append(']'); + } + if (multiVariable.getVarargs() != null) { + visitSpace(multiVariable.getVarargs(), Space.Location.VARARGS, p); + p.append("..."); + } + visitRightPadded(multiVariable.getPadding().getVariables(), JRightPadded.Location.NAMED_VARIABLE, ",", p); + afterSyntax(multiVariable, p); + return multiVariable; + } + @Override public J visitLambda(J.Lambda lambda, PrintOutputCapture

p) { beforeSyntax(lambda, Space.Location.LAMBDA_PREFIX, p); LambdaStyle ls = lambda.getMarkers().findFirst(LambdaStyle.class) .orElse(new LambdaStyle(null, false, !lambda.getParameters().getParameters().isEmpty())); boolean parenthesized = lambda.getParameters().isParenthesized(); - if(!ls.isJavaStyle()) { + if (!ls.isJavaStyle()) { p.append('{'); } visitMarkers(lambda.getParameters().getMarkers(), p); visitSpace(lambda.getParameters().getPrefix(), Space.Location.LAMBDA_PARAMETERS_PREFIX, p); - if(parenthesized) { + if (parenthesized) { p.append('('); } visitRightPadded(lambda.getParameters().getPadding().getParameters(), JRightPadded.Location.LAMBDA_PARAM, ",", p); - if(parenthesized) { + if (parenthesized) { p.append(')'); } if (ls.isArrow()) { @@ -287,7 +323,7 @@ public J visitLambda(J.Lambda lambda, PrintOutputCapture

p) { } else { visit(lambda.getBody(), p); } - if(!ls.isJavaStyle()) { + if (!ls.isJavaStyle()) { p.append('}'); } afterSyntax(lambda, p); @@ -328,6 +364,40 @@ public J visitForEachLoop(J.ForEachLoop forEachLoop, PrintOutputCapture

p) { return forEachLoop; } + @Override + public J visitMethodDeclaration(J.MethodDeclaration method, PrintOutputCapture

p) { + beforeSyntax(method, Space.Location.METHOD_DECLARATION_PREFIX, p); + visitSpace(Space.EMPTY, Space.Location.ANNOTATIONS, p); + visit(method.getLeadingAnnotations(), p); + for (J.Modifier m : method.getModifiers()) { + visitModifier(m, p); + } + J.TypeParameters typeParameters = method.getAnnotations().getTypeParameters(); + if (typeParameters != null) { + visit(typeParameters.getAnnotations(), p); + visitSpace(typeParameters.getPrefix(), Space.Location.TYPE_PARAMETERS, p); + visitMarkers(typeParameters.getMarkers(), p); + p.append('<'); + visitRightPadded(typeParameters.getPadding().getTypeParameters(), JRightPadded.Location.TYPE_PARAMETER, ",", p); + p.append('>'); + } + method.getMarkers().findFirst(RedundantDef.class).ifPresent(def -> { + visitSpace(def.getPrefix(), Space.Location.LANGUAGE_EXTENSION, p); + p.append("def"); + }); + visit(method.getReturnTypeExpression(), p); + visit(method.getAnnotations().getName().getAnnotations(), p); + visit(method.getName(), p); + if (!method.getMarkers().findFirst(CompactConstructor.class).isPresent()) { + visitContainer("(", method.getPadding().getParameters(), JContainer.Location.METHOD_DECLARATION_PARAMETERS, ",", ")", p); + } + visitContainer("throws", method.getPadding().getThrows(), JContainer.Location.THROWS, ",", null, p); + visit(method.getBody(), p); + visitLeftPadded("default", method.getPadding().getDefaultValue(), JLeftPadded.Location.METHOD_DECLARATION_DEFAULT_VALUE, p); + afterSyntax(method, p); + return method; + } + @Override public J visitMethodInvocation(J.MethodInvocation method, PrintOutputCapture

p) { beforeSyntax(method, Space.Location.METHOD_INVOCATION_PREFIX, p); @@ -353,29 +423,32 @@ public J visitMethodInvocation(J.MethodInvocation method, PrintOutputCapture

visitSpace(argContainer.getBefore(), Space.Location.METHOD_INVOCATION_ARGUMENTS, p); List> args = argContainer.getPadding().getElements(); + boolean argsAreAllClosures = args.stream().allMatch(it -> it.getElement() instanceof J.Lambda); + boolean hasParentheses = true; + boolean applyTrailingLambdaParenthese = true; for (int i = 0; i < args.size(); i++) { JRightPadded arg = args.get(i); - boolean omitParens = arg.getElement().getMarkers() - .findFirst(OmitParentheses.class) - .isPresent() || - arg.getElement().getMarkers() - .findFirst(org.openrewrite.java.marker.OmitParentheses.class) - .isPresent(); - - if (i == 0 && !omitParens) { - p.append('('); - } else if (i > 0 && omitParens && ( - !args.get(0).getElement().getMarkers().findFirst(OmitParentheses.class).isPresent() && - !args.get(0).getElement().getMarkers().findFirst(org.openrewrite.java.marker.OmitParentheses.class).isPresent() - )) { - p.append(')'); - } else if (i > 0) { + boolean omitParensCurrElem = arg.getElement().getMarkers().findFirst(OmitParentheses.class).isPresent() || + arg.getElement().getMarkers().findFirst(org.openrewrite.java.marker.OmitParentheses.class).isPresent(); + + if (i == 0) { + if (omitParensCurrElem) { + hasParentheses = false; + } else { + p.append('('); + } + } else if (hasParentheses && omitParensCurrElem) { // first trailing lambda, eg: `stage('Build..') {}`, should close the method + if (applyTrailingLambdaParenthese) { // apply once, to support multiple closures: `foo("baz") {} {} + p.append(')'); + applyTrailingLambdaParenthese = false; + } + } else if (hasParentheses || !argsAreAllClosures) { p.append(','); } visitRightPadded(arg, JRightPadded.Location.METHOD_INVOCATION_ARGUMENT, p); - if (i == args.size() - 1 && !omitParens) { + if (i == args.size() - 1 && !omitParensCurrElem) { p.append(')'); } } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java index 872160fa148..66d13198117 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java @@ -100,7 +100,7 @@ private JavaType.FullyQualified classType(ClassNode node, String signature) { JavaType.FullyQualified owner = TypeUtils.asFullyQualified(type(node.getOuterClass())); List fields = null; - if(node.getFields().size() > 0) { + if(!node.getFields().isEmpty()) { fields = new ArrayList<>(node.getFields().size()); for (FieldNode field : node.getFields()) { if (!field.isSynthetic()) { @@ -110,9 +110,9 @@ private JavaType.FullyQualified classType(ClassNode node, String signature) { } List methods = null; - if(node.getAllDeclaredMethods().size() > 0) { - methods = new ArrayList<>(node.getAllDeclaredMethods().size()); - for (MethodNode method : node.getAllDeclaredMethods()) { + if(!node.getMethods().isEmpty()) { + methods = new ArrayList<>(node.getMethods().size()); + for (MethodNode method : node.getMethods()) { if (!method.isSynthetic()) { methods.add(methodType(method)); } @@ -244,7 +244,7 @@ private JavaType genericType(GenericsType g, String signature) { } } - List thrownExceptions = null; + List thrownExceptions = null; if(node.getExceptions() != null) { for (ClassNode e : node.getExceptions()) { thrownExceptions = new ArrayList<>(node.getExceptions().length); diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/MultiVariable.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/MultiVariable.java new file mode 100644 index 00000000000..3554529bf6b --- /dev/null +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/MultiVariable.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.groovy.marker; + +import lombok.Value; +import lombok.With; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + +@Value +@With +public class MultiVariable implements Marker { + UUID id; + Space prefix; +} diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/RedundantDef.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/RedundantDef.java new file mode 100644 index 00000000000..34157a68d97 --- /dev/null +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/RedundantDef.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.groovy.marker; + +import lombok.Value; +import lombok.With; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + +/** + * In Groovy methods can be declared with a return type and also a redundant 'def' keyword. + * This captures the extra def keyword. + */ +@Value +@With +public class RedundantDef implements Marker { + UUID id; + Space prefix; +} diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyTypeAttributionTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyTypeAttributionTest.java index 432f70cca52..1a5b3534c8f 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyTypeAttributionTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyTypeAttributionTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.groovy.tree.G; @@ -180,7 +181,7 @@ public String register(@DelegatesTo(String) Closure stringAction) { } @SuppressWarnings({"GrPackage", "OptionalGetWithoutIsPresent"}) - @Disabled + @ExpectedToFail @Test void infersDelegateViaSimilarGradleApi() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/JenkinsFileTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/JenkinsFileTest.java index f677fb9f019..75ff46b9ffe 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/JenkinsFileTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/JenkinsFileTest.java @@ -16,6 +16,7 @@ package org.openrewrite.groovy; import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.openrewrite.groovy.Assertions.groovy; @@ -119,4 +120,19 @@ void jenkinsfile() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite/pull/4887") + @Test + void jenkinsfileWithComment() { + // the Jenkinsfile adapted from https://github.com/jenkinsci/ssh-plugin/blob/158.ve2a_e90fb_7319/Jenkinsfile + rewriteRun( + groovy( + """ + /* https://github.com */ + foo() + """, + spec -> spec.path("Jenkinsfile") + ) + ); + } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/format/OmitParenthesesForLastArgumentLambdaTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/format/OmitParenthesesForLastArgumentLambdaTest.java index 5b32551363d..5e7c28f6d6b 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/format/OmitParenthesesForLastArgumentLambdaTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/format/OmitParenthesesForLastArgumentLambdaTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.groovy.format; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -38,7 +37,6 @@ public void defaults(RecipeSpec spec) { spec.recipe(new OmitParenthesesForLastArgumentLambda()); } - @Disabled("Not yet implemented") @Test void lastClosureArgument() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AnnotationTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AnnotationTest.java index a87695e7d71..c9f14d23010 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AnnotationTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AnnotationTest.java @@ -30,22 +30,135 @@ void simple() { groovy( """ @Foo - class Test { + class Test implements Runnable { + @java.lang.Override + void run() {} } """ ) ); } + @Test + void simpleFQN() { + rewriteRun( + groovy( + """ + @org.springframework.stereotype.Service + class Test {} + """ + ) + ); + } + + @Test + void withParentheses() { + rewriteRun( + groovy( + """ + @Foo() + class Test {} + """ + ) + ); + } + + @Test + void inline() { + rewriteRun( + groovy( + """ + @Foo class Test implements Runnable { + @Override void run() {} + } + """ + ) + ); + } + + @Test + void withProperties() { + rewriteRun( + groovy( + """ + @Foo(value = "A", version = "1.0") + class Test {} + """ + ) + ); + } + + @Test + void withImplicitValueProperty() { + rewriteRun( + groovy( + """ + @Foo("A") + class Test {} + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/4055") @Test void nested() { rewriteRun( groovy( """ - @Foo(bar = @Bar) - class Test { - } + @Foo(bar = @Bar(@Baz(baz = @Qux("1.0")))) + class Test {} + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4254") + @Test + void groovyTransformAnnotation() { + rewriteRun( + groovy( + """ + import groovy.transform.EqualsAndHashCode + import groovy.transform.ToString + + @Foo + @ToString + @EqualsAndHashCode + @Bar + class Test {} + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4254") + @Test + void groovyTransformImmutableAnnotation() { + rewriteRun( + groovy( + """ + import groovy.transform.Immutable + import groovy.transform.TupleConstructor + + @Foo + @TupleConstructor + @Immutable + @Bar + class Test {} + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4254") + @Test + void groovyTransformImmutableFQNAnnotation() { + rewriteRun( + groovy( + """ + @groovy.transform.Immutable + class Test {} """ ) ); diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssignmentTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssignmentTest.java index 8a15073a41b..1d04f9bd706 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssignmentTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssignmentTest.java @@ -24,6 +24,41 @@ @SuppressWarnings({"GroovyUnusedAssignment", "GrUnnecessarySemicolon"}) class AssignmentTest implements RewriteTest { + @Test + void noKeyword() { + rewriteRun( + groovy( + """ + x = "s" + """ + ) + ); + } + + @Test + void simple() { + rewriteRun( + groovy( + """ + def x = "s" + """ + ) + ); + } + + @Test + void simpleWithFinal() { + rewriteRun( + groovy( + """ + final def x = "x" + def final y = "y" + final z = "z" + """ + ) + ); + } + @Test void concat() { rewriteRun( @@ -50,6 +85,28 @@ void assignment() { ); } + @Test + void classAssignment() { + rewriteRun( + groovy( + """ + def s = String + """ + ) + ); + } + + @Test + void classAssignmentJavaStyle() { + rewriteRun( + groovy( + """ + def s = String.class + """ + ) + ); + } + @Test void unaryMinus() { rewriteRun( @@ -91,4 +148,52 @@ void baseNConversions() { ) ); } + + @Test + void multipleAssignmentsAtOneLine() { + rewriteRun( + groovy( + """ + def startItem = '| ', endItem = ' |' + def repeatLength = startItem.length() + output.length() + endItem.length() + println("\\n" + ("-" * repeatLength) + "\\n| " + startItem + output + endItem + " |\\n" + ("-" * repeatLength)) + """ + ) + ); + } + + @Test + void multipleAssignmentsAtOneLineSimple() { + rewriteRun( + groovy( + """ + def a = '1', b = '2' + """ + ) + ); + } + + @Test + void multipleAssignmentsAtMultipleLineDynamicType() { + rewriteRun( + groovy( + """ + def a = '1' , + b = '2' + """ + ) + ); + } + + @Test + void multipleAssignmentsAtMultipleLineStaticType() { + rewriteRun( + groovy( + """ + String a = '1' , + b = '2' + """ + ) + ); + } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AttributeTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AttributeTest.java index bfbadb76655..2b7d4c82756 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AttributeTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AttributeTest.java @@ -20,19 +20,26 @@ import static org.openrewrite.groovy.Assertions.groovy; -@SuppressWarnings({"GroovyUnusedAssignment", "GrUnnecessarySemicolon"}) class AttributeTest implements RewriteTest { @Test - void usingGroovyNode() { + void attribute() { rewriteRun( - groovy( - """ - def xml = new Node(null, "ivy") - def n = xml.dependencies.dependency.find { it.@name == 'test-module' } - n.@conf = 'runtime->default;docs->docs;sources->sources' - """ - ) + groovy("new User('Bob').@name") + ); + } + + @Test + void attributeInClosure() { + rewriteRun( + groovy("[new User('Bob')].collect { it.@name }") + ); + } + + @Test + void attributeWithParentheses() { + rewriteRun( + groovy("(new User('Bob').@name)") ); } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java index 2d2675bfe72..ea37ae1215d 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java @@ -15,8 +15,8 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -34,7 +34,10 @@ void insideParentheses() { // NOT inside parentheses, but verifies the parser's // test for "inside parentheses" condition - groovy("(1) + 1") + groovy("( 1 ) + 1"), + groovy("(1) + 1"), + // And combine the two cases + groovy("((1) + 1)") ); } @@ -62,6 +65,31 @@ void in() { ); } + @Test + void withVariable() { + rewriteRun( + groovy( + """ + def foo(int a) { + 60 + a + } + """ + ) + ); + } + + @Test + void regexPatternOperator() { + rewriteRun( + groovy( + """ + def PATTERN = ~/foo/ + def result = PATTERN.matcher('4711').matches() + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1531") @Test void regexFindOperator() { @@ -201,17 +229,41 @@ void stringMultipliedInParentheses() { ); } - @Disabled @Issue("https://github.com/openrewrite/rewrite/issues/4703") @Test - void extraParensAroundInfixOperator() { + void cxds() { rewriteRun( groovy( """ - def foo(Map map) { - ((map.containsKey("foo")) - && ((map.get("foo")).equals("bar"))) + def differenceInDays(int time) { + return (int) ((time)/(1000*60*60*24)) } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4703") + @Test + void extraParensAroundInfixOxxxperator() { + rewriteRun( + groovy( + """ + def timestamp(int hours, int minutes, int seconds) { + 30 * (hours) + (((((hours))))) * 30 + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4703") + @Test + void extraParensAroundInfixOperator() { + rewriteRun( + groovy( + """ def timestamp(int hours, int minutes, int seconds) { (hours) * 60 * 60 + (minutes * 60) + seconds } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/CastTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/CastTest.java index e5fc6a0cb95..4c009de95e3 100755 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/CastTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/CastTest.java @@ -24,6 +24,18 @@ @SuppressWarnings({"UnnecessaryQualifiedReference", "GroovyUnusedAssignment", "GrUnnecessarySemicolon"}) class CastTest implements RewriteTest { + @Test + void cast() { + rewriteRun( + groovy( + """ + String foo = ( String ) "hallo" + String bar = "hallo" as String + """ + ) + ); + } + @Test void javaStyleCast() { rewriteRun( @@ -73,7 +85,18 @@ void groovyCastAndInvokeMethod() { rewriteRun( groovy( """ - ( "" as String ).toString() + ( "x" as String ).toString() + """ + ) + ); + } + + @Test + void groovyCastAndInvokeMethodWithParentheses() { + rewriteRun( + groovy( + """ + (((((( "" as String ))))).toString()) """ ) ); @@ -89,4 +112,15 @@ void javaCastAndInvokeMethod() { ) ); } + + @Test + void javaCastAndInvokeMethodWithParentheses() { + rewriteRun( + groovy( + """ + (((((( (String) "" )).toString())))) + """ + ) + ); + } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ClassDeclarationTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ClassDeclarationTest.java index 99d937b3168..02fd20658c8 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ClassDeclarationTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ClassDeclarationTest.java @@ -16,14 +16,12 @@ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.Issue; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RewriteTest; -import org.openrewrite.test.TypeValidation; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; @@ -86,6 +84,28 @@ public abstract class A {} ); } + @Test + void extendsObject() { + rewriteRun( + groovy( + """ + class B extends Object {} + """ + ) + ); + } + + @Test + void extendsScript() { + rewriteRun( + groovy( + """ + abstract class B extends Script {} + """ + ) + ); + } + @Test void interfaceExtendsInterface() { rewriteRun( @@ -137,6 +157,19 @@ public class A{} ); } + @Test + void hasPackageWithTrailingComma() { + rewriteRun( + groovy( + """ + package org.openrewrite; + + class A{} + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1736") @Test void parameterizedFieldDoesNotAffectClassType() { @@ -227,6 +260,20 @@ class Inner { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/4705") + @Test + void constructorWithDef() { + rewriteRun( + groovy( + """ + class A { + def A() {} + } + """ + ) + ); + } + @Test void newParameterizedConstructor() { rewriteRun( @@ -323,17 +370,90 @@ class A { ); } - @Issue("https://github.com/openrewrite/rewrite/pull/4346") @Test - @Disabled("Known issue; still need to explore a fix") - void classExtendsGroovyLangScript() { + void anonymousInnerClass() { + rewriteRun( + groovy( + """ + interface Something {} + + class Test { + Something something = new Something() {} + static def test() { + new Something() {} + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4063") + void nestedClassWithoutParameters() { + rewriteRun( + groovy( + """ + class A { + class B { + B() {} + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4063") + void nestedClass() { + rewriteRun( + groovy( + """ + class A { + class B { + String a;String[] b + B(String $a, String... b) { + this.a = $a + this.b = b + } + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4063") + void nestedStaticClassWithoutParameters() { + rewriteRun( + groovy( + """ + class A { + static class B { + B() {} + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4063") + void nestedStaticClass() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), - // Reduced from https://github.com/openrewrite/rewrite/blob/3de7723ba43d8c44d7b524273ad7548d4fcd04eb/rewrite-gradle/src/main/groovy/RewriteSettings.groovy#L32 - // "Source file was parsed into an LST that contains non-whitespace characters in its whitespace. This is indicative of a bug in the parser." groovy( """ - class RewriteSettings extends groovy.lang.Script { + class A { + static class B { + String a;String[] b + B(String a, String... b) { + this.a = a + this.b = b + } + } } """ ) diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ConstructorTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ConstructorTest.java index 16e575e0c96..7c7b5c66d72 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ConstructorTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ConstructorTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; @@ -70,7 +69,6 @@ int one() { } @Test - @Disabled("Fails to parse") void implicitPublic() { rewriteRun( groovy( @@ -85,7 +83,6 @@ class T { } @Test - @Disabled("Fails to parse") void defaultConstructorArguments() { rewriteRun( groovy( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/EnumTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/EnumTest.java index 438fc53d27f..10de8091922 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/EnumTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/EnumTest.java @@ -15,15 +15,15 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.test.RewriteTest; import static org.openrewrite.groovy.Assertions.groovy; class EnumTest implements RewriteTest { - @Disabled + @ExpectedToFail @Test void enumDefinition() { rewriteRun( @@ -38,7 +38,7 @@ enum A { ); } - @Disabled + @ExpectedToFail @Test void innerEnum() { rewriteRun( @@ -54,7 +54,7 @@ enum B { ); } - @Disabled + @ExpectedToFail @Test void enumWithAnnotations() { rewriteRun( @@ -72,7 +72,7 @@ enum Test { ); } - @Disabled + @ExpectedToFail @Test void anonymousClassInitializer() { rewriteRun( @@ -99,7 +99,7 @@ void foo() {} ); } - @Disabled + @ExpectedToFail @Test void enumConstructor() { rewriteRun( @@ -123,7 +123,7 @@ private static Object[] combineArguments(String context, Throwable ex, Object[] ); } - @Disabled + @ExpectedToFail @Test void noArguments() { rewriteRun( @@ -137,7 +137,7 @@ enum A { ); } - @Disabled + @ExpectedToFail @Test void enumWithParameters() { rewriteRun( @@ -154,7 +154,7 @@ enum A { ); } - @Disabled + @ExpectedToFail @Test void enumWithoutParameters() { rewriteRun( @@ -164,7 +164,7 @@ void enumWithoutParameters() { ); } - @Disabled + @ExpectedToFail @Test void enumUnnecessarilyTerminatedWithSemicolon() { rewriteRun( @@ -174,7 +174,7 @@ void enumUnnecessarilyTerminatedWithSemicolon() { ); } - @Disabled + @ExpectedToFail @Test void enumWithEmptyParameters() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java index a1432f73256..b2564fabf91 100755 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; @@ -148,7 +147,6 @@ void initializerIsAnAssignment() { ); } - @Disabled @Test void multiVariableInitialization() { rewriteRun( @@ -162,6 +160,17 @@ void multiVariableInitialization() { @Test void forEachWithColon() { + rewriteRun( + groovy( + """ + for(def i : [1, 2, 3]) {} + """ + ) + ); + } + + @Test + void forEachTypedWithColon() { rewriteRun( groovy( """ @@ -176,9 +185,7 @@ void forIn() { rewriteRun( groovy( """ - def dependenciesType = ['implementation', 'testImplementation'] - for (type in dependenciesType) { - } + for (type in ['implementation', 'testImplementation']) {} """ ) ); diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ListTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ListTest.java index b42f9579fd4..d921377b661 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ListTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ListTest.java @@ -23,6 +23,29 @@ @SuppressWarnings("GroovyUnusedAssignment") class ListTest implements RewriteTest { + @Test + void emptyListLiteral() { + rewriteRun( + groovy( + """ + def a = [] + def b = [ ] + """ + ) + ); + } + + @Test + void emptyListLiteralWithParentheses() { + rewriteRun( + groovy( + """ + def y = ([]) + """ + ) + ); + } + @Test void listLiteral() { rewriteRun( @@ -33,4 +56,15 @@ void listLiteral() { ) ); } + + @Test + void listLiteralTrailingComma() { + rewriteRun( + groovy( + """ + def a = [ "foo" /* "foo" suffix */ , /* "]" prefix */ ] + """ + ) + ); + } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java index 46851b2f65a..0bc2ac3535d 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java @@ -84,6 +84,17 @@ void slashString() { ); } + @Test + void escapedString() { + rewriteRun( + groovy( + """ + "f\\"o\\\\\\"o" + """ + ) + ); + } + @Test void gString() { rewriteRun( @@ -169,6 +180,17 @@ void gStringWithSpace() { ); } + @Test + void gStringWithEscapedDelimiter() { + rewriteRun( + groovy( + """ + String s = "${displayName}" + """ + ) + ); + } + @Test void mapLiteral() { rewriteRun( @@ -216,18 +238,6 @@ void literalValueAndTypeAgree() { ); } - @Test - void emptyListLiteral() { - rewriteRun( - groovy( - """ - def a = [] - def b = [ ] - """ - ) - ); - } - @Test void multilineStringWithApostrophes() { rewriteRun( @@ -254,17 +264,6 @@ void mapLiteralTrailingComma() { ); } - @Test - void listLiteralTrailingComma() { - rewriteRun( - groovy( - """ - def a = [ "foo" /* "foo" suffix */ , /* "]" prefix */ ] - """ - ) - ); - } - @Test void gStringThatHasEmptyValueExpressionForUnknownReason() { rewriteRun( @@ -282,7 +281,7 @@ void escapeCharacters() { rewriteRun( groovy( """ - "\\\\n\\t" + "\\\\\\\\n\\\\t" '\\\\n\\t' ///\\\\n\\t/// """ @@ -307,7 +306,7 @@ void stringLiteralInParentheses() { rewriteRun( groovy( """ - def a = ("-") + def a = ( "-" ) """ ), groovy( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MapEntryTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MapEntryTest.java index 65bb4a17492..67ee80177cd 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MapEntryTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MapEntryTest.java @@ -55,6 +55,13 @@ void emptyMapLiteral() { ); } + @Test + void emptyMapLiteralWithParentheses() { + rewriteRun( + groovy("Map m = ([ : ])") + ); + } + @Test void mapAccess() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodDeclarationTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodDeclarationTest.java index 1963ef2db08..6d238895d62 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodDeclarationTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodDeclarationTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.Issue; import org.openrewrite.java.JavaIsoVisitor; @@ -80,6 +79,19 @@ static int accept(Map m) { ); } + @Test + void genericTypeParameter() { + rewriteRun( + groovy( + """ + interface Foo { + T task(Class type) + } + """ + ) + ); + } + @Test void emptyArguments() { rewriteRun( @@ -111,6 +123,19 @@ def foo(bar, baz) { ); } + @Test + void varargsArguments() { + rewriteRun( + groovy( + """ + def foo(String... messages) { + println(messages[0]) + } + """ + ) + ); + } + @Test void defaultArgumentValues() { rewriteRun( @@ -182,16 +207,16 @@ void escapedMethodNameWithSpacesTest() { } @Issue("https://github.com/openrewrite/rewrite/issues/4705") - @Disabled @Test void functionWithDefAndExplicitReturnType() { rewriteRun( groovy( - """ - class A { - def int one() { 1 } - } """ + class A { + def /*int*/ int one() { 1 } + @Foo def /*Object*/ Object two() { 2 } + } + """ ) ); } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java index e22c1e08d46..4e893f7ffb0 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java @@ -30,11 +30,11 @@ void gradle() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation 'org.hibernate:hibernate-core:3.6.7.Final' api 'com.google.guava:guava:23.0' @@ -45,6 +45,21 @@ void gradle() { ); } + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4615") + void gradleWithParentheses() { + rewriteRun( + groovy( + """ + plugins { + id 'java-library' + } + def version = (rootProject.jobName.startsWith('a')) ? "latest.release" : "3.0" + """ + ) + ); + } + @Test void emptyArgsWithParens() { rewriteRun( @@ -52,6 +67,19 @@ void emptyArgsWithParens() { ); } + @Test + void noParentheses() { + rewriteRun( + groovy( + """ + class SomeObject {} + def foo(String a, int b, SomeObject c, String d) {} + foo "a", 3, new SomeObject(), "d" + """ + ) + ); + } + @Test @SuppressWarnings("GroovyVariableNotAssigned") void nullSafeDereference() { @@ -148,6 +176,28 @@ def acceptsNamedArguments(Map a, int n, int m) { } ); } + @Test + void useClassAsArgument() { + rewriteRun( + groovy( + """ + foo(String) + """ + ) + ); + } + + @Test + void useClassAsArgumentJavaStyle() { + rewriteRun( + groovy( + """ + foo(String.class) + """ + ) + ); + } + @Test @SuppressWarnings("GroovyAssignabilityCheck") void closureWithImplicitParameter() { @@ -163,6 +213,88 @@ def acceptsClosure(Closure cl) {} ); } + @Test + void closureInObjectInObject() { + rewriteRun( + groovy( + """ + class Test { + Test child = new Test() + def acceptsClosure(Closure cl) {} + } + + new Test().child.acceptsClosure {} + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4766") + @Test + void gradleFileWithMultipleClosuresWithoutParentheses() { + rewriteRun( + groovy( + """ + copySpec { + from { 'src/main/webapp' } { exclude "**/*.jpg" } + rename '(.+)-staging(.+)', '$1$2' + } + """ + ) + ); + } + + @Test + void multipleClosureArgumentsWithoutParentheses() { + rewriteRun( + groovy( + """ + def foo(Closure a, Closure b, Closure c) {} + foo { } { } { + } + """ + ) + ); + } + + @Test + void multipleClosureArgumentsWithParentheses() { + rewriteRun( + groovy( + """ + def foo(Closure a, Closure b, Closure c) {} + foo({ }, { }, { + }) + """ + ) + ); + } + + @Test + void multipleArgumentsWithClosuresAndNonClosuresWithoutParentheses() { + rewriteRun( + groovy( + """ + def foo(String a, Closure b, Closure c, String d) {} + foo "a", { }, { + }, "d" + """ + ) + ); + } + + @Test + void trailingClosures() { + rewriteRun( + groovy( + """ + def foo(String a, int b, String c, Closure d, Closure e, Closure f) {} + foo("bar", 3, "baz") { } { } {} + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1236") @Test @SuppressWarnings("GroovyAssignabilityCheck") @@ -230,6 +362,25 @@ void closureReturn() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/4055") + @Test + void chainOfMethodInvocations() { + rewriteRun( + groovy( + """ + Micronaut.build(args) + .banner(false) + .propertySources(PropertySource.of("my-config", [name: "MyApp"])) + .environments("prod") // Only prod + .overrideConfig("custom-config.yml") // Load custom config + .packages("com.company") + .mainClass(Application) + .start() + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/2552") @Test void closureInvocation() { @@ -253,7 +404,7 @@ class StringUtils { static boolean isEmpty(String value) { return value == null || value.isEmpty() } - + static void main(String[] args) { isEmpty("") } @@ -262,4 +413,82 @@ static void main(String[] args) { ) ); } + + @Issue("https://github.com/openrewrite/rewrite/issues/4703") + @Test + void insideParenthesesSimple() { + rewriteRun( + groovy( + """ + ((a.invoke "b" )) + """ + ) + ); + } + + @Test + void lotOfSpacesAroundConstantWithParentheses() { + rewriteRun( + groovy( + """ + ( ( ( "x" ) ).toString() ) + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4703") + @Test + void insideParentheses() { + rewriteRun( + groovy( + """ + static def foo(Map map) { + ((map.containsKey("foo")) + && ((map.get("foo")).equals("bar"))) + } + """ + ) + ); + } + + @Test + void insideParenthesesWithNewline() { + rewriteRun( + groovy( + """ + static def foo(Map map) { + (( + map.containsKey("foo")) + && ((map.get("foo")).equals("bar"))) + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4703") + @Test + void insideParenthesesWithoutNewLineAndEscapedMethodName() { + rewriteRun( + groovy( + """ + static def foo(Map someMap) {((((((someMap.get("(bar")))) ).'equals' "baz" ) ) } + """ + ) + ); + } + + @Test + void insideFourParenthesesAndEnters() { + rewriteRun( + groovy( + """ + (((( + something(a) + )))) + """ + ) + ); + } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java index bdea313eb33..e0ff32f3d94 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java @@ -46,4 +46,17 @@ void parenthesized() { ) ); } + + @Test + void parenthesizedAndInvokeMethodWithParentheses() { + rewriteRun( + groovy( + """ + (((( 8..19 ))).each { majorVersion -> + if (majorVersion == 9) return + }) + """ + ) + ); + } } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RealWorldGroovyTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RealWorldGroovyTest.java new file mode 100644 index 00000000000..4b128e21c18 --- /dev/null +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RealWorldGroovyTest.java @@ -0,0 +1,420 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.groovy.tree; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; +import org.openrewrite.Issue; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.groovy.Assertions.groovy; + +/** + * A test with groovy examples picked from OSS repositories. + */ +class RealWorldGroovyTest implements RewriteTest { + + @Test + @Issue("https://github.com/spring-projects/spring-boot/blob/v3.4.1/settings.gradle") + void springBootSettingsGradle() { + rewriteRun( + groovy( + """ + pluginManagement { + evaluate(new File("${rootDir}/buildSrc/SpringRepositorySupport.groovy")).apply(this) + repositories { + mavenCentral() + gradlePluginPortal() + spring.mavenRepositories(); + } + resolutionStrategy { + eachPlugin { + if (requested.id.id == "org.jetbrains.kotlin.jvm") { + useVersion "${kotlinVersion}" + } + if (requested.id.id == "org.jetbrains.kotlin.plugin.spring") { + useVersion "${kotlinVersion}" + } + } + } + } + + plugins { + id "io.spring.develocity.conventions" version "0.0.22" + } + + rootProject.name="spring-boot-build" + + enableFeaturePreview("STABLE_CONFIGURATION_CACHE") + + settings.gradle.projectsLoaded { + develocity { + buildScan { + def toolchainVersion = settings.gradle.rootProject.findProperty('toolchainVersion') + if (toolchainVersion != null) { + value('Toolchain version', toolchainVersion) + tag("JDK-$toolchainVersion") + } + } + } + } + + include "spring-boot-system-tests:spring-boot-image-tests" + + file("${rootDir}/spring-boot-project/spring-boot-starters").eachDirMatch(~/spring-boot-starter.*/) { + include "spring-boot-project:spring-boot-starters:${it.name}" + } + + file("${rootDir}/spring-boot-tests/spring-boot-smoke-tests").eachDirMatch(~/spring-boot-smoke-test.*/) { + include "spring-boot-tests:spring-boot-smoke-tests:${it.name}" + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/spring-projects/spring-boot/blob/v3.4.1/spring-boot-project/spring-boot-tools/spring-boot-cli/src/test/resources/classloader-test-app.groovy") + void springBootClassloaderTestApp() { + rewriteRun( + groovy( + """ + import org.springframework.util.* + + @Component + public class Test implements CommandLineRunner { + + public void run(String... args) throws Exception { + println "HasClasses-" + ClassUtils.isPresent("missing", null) + "-" + + ClassUtils.isPresent("org.springframework.boot.SpringApplication", null) + "-" + + ClassUtils.isPresent(args[0], null) + } + + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/spring-projects/spring-ldap/blob/v3.4.1/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocOptionsPlugin.groovy") + void springLdapJavadocOptionsPlugin() { + rewriteRun( + groovy( + """ + import org.gradle.api.Plugin + import org.gradle.api.Project + import org.gradle.api.tasks.javadoc.Javadoc + + public class JavadocOptionsPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getTasks().withType(Javadoc).all { t-> + t.options.addStringOption('Xdoclint:none', '-quiet') + } + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/spring-projects/spring-ldap/blob/v3.4.1/buildSrc/src/test/resources/samples/integrationtest/withgroovy/src/integration-test/groovy/sample/TheTest.groovy") + void springLdapTheTest() { + rewriteRun( + groovy( + """ + import org.springframework.core.Ordered + import spock.lang.Specification + + class TheTest extends Specification { + def "has Ordered"() { + expect: 'Loads Ordered fine' + Ordered ordered = new Ordered() { + @Override + int getOrder() { + return 0 + } + } + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/spring-projects/spring-session-data-geode/blob/v3.4.1/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy") + void springTestDataGeodeSchemaZipPlugin() { + rewriteRun( + groovy( + """ + import org.gradle.api.Plugin + import org.gradle.api.Project + import org.gradle.api.file.DuplicatesStrategy + import org.gradle.api.plugins.JavaPlugin + import org.gradle.api.tasks.bundling.Zip + + /** + * Zips all Spring XML schemas (XSD) files. + * + * @author Rob Winch + * @author John Blum + * @see org.gradle.api.Plugin + * @see org.gradle.api.Project + */ + class SchemaZipPlugin implements Plugin { + + @Override + void apply(Project project) { + + Zip schemaZip = project.tasks.create('schemaZip', Zip) + + schemaZip.archiveBaseName = project.rootProject.name + schemaZip.archiveClassifier = 'schema' + schemaZip.description = "Builds -${schemaZip.archiveClassifier} archive containing all XSDs" + + " for deployment to static.springframework.org/schema." + schemaZip.group = 'Distribution' + + project.rootProject.subprojects.each { module -> + + module.getPlugins().withType(JavaPlugin.class).all { + + Properties schemas = new Properties(); + + module.sourceSets.main.resources + .find { it.path.endsWith('META-INF/spring.schemas') } + ?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + + def zipEntryName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1') + + assert zipEntryName != key + + File xsdFile = module.sourceSets.main.resources.find { + it.path.endsWith(schemas.get(key)) + } + + assert xsdFile != null + + schemaZip.into(zipEntryName) { + duplicatesStrategy DuplicatesStrategy.EXCLUDE + from xsdFile.path + } + } + } + } + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/spring-projects/spring-webflow/blob/v3.4.1/gradle/docs.gradle") + void springWebflowGradleDocs() { + rewriteRun( + groovy( + """ + apply plugin: "maven-publish" + + configurations { + asciidoctorExtensions + } + + dependencies { + asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.7" + } + + asciidoctorPdf { + baseDirFollowsSourceFile() + asciidoctorj { + sources { + include 'index.adoc' + } + options doctype: 'book' + attributes 'icons': 'font', + 'sectanchors': '', + 'sectnums': '', + 'toc': '', + 'source-highlighter' : 'coderay', + revnumber: project.version, + 'project-version': project.version + } + } + + asciidoctor { + baseDirFollowsSourceFile() + configurations "asciidoctorExtensions" + outputOptions { + backends "spring-html" + } + sources { + include 'index.adoc' + } + options doctype: 'book' + + attributes 'docinfo': 'shared', + stylesdir: 'css/', + stylesheet: 'spring.css', + 'linkcss': true, + 'icons': 'font', + 'sectanchors': '', + 'source-highlighter': 'highlight.js', + 'highlightjsdir': 'js/highlight', + 'highlightjs-theme': 'github', + 'idprefix': '', + 'idseparator': '-', + 'spring-version': project.version, + 'allow-uri-read': '', + 'toc': 'left', + 'toclevels': '4', + revnumber: project.version, + 'project-version': project.version + } + + task api(type: Javadoc) { + group = "Documentation" + description = "Generates aggregated Javadoc API documentation." + title = "${rootProject.description} ${version} API" + options.memberLevel = JavadocMemberLevel.PROTECTED + options.author = true + options.header = rootProject.description + options.overview = "src/api/overview.html" + source subprojects.collect { project -> + project.sourceSets.main.allJava + } + destinationDir = new File(buildDir, "api") + classpath = files(subprojects.collect { project -> + project.sourceSets.main.compileClasspath + }) + maxMemory = "1024m" + } + + task docsZip(type: Zip) { + group = "Distribution" + archiveBaseName = "spring-webflow" + archiveClassifier = "docs" + description = "Builds -${archiveClassifier.get()} archive containing api and reference " + + "for deployment at static.springframework.org/spring-webflow/docs." + + from (api) { + into "api" + } + from (asciidoctor) { + into "reference" + } + from (asciidoctorPdf) { + into "reference" + } + } + + task schemaZip(type: Zip) { + group = "Distribution" + archiveBaseName = "spring-webflow" + archiveClassifier = "schema" + description = "Builds -${archiveClassifier.get()} archive containing all " + + "XSDs for deployment at static.springframework.org/schema." + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + subprojects.each { subproject -> + Properties schemas = new Properties() + + subproject.sourceSets.main.resources.find { + it.path.endsWith("META-INF/spring.schemas") + }?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1') + assert shortName != key + File xsdFile = subproject.sourceSets.main.allSource.find { + it.path.endsWith(schemas.get(key)) + } as File + assert xsdFile != null + into (shortName) { + from xsdFile.path + } + } + } + + project(":spring-webflow").sourceSets.main.resources.matching { + include '**/engine/model/builder/xml/*.xsd' + }.each { File file -> + into ('webflow') { + from file.path + } + } + } + + task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { + group = "Distribution" + archiveBaseName = "spring-webflow" + archiveClassifier = "dist" + description = "Builds -${archiveClassifier.get()} archive, containing all jars and docs, " + + "suitable for community download page." + + def baseDir = "${archiveBaseName.get()}-${project.version}" + + from("src/dist") { + include "notice.txt" + into "${baseDir}" + expand(copyright: new Date().format("yyyy"), version: project.version) + } + from("src/dist") { + include "readme.txt" + include "license.txt" + into "${baseDir}" + expand(version: project.version) + } + from(zipTree(docsZip.archiveFile)) { + into "${baseDir}/docs" + } + from(zipTree(schemaZip.archiveFile)) { + into "${baseDir}/schema" + } + + subprojects.each { subproject -> + into ("${baseDir}/libs") { + from subproject.jar + if (subproject.tasks.findByPath("sourcesJar")) { + from subproject.sourcesJar + } + if (subproject.tasks.findByPath("javadocJar")) { + from subproject.javadocJar + } + } + } + } + + publishing { + publications { + mavenJava(MavenPublication) { + artifact docsZip + artifact schemaZip + artifact distZip + } + } + } + """ + ) + ); + } +} diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java index 09ef3f92b64..bb6bfb5fb4a 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java @@ -31,7 +31,9 @@ void insideParentheses() { // NOT inside parentheses, but verifies the parser's // test for "inside parentheses" condition - groovy("(true) ? 1 : 2") + groovy("(true) ? 1 : 2"), + // And combine the two cases + groovy("((true) ? 1 : 2)") ); } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TryTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TryTest.java index d580583b017..5dd1e34fd58 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TryTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TryTest.java @@ -15,8 +15,8 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -90,7 +90,7 @@ void tryCatchFinally() { } @Issue("https://github.com/openrewrite/rewrite/issues/1944") - @Disabled + @ExpectedToFail @Test void multiCatch() { rewriteRun( @@ -106,7 +106,7 @@ void multiCatch() { } @Issue("https://github.com/openrewrite/rewrite/issues/1945") - @Disabled + @ExpectedToFail @Test void tryWithResource() { rewriteRun( @@ -123,7 +123,7 @@ void tryWithResource() { } @Issue("https://github.com/openrewrite/rewrite/issues/1945") - @Disabled + @ExpectedToFail @Test void tryWithResources() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java index 3915c6c39f8..df3cf0ff4d6 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.groovy.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; import org.openrewrite.Issue; @@ -32,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.groovy.Assertions.groovy; -@SuppressWarnings({"GroovyUnusedAssignment", "DataFlowIssue"}) +@SuppressWarnings({"GroovyUnusedAssignment", "DataFlowIssue", "GrUnnecessaryDefModifier"}) class VariableDeclarationsTest implements RewriteTest { @Test @@ -129,7 +128,6 @@ void numericValueWithUnderscores() { ); } - @Disabled @Test void singleTypeMultipleVariableDeclaration() { rewriteRun( @@ -137,7 +135,6 @@ void singleTypeMultipleVariableDeclaration() { ); } - @Disabled @Test void multipleTypeMultipleVariableDeclaration() { rewriteRun( @@ -190,7 +187,6 @@ public J visitVariableDeclarations(J.VariableDeclarations multiVariable, Executi } @Issue("https://github.com/openrewrite/rewrite/issues/4702") - @Disabled @Test void nestedTypeParameters() { rewriteRun( @@ -203,4 +199,29 @@ class A { ) ); } + + @Issue("https://github.com/openrewrite/rewrite/issues/4705") + @Test + void defAndExplicitReturnType() { + rewriteRun( + groovy( + """ + def /*int*/ int one = 1 + def /*Object*/ Object two = 2 + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4877") + @Test + void defVariableStartsWithDef() { + rewriteRun( + groovy( + """ + def defaultPublicStaticFinal = 0 + """ + ) + ); + } } diff --git a/rewrite-hcl/src/main/antlr/HCLLexer.g4 b/rewrite-hcl/src/main/antlr/HCLLexer.g4 index 474d5a9babf..8ae20167657 100644 --- a/rewrite-hcl/src/main/antlr/HCLLexer.g4 +++ b/rewrite-hcl/src/main/antlr/HCLLexer.g4 @@ -14,8 +14,8 @@ lexer grammar HCLLexer; private Stack heredocIdentifier = new Stack(); } -FOR_BRACE : '{' (WS|NEWLINE)* 'for' WS; -FOR_BRACK : '[' (WS|NEWLINE)* 'for' WS; +FOR_BRACE : '{' (WS|NEWLINE|COMMENT|LINE_COMMENT)* 'for' WS; +FOR_BRACK : '[' (WS|NEWLINE|COMMENT|LINE_COMMENT)* 'for' WS; IF : 'if'; IN : 'in'; @@ -49,10 +49,10 @@ Identifier // Lexical Elements - Comments and Whitespace // https://github.com/hashicorp/hcl2/blob/master/hcl/hclsyntax/spec.md#comments-and-whitespace -WS : [ \t\r\u000C]+ -> channel(HIDDEN); -COMMENT : '/*' .*? '*/' -> channel(HIDDEN); -LINE_COMMENT : ('//' | '#') ~[\r\n]* '\r'?'\n' -> channel(HIDDEN); -NEWLINE : '\n' -> channel(HIDDEN); +WS : [ \t\r\u000C]+ -> channel(HIDDEN); +COMMENT : '/*' .*? '*/' -> channel(HIDDEN); +LINE_COMMENT : ('//' | '#') ~[\r\n]* '\r'? ('\n' | EOF) -> channel(HIDDEN); +NEWLINE : '\n' -> channel(HIDDEN); fragment LetterOrDigit : Letter @@ -81,7 +81,7 @@ fragment HexDigit // https://github.com/hashicorp/hcl2/blob/master/hcl/hclsyntax/spec.md#numeric-literals NumericLiteral - : [0-9]+ '.' [0-9]* ExponentPart? + : [0-9]+ '.' [0-9]+ ExponentPart? | [0-9]+ ExponentPart | [0-9]+ ; @@ -129,6 +129,7 @@ MOD : '%'; ELLIPSIS : '...'; TILDE : '~'; + // ---------------------------------------------------------------------------------------------- mode TEMPLATE; // ---------------------------------------------------------------------------------------------- diff --git a/rewrite-hcl/src/main/antlr/HCLParser.g4 b/rewrite-hcl/src/main/antlr/HCLParser.g4 index 9e8bebafb83..5d48086569c 100644 --- a/rewrite-hcl/src/main/antlr/HCLParser.g4 +++ b/rewrite-hcl/src/main/antlr/HCLParser.g4 @@ -52,6 +52,7 @@ exprTerm | functionCall #FunctionCallExpression | exprTerm index #IndexAccessExpression | exprTerm getAttr #AttributeAccessExpression + | exprTerm legacyIndexAttr #LegacyIndexAttributeExpression | exprTerm splat #SplatExpression | LPAREN expression RPAREN #ParentheticalExpression ; @@ -86,7 +87,7 @@ object ; objectelem - : (Identifier | LPAREN Identifier RPAREN | QUOTE quotedTemplatePart* QUOTE) (ASSIGN | COLON) expression COMMA? + : (Identifier | LPAREN Identifier RPAREN | QUOTE quotedTemplatePart* QUOTE | expression) (ASSIGN | COLON) expression COMMA? ; // For Expressions @@ -144,6 +145,14 @@ getAttr : DOT Identifier ; +// Legacy Index Access Operator +// https://github.com/hashicorp/hcl/blob/923b06b5d6adf61a647101c605f7463a1bd56cbf/hclsyntax/spec.md#L547 +// Interestingly not mentioned in hcl2 syntax specification anymore + +legacyIndexAttr + : DOT NumericLiteral + ; + // Splat Operators // https://github.com/hashicorp/hcl2/blob/master/hcl/hclsyntax/spec.md#splat-operators diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclIsoVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclIsoVisitor.java index 6f5b3d2ceb9..b442182a944 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclIsoVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclIsoVisitor.java @@ -102,6 +102,11 @@ public Hcl.Index.Position visitIndexPosition(Hcl.Index.Position indexPosition, P return (Hcl.Index.Position) super.visitIndexPosition(indexPosition, p); } + @Override + public Hcl.LegacyIndexAttributeAccess visitLegacyIndexAttribute(Hcl.LegacyIndexAttributeAccess legacyIndexAttributeAccess, P p) { + return (Hcl.LegacyIndexAttributeAccess) super.visitLegacyIndexAttribute(legacyIndexAttributeAccess, p); + } + @Override public Hcl.Literal visitLiteral(Hcl.Literal literal, P p) { return (Hcl.Literal) super.visitLiteral(literal, p); diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclTemplate.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclTemplate.java index 5fb3297f4c3..e7b2dd0933e 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclTemplate.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclTemplate.java @@ -69,7 +69,7 @@ public H apply(Cursor scope, HclCoordinates coordinates, Object. @Override public Hcl visitConfigFile(Hcl.ConfigFile configFile, Integer p) { Hcl.ConfigFile c = (Hcl.ConfigFile) super.visitConfigFile(configFile, p); - if (loc.equals(Space.Location.CONFIG_FILE_EOF)) { + if (loc == Space.Location.CONFIG_FILE_EOF) { List gen = substitutions.unsubstitute(templateParser.parseBodyContent(substitutedTemplate)); if (coordinates.getComparator() != null) { @@ -100,7 +100,7 @@ public Hcl visitConfigFile(Hcl.ConfigFile configFile, Integer p) { ) ); } - } else if (loc.equals(Space.Location.CONFIG_FILE)) { + } else if (loc == Space.Location.CONFIG_FILE) { List gen = substitutions.unsubstitute(templateParser.parseBodyContent(substitutedTemplate)); c = c.withBody( ListUtils.concatAll( @@ -116,7 +116,7 @@ public Hcl visitConfigFile(Hcl.ConfigFile configFile, Integer p) { @Override public Hcl visitBlock(Hcl.Block block, Integer p) { Hcl.Block b = (Hcl.Block) super.visitBlock(block, p); - if (loc.equals(Space.Location.BLOCK_CLOSE)) { + if (loc == Space.Location.BLOCK_CLOSE) { if (b.isScope(insertionPoint)) { List gen = substitutions.unsubstitute(templateParser.parseBodyContent(substitutedTemplate)); @@ -145,7 +145,7 @@ public Hcl visitBlock(Hcl.Block block, Integer p) { .orElse(SpacesStyle.DEFAULT)).visit(b, p, getCursor().getParentOrThrow()); assert b != null; } - } else if (loc.equals(Space.Location.BLOCK)) { + } else if (loc == Space.Location.BLOCK) { if (b.isScope(insertionPoint)) { b = (Hcl.Block) autoFormat(templateParser.parseBodyContent(substitutedTemplate).get(0), p, getCursor().getParentOrThrow()); @@ -158,7 +158,7 @@ public Hcl visitBlock(Hcl.Block block, Integer p) { public Hcl visitExpression(Expression expression, Integer p) { Hcl e = super.visitExpression(expression, p); - if (loc.equals(Space.Location.EXPRESSION_PREFIX)) { + if (loc == Space.Location.EXPRESSION_PREFIX) { if (e.isScope(insertionPoint)) { e = templateParser.parseExpression(substitutedTemplate).withPrefix(expression.getPrefix()); } diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclVisitor.java index 6c7d095e00a..119e13d06ee 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclVisitor.java @@ -77,6 +77,23 @@ public Hcl visitAttributeAccess(Hcl.AttributeAccess attributeAccess, P p) { return a; } + public Hcl visitLegacyIndexAttribute(Hcl.LegacyIndexAttributeAccess legacyIndexAttributeAccess, P p) { + Hcl.LegacyIndexAttributeAccess li = legacyIndexAttributeAccess; + li = li.withPrefix(visitSpace(li.getPrefix(), Space.Location.LEGACY_INDEX_ATTRIBUTE_ACCESS, p)); + li = li.withMarkers(visitMarkers(li.getMarkers(), p)); + Expression temp = (Expression) visitExpression(li, p); + if (!(temp instanceof Hcl.LegacyIndexAttributeAccess)) { + return temp; + } else { + li = (Hcl.LegacyIndexAttributeAccess) temp; + } + li = li.getPadding().withBase( + visitRightPadded(li.getPadding().getBase(), HclRightPadded.Location.LEGACY_INDEX_ATTRIBUTE_ACCESS_BASE, p)); + li = li.withIndex((Hcl.Literal) visitLiteral(li.getIndex(), p)); + return li; + } + + public Hcl visitBinary(Hcl.Binary binary, P p) { Hcl.Binary b = binary; b = b.withPrefix(visitSpace(b.getPrefix(), Space.Location.BINARY, p)); diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/AttributeSpaceVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/AttributeSpaceVisitor.java index dc497dd9af1..a384b1d7d4b 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/AttributeSpaceVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/AttributeSpaceVisitor.java @@ -52,7 +52,7 @@ public Hcl.Attribute visitAttribute(Hcl.Attribute attribute, P p) { if (parent instanceof Hcl.Block || parent instanceof Hcl.ObjectValue) { List siblingAttributes = getSiblingAttributes(parent); - if (attribute.getType().equals(Hcl.Attribute.Type.Assignment)) { + if (attribute.getType() == Hcl.Attribute.Type.Assignment) { HclLeftPadded type = a.getPadding().getType(); if (Boolean.TRUE.equals(style.getBodyContent().getColumnarAlignment())) { @@ -106,7 +106,7 @@ private List attributesInGroup(List siblings, Hcl. boolean groupFound = false; Hcl.Attribute perviousSibling = null; for (Hcl.Attribute sibling : siblings) { - if (sibling.getType().equals(Hcl.Attribute.Type.Assignment)) { + if (sibling.getType() == Hcl.Attribute.Type.Assignment) { boolean siblingPrefixHasNewLines = sibling.getPrefix().getWhitespace().split("\r\n|\r|\n").length > 2; boolean siblingIsMultiline = sibling.getValue().print(getCursor()).split("\r\n|\r|\n").length > 2; boolean previousSiblingIsMultiline = perviousSibling != null && perviousSibling.getValue().print(getCursor()).split("\r\n|\r|\n").length > 2; diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/BracketsVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/BracketsVisitor.java index 6bbd596c11e..cb885fa0818 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/BracketsVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/BracketsVisitor.java @@ -42,11 +42,11 @@ public BracketsVisitor(BracketsStyle style, @Nullable Tree stopAfter) { @Override public Space visitSpace(Space space, Space.Location loc, P p) { - if (loc.equals(Space.Location.BLOCK_CLOSE) && !space.getLastWhitespace().contains("\n")) { + if (loc == Space.Location.BLOCK_CLOSE && !space.getLastWhitespace().contains("\n")) { return space.withLastWhitespace("\n"); } - if (loc.equals(Space.Location.BLOCK_OPEN) && !space.getWhitespace().equals(" ")) { + if (loc == Space.Location.BLOCK_OPEN && !space.getWhitespace().equals(" ")) { return space.withWhitespace(" "); } diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/TabsAndIndentsVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/TabsAndIndentsVisitor.java index 8c2be10c242..1b561786f24 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/TabsAndIndentsVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/format/TabsAndIndentsVisitor.java @@ -103,7 +103,7 @@ public Space visitSpace(Space space, Space.Location loc, P p) { IndentType indentType = getCursor().getParentOrThrow().getNearestMessage("indentType", IndentType.ALIGN); // block spaces are always aligned to their parent - boolean alignBlockToParent = loc.equals(Space.Location.BLOCK_CLOSE) || loc.equals(Space.Location.OBJECT_VALUE_ATTRIBUTE_SUFFIX); + boolean alignBlockToParent = loc == Space.Location.BLOCK_CLOSE || loc == Space.Location.OBJECT_VALUE_ATTRIBUTE_SUFFIX; if (alignBlockToParent) { indentType = IndentType.ALIGN; @@ -266,7 +266,7 @@ private Space indentTo(Space space, int column, Space.Location spaceLocation) { space = space.withComments(ListUtils.map(space.getComments(), c -> { // The suffix of the last element in the comment list sets the whitespace for the end of the block. // The column for comments that come before the last element are incremented. - int incrementBy = spaceLocation.equals(Space.Location.BLOCK_CLOSE) && !c.equals(lastElement) ? style.getIndentSize() : 0; + int incrementBy = spaceLocation == Space.Location.BLOCK_CLOSE && !c.equals(lastElement) ? style.getIndentSize() : 0; return c.getStyle() == Comment.Style.INLINE ? indentMultilineComment(c, column + incrementBy) : indentSingleLineComment(c, column + incrementBy); @@ -274,8 +274,8 @@ private Space indentTo(Space space, int column, Space.Location spaceLocation) { // Prevent formatting trailing comments, the whitespace in a trailing comment won't have a new line character. // Compilation unit prefixes are an exception, since they do not exist in a block. - if (space.getWhitespace().contains("\n") || spaceLocation.equals(Space.Location.CONFIG_FILE)) { - int incrementBy = spaceLocation.equals(Space.Location.BLOCK_CLOSE) ? style.getIndentSize() : 0; + if (space.getWhitespace().contains("\n") || spaceLocation == Space.Location.CONFIG_FILE) { + int incrementBy = spaceLocation == Space.Location.BLOCK_CLOSE ? style.getIndentSize() : 0; int indent = getLengthOfWhitespace(space.getWhitespace()); if (indent != (column + incrementBy)) { int shift = column + incrementBy - indent; diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclParserVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclParserVisitor.java index 9974ff15de1..8e538c32cd9 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclParserVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclParserVisitor.java @@ -409,6 +409,25 @@ public Hcl visitIndexAccessExpression(HCLParser.IndexAccessExpressionContext ctx )); } + @Override + public Hcl visitLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx) { + return convert(ctx, (c, prefix) -> { + String valueSource = c.legacyIndexAttr().NumericLiteral().getText(); + Integer value = Integer.parseInt(valueSource); + return new Hcl.LegacyIndexAttributeAccess( + randomId(), + Space.format(prefix), + Markers.EMPTY, + new HclRightPadded<>( + (Expression) visit(c.exprTerm()), + sourceBefore("."), + Markers.EMPTY + ), + new Hcl.Literal(randomId(), Space.format(prefix(c.legacyIndexAttr().NumericLiteral())), Markers.EMPTY, value, valueSource) + ); + }); + } + @Override public Hcl visitLiteralValue(HCLParser.LiteralValueContext ctx) { return convert(ctx, (c, prefix) -> { @@ -444,10 +463,15 @@ public Hcl visitObject(HCLParser.ObjectContext ctx) { Space tuplePrefix = sourceBefore("{"); List> mappedValues = new ArrayList<>(); List values = ctx.objectelem(); - for (int i = 0; i < values.size(); i++) { - HCLParser.ObjectelemContext value = values.get(i); - mappedValues.add(HclRightPadded.build((Expression) visit(value)) - .withAfter(i == values.size() - 1 ? sourceBefore("}") : Space.EMPTY)); + if (values.isEmpty()) { + mappedValues.add( + HclRightPadded.build(new Hcl.Empty(randomId(), sourceBefore("}"), Markers.EMPTY))); + } else { + for (int i = 0; i < values.size(); i++) { + HCLParser.ObjectelemContext value = values.get(i); + mappedValues.add(HclRightPadded.build((Expression) visit(value)) + .withAfter(i == values.size() - 1 ? sourceBefore("}") : Space.EMPTY)); + } } return new Hcl.ObjectValue(randomId(), Space.format(prefix), Markers.EMPTY, @@ -469,7 +493,13 @@ public Hcl visitObjectelem(HCLParser.ObjectelemContext ctx) { if (ctx.LPAREN() != null) { parenthesesPrefix = sourceBefore("("); } - name = visitIdentifier(ctx.Identifier()); + if (ctx.Identifier() != null) { + name = visitIdentifier(ctx.Identifier()); + } else if (ctx.expression(0) != null) { + name = (Expression) visit(ctx.expression(0)); + } else { + throw new IllegalStateException("Unsupported LHS in object element"); + } if (ctx.RPAREN() != null) { name = new Hcl.Parentheses(randomId(), parenthesesPrefix, Markers.EMPTY, HclRightPadded.build(name).withAfter(sourceBefore(")"))); @@ -486,7 +516,7 @@ public Hcl visitObjectelem(HCLParser.ObjectelemContext ctx) { c.ASSIGN() != null ? Hcl.Attribute.Type.Assignment : Hcl.Attribute.Type.ObjectElement, Markers.EMPTY ), - (Expression) visit(c.expression()), + (Expression) visit(c.expression().get(c.expression().size() - 1)), ctx.COMMA() == null ? null : new Hcl.Empty(randomId(), sourceBefore(","), Markers.EMPTY) @@ -731,25 +761,26 @@ private int positionOfNext(String untilDelim, @Nullable Character stop) { int delimIndex = cursor; for (; delimIndex < source.length() - untilDelim.length() + 1; delimIndex++) { - if (inSingleLineComment && source.charAt(delimIndex) == '\n') { - inSingleLineComment = false; + if (inSingleLineComment) { + if (source.charAt(delimIndex) == '\n') { + inSingleLineComment = false; + } } else { if (source.length() - untilDelim.length() > delimIndex + 1) { if ('#' == source.charAt(delimIndex)) { inSingleLineComment = true; - delimIndex++; } else switch (source.substring(delimIndex, delimIndex + 2)) { case "//": inSingleLineComment = true; - delimIndex += 2; + delimIndex += 1; break; case "/*": inMultiLineComment = true; - delimIndex += 2; + delimIndex += 1; break; case "*/": inMultiLineComment = false; - delimIndex += 2; + delimIndex += 1; break; } } diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclPrinter.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclPrinter.java index 76bb09325a4..cd9bc28e7f1 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclPrinter.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/HclPrinter.java @@ -23,6 +23,7 @@ import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; +import java.util.Collections; import java.util.List; import java.util.function.UnaryOperator; @@ -90,7 +91,7 @@ public Hcl visitAttribute(Hcl.Attribute attribute, PrintOutputCapture

p) { beforeSyntax(attribute, Space.Location.ATTRIBUTE, p); visit(attribute.getName(), p); visitSpace(attribute.getPadding().getType().getBefore(), Space.Location.ATTRIBUTE_ASSIGNMENT, p); - p.append(attribute.getType().equals(Hcl.Attribute.Type.Assignment) ? "=" : ":"); + p.append(attribute.getType() == Hcl.Attribute.Type.Assignment ? "=" : ":"); visit(attribute.getValue(), p); if (attribute.getComma() != null) { visitSpace(attribute.getComma().getPrefix(), Space.Location.OBJECT_VALUE_ATTRIBUTE_COMMA, p); @@ -288,6 +289,18 @@ public Hcl visitIndexPosition(Hcl.Index.Position indexPosition, PrintOutputCaptu return indexPosition; } + @Override + public Hcl visitLegacyIndexAttribute(Hcl.LegacyIndexAttributeAccess laccess, PrintOutputCapture

p) { + beforeSyntax(laccess, Space.Location.LEGACY_INDEX_ATTRIBUTE_ACCESS, p); + visitRightPadded( + Collections.singletonList(laccess.getPadding().getBase()), + HclRightPadded.Location.LEGACY_INDEX_ATTRIBUTE_ACCESS_BASE, "", p); + p.append("."); + visitLiteral(laccess.getIndex(), p); + afterSyntax(laccess, p); + return laccess; + } + @Override public Hcl visitLiteral(Hcl.Literal literal, PrintOutputCapture

p) { beforeSyntax(literal, Space.Location.LITERAL, p); diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.interp b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.interp index b210722ef06..6cbc5b58e15 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.interp +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.interp @@ -169,4 +169,4 @@ HEREDOC_PREAMBLE HEREDOC atn: -[4, 0, 47, 446, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 1, 0, 1, 0, 1, 0, 5, 0, 124, 8, 0, 10, 0, 12, 0, 127, 9, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 5, 1, 138, 8, 1, 10, 1, 12, 1, 141, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 3, 7, 165, 8, 7, 1, 8, 1, 8, 1, 8, 5, 8, 170, 8, 8, 10, 8, 12, 8, 173, 9, 8, 1, 9, 4, 9, 176, 8, 9, 11, 9, 12, 9, 177, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 186, 8, 10, 10, 10, 12, 10, 189, 9, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 3, 11, 199, 8, 11, 1, 11, 5, 11, 202, 8, 11, 10, 11, 12, 11, 205, 9, 11, 1, 11, 3, 11, 208, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 3, 13, 220, 8, 13, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 226, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 246, 8, 15, 1, 16, 1, 16, 1, 17, 4, 17, 251, 8, 17, 11, 17, 12, 17, 252, 1, 17, 1, 17, 5, 17, 257, 8, 17, 10, 17, 12, 17, 260, 9, 17, 1, 17, 3, 17, 263, 8, 17, 1, 17, 4, 17, 266, 8, 17, 11, 17, 12, 17, 267, 1, 17, 1, 17, 4, 17, 272, 8, 17, 11, 17, 12, 17, 273, 3, 17, 276, 8, 17, 1, 18, 1, 18, 3, 18, 280, 8, 18, 1, 18, 4, 18, 283, 8, 18, 11, 18, 12, 18, 284, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 296, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 311, 8, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 4, 49, 382, 8, 49, 11, 49, 12, 49, 383, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 396, 8, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 5, 53, 411, 8, 53, 10, 53, 12, 53, 414, 9, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 4, 56, 433, 8, 56, 11, 56, 12, 56, 434, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 445, 8, 57, 1, 187, 0, 58, 4, 1, 6, 2, 8, 3, 10, 4, 12, 5, 14, 6, 16, 7, 18, 0, 20, 8, 22, 9, 24, 10, 26, 11, 28, 12, 30, 0, 32, 0, 34, 0, 36, 0, 38, 13, 40, 0, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 25, 66, 26, 68, 27, 70, 28, 72, 29, 74, 30, 76, 31, 78, 32, 80, 33, 82, 34, 84, 35, 86, 36, 88, 37, 90, 38, 92, 39, 94, 40, 96, 41, 98, 42, 100, 43, 102, 44, 104, 45, 106, 0, 108, 0, 110, 0, 112, 0, 114, 0, 116, 46, 118, 47, 4, 0, 1, 2, 3, 14, 4, 0, 10, 10, 13, 13, 34, 34, 36, 37, 3, 0, 9, 9, 12, 13, 32, 32, 2, 0, 10, 10, 13, 13, 1, 0, 48, 57, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 2, 0, 0, 127, 55296, 56319, 1, 0, 55296, 56319, 1, 0, 56320, 57343, 5, 0, 34, 34, 92, 92, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 3, 0, 10, 10, 13, 13, 36, 37, 1, 0, 123, 123, 476, 0, 4, 1, 0, 0, 0, 0, 6, 1, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 10, 1, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 14, 1, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 78, 1, 0, 0, 0, 0, 80, 1, 0, 0, 0, 0, 82, 1, 0, 0, 0, 0, 84, 1, 0, 0, 0, 0, 86, 1, 0, 0, 0, 0, 88, 1, 0, 0, 0, 0, 90, 1, 0, 0, 0, 0, 92, 1, 0, 0, 0, 0, 94, 1, 0, 0, 0, 0, 96, 1, 0, 0, 0, 0, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 2, 108, 1, 0, 0, 0, 2, 110, 1, 0, 0, 0, 3, 112, 1, 0, 0, 0, 3, 114, 1, 0, 0, 0, 3, 116, 1, 0, 0, 0, 3, 118, 1, 0, 0, 0, 4, 120, 1, 0, 0, 0, 6, 134, 1, 0, 0, 0, 8, 148, 1, 0, 0, 0, 10, 151, 1, 0, 0, 0, 12, 154, 1, 0, 0, 0, 14, 157, 1, 0, 0, 0, 16, 160, 1, 0, 0, 0, 18, 164, 1, 0, 0, 0, 20, 166, 1, 0, 0, 0, 22, 175, 1, 0, 0, 0, 24, 181, 1, 0, 0, 0, 26, 198, 1, 0, 0, 0, 28, 213, 1, 0, 0, 0, 30, 219, 1, 0, 0, 0, 32, 225, 1, 0, 0, 0, 34, 245, 1, 0, 0, 0, 36, 247, 1, 0, 0, 0, 38, 275, 1, 0, 0, 0, 40, 277, 1, 0, 0, 0, 42, 295, 1, 0, 0, 0, 44, 297, 1, 0, 0, 0, 46, 301, 1, 0, 0, 0, 48, 306, 1, 0, 0, 0, 50, 314, 1, 0, 0, 0, 52, 316, 1, 0, 0, 0, 54, 319, 1, 0, 0, 0, 56, 322, 1, 0, 0, 0, 58, 324, 1, 0, 0, 0, 60, 326, 1, 0, 0, 0, 62, 328, 1, 0, 0, 0, 64, 330, 1, 0, 0, 0, 66, 332, 1, 0, 0, 0, 68, 335, 1, 0, 0, 0, 70, 338, 1, 0, 0, 0, 72, 340, 1, 0, 0, 0, 74, 342, 1, 0, 0, 0, 76, 344, 1, 0, 0, 0, 78, 346, 1, 0, 0, 0, 80, 348, 1, 0, 0, 0, 82, 350, 1, 0, 0, 0, 84, 353, 1, 0, 0, 0, 86, 355, 1, 0, 0, 0, 88, 357, 1, 0, 0, 0, 90, 360, 1, 0, 0, 0, 92, 363, 1, 0, 0, 0, 94, 365, 1, 0, 0, 0, 96, 367, 1, 0, 0, 0, 98, 371, 1, 0, 0, 0, 100, 373, 1, 0, 0, 0, 102, 381, 1, 0, 0, 0, 104, 395, 1, 0, 0, 0, 106, 397, 1, 0, 0, 0, 108, 402, 1, 0, 0, 0, 110, 407, 1, 0, 0, 0, 112, 419, 1, 0, 0, 0, 114, 423, 1, 0, 0, 0, 116, 432, 1, 0, 0, 0, 118, 444, 1, 0, 0, 0, 120, 125, 5, 123, 0, 0, 121, 124, 3, 22, 9, 0, 122, 124, 3, 28, 12, 0, 123, 121, 1, 0, 0, 0, 123, 122, 1, 0, 0, 0, 124, 127, 1, 0, 0, 0, 125, 123, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 128, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 128, 129, 5, 102, 0, 0, 129, 130, 5, 111, 0, 0, 130, 131, 5, 114, 0, 0, 131, 132, 1, 0, 0, 0, 132, 133, 3, 22, 9, 0, 133, 5, 1, 0, 0, 0, 134, 139, 5, 91, 0, 0, 135, 138, 3, 22, 9, 0, 136, 138, 3, 28, 12, 0, 137, 135, 1, 0, 0, 0, 137, 136, 1, 0, 0, 0, 138, 141, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 142, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 142, 143, 5, 102, 0, 0, 143, 144, 5, 111, 0, 0, 144, 145, 5, 114, 0, 0, 145, 146, 1, 0, 0, 0, 146, 147, 3, 22, 9, 0, 147, 7, 1, 0, 0, 0, 148, 149, 5, 105, 0, 0, 149, 150, 5, 102, 0, 0, 150, 9, 1, 0, 0, 0, 151, 152, 5, 105, 0, 0, 152, 153, 5, 110, 0, 0, 153, 11, 1, 0, 0, 0, 154, 155, 5, 123, 0, 0, 155, 156, 6, 4, 0, 0, 156, 13, 1, 0, 0, 0, 157, 158, 5, 125, 0, 0, 158, 159, 6, 5, 1, 0, 159, 15, 1, 0, 0, 0, 160, 161, 5, 61, 0, 0, 161, 17, 1, 0, 0, 0, 162, 165, 8, 0, 0, 0, 163, 165, 3, 34, 15, 0, 164, 162, 1, 0, 0, 0, 164, 163, 1, 0, 0, 0, 165, 19, 1, 0, 0, 0, 166, 171, 3, 32, 14, 0, 167, 170, 3, 30, 13, 0, 168, 170, 5, 45, 0, 0, 169, 167, 1, 0, 0, 0, 169, 168, 1, 0, 0, 0, 170, 173, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 171, 172, 1, 0, 0, 0, 172, 21, 1, 0, 0, 0, 173, 171, 1, 0, 0, 0, 174, 176, 7, 1, 0, 0, 175, 174, 1, 0, 0, 0, 176, 177, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 177, 178, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 180, 6, 9, 2, 0, 180, 23, 1, 0, 0, 0, 181, 182, 5, 47, 0, 0, 182, 183, 5, 42, 0, 0, 183, 187, 1, 0, 0, 0, 184, 186, 9, 0, 0, 0, 185, 184, 1, 0, 0, 0, 186, 189, 1, 0, 0, 0, 187, 188, 1, 0, 0, 0, 187, 185, 1, 0, 0, 0, 188, 190, 1, 0, 0, 0, 189, 187, 1, 0, 0, 0, 190, 191, 5, 42, 0, 0, 191, 192, 5, 47, 0, 0, 192, 193, 1, 0, 0, 0, 193, 194, 6, 10, 2, 0, 194, 25, 1, 0, 0, 0, 195, 196, 5, 47, 0, 0, 196, 199, 5, 47, 0, 0, 197, 199, 5, 35, 0, 0, 198, 195, 1, 0, 0, 0, 198, 197, 1, 0, 0, 0, 199, 203, 1, 0, 0, 0, 200, 202, 8, 2, 0, 0, 201, 200, 1, 0, 0, 0, 202, 205, 1, 0, 0, 0, 203, 201, 1, 0, 0, 0, 203, 204, 1, 0, 0, 0, 204, 207, 1, 0, 0, 0, 205, 203, 1, 0, 0, 0, 206, 208, 5, 13, 0, 0, 207, 206, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 209, 1, 0, 0, 0, 209, 210, 5, 10, 0, 0, 210, 211, 1, 0, 0, 0, 211, 212, 6, 11, 2, 0, 212, 27, 1, 0, 0, 0, 213, 214, 5, 10, 0, 0, 214, 215, 1, 0, 0, 0, 215, 216, 6, 12, 2, 0, 216, 29, 1, 0, 0, 0, 217, 220, 3, 32, 14, 0, 218, 220, 7, 3, 0, 0, 219, 217, 1, 0, 0, 0, 219, 218, 1, 0, 0, 0, 220, 31, 1, 0, 0, 0, 221, 226, 7, 4, 0, 0, 222, 226, 8, 5, 0, 0, 223, 224, 7, 6, 0, 0, 224, 226, 7, 7, 0, 0, 225, 221, 1, 0, 0, 0, 225, 222, 1, 0, 0, 0, 225, 223, 1, 0, 0, 0, 226, 33, 1, 0, 0, 0, 227, 228, 5, 92, 0, 0, 228, 246, 7, 8, 0, 0, 229, 230, 5, 92, 0, 0, 230, 231, 3, 36, 16, 0, 231, 232, 3, 36, 16, 0, 232, 233, 3, 36, 16, 0, 233, 234, 3, 36, 16, 0, 234, 246, 1, 0, 0, 0, 235, 236, 5, 92, 0, 0, 236, 237, 3, 36, 16, 0, 237, 238, 3, 36, 16, 0, 238, 239, 3, 36, 16, 0, 239, 240, 3, 36, 16, 0, 240, 241, 3, 36, 16, 0, 241, 242, 3, 36, 16, 0, 242, 243, 3, 36, 16, 0, 243, 244, 3, 36, 16, 0, 244, 246, 1, 0, 0, 0, 245, 227, 1, 0, 0, 0, 245, 229, 1, 0, 0, 0, 245, 235, 1, 0, 0, 0, 246, 35, 1, 0, 0, 0, 247, 248, 7, 9, 0, 0, 248, 37, 1, 0, 0, 0, 249, 251, 7, 3, 0, 0, 250, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 250, 1, 0, 0, 0, 252, 253, 1, 0, 0, 0, 253, 254, 1, 0, 0, 0, 254, 258, 5, 46, 0, 0, 255, 257, 7, 3, 0, 0, 256, 255, 1, 0, 0, 0, 257, 260, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 258, 259, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 261, 263, 3, 40, 18, 0, 262, 261, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 276, 1, 0, 0, 0, 264, 266, 7, 3, 0, 0, 265, 264, 1, 0, 0, 0, 266, 267, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 267, 268, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 276, 3, 40, 18, 0, 270, 272, 7, 3, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 271, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 276, 1, 0, 0, 0, 275, 250, 1, 0, 0, 0, 275, 265, 1, 0, 0, 0, 275, 271, 1, 0, 0, 0, 276, 39, 1, 0, 0, 0, 277, 279, 7, 10, 0, 0, 278, 280, 7, 11, 0, 0, 279, 278, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 283, 7, 3, 0, 0, 282, 281, 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 282, 1, 0, 0, 0, 284, 285, 1, 0, 0, 0, 285, 41, 1, 0, 0, 0, 286, 287, 5, 116, 0, 0, 287, 288, 5, 114, 0, 0, 288, 289, 5, 117, 0, 0, 289, 296, 5, 101, 0, 0, 290, 291, 5, 102, 0, 0, 291, 292, 5, 97, 0, 0, 292, 293, 5, 108, 0, 0, 293, 294, 5, 115, 0, 0, 294, 296, 5, 101, 0, 0, 295, 286, 1, 0, 0, 0, 295, 290, 1, 0, 0, 0, 296, 43, 1, 0, 0, 0, 297, 298, 5, 34, 0, 0, 298, 299, 1, 0, 0, 0, 299, 300, 6, 20, 3, 0, 300, 45, 1, 0, 0, 0, 301, 302, 5, 110, 0, 0, 302, 303, 5, 117, 0, 0, 303, 304, 5, 108, 0, 0, 304, 305, 5, 108, 0, 0, 305, 47, 1, 0, 0, 0, 306, 307, 5, 60, 0, 0, 307, 308, 5, 60, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 5, 45, 0, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 313, 6, 22, 4, 0, 313, 49, 1, 0, 0, 0, 314, 315, 5, 43, 0, 0, 315, 51, 1, 0, 0, 0, 316, 317, 5, 38, 0, 0, 317, 318, 5, 38, 0, 0, 318, 53, 1, 0, 0, 0, 319, 320, 5, 61, 0, 0, 320, 321, 5, 61, 0, 0, 321, 55, 1, 0, 0, 0, 322, 323, 5, 60, 0, 0, 323, 57, 1, 0, 0, 0, 324, 325, 5, 58, 0, 0, 325, 59, 1, 0, 0, 0, 326, 327, 5, 91, 0, 0, 327, 61, 1, 0, 0, 0, 328, 329, 5, 40, 0, 0, 329, 63, 1, 0, 0, 0, 330, 331, 5, 45, 0, 0, 331, 65, 1, 0, 0, 0, 332, 333, 5, 124, 0, 0, 333, 334, 5, 124, 0, 0, 334, 67, 1, 0, 0, 0, 335, 336, 5, 33, 0, 0, 336, 337, 5, 61, 0, 0, 337, 69, 1, 0, 0, 0, 338, 339, 5, 62, 0, 0, 339, 71, 1, 0, 0, 0, 340, 341, 5, 63, 0, 0, 341, 73, 1, 0, 0, 0, 342, 343, 5, 93, 0, 0, 343, 75, 1, 0, 0, 0, 344, 345, 5, 41, 0, 0, 345, 77, 1, 0, 0, 0, 346, 347, 5, 42, 0, 0, 347, 79, 1, 0, 0, 0, 348, 349, 5, 33, 0, 0, 349, 81, 1, 0, 0, 0, 350, 351, 5, 60, 0, 0, 351, 352, 5, 61, 0, 0, 352, 83, 1, 0, 0, 0, 353, 354, 5, 46, 0, 0, 354, 85, 1, 0, 0, 0, 355, 356, 5, 47, 0, 0, 356, 87, 1, 0, 0, 0, 357, 358, 5, 62, 0, 0, 358, 359, 5, 61, 0, 0, 359, 89, 1, 0, 0, 0, 360, 361, 5, 61, 0, 0, 361, 362, 5, 62, 0, 0, 362, 91, 1, 0, 0, 0, 363, 364, 5, 44, 0, 0, 364, 93, 1, 0, 0, 0, 365, 366, 5, 37, 0, 0, 366, 95, 1, 0, 0, 0, 367, 368, 5, 46, 0, 0, 368, 369, 5, 46, 0, 0, 369, 370, 5, 46, 0, 0, 370, 97, 1, 0, 0, 0, 371, 372, 5, 126, 0, 0, 372, 99, 1, 0, 0, 0, 373, 374, 5, 36, 0, 0, 374, 375, 5, 123, 0, 0, 375, 376, 1, 0, 0, 0, 376, 377, 6, 48, 5, 0, 377, 378, 1, 0, 0, 0, 378, 379, 6, 48, 6, 0, 379, 101, 1, 0, 0, 0, 380, 382, 3, 104, 50, 0, 381, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 381, 1, 0, 0, 0, 383, 384, 1, 0, 0, 0, 384, 103, 1, 0, 0, 0, 385, 396, 8, 0, 0, 0, 386, 387, 5, 36, 0, 0, 387, 396, 5, 36, 0, 0, 388, 389, 5, 36, 0, 0, 389, 396, 4, 50, 0, 0, 390, 391, 5, 37, 0, 0, 391, 396, 5, 37, 0, 0, 392, 393, 5, 37, 0, 0, 393, 396, 4, 50, 1, 0, 394, 396, 3, 34, 15, 0, 395, 385, 1, 0, 0, 0, 395, 386, 1, 0, 0, 0, 395, 388, 1, 0, 0, 0, 395, 390, 1, 0, 0, 0, 395, 392, 1, 0, 0, 0, 395, 394, 1, 0, 0, 0, 396, 105, 1, 0, 0, 0, 397, 398, 5, 34, 0, 0, 398, 399, 1, 0, 0, 0, 399, 400, 6, 51, 7, 0, 400, 401, 6, 51, 8, 0, 401, 107, 1, 0, 0, 0, 402, 403, 5, 10, 0, 0, 403, 404, 1, 0, 0, 0, 404, 405, 6, 52, 9, 0, 405, 406, 6, 52, 10, 0, 406, 109, 1, 0, 0, 0, 407, 412, 3, 32, 14, 0, 408, 411, 3, 30, 13, 0, 409, 411, 5, 45, 0, 0, 410, 408, 1, 0, 0, 0, 410, 409, 1, 0, 0, 0, 411, 414, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 412, 413, 1, 0, 0, 0, 413, 415, 1, 0, 0, 0, 414, 412, 1, 0, 0, 0, 415, 416, 6, 53, 11, 0, 416, 417, 1, 0, 0, 0, 417, 418, 6, 53, 12, 0, 418, 111, 1, 0, 0, 0, 419, 420, 5, 10, 0, 0, 420, 421, 1, 0, 0, 0, 421, 422, 6, 54, 9, 0, 422, 113, 1, 0, 0, 0, 423, 424, 5, 36, 0, 0, 424, 425, 5, 123, 0, 0, 425, 426, 1, 0, 0, 0, 426, 427, 6, 55, 13, 0, 427, 428, 1, 0, 0, 0, 428, 429, 6, 55, 14, 0, 429, 430, 6, 55, 6, 0, 430, 115, 1, 0, 0, 0, 431, 433, 3, 118, 57, 0, 432, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 436, 1, 0, 0, 0, 436, 437, 6, 56, 15, 0, 437, 117, 1, 0, 0, 0, 438, 445, 8, 12, 0, 0, 439, 440, 5, 36, 0, 0, 440, 445, 8, 13, 0, 0, 441, 442, 5, 37, 0, 0, 442, 445, 8, 13, 0, 0, 443, 445, 3, 34, 15, 0, 444, 438, 1, 0, 0, 0, 444, 439, 1, 0, 0, 0, 444, 441, 1, 0, 0, 0, 444, 443, 1, 0, 0, 0, 445, 119, 1, 0, 0, 0, 35, 0, 1, 2, 3, 123, 125, 137, 139, 164, 169, 171, 177, 187, 198, 203, 207, 219, 225, 245, 252, 258, 262, 267, 273, 275, 279, 284, 295, 310, 383, 395, 410, 412, 434, 444, 16, 1, 4, 0, 1, 5, 1, 0, 1, 0, 5, 1, 0, 5, 2, 0, 1, 48, 2, 5, 0, 0, 7, 15, 0, 4, 0, 0, 7, 12, 0, 2, 3, 0, 1, 53, 3, 7, 8, 0, 1, 55, 4, 7, 43, 0, 1, 56, 5] \ No newline at end of file +[4, 0, 47, 450, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 126, 8, 0, 10, 0, 12, 0, 129, 9, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 142, 8, 1, 10, 1, 12, 1, 145, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 3, 7, 169, 8, 7, 1, 8, 1, 8, 1, 8, 5, 8, 174, 8, 8, 10, 8, 12, 8, 177, 9, 8, 1, 9, 4, 9, 180, 8, 9, 11, 9, 12, 9, 181, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 190, 8, 10, 10, 10, 12, 10, 193, 9, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 3, 11, 203, 8, 11, 1, 11, 5, 11, 206, 8, 11, 10, 11, 12, 11, 209, 9, 11, 1, 11, 3, 11, 212, 8, 11, 1, 11, 3, 11, 215, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 3, 13, 225, 8, 13, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 231, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 251, 8, 15, 1, 16, 1, 16, 1, 17, 4, 17, 256, 8, 17, 11, 17, 12, 17, 257, 1, 17, 1, 17, 4, 17, 262, 8, 17, 11, 17, 12, 17, 263, 1, 17, 3, 17, 267, 8, 17, 1, 17, 4, 17, 270, 8, 17, 11, 17, 12, 17, 271, 1, 17, 1, 17, 4, 17, 276, 8, 17, 11, 17, 12, 17, 277, 3, 17, 280, 8, 17, 1, 18, 1, 18, 3, 18, 284, 8, 18, 1, 18, 4, 18, 287, 8, 18, 11, 18, 12, 18, 288, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 300, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 315, 8, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 4, 49, 386, 8, 49, 11, 49, 12, 49, 387, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 400, 8, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 5, 53, 415, 8, 53, 10, 53, 12, 53, 418, 9, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 4, 56, 437, 8, 56, 11, 56, 12, 56, 438, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 449, 8, 57, 1, 191, 0, 58, 4, 1, 6, 2, 8, 3, 10, 4, 12, 5, 14, 6, 16, 7, 18, 0, 20, 8, 22, 9, 24, 10, 26, 11, 28, 12, 30, 0, 32, 0, 34, 0, 36, 0, 38, 13, 40, 0, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 25, 66, 26, 68, 27, 70, 28, 72, 29, 74, 30, 76, 31, 78, 32, 80, 33, 82, 34, 84, 35, 86, 36, 88, 37, 90, 38, 92, 39, 94, 40, 96, 41, 98, 42, 100, 43, 102, 44, 104, 45, 106, 0, 108, 0, 110, 0, 112, 0, 114, 0, 116, 46, 118, 47, 4, 0, 1, 2, 3, 15, 4, 0, 10, 10, 13, 13, 34, 34, 36, 37, 3, 0, 9, 9, 12, 13, 32, 32, 2, 0, 10, 10, 13, 13, 1, 1, 10, 10, 1, 0, 48, 57, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 2, 0, 0, 127, 55296, 56319, 1, 0, 55296, 56319, 1, 0, 56320, 57343, 5, 0, 34, 34, 92, 92, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 3, 0, 10, 10, 13, 13, 36, 37, 1, 0, 123, 123, 484, 0, 4, 1, 0, 0, 0, 0, 6, 1, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 10, 1, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 14, 1, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 78, 1, 0, 0, 0, 0, 80, 1, 0, 0, 0, 0, 82, 1, 0, 0, 0, 0, 84, 1, 0, 0, 0, 0, 86, 1, 0, 0, 0, 0, 88, 1, 0, 0, 0, 0, 90, 1, 0, 0, 0, 0, 92, 1, 0, 0, 0, 0, 94, 1, 0, 0, 0, 0, 96, 1, 0, 0, 0, 0, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 2, 108, 1, 0, 0, 0, 2, 110, 1, 0, 0, 0, 3, 112, 1, 0, 0, 0, 3, 114, 1, 0, 0, 0, 3, 116, 1, 0, 0, 0, 3, 118, 1, 0, 0, 0, 4, 120, 1, 0, 0, 0, 6, 136, 1, 0, 0, 0, 8, 152, 1, 0, 0, 0, 10, 155, 1, 0, 0, 0, 12, 158, 1, 0, 0, 0, 14, 161, 1, 0, 0, 0, 16, 164, 1, 0, 0, 0, 18, 168, 1, 0, 0, 0, 20, 170, 1, 0, 0, 0, 22, 179, 1, 0, 0, 0, 24, 185, 1, 0, 0, 0, 26, 202, 1, 0, 0, 0, 28, 218, 1, 0, 0, 0, 30, 224, 1, 0, 0, 0, 32, 230, 1, 0, 0, 0, 34, 250, 1, 0, 0, 0, 36, 252, 1, 0, 0, 0, 38, 279, 1, 0, 0, 0, 40, 281, 1, 0, 0, 0, 42, 299, 1, 0, 0, 0, 44, 301, 1, 0, 0, 0, 46, 305, 1, 0, 0, 0, 48, 310, 1, 0, 0, 0, 50, 318, 1, 0, 0, 0, 52, 320, 1, 0, 0, 0, 54, 323, 1, 0, 0, 0, 56, 326, 1, 0, 0, 0, 58, 328, 1, 0, 0, 0, 60, 330, 1, 0, 0, 0, 62, 332, 1, 0, 0, 0, 64, 334, 1, 0, 0, 0, 66, 336, 1, 0, 0, 0, 68, 339, 1, 0, 0, 0, 70, 342, 1, 0, 0, 0, 72, 344, 1, 0, 0, 0, 74, 346, 1, 0, 0, 0, 76, 348, 1, 0, 0, 0, 78, 350, 1, 0, 0, 0, 80, 352, 1, 0, 0, 0, 82, 354, 1, 0, 0, 0, 84, 357, 1, 0, 0, 0, 86, 359, 1, 0, 0, 0, 88, 361, 1, 0, 0, 0, 90, 364, 1, 0, 0, 0, 92, 367, 1, 0, 0, 0, 94, 369, 1, 0, 0, 0, 96, 371, 1, 0, 0, 0, 98, 375, 1, 0, 0, 0, 100, 377, 1, 0, 0, 0, 102, 385, 1, 0, 0, 0, 104, 399, 1, 0, 0, 0, 106, 401, 1, 0, 0, 0, 108, 406, 1, 0, 0, 0, 110, 411, 1, 0, 0, 0, 112, 423, 1, 0, 0, 0, 114, 427, 1, 0, 0, 0, 116, 436, 1, 0, 0, 0, 118, 448, 1, 0, 0, 0, 120, 127, 5, 123, 0, 0, 121, 126, 3, 22, 9, 0, 122, 126, 3, 28, 12, 0, 123, 126, 3, 24, 10, 0, 124, 126, 3, 26, 11, 0, 125, 121, 1, 0, 0, 0, 125, 122, 1, 0, 0, 0, 125, 123, 1, 0, 0, 0, 125, 124, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 130, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 130, 131, 5, 102, 0, 0, 131, 132, 5, 111, 0, 0, 132, 133, 5, 114, 0, 0, 133, 134, 1, 0, 0, 0, 134, 135, 3, 22, 9, 0, 135, 5, 1, 0, 0, 0, 136, 143, 5, 91, 0, 0, 137, 142, 3, 22, 9, 0, 138, 142, 3, 28, 12, 0, 139, 142, 3, 24, 10, 0, 140, 142, 3, 26, 11, 0, 141, 137, 1, 0, 0, 0, 141, 138, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 141, 140, 1, 0, 0, 0, 142, 145, 1, 0, 0, 0, 143, 141, 1, 0, 0, 0, 143, 144, 1, 0, 0, 0, 144, 146, 1, 0, 0, 0, 145, 143, 1, 0, 0, 0, 146, 147, 5, 102, 0, 0, 147, 148, 5, 111, 0, 0, 148, 149, 5, 114, 0, 0, 149, 150, 1, 0, 0, 0, 150, 151, 3, 22, 9, 0, 151, 7, 1, 0, 0, 0, 152, 153, 5, 105, 0, 0, 153, 154, 5, 102, 0, 0, 154, 9, 1, 0, 0, 0, 155, 156, 5, 105, 0, 0, 156, 157, 5, 110, 0, 0, 157, 11, 1, 0, 0, 0, 158, 159, 5, 123, 0, 0, 159, 160, 6, 4, 0, 0, 160, 13, 1, 0, 0, 0, 161, 162, 5, 125, 0, 0, 162, 163, 6, 5, 1, 0, 163, 15, 1, 0, 0, 0, 164, 165, 5, 61, 0, 0, 165, 17, 1, 0, 0, 0, 166, 169, 8, 0, 0, 0, 167, 169, 3, 34, 15, 0, 168, 166, 1, 0, 0, 0, 168, 167, 1, 0, 0, 0, 169, 19, 1, 0, 0, 0, 170, 175, 3, 32, 14, 0, 171, 174, 3, 30, 13, 0, 172, 174, 5, 45, 0, 0, 173, 171, 1, 0, 0, 0, 173, 172, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 21, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 180, 7, 1, 0, 0, 179, 178, 1, 0, 0, 0, 180, 181, 1, 0, 0, 0, 181, 179, 1, 0, 0, 0, 181, 182, 1, 0, 0, 0, 182, 183, 1, 0, 0, 0, 183, 184, 6, 9, 2, 0, 184, 23, 1, 0, 0, 0, 185, 186, 5, 47, 0, 0, 186, 187, 5, 42, 0, 0, 187, 191, 1, 0, 0, 0, 188, 190, 9, 0, 0, 0, 189, 188, 1, 0, 0, 0, 190, 193, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 191, 189, 1, 0, 0, 0, 192, 194, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 194, 195, 5, 42, 0, 0, 195, 196, 5, 47, 0, 0, 196, 197, 1, 0, 0, 0, 197, 198, 6, 10, 2, 0, 198, 25, 1, 0, 0, 0, 199, 200, 5, 47, 0, 0, 200, 203, 5, 47, 0, 0, 201, 203, 5, 35, 0, 0, 202, 199, 1, 0, 0, 0, 202, 201, 1, 0, 0, 0, 203, 207, 1, 0, 0, 0, 204, 206, 8, 2, 0, 0, 205, 204, 1, 0, 0, 0, 206, 209, 1, 0, 0, 0, 207, 205, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 211, 1, 0, 0, 0, 209, 207, 1, 0, 0, 0, 210, 212, 5, 13, 0, 0, 211, 210, 1, 0, 0, 0, 211, 212, 1, 0, 0, 0, 212, 214, 1, 0, 0, 0, 213, 215, 7, 3, 0, 0, 214, 213, 1, 0, 0, 0, 215, 216, 1, 0, 0, 0, 216, 217, 6, 11, 2, 0, 217, 27, 1, 0, 0, 0, 218, 219, 5, 10, 0, 0, 219, 220, 1, 0, 0, 0, 220, 221, 6, 12, 2, 0, 221, 29, 1, 0, 0, 0, 222, 225, 3, 32, 14, 0, 223, 225, 7, 4, 0, 0, 224, 222, 1, 0, 0, 0, 224, 223, 1, 0, 0, 0, 225, 31, 1, 0, 0, 0, 226, 231, 7, 5, 0, 0, 227, 231, 8, 6, 0, 0, 228, 229, 7, 7, 0, 0, 229, 231, 7, 8, 0, 0, 230, 226, 1, 0, 0, 0, 230, 227, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 231, 33, 1, 0, 0, 0, 232, 233, 5, 92, 0, 0, 233, 251, 7, 9, 0, 0, 234, 235, 5, 92, 0, 0, 235, 236, 3, 36, 16, 0, 236, 237, 3, 36, 16, 0, 237, 238, 3, 36, 16, 0, 238, 239, 3, 36, 16, 0, 239, 251, 1, 0, 0, 0, 240, 241, 5, 92, 0, 0, 241, 242, 3, 36, 16, 0, 242, 243, 3, 36, 16, 0, 243, 244, 3, 36, 16, 0, 244, 245, 3, 36, 16, 0, 245, 246, 3, 36, 16, 0, 246, 247, 3, 36, 16, 0, 247, 248, 3, 36, 16, 0, 248, 249, 3, 36, 16, 0, 249, 251, 1, 0, 0, 0, 250, 232, 1, 0, 0, 0, 250, 234, 1, 0, 0, 0, 250, 240, 1, 0, 0, 0, 251, 35, 1, 0, 0, 0, 252, 253, 7, 10, 0, 0, 253, 37, 1, 0, 0, 0, 254, 256, 7, 4, 0, 0, 255, 254, 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 259, 1, 0, 0, 0, 259, 261, 5, 46, 0, 0, 260, 262, 7, 4, 0, 0, 261, 260, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 261, 1, 0, 0, 0, 263, 264, 1, 0, 0, 0, 264, 266, 1, 0, 0, 0, 265, 267, 3, 40, 18, 0, 266, 265, 1, 0, 0, 0, 266, 267, 1, 0, 0, 0, 267, 280, 1, 0, 0, 0, 268, 270, 7, 4, 0, 0, 269, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 272, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 280, 3, 40, 18, 0, 274, 276, 7, 4, 0, 0, 275, 274, 1, 0, 0, 0, 276, 277, 1, 0, 0, 0, 277, 275, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 280, 1, 0, 0, 0, 279, 255, 1, 0, 0, 0, 279, 269, 1, 0, 0, 0, 279, 275, 1, 0, 0, 0, 280, 39, 1, 0, 0, 0, 281, 283, 7, 11, 0, 0, 282, 284, 7, 12, 0, 0, 283, 282, 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 286, 1, 0, 0, 0, 285, 287, 7, 4, 0, 0, 286, 285, 1, 0, 0, 0, 287, 288, 1, 0, 0, 0, 288, 286, 1, 0, 0, 0, 288, 289, 1, 0, 0, 0, 289, 41, 1, 0, 0, 0, 290, 291, 5, 116, 0, 0, 291, 292, 5, 114, 0, 0, 292, 293, 5, 117, 0, 0, 293, 300, 5, 101, 0, 0, 294, 295, 5, 102, 0, 0, 295, 296, 5, 97, 0, 0, 296, 297, 5, 108, 0, 0, 297, 298, 5, 115, 0, 0, 298, 300, 5, 101, 0, 0, 299, 290, 1, 0, 0, 0, 299, 294, 1, 0, 0, 0, 300, 43, 1, 0, 0, 0, 301, 302, 5, 34, 0, 0, 302, 303, 1, 0, 0, 0, 303, 304, 6, 20, 3, 0, 304, 45, 1, 0, 0, 0, 305, 306, 5, 110, 0, 0, 306, 307, 5, 117, 0, 0, 307, 308, 5, 108, 0, 0, 308, 309, 5, 108, 0, 0, 309, 47, 1, 0, 0, 0, 310, 311, 5, 60, 0, 0, 311, 312, 5, 60, 0, 0, 312, 314, 1, 0, 0, 0, 313, 315, 5, 45, 0, 0, 314, 313, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 316, 317, 6, 22, 4, 0, 317, 49, 1, 0, 0, 0, 318, 319, 5, 43, 0, 0, 319, 51, 1, 0, 0, 0, 320, 321, 5, 38, 0, 0, 321, 322, 5, 38, 0, 0, 322, 53, 1, 0, 0, 0, 323, 324, 5, 61, 0, 0, 324, 325, 5, 61, 0, 0, 325, 55, 1, 0, 0, 0, 326, 327, 5, 60, 0, 0, 327, 57, 1, 0, 0, 0, 328, 329, 5, 58, 0, 0, 329, 59, 1, 0, 0, 0, 330, 331, 5, 91, 0, 0, 331, 61, 1, 0, 0, 0, 332, 333, 5, 40, 0, 0, 333, 63, 1, 0, 0, 0, 334, 335, 5, 45, 0, 0, 335, 65, 1, 0, 0, 0, 336, 337, 5, 124, 0, 0, 337, 338, 5, 124, 0, 0, 338, 67, 1, 0, 0, 0, 339, 340, 5, 33, 0, 0, 340, 341, 5, 61, 0, 0, 341, 69, 1, 0, 0, 0, 342, 343, 5, 62, 0, 0, 343, 71, 1, 0, 0, 0, 344, 345, 5, 63, 0, 0, 345, 73, 1, 0, 0, 0, 346, 347, 5, 93, 0, 0, 347, 75, 1, 0, 0, 0, 348, 349, 5, 41, 0, 0, 349, 77, 1, 0, 0, 0, 350, 351, 5, 42, 0, 0, 351, 79, 1, 0, 0, 0, 352, 353, 5, 33, 0, 0, 353, 81, 1, 0, 0, 0, 354, 355, 5, 60, 0, 0, 355, 356, 5, 61, 0, 0, 356, 83, 1, 0, 0, 0, 357, 358, 5, 46, 0, 0, 358, 85, 1, 0, 0, 0, 359, 360, 5, 47, 0, 0, 360, 87, 1, 0, 0, 0, 361, 362, 5, 62, 0, 0, 362, 363, 5, 61, 0, 0, 363, 89, 1, 0, 0, 0, 364, 365, 5, 61, 0, 0, 365, 366, 5, 62, 0, 0, 366, 91, 1, 0, 0, 0, 367, 368, 5, 44, 0, 0, 368, 93, 1, 0, 0, 0, 369, 370, 5, 37, 0, 0, 370, 95, 1, 0, 0, 0, 371, 372, 5, 46, 0, 0, 372, 373, 5, 46, 0, 0, 373, 374, 5, 46, 0, 0, 374, 97, 1, 0, 0, 0, 375, 376, 5, 126, 0, 0, 376, 99, 1, 0, 0, 0, 377, 378, 5, 36, 0, 0, 378, 379, 5, 123, 0, 0, 379, 380, 1, 0, 0, 0, 380, 381, 6, 48, 5, 0, 381, 382, 1, 0, 0, 0, 382, 383, 6, 48, 6, 0, 383, 101, 1, 0, 0, 0, 384, 386, 3, 104, 50, 0, 385, 384, 1, 0, 0, 0, 386, 387, 1, 0, 0, 0, 387, 385, 1, 0, 0, 0, 387, 388, 1, 0, 0, 0, 388, 103, 1, 0, 0, 0, 389, 400, 8, 0, 0, 0, 390, 391, 5, 36, 0, 0, 391, 400, 5, 36, 0, 0, 392, 393, 5, 36, 0, 0, 393, 400, 4, 50, 0, 0, 394, 395, 5, 37, 0, 0, 395, 400, 5, 37, 0, 0, 396, 397, 5, 37, 0, 0, 397, 400, 4, 50, 1, 0, 398, 400, 3, 34, 15, 0, 399, 389, 1, 0, 0, 0, 399, 390, 1, 0, 0, 0, 399, 392, 1, 0, 0, 0, 399, 394, 1, 0, 0, 0, 399, 396, 1, 0, 0, 0, 399, 398, 1, 0, 0, 0, 400, 105, 1, 0, 0, 0, 401, 402, 5, 34, 0, 0, 402, 403, 1, 0, 0, 0, 403, 404, 6, 51, 7, 0, 404, 405, 6, 51, 8, 0, 405, 107, 1, 0, 0, 0, 406, 407, 5, 10, 0, 0, 407, 408, 1, 0, 0, 0, 408, 409, 6, 52, 9, 0, 409, 410, 6, 52, 10, 0, 410, 109, 1, 0, 0, 0, 411, 416, 3, 32, 14, 0, 412, 415, 3, 30, 13, 0, 413, 415, 5, 45, 0, 0, 414, 412, 1, 0, 0, 0, 414, 413, 1, 0, 0, 0, 415, 418, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 419, 1, 0, 0, 0, 418, 416, 1, 0, 0, 0, 419, 420, 6, 53, 11, 0, 420, 421, 1, 0, 0, 0, 421, 422, 6, 53, 12, 0, 422, 111, 1, 0, 0, 0, 423, 424, 5, 10, 0, 0, 424, 425, 1, 0, 0, 0, 425, 426, 6, 54, 9, 0, 426, 113, 1, 0, 0, 0, 427, 428, 5, 36, 0, 0, 428, 429, 5, 123, 0, 0, 429, 430, 1, 0, 0, 0, 430, 431, 6, 55, 13, 0, 431, 432, 1, 0, 0, 0, 432, 433, 6, 55, 14, 0, 433, 434, 6, 55, 6, 0, 434, 115, 1, 0, 0, 0, 435, 437, 3, 118, 57, 0, 436, 435, 1, 0, 0, 0, 437, 438, 1, 0, 0, 0, 438, 436, 1, 0, 0, 0, 438, 439, 1, 0, 0, 0, 439, 440, 1, 0, 0, 0, 440, 441, 6, 56, 15, 0, 441, 117, 1, 0, 0, 0, 442, 449, 8, 13, 0, 0, 443, 444, 5, 36, 0, 0, 444, 449, 8, 14, 0, 0, 445, 446, 5, 37, 0, 0, 446, 449, 8, 14, 0, 0, 447, 449, 3, 34, 15, 0, 448, 442, 1, 0, 0, 0, 448, 443, 1, 0, 0, 0, 448, 445, 1, 0, 0, 0, 448, 447, 1, 0, 0, 0, 449, 119, 1, 0, 0, 0, 36, 0, 1, 2, 3, 125, 127, 141, 143, 168, 173, 175, 181, 191, 202, 207, 211, 214, 224, 230, 250, 257, 263, 266, 271, 277, 279, 283, 288, 299, 314, 387, 399, 414, 416, 438, 448, 16, 1, 4, 0, 1, 5, 1, 0, 1, 0, 5, 1, 0, 5, 2, 0, 1, 48, 2, 5, 0, 0, 7, 15, 0, 4, 0, 0, 7, 12, 0, 2, 3, 0, 1, 53, 3, 7, 8, 0, 1, 55, 4, 7, 43, 0, 1, 56, 5] \ No newline at end of file diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.java index 31598f56b7a..30ec52013ec 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLLexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,15 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.hcl.internal.grammar; - +import java.util.Stack; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; - -import java.util.Stack; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class HCLLexer extends Lexer { @@ -33,12 +33,12 @@ public class HCLLexer extends Lexer { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - FOR_BRACE=1, FOR_BRACK=2, IF=3, IN=4, LBRACE=5, RBRACE=6, ASSIGN=7, Identifier=8, - WS=9, COMMENT=10, LINE_COMMENT=11, NEWLINE=12, NumericLiteral=13, BooleanLiteral=14, - QUOTE=15, NULL=16, HEREDOC_START=17, PLUS=18, AND=19, EQ=20, LT=21, COLON=22, - LBRACK=23, LPAREN=24, MINUS=25, OR=26, NEQ=27, GT=28, QUESTION=29, RBRACK=30, - RPAREN=31, MUL=32, NOT=33, LEQ=34, DOT=35, DIV=36, GEQ=37, ARROW=38, COMMA=39, - MOD=40, ELLIPSIS=41, TILDE=42, TEMPLATE_INTERPOLATION_START=43, TemplateStringLiteral=44, + FOR_BRACE=1, FOR_BRACK=2, IF=3, IN=4, LBRACE=5, RBRACE=6, ASSIGN=7, Identifier=8, + WS=9, COMMENT=10, LINE_COMMENT=11, NEWLINE=12, NumericLiteral=13, BooleanLiteral=14, + QUOTE=15, NULL=16, HEREDOC_START=17, PLUS=18, AND=19, EQ=20, LT=21, COLON=22, + LBRACK=23, LPAREN=24, MINUS=25, OR=26, NEQ=27, GT=28, QUESTION=29, RBRACK=30, + RPAREN=31, MUL=32, NOT=33, LEQ=34, DOT=35, DIV=36, GEQ=37, ARROW=38, COMMA=39, + MOD=40, ELLIPSIS=41, TILDE=42, TEMPLATE_INTERPOLATION_START=43, TemplateStringLiteral=44, TemplateStringLiteralChar=45, HTemplateLiteral=46, HTemplateLiteralChar=47; public static final int TEMPLATE=1, HEREDOC_PREAMBLE=2, HEREDOC=3; @@ -52,15 +52,15 @@ public class HCLLexer extends Lexer { private static String[] makeRuleNames() { return new String[] { - "FOR_BRACE", "FOR_BRACK", "IF", "IN", "LBRACE", "RBRACE", "ASSIGN", "StringLiteralChar", - "Identifier", "WS", "COMMENT", "LINE_COMMENT", "NEWLINE", "LetterOrDigit", - "Letter", "EscapeSequence", "HexDigit", "NumericLiteral", "ExponentPart", - "BooleanLiteral", "QUOTE", "NULL", "HEREDOC_START", "PLUS", "AND", "EQ", - "LT", "COLON", "LBRACK", "LPAREN", "MINUS", "OR", "NEQ", "GT", "QUESTION", - "RBRACK", "RPAREN", "MUL", "NOT", "LEQ", "DOT", "DIV", "GEQ", "ARROW", - "COMMA", "MOD", "ELLIPSIS", "TILDE", "TEMPLATE_INTERPOLATION_START", - "TemplateStringLiteral", "TemplateStringLiteralChar", "END_QUOTE", "HP_NEWLINE", - "HPIdentifier", "H_NEWLINE", "H_TEMPLATE_INTERPOLATION_START", "HTemplateLiteral", + "FOR_BRACE", "FOR_BRACK", "IF", "IN", "LBRACE", "RBRACE", "ASSIGN", "StringLiteralChar", + "Identifier", "WS", "COMMENT", "LINE_COMMENT", "NEWLINE", "LetterOrDigit", + "Letter", "EscapeSequence", "HexDigit", "NumericLiteral", "ExponentPart", + "BooleanLiteral", "QUOTE", "NULL", "HEREDOC_START", "PLUS", "AND", "EQ", + "LT", "COLON", "LBRACK", "LPAREN", "MINUS", "OR", "NEQ", "GT", "QUESTION", + "RBRACK", "RPAREN", "MUL", "NOT", "LEQ", "DOT", "DIV", "GEQ", "ARROW", + "COMMA", "MOD", "ELLIPSIS", "TILDE", "TEMPLATE_INTERPOLATION_START", + "TemplateStringLiteral", "TemplateStringLiteralChar", "END_QUOTE", "HP_NEWLINE", + "HPIdentifier", "H_NEWLINE", "H_TEMPLATE_INTERPOLATION_START", "HTemplateLiteral", "HTemplateLiteralChar" }; } @@ -68,23 +68,23 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, null, null, "'if'", "'in'", "'{'", "'}'", "'='", null, null, null, - null, null, null, null, null, "'null'", null, "'+'", "'&&'", "'=='", - "'<'", "':'", "'['", "'('", "'-'", "'||'", "'!='", "'>'", "'?'", "']'", - "')'", "'*'", "'!'", "'<='", "'.'", "'/'", "'>='", "'=>'", "','", "'%'", + null, null, null, "'if'", "'in'", "'{'", "'}'", "'='", null, null, null, + null, null, null, null, null, "'null'", null, "'+'", "'&&'", "'=='", + "'<'", "':'", "'['", "'('", "'-'", "'||'", "'!='", "'>'", "'?'", "']'", + "')'", "'*'", "'!'", "'<='", "'.'", "'/'", "'>='", "'=>'", "','", "'%'", "'...'", "'~'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "FOR_BRACE", "FOR_BRACK", "IF", "IN", "LBRACE", "RBRACE", "ASSIGN", - "Identifier", "WS", "COMMENT", "LINE_COMMENT", "NEWLINE", "NumericLiteral", - "BooleanLiteral", "QUOTE", "NULL", "HEREDOC_START", "PLUS", "AND", "EQ", - "LT", "COLON", "LBRACK", "LPAREN", "MINUS", "OR", "NEQ", "GT", "QUESTION", - "RBRACK", "RPAREN", "MUL", "NOT", "LEQ", "DOT", "DIV", "GEQ", "ARROW", - "COMMA", "MOD", "ELLIPSIS", "TILDE", "TEMPLATE_INTERPOLATION_START", - "TemplateStringLiteral", "TemplateStringLiteralChar", "HTemplateLiteral", + null, "FOR_BRACE", "FOR_BRACK", "IF", "IN", "LBRACE", "RBRACE", "ASSIGN", + "Identifier", "WS", "COMMENT", "LINE_COMMENT", "NEWLINE", "NumericLiteral", + "BooleanLiteral", "QUOTE", "NULL", "HEREDOC_START", "PLUS", "AND", "EQ", + "LT", "COLON", "LBRACK", "LPAREN", "MINUS", "OR", "NEQ", "GT", "QUESTION", + "RBRACK", "RPAREN", "MUL", "NOT", "LEQ", "DOT", "DIV", "GEQ", "ARROW", + "COMMA", "MOD", "ELLIPSIS", "TILDE", "TEMPLATE_INTERPOLATION_START", + "TemplateStringLiteral", "TemplateStringLiteralChar", "HTemplateLiteral", "HTemplateLiteralChar" }; } @@ -257,7 +257,7 @@ private boolean TemplateStringLiteralChar_sempred(RuleContext _localctx, int pre } public static final String _serializedATN = - "\u0004\u0000/\u01be\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ + "\u0004\u0000/\u01c2\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ "\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ @@ -274,62 +274,63 @@ private boolean TemplateStringLiteralChar_sempred(RuleContext _localctx, int pre "-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u0002"+ "2\u00072\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u0002"+ "7\u00077\u00028\u00078\u00029\u00079\u0001\u0000\u0001\u0000\u0001\u0000"+ - "\u0005\u0000|\b\u0000\n\u0000\f\u0000\u007f\t\u0000\u0001\u0000\u0001"+ - "\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0001\u0001"+ - "\u0001\u0001\u0001\u0005\u0001\u008a\b\u0001\n\u0001\f\u0001\u008d\t\u0001"+ - "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ - "\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003\u0001\u0003"+ - "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005"+ - "\u0001\u0006\u0001\u0006\u0001\u0007\u0001\u0007\u0003\u0007\u00a5\b\u0007"+ - "\u0001\b\u0001\b\u0001\b\u0005\b\u00aa\b\b\n\b\f\b\u00ad\t\b\u0001\t\u0004"+ - "\t\u00b0\b\t\u000b\t\f\t\u00b1\u0001\t\u0001\t\u0001\n\u0001\n\u0001\n"+ - "\u0001\n\u0005\n\u00ba\b\n\n\n\f\n\u00bd\t\n\u0001\n\u0001\n\u0001\n\u0001"+ - "\n\u0001\n\u0001\u000b\u0001\u000b\u0001\u000b\u0003\u000b\u00c7\b\u000b"+ - "\u0001\u000b\u0005\u000b\u00ca\b\u000b\n\u000b\f\u000b\u00cd\t\u000b\u0001"+ - "\u000b\u0003\u000b\u00d0\b\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001"+ - "\u000b\u0001\f\u0001\f\u0001\f\u0001\f\u0001\r\u0001\r\u0003\r\u00dc\b"+ - "\r\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0003\u000e\u00e2\b"+ - "\u000e\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001"+ + "\u0001\u0000\u0001\u0000\u0005\u0000~\b\u0000\n\u0000\f\u0000\u0081\t"+ + "\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+ + "\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0005"+ + "\u0001\u008e\b\u0001\n\u0001\f\u0001\u0091\t\u0001\u0001\u0001\u0001\u0001"+ + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002"+ + "\u0001\u0002\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004"+ + "\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0006\u0001\u0006"+ + "\u0001\u0007\u0001\u0007\u0003\u0007\u00a9\b\u0007\u0001\b\u0001\b\u0001"+ + "\b\u0005\b\u00ae\b\b\n\b\f\b\u00b1\t\b\u0001\t\u0004\t\u00b4\b\t\u000b"+ + "\t\f\t\u00b5\u0001\t\u0001\t\u0001\n\u0001\n\u0001\n\u0001\n\u0005\n\u00be"+ + "\b\n\n\n\f\n\u00c1\t\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\u000b"+ + "\u0001\u000b\u0001\u000b\u0003\u000b\u00cb\b\u000b\u0001\u000b\u0005\u000b"+ + "\u00ce\b\u000b\n\u000b\f\u000b\u00d1\t\u000b\u0001\u000b\u0003\u000b\u00d4"+ + "\b\u000b\u0001\u000b\u0003\u000b\u00d7\b\u000b\u0001\u000b\u0001\u000b"+ + "\u0001\f\u0001\f\u0001\f\u0001\f\u0001\r\u0001\r\u0003\r\u00e1\b\r\u0001"+ + "\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0003\u000e\u00e7\b\u000e\u0001"+ "\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001"+ "\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001"+ - "\u000f\u0003\u000f\u00f6\b\u000f\u0001\u0010\u0001\u0010\u0001\u0011\u0004"+ - "\u0011\u00fb\b\u0011\u000b\u0011\f\u0011\u00fc\u0001\u0011\u0001\u0011"+ - "\u0005\u0011\u0101\b\u0011\n\u0011\f\u0011\u0104\t\u0011\u0001\u0011\u0003"+ - "\u0011\u0107\b\u0011\u0001\u0011\u0004\u0011\u010a\b\u0011\u000b\u0011"+ - "\f\u0011\u010b\u0001\u0011\u0001\u0011\u0004\u0011\u0110\b\u0011\u000b"+ - "\u0011\f\u0011\u0111\u0003\u0011\u0114\b\u0011\u0001\u0012\u0001\u0012"+ - "\u0003\u0012\u0118\b\u0012\u0001\u0012\u0004\u0012\u011b\b\u0012\u000b"+ - "\u0012\f\u0012\u011c\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001"+ - "\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0003\u0013\u0128"+ - "\b\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0015\u0001"+ - "\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0016\u0001\u0016\u0001"+ - "\u0016\u0001\u0016\u0003\u0016\u0137\b\u0016\u0001\u0016\u0001\u0016\u0001"+ - "\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0019\u0001"+ - "\u0019\u0001\u0019\u0001\u001a\u0001\u001a\u0001\u001b\u0001\u001b\u0001"+ - "\u001c\u0001\u001c\u0001\u001d\u0001\u001d\u0001\u001e\u0001\u001e\u0001"+ - "\u001f\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001 \u0001!\u0001!\u0001"+ - "\"\u0001\"\u0001#\u0001#\u0001$\u0001$\u0001%\u0001%\u0001&\u0001&\u0001"+ - "\'\u0001\'\u0001\'\u0001(\u0001(\u0001)\u0001)\u0001*\u0001*\u0001*\u0001"+ - "+\u0001+\u0001+\u0001,\u0001,\u0001-\u0001-\u0001.\u0001.\u0001.\u0001"+ - ".\u0001/\u0001/\u00010\u00010\u00010\u00010\u00010\u00010\u00010\u0001"+ - "1\u00041\u017e\b1\u000b1\f1\u017f\u00012\u00012\u00012\u00012\u00012\u0001"+ - "2\u00012\u00012\u00012\u00012\u00032\u018c\b2\u00013\u00013\u00013\u0001"+ - "3\u00013\u00014\u00014\u00014\u00014\u00014\u00015\u00015\u00015\u0005"+ - "5\u019b\b5\n5\f5\u019e\t5\u00015\u00015\u00015\u00015\u00016\u00016\u0001"+ - "6\u00016\u00017\u00017\u00017\u00017\u00017\u00017\u00017\u00017\u0001"+ - "8\u00048\u01b1\b8\u000b8\f8\u01b2\u00018\u00018\u00019\u00019\u00019\u0001"+ - "9\u00019\u00019\u00039\u01bd\b9\u0001\u00bb\u0000:\u0004\u0001\u0006\u0002"+ - "\b\u0003\n\u0004\f\u0005\u000e\u0006\u0010\u0007\u0012\u0000\u0014\b\u0016"+ - "\t\u0018\n\u001a\u000b\u001c\f\u001e\u0000 \u0000\"\u0000$\u0000&\r(\u0000"+ - "*\u000e,\u000f.\u00100\u00112\u00124\u00136\u00148\u0015:\u0016<\u0017"+ - ">\u0018@\u0019B\u001aD\u001bF\u001cH\u001dJ\u001eL\u001fN P!R\"T#V$X%"+ - "Z&\\\'^(`)b*d+f,h-j\u0000l\u0000n\u0000p\u0000r\u0000t.v/\u0004\u0000"+ - "\u0001\u0002\u0003\u000e\u0004\u0000\n\n\r\r\"\"$%\u0003\u0000\t\t\f\r"+ - " \u0002\u0000\n\n\r\r\u0001\u000009\u0004\u0000$$AZ__az\u0002\u0000\u0000"+ - "\u007f\u8000\ud800\u8000\udbff\u0001\u0000\u8000\ud800\u8000\udbff\u0001"+ - "\u0000\u8000\udc00\u8000\udfff\u0005\u0000\"\"\\\\nnrrtt\u0003\u00000"+ - "9AFaf\u0002\u0000EEee\u0002\u0000++--\u0003\u0000\n\n\r\r$%\u0001\u0000"+ - "{{\u01dc\u0000\u0004\u0001\u0000\u0000\u0000\u0000\u0006\u0001\u0000\u0000"+ + "\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0003"+ + "\u000f\u00fb\b\u000f\u0001\u0010\u0001\u0010\u0001\u0011\u0004\u0011\u0100"+ + "\b\u0011\u000b\u0011\f\u0011\u0101\u0001\u0011\u0001\u0011\u0004\u0011"+ + "\u0106\b\u0011\u000b\u0011\f\u0011\u0107\u0001\u0011\u0003\u0011\u010b"+ + "\b\u0011\u0001\u0011\u0004\u0011\u010e\b\u0011\u000b\u0011\f\u0011\u010f"+ + "\u0001\u0011\u0001\u0011\u0004\u0011\u0114\b\u0011\u000b\u0011\f\u0011"+ + "\u0115\u0003\u0011\u0118\b\u0011\u0001\u0012\u0001\u0012\u0003\u0012\u011c"+ + "\b\u0012\u0001\u0012\u0004\u0012\u011f\b\u0012\u000b\u0012\f\u0012\u0120"+ + "\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013"+ + "\u0001\u0013\u0001\u0013\u0001\u0013\u0003\u0013\u012c\b\u0013\u0001\u0014"+ + "\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0015\u0001\u0015\u0001\u0015"+ + "\u0001\u0015\u0001\u0015\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016"+ + "\u0003\u0016\u013b\b\u0016\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017"+ + "\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0001\u0019"+ + "\u0001\u001a\u0001\u001a\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c"+ + "\u0001\u001d\u0001\u001d\u0001\u001e\u0001\u001e\u0001\u001f\u0001\u001f"+ + "\u0001\u001f\u0001 \u0001 \u0001 \u0001!\u0001!\u0001\"\u0001\"\u0001"+ + "#\u0001#\u0001$\u0001$\u0001%\u0001%\u0001&\u0001&\u0001\'\u0001\'\u0001"+ + "\'\u0001(\u0001(\u0001)\u0001)\u0001*\u0001*\u0001*\u0001+\u0001+\u0001"+ + "+\u0001,\u0001,\u0001-\u0001-\u0001.\u0001.\u0001.\u0001.\u0001/\u0001"+ + "/\u00010\u00010\u00010\u00010\u00010\u00010\u00010\u00011\u00041\u0182"+ + "\b1\u000b1\f1\u0183\u00012\u00012\u00012\u00012\u00012\u00012\u00012\u0001"+ + "2\u00012\u00012\u00032\u0190\b2\u00013\u00013\u00013\u00013\u00013\u0001"+ + "4\u00014\u00014\u00014\u00014\u00015\u00015\u00015\u00055\u019f\b5\n5"+ + "\f5\u01a2\t5\u00015\u00015\u00015\u00015\u00016\u00016\u00016\u00016\u0001"+ + "7\u00017\u00017\u00017\u00017\u00017\u00017\u00017\u00018\u00048\u01b5"+ + "\b8\u000b8\f8\u01b6\u00018\u00018\u00019\u00019\u00019\u00019\u00019\u0001"+ + "9\u00039\u01c1\b9\u0001\u00bf\u0000:\u0004\u0001\u0006\u0002\b\u0003\n"+ + "\u0004\f\u0005\u000e\u0006\u0010\u0007\u0012\u0000\u0014\b\u0016\t\u0018"+ + "\n\u001a\u000b\u001c\f\u001e\u0000 \u0000\"\u0000$\u0000&\r(\u0000*\u000e"+ + ",\u000f.\u00100\u00112\u00124\u00136\u00148\u0015:\u0016<\u0017>\u0018"+ + "@\u0019B\u001aD\u001bF\u001cH\u001dJ\u001eL\u001fN P!R\"T#V$X%Z&\\\'^"+ + "(`)b*d+f,h-j\u0000l\u0000n\u0000p\u0000r\u0000t.v/\u0004\u0000\u0001\u0002"+ + "\u0003\u000f\u0004\u0000\n\n\r\r\"\"$%\u0003\u0000\t\t\f\r \u0002\u0000"+ + "\n\n\r\r\u0001\u0001\n\n\u0001\u000009\u0004\u0000$$AZ__az\u0002\u0000"+ + "\u0000\u007f\u8000\ud800\u8000\udbff\u0001\u0000\u8000\ud800\u8000\udbff"+ + "\u0001\u0000\u8000\udc00\u8000\udfff\u0005\u0000\"\"\\\\nnrrtt\u0003\u0000"+ + "09AFaf\u0002\u0000EEee\u0002\u0000++--\u0003\u0000\n\n\r\r$%\u0001\u0000"+ + "{{\u01e4\u0000\u0004\u0001\u0000\u0000\u0000\u0000\u0006\u0001\u0000\u0000"+ "\u0000\u0000\b\u0001\u0000\u0000\u0000\u0000\n\u0001\u0000\u0000\u0000"+ "\u0000\f\u0001\u0000\u0000\u0000\u0000\u000e\u0001\u0000\u0000\u0000\u0000"+ "\u0010\u0001\u0000\u0000\u0000\u0000\u0014\u0001\u0000\u0000\u0000\u0000"+ @@ -352,206 +353,210 @@ private boolean TemplateStringLiteralChar_sempred(RuleContext _localctx, int pre "\u0000\u0000\u0001j\u0001\u0000\u0000\u0000\u0002l\u0001\u0000\u0000\u0000"+ "\u0002n\u0001\u0000\u0000\u0000\u0003p\u0001\u0000\u0000\u0000\u0003r"+ "\u0001\u0000\u0000\u0000\u0003t\u0001\u0000\u0000\u0000\u0003v\u0001\u0000"+ - "\u0000\u0000\u0004x\u0001\u0000\u0000\u0000\u0006\u0086\u0001\u0000\u0000"+ - "\u0000\b\u0094\u0001\u0000\u0000\u0000\n\u0097\u0001\u0000\u0000\u0000"+ - "\f\u009a\u0001\u0000\u0000\u0000\u000e\u009d\u0001\u0000\u0000\u0000\u0010"+ - "\u00a0\u0001\u0000\u0000\u0000\u0012\u00a4\u0001\u0000\u0000\u0000\u0014"+ - "\u00a6\u0001\u0000\u0000\u0000\u0016\u00af\u0001\u0000\u0000\u0000\u0018"+ - "\u00b5\u0001\u0000\u0000\u0000\u001a\u00c6\u0001\u0000\u0000\u0000\u001c"+ - "\u00d5\u0001\u0000\u0000\u0000\u001e\u00db\u0001\u0000\u0000\u0000 \u00e1"+ - "\u0001\u0000\u0000\u0000\"\u00f5\u0001\u0000\u0000\u0000$\u00f7\u0001"+ - "\u0000\u0000\u0000&\u0113\u0001\u0000\u0000\u0000(\u0115\u0001\u0000\u0000"+ - "\u0000*\u0127\u0001\u0000\u0000\u0000,\u0129\u0001\u0000\u0000\u0000."+ - "\u012d\u0001\u0000\u0000\u00000\u0132\u0001\u0000\u0000\u00002\u013a\u0001"+ - "\u0000\u0000\u00004\u013c\u0001\u0000\u0000\u00006\u013f\u0001\u0000\u0000"+ - "\u00008\u0142\u0001\u0000\u0000\u0000:\u0144\u0001\u0000\u0000\u0000<"+ - "\u0146\u0001\u0000\u0000\u0000>\u0148\u0001\u0000\u0000\u0000@\u014a\u0001"+ - "\u0000\u0000\u0000B\u014c\u0001\u0000\u0000\u0000D\u014f\u0001\u0000\u0000"+ - "\u0000F\u0152\u0001\u0000\u0000\u0000H\u0154\u0001\u0000\u0000\u0000J"+ - "\u0156\u0001\u0000\u0000\u0000L\u0158\u0001\u0000\u0000\u0000N\u015a\u0001"+ - "\u0000\u0000\u0000P\u015c\u0001\u0000\u0000\u0000R\u015e\u0001\u0000\u0000"+ - "\u0000T\u0161\u0001\u0000\u0000\u0000V\u0163\u0001\u0000\u0000\u0000X"+ - "\u0165\u0001\u0000\u0000\u0000Z\u0168\u0001\u0000\u0000\u0000\\\u016b"+ - "\u0001\u0000\u0000\u0000^\u016d\u0001\u0000\u0000\u0000`\u016f\u0001\u0000"+ - "\u0000\u0000b\u0173\u0001\u0000\u0000\u0000d\u0175\u0001\u0000\u0000\u0000"+ - "f\u017d\u0001\u0000\u0000\u0000h\u018b\u0001\u0000\u0000\u0000j\u018d"+ - "\u0001\u0000\u0000\u0000l\u0192\u0001\u0000\u0000\u0000n\u0197\u0001\u0000"+ - "\u0000\u0000p\u01a3\u0001\u0000\u0000\u0000r\u01a7\u0001\u0000\u0000\u0000"+ - "t\u01b0\u0001\u0000\u0000\u0000v\u01bc\u0001\u0000\u0000\u0000x}\u0005"+ - "{\u0000\u0000y|\u0003\u0016\t\u0000z|\u0003\u001c\f\u0000{y\u0001\u0000"+ - "\u0000\u0000{z\u0001\u0000\u0000\u0000|\u007f\u0001\u0000\u0000\u0000"+ - "}{\u0001\u0000\u0000\u0000}~\u0001\u0000\u0000\u0000~\u0080\u0001\u0000"+ - "\u0000\u0000\u007f}\u0001\u0000\u0000\u0000\u0080\u0081\u0005f\u0000\u0000"+ - "\u0081\u0082\u0005o\u0000\u0000\u0082\u0083\u0005r\u0000\u0000\u0083\u0084"+ - "\u0001\u0000\u0000\u0000\u0084\u0085\u0003\u0016\t\u0000\u0085\u0005\u0001"+ - "\u0000\u0000\u0000\u0086\u008b\u0005[\u0000\u0000\u0087\u008a\u0003\u0016"+ - "\t\u0000\u0088\u008a\u0003\u001c\f\u0000\u0089\u0087\u0001\u0000\u0000"+ - "\u0000\u0089\u0088\u0001\u0000\u0000\u0000\u008a\u008d\u0001\u0000\u0000"+ - "\u0000\u008b\u0089\u0001\u0000\u0000\u0000\u008b\u008c\u0001\u0000\u0000"+ - "\u0000\u008c\u008e\u0001\u0000\u0000\u0000\u008d\u008b\u0001\u0000\u0000"+ - "\u0000\u008e\u008f\u0005f\u0000\u0000\u008f\u0090\u0005o\u0000\u0000\u0090"+ - "\u0091\u0005r\u0000\u0000\u0091\u0092\u0001\u0000\u0000\u0000\u0092\u0093"+ - "\u0003\u0016\t\u0000\u0093\u0007\u0001\u0000\u0000\u0000\u0094\u0095\u0005"+ - "i\u0000\u0000\u0095\u0096\u0005f\u0000\u0000\u0096\t\u0001\u0000\u0000"+ - "\u0000\u0097\u0098\u0005i\u0000\u0000\u0098\u0099\u0005n\u0000\u0000\u0099"+ - "\u000b\u0001\u0000\u0000\u0000\u009a\u009b\u0005{\u0000\u0000\u009b\u009c"+ - "\u0006\u0004\u0000\u0000\u009c\r\u0001\u0000\u0000\u0000\u009d\u009e\u0005"+ - "}\u0000\u0000\u009e\u009f\u0006\u0005\u0001\u0000\u009f\u000f\u0001\u0000"+ - "\u0000\u0000\u00a0\u00a1\u0005=\u0000\u0000\u00a1\u0011\u0001\u0000\u0000"+ - "\u0000\u00a2\u00a5\b\u0000\u0000\u0000\u00a3\u00a5\u0003\"\u000f\u0000"+ - "\u00a4\u00a2\u0001\u0000\u0000\u0000\u00a4\u00a3\u0001\u0000\u0000\u0000"+ - "\u00a5\u0013\u0001\u0000\u0000\u0000\u00a6\u00ab\u0003 \u000e\u0000\u00a7"+ - "\u00aa\u0003\u001e\r\u0000\u00a8\u00aa\u0005-\u0000\u0000\u00a9\u00a7"+ - "\u0001\u0000\u0000\u0000\u00a9\u00a8\u0001\u0000\u0000\u0000\u00aa\u00ad"+ - "\u0001\u0000\u0000\u0000\u00ab\u00a9\u0001\u0000\u0000\u0000\u00ab\u00ac"+ - "\u0001\u0000\u0000\u0000\u00ac\u0015\u0001\u0000\u0000\u0000\u00ad\u00ab"+ - "\u0001\u0000\u0000\u0000\u00ae\u00b0\u0007\u0001\u0000\u0000\u00af\u00ae"+ - "\u0001\u0000\u0000\u0000\u00b0\u00b1\u0001\u0000\u0000\u0000\u00b1\u00af"+ - "\u0001\u0000\u0000\u0000\u00b1\u00b2\u0001\u0000\u0000\u0000\u00b2\u00b3"+ - "\u0001\u0000\u0000\u0000\u00b3\u00b4\u0006\t\u0002\u0000\u00b4\u0017\u0001"+ - "\u0000\u0000\u0000\u00b5\u00b6\u0005/\u0000\u0000\u00b6\u00b7\u0005*\u0000"+ - "\u0000\u00b7\u00bb\u0001\u0000\u0000\u0000\u00b8\u00ba\t\u0000\u0000\u0000"+ - "\u00b9\u00b8\u0001\u0000\u0000\u0000\u00ba\u00bd\u0001\u0000\u0000\u0000"+ - "\u00bb\u00bc\u0001\u0000\u0000\u0000\u00bb\u00b9\u0001\u0000\u0000\u0000"+ - "\u00bc\u00be\u0001\u0000\u0000\u0000\u00bd\u00bb\u0001\u0000\u0000\u0000"+ - "\u00be\u00bf\u0005*\u0000\u0000\u00bf\u00c0\u0005/\u0000\u0000\u00c0\u00c1"+ - "\u0001\u0000\u0000\u0000\u00c1\u00c2\u0006\n\u0002\u0000\u00c2\u0019\u0001"+ - "\u0000\u0000\u0000\u00c3\u00c4\u0005/\u0000\u0000\u00c4\u00c7\u0005/\u0000"+ - "\u0000\u00c5\u00c7\u0005#\u0000\u0000\u00c6\u00c3\u0001\u0000\u0000\u0000"+ - "\u00c6\u00c5\u0001\u0000\u0000\u0000\u00c7\u00cb\u0001\u0000\u0000\u0000"+ - "\u00c8\u00ca\b\u0002\u0000\u0000\u00c9\u00c8\u0001\u0000\u0000\u0000\u00ca"+ - "\u00cd\u0001\u0000\u0000\u0000\u00cb\u00c9\u0001\u0000\u0000\u0000\u00cb"+ - "\u00cc\u0001\u0000\u0000\u0000\u00cc\u00cf\u0001\u0000\u0000\u0000\u00cd"+ - "\u00cb\u0001\u0000\u0000\u0000\u00ce\u00d0\u0005\r\u0000\u0000\u00cf\u00ce"+ - "\u0001\u0000\u0000\u0000\u00cf\u00d0\u0001\u0000\u0000\u0000\u00d0\u00d1"+ - "\u0001\u0000\u0000\u0000\u00d1\u00d2\u0005\n\u0000\u0000\u00d2\u00d3\u0001"+ - "\u0000\u0000\u0000\u00d3\u00d4\u0006\u000b\u0002\u0000\u00d4\u001b\u0001"+ - "\u0000\u0000\u0000\u00d5\u00d6\u0005\n\u0000\u0000\u00d6\u00d7\u0001\u0000"+ - "\u0000\u0000\u00d7\u00d8\u0006\f\u0002\u0000\u00d8\u001d\u0001\u0000\u0000"+ - "\u0000\u00d9\u00dc\u0003 \u000e\u0000\u00da\u00dc\u0007\u0003\u0000\u0000"+ - "\u00db\u00d9\u0001\u0000\u0000\u0000\u00db\u00da\u0001\u0000\u0000\u0000"+ - "\u00dc\u001f\u0001\u0000\u0000\u0000\u00dd\u00e2\u0007\u0004\u0000\u0000"+ - "\u00de\u00e2\b\u0005\u0000\u0000\u00df\u00e0\u0007\u0006\u0000\u0000\u00e0"+ - "\u00e2\u0007\u0007\u0000\u0000\u00e1\u00dd\u0001\u0000\u0000\u0000\u00e1"+ - "\u00de\u0001\u0000\u0000\u0000\u00e1\u00df\u0001\u0000\u0000\u0000\u00e2"+ - "!\u0001\u0000\u0000\u0000\u00e3\u00e4\u0005\\\u0000\u0000\u00e4\u00f6"+ - "\u0007\b\u0000\u0000\u00e5\u00e6\u0005\\\u0000\u0000\u00e6\u00e7\u0003"+ - "$\u0010\u0000\u00e7\u00e8\u0003$\u0010\u0000\u00e8\u00e9\u0003$\u0010"+ - "\u0000\u00e9\u00ea\u0003$\u0010\u0000\u00ea\u00f6\u0001\u0000\u0000\u0000"+ - "\u00eb\u00ec\u0005\\\u0000\u0000\u00ec\u00ed\u0003$\u0010\u0000\u00ed"+ - "\u00ee\u0003$\u0010\u0000\u00ee\u00ef\u0003$\u0010\u0000\u00ef\u00f0\u0003"+ - "$\u0010\u0000\u00f0\u00f1\u0003$\u0010\u0000\u00f1\u00f2\u0003$\u0010"+ - "\u0000\u00f2\u00f3\u0003$\u0010\u0000\u00f3\u00f4\u0003$\u0010\u0000\u00f4"+ - "\u00f6\u0001\u0000\u0000\u0000\u00f5\u00e3\u0001\u0000\u0000\u0000\u00f5"+ - "\u00e5\u0001\u0000\u0000\u0000\u00f5\u00eb\u0001\u0000\u0000\u0000\u00f6"+ - "#\u0001\u0000\u0000\u0000\u00f7\u00f8\u0007\t\u0000\u0000\u00f8%\u0001"+ - "\u0000\u0000\u0000\u00f9\u00fb\u0007\u0003\u0000\u0000\u00fa\u00f9\u0001"+ - "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u00fa\u0001"+ - "\u0000\u0000\u0000\u00fc\u00fd\u0001\u0000\u0000\u0000\u00fd\u00fe\u0001"+ - "\u0000\u0000\u0000\u00fe\u0102\u0005.\u0000\u0000\u00ff\u0101\u0007\u0003"+ - "\u0000\u0000\u0100\u00ff\u0001\u0000\u0000\u0000\u0101\u0104\u0001\u0000"+ - "\u0000\u0000\u0102\u0100\u0001\u0000\u0000\u0000\u0102\u0103\u0001\u0000"+ - "\u0000\u0000\u0103\u0106\u0001\u0000\u0000\u0000\u0104\u0102\u0001\u0000"+ - "\u0000\u0000\u0105\u0107\u0003(\u0012\u0000\u0106\u0105\u0001\u0000\u0000"+ - "\u0000\u0106\u0107\u0001\u0000\u0000\u0000\u0107\u0114\u0001\u0000\u0000"+ - "\u0000\u0108\u010a\u0007\u0003\u0000\u0000\u0109\u0108\u0001\u0000\u0000"+ - "\u0000\u010a\u010b\u0001\u0000\u0000\u0000\u010b\u0109\u0001\u0000\u0000"+ - "\u0000\u010b\u010c\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000"+ - "\u0000\u010d\u0114\u0003(\u0012\u0000\u010e\u0110\u0007\u0003\u0000\u0000"+ - "\u010f\u010e\u0001\u0000\u0000\u0000\u0110\u0111\u0001\u0000\u0000\u0000"+ - "\u0111\u010f\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000"+ - "\u0112\u0114\u0001\u0000\u0000\u0000\u0113\u00fa\u0001\u0000\u0000\u0000"+ - "\u0113\u0109\u0001\u0000\u0000\u0000\u0113\u010f\u0001\u0000\u0000\u0000"+ - "\u0114\'\u0001\u0000\u0000\u0000\u0115\u0117\u0007\n\u0000\u0000\u0116"+ - "\u0118\u0007\u000b\u0000\u0000\u0117\u0116\u0001\u0000\u0000\u0000\u0117"+ - "\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119"+ - "\u011b\u0007\u0003\u0000\u0000\u011a\u0119\u0001\u0000\u0000\u0000\u011b"+ - "\u011c\u0001\u0000\u0000\u0000\u011c\u011a\u0001\u0000\u0000\u0000\u011c"+ - "\u011d\u0001\u0000\u0000\u0000\u011d)\u0001\u0000\u0000\u0000\u011e\u011f"+ - "\u0005t\u0000\u0000\u011f\u0120\u0005r\u0000\u0000\u0120\u0121\u0005u"+ - "\u0000\u0000\u0121\u0128\u0005e\u0000\u0000\u0122\u0123\u0005f\u0000\u0000"+ - "\u0123\u0124\u0005a\u0000\u0000\u0124\u0125\u0005l\u0000\u0000\u0125\u0126"+ - "\u0005s\u0000\u0000\u0126\u0128\u0005e\u0000\u0000\u0127\u011e\u0001\u0000"+ - "\u0000\u0000\u0127\u0122\u0001\u0000\u0000\u0000\u0128+\u0001\u0000\u0000"+ - "\u0000\u0129\u012a\u0005\"\u0000\u0000\u012a\u012b\u0001\u0000\u0000\u0000"+ - "\u012b\u012c\u0006\u0014\u0003\u0000\u012c-\u0001\u0000\u0000\u0000\u012d"+ - "\u012e\u0005n\u0000\u0000\u012e\u012f\u0005u\u0000\u0000\u012f\u0130\u0005"+ - "l\u0000\u0000\u0130\u0131\u0005l\u0000\u0000\u0131/\u0001\u0000\u0000"+ - "\u0000\u0132\u0133\u0005<\u0000\u0000\u0133\u0134\u0005<\u0000\u0000\u0134"+ - "\u0136\u0001\u0000\u0000\u0000\u0135\u0137\u0005-\u0000\u0000\u0136\u0135"+ - "\u0001\u0000\u0000\u0000\u0136\u0137\u0001\u0000\u0000\u0000\u0137\u0138"+ - "\u0001\u0000\u0000\u0000\u0138\u0139\u0006\u0016\u0004\u0000\u01391\u0001"+ - "\u0000\u0000\u0000\u013a\u013b\u0005+\u0000\u0000\u013b3\u0001\u0000\u0000"+ - "\u0000\u013c\u013d\u0005&\u0000\u0000\u013d\u013e\u0005&\u0000\u0000\u013e"+ - "5\u0001\u0000\u0000\u0000\u013f\u0140\u0005=\u0000\u0000\u0140\u0141\u0005"+ - "=\u0000\u0000\u01417\u0001\u0000\u0000\u0000\u0142\u0143\u0005<\u0000"+ - "\u0000\u01439\u0001\u0000\u0000\u0000\u0144\u0145\u0005:\u0000\u0000\u0145"+ - ";\u0001\u0000\u0000\u0000\u0146\u0147\u0005[\u0000\u0000\u0147=\u0001"+ - "\u0000\u0000\u0000\u0148\u0149\u0005(\u0000\u0000\u0149?\u0001\u0000\u0000"+ - "\u0000\u014a\u014b\u0005-\u0000\u0000\u014bA\u0001\u0000\u0000\u0000\u014c"+ - "\u014d\u0005|\u0000\u0000\u014d\u014e\u0005|\u0000\u0000\u014eC\u0001"+ - "\u0000\u0000\u0000\u014f\u0150\u0005!\u0000\u0000\u0150\u0151\u0005=\u0000"+ - "\u0000\u0151E\u0001\u0000\u0000\u0000\u0152\u0153\u0005>\u0000\u0000\u0153"+ - "G\u0001\u0000\u0000\u0000\u0154\u0155\u0005?\u0000\u0000\u0155I\u0001"+ - "\u0000\u0000\u0000\u0156\u0157\u0005]\u0000\u0000\u0157K\u0001\u0000\u0000"+ - "\u0000\u0158\u0159\u0005)\u0000\u0000\u0159M\u0001\u0000\u0000\u0000\u015a"+ - "\u015b\u0005*\u0000\u0000\u015bO\u0001\u0000\u0000\u0000\u015c\u015d\u0005"+ - "!\u0000\u0000\u015dQ\u0001\u0000\u0000\u0000\u015e\u015f\u0005<\u0000"+ - "\u0000\u015f\u0160\u0005=\u0000\u0000\u0160S\u0001\u0000\u0000\u0000\u0161"+ - "\u0162\u0005.\u0000\u0000\u0162U\u0001\u0000\u0000\u0000\u0163\u0164\u0005"+ - "/\u0000\u0000\u0164W\u0001\u0000\u0000\u0000\u0165\u0166\u0005>\u0000"+ - "\u0000\u0166\u0167\u0005=\u0000\u0000\u0167Y\u0001\u0000\u0000\u0000\u0168"+ - "\u0169\u0005=\u0000\u0000\u0169\u016a\u0005>\u0000\u0000\u016a[\u0001"+ - "\u0000\u0000\u0000\u016b\u016c\u0005,\u0000\u0000\u016c]\u0001\u0000\u0000"+ - "\u0000\u016d\u016e\u0005%\u0000\u0000\u016e_\u0001\u0000\u0000\u0000\u016f"+ - "\u0170\u0005.\u0000\u0000\u0170\u0171\u0005.\u0000\u0000\u0171\u0172\u0005"+ - ".\u0000\u0000\u0172a\u0001\u0000\u0000\u0000\u0173\u0174\u0005~\u0000"+ - "\u0000\u0174c\u0001\u0000\u0000\u0000\u0175\u0176\u0005$\u0000\u0000\u0176"+ - "\u0177\u0005{\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000\u0178\u0179"+ - "\u00060\u0005\u0000\u0179\u017a\u0001\u0000\u0000\u0000\u017a\u017b\u0006"+ - "0\u0006\u0000\u017be\u0001\u0000\u0000\u0000\u017c\u017e\u0003h2\u0000"+ - "\u017d\u017c\u0001\u0000\u0000\u0000\u017e\u017f\u0001\u0000\u0000\u0000"+ - "\u017f\u017d\u0001\u0000\u0000\u0000\u017f\u0180\u0001\u0000\u0000\u0000"+ - "\u0180g\u0001\u0000\u0000\u0000\u0181\u018c\b\u0000\u0000\u0000\u0182"+ - "\u0183\u0005$\u0000\u0000\u0183\u018c\u0005$\u0000\u0000\u0184\u0185\u0005"+ - "$\u0000\u0000\u0185\u018c\u00042\u0000\u0000\u0186\u0187\u0005%\u0000"+ - "\u0000\u0187\u018c\u0005%\u0000\u0000\u0188\u0189\u0005%\u0000\u0000\u0189"+ - "\u018c\u00042\u0001\u0000\u018a\u018c\u0003\"\u000f\u0000\u018b\u0181"+ - "\u0001\u0000\u0000\u0000\u018b\u0182\u0001\u0000\u0000\u0000\u018b\u0184"+ - "\u0001\u0000\u0000\u0000\u018b\u0186\u0001\u0000\u0000\u0000\u018b\u0188"+ - "\u0001\u0000\u0000\u0000\u018b\u018a\u0001\u0000\u0000\u0000\u018ci\u0001"+ - "\u0000\u0000\u0000\u018d\u018e\u0005\"\u0000\u0000\u018e\u018f\u0001\u0000"+ - "\u0000\u0000\u018f\u0190\u00063\u0007\u0000\u0190\u0191\u00063\b\u0000"+ - "\u0191k\u0001\u0000\u0000\u0000\u0192\u0193\u0005\n\u0000\u0000\u0193"+ - "\u0194\u0001\u0000\u0000\u0000\u0194\u0195\u00064\t\u0000\u0195\u0196"+ - "\u00064\n\u0000\u0196m\u0001\u0000\u0000\u0000\u0197\u019c\u0003 \u000e"+ - "\u0000\u0198\u019b\u0003\u001e\r\u0000\u0199\u019b\u0005-\u0000\u0000"+ - "\u019a\u0198\u0001\u0000\u0000\u0000\u019a\u0199\u0001\u0000\u0000\u0000"+ - "\u019b\u019e\u0001\u0000\u0000\u0000\u019c\u019a\u0001\u0000\u0000\u0000"+ - "\u019c\u019d\u0001\u0000\u0000\u0000\u019d\u019f\u0001\u0000\u0000\u0000"+ - "\u019e\u019c\u0001\u0000\u0000\u0000\u019f\u01a0\u00065\u000b\u0000\u01a0"+ - "\u01a1\u0001\u0000\u0000\u0000\u01a1\u01a2\u00065\f\u0000\u01a2o\u0001"+ - "\u0000\u0000\u0000\u01a3\u01a4\u0005\n\u0000\u0000\u01a4\u01a5\u0001\u0000"+ - "\u0000\u0000\u01a5\u01a6\u00066\t\u0000\u01a6q\u0001\u0000\u0000\u0000"+ - "\u01a7\u01a8\u0005$\u0000\u0000\u01a8\u01a9\u0005{\u0000\u0000\u01a9\u01aa"+ - "\u0001\u0000\u0000\u0000\u01aa\u01ab\u00067\r\u0000\u01ab\u01ac\u0001"+ - "\u0000\u0000\u0000\u01ac\u01ad\u00067\u000e\u0000\u01ad\u01ae\u00067\u0006"+ - "\u0000\u01aes\u0001\u0000\u0000\u0000\u01af\u01b1\u0003v9\u0000\u01b0"+ - "\u01af\u0001\u0000\u0000\u0000\u01b1\u01b2\u0001\u0000\u0000\u0000\u01b2"+ - "\u01b0\u0001\u0000\u0000\u0000\u01b2\u01b3\u0001\u0000\u0000\u0000\u01b3"+ - "\u01b4\u0001\u0000\u0000\u0000\u01b4\u01b5\u00068\u000f\u0000\u01b5u\u0001"+ - "\u0000\u0000\u0000\u01b6\u01bd\b\f\u0000\u0000\u01b7\u01b8\u0005$\u0000"+ - "\u0000\u01b8\u01bd\b\r\u0000\u0000\u01b9\u01ba\u0005%\u0000\u0000\u01ba"+ - "\u01bd\b\r\u0000\u0000\u01bb\u01bd\u0003\"\u000f\u0000\u01bc\u01b6\u0001"+ - "\u0000\u0000\u0000\u01bc\u01b7\u0001\u0000\u0000\u0000\u01bc\u01b9\u0001"+ - "\u0000\u0000\u0000\u01bc\u01bb\u0001\u0000\u0000\u0000\u01bdw\u0001\u0000"+ - "\u0000\u0000#\u0000\u0001\u0002\u0003{}\u0089\u008b\u00a4\u00a9\u00ab"+ - "\u00b1\u00bb\u00c6\u00cb\u00cf\u00db\u00e1\u00f5\u00fc\u0102\u0106\u010b"+ - "\u0111\u0113\u0117\u011c\u0127\u0136\u017f\u018b\u019a\u019c\u01b2\u01bc"+ - "\u0010\u0001\u0004\u0000\u0001\u0005\u0001\u0000\u0001\u0000\u0005\u0001"+ - "\u0000\u0005\u0002\u0000\u00010\u0002\u0005\u0000\u0000\u0007\u000f\u0000"+ - "\u0004\u0000\u0000\u0007\f\u0000\u0002\u0003\u0000\u00015\u0003\u0007"+ - "\b\u0000\u00017\u0004\u0007+\u0000\u00018\u0005"; + "\u0000\u0000\u0004x\u0001\u0000\u0000\u0000\u0006\u0088\u0001\u0000\u0000"+ + "\u0000\b\u0098\u0001\u0000\u0000\u0000\n\u009b\u0001\u0000\u0000\u0000"+ + "\f\u009e\u0001\u0000\u0000\u0000\u000e\u00a1\u0001\u0000\u0000\u0000\u0010"+ + "\u00a4\u0001\u0000\u0000\u0000\u0012\u00a8\u0001\u0000\u0000\u0000\u0014"+ + "\u00aa\u0001\u0000\u0000\u0000\u0016\u00b3\u0001\u0000\u0000\u0000\u0018"+ + "\u00b9\u0001\u0000\u0000\u0000\u001a\u00ca\u0001\u0000\u0000\u0000\u001c"+ + "\u00da\u0001\u0000\u0000\u0000\u001e\u00e0\u0001\u0000\u0000\u0000 \u00e6"+ + "\u0001\u0000\u0000\u0000\"\u00fa\u0001\u0000\u0000\u0000$\u00fc\u0001"+ + "\u0000\u0000\u0000&\u0117\u0001\u0000\u0000\u0000(\u0119\u0001\u0000\u0000"+ + "\u0000*\u012b\u0001\u0000\u0000\u0000,\u012d\u0001\u0000\u0000\u0000."+ + "\u0131\u0001\u0000\u0000\u00000\u0136\u0001\u0000\u0000\u00002\u013e\u0001"+ + "\u0000\u0000\u00004\u0140\u0001\u0000\u0000\u00006\u0143\u0001\u0000\u0000"+ + "\u00008\u0146\u0001\u0000\u0000\u0000:\u0148\u0001\u0000\u0000\u0000<"+ + "\u014a\u0001\u0000\u0000\u0000>\u014c\u0001\u0000\u0000\u0000@\u014e\u0001"+ + "\u0000\u0000\u0000B\u0150\u0001\u0000\u0000\u0000D\u0153\u0001\u0000\u0000"+ + "\u0000F\u0156\u0001\u0000\u0000\u0000H\u0158\u0001\u0000\u0000\u0000J"+ + "\u015a\u0001\u0000\u0000\u0000L\u015c\u0001\u0000\u0000\u0000N\u015e\u0001"+ + "\u0000\u0000\u0000P\u0160\u0001\u0000\u0000\u0000R\u0162\u0001\u0000\u0000"+ + "\u0000T\u0165\u0001\u0000\u0000\u0000V\u0167\u0001\u0000\u0000\u0000X"+ + "\u0169\u0001\u0000\u0000\u0000Z\u016c\u0001\u0000\u0000\u0000\\\u016f"+ + "\u0001\u0000\u0000\u0000^\u0171\u0001\u0000\u0000\u0000`\u0173\u0001\u0000"+ + "\u0000\u0000b\u0177\u0001\u0000\u0000\u0000d\u0179\u0001\u0000\u0000\u0000"+ + "f\u0181\u0001\u0000\u0000\u0000h\u018f\u0001\u0000\u0000\u0000j\u0191"+ + "\u0001\u0000\u0000\u0000l\u0196\u0001\u0000\u0000\u0000n\u019b\u0001\u0000"+ + "\u0000\u0000p\u01a7\u0001\u0000\u0000\u0000r\u01ab\u0001\u0000\u0000\u0000"+ + "t\u01b4\u0001\u0000\u0000\u0000v\u01c0\u0001\u0000\u0000\u0000x\u007f"+ + "\u0005{\u0000\u0000y~\u0003\u0016\t\u0000z~\u0003\u001c\f\u0000{~\u0003"+ + "\u0018\n\u0000|~\u0003\u001a\u000b\u0000}y\u0001\u0000\u0000\u0000}z\u0001"+ + "\u0000\u0000\u0000}{\u0001\u0000\u0000\u0000}|\u0001\u0000\u0000\u0000"+ + "~\u0081\u0001\u0000\u0000\u0000\u007f}\u0001\u0000\u0000\u0000\u007f\u0080"+ + "\u0001\u0000\u0000\u0000\u0080\u0082\u0001\u0000\u0000\u0000\u0081\u007f"+ + "\u0001\u0000\u0000\u0000\u0082\u0083\u0005f\u0000\u0000\u0083\u0084\u0005"+ + "o\u0000\u0000\u0084\u0085\u0005r\u0000\u0000\u0085\u0086\u0001\u0000\u0000"+ + "\u0000\u0086\u0087\u0003\u0016\t\u0000\u0087\u0005\u0001\u0000\u0000\u0000"+ + "\u0088\u008f\u0005[\u0000\u0000\u0089\u008e\u0003\u0016\t\u0000\u008a"+ + "\u008e\u0003\u001c\f\u0000\u008b\u008e\u0003\u0018\n\u0000\u008c\u008e"+ + "\u0003\u001a\u000b\u0000\u008d\u0089\u0001\u0000\u0000\u0000\u008d\u008a"+ + "\u0001\u0000\u0000\u0000\u008d\u008b\u0001\u0000\u0000\u0000\u008d\u008c"+ + "\u0001\u0000\u0000\u0000\u008e\u0091\u0001\u0000\u0000\u0000\u008f\u008d"+ + "\u0001\u0000\u0000\u0000\u008f\u0090\u0001\u0000\u0000\u0000\u0090\u0092"+ + "\u0001\u0000\u0000\u0000\u0091\u008f\u0001\u0000\u0000\u0000\u0092\u0093"+ + "\u0005f\u0000\u0000\u0093\u0094\u0005o\u0000\u0000\u0094\u0095\u0005r"+ + "\u0000\u0000\u0095\u0096\u0001\u0000\u0000\u0000\u0096\u0097\u0003\u0016"+ + "\t\u0000\u0097\u0007\u0001\u0000\u0000\u0000\u0098\u0099\u0005i\u0000"+ + "\u0000\u0099\u009a\u0005f\u0000\u0000\u009a\t\u0001\u0000\u0000\u0000"+ + "\u009b\u009c\u0005i\u0000\u0000\u009c\u009d\u0005n\u0000\u0000\u009d\u000b"+ + "\u0001\u0000\u0000\u0000\u009e\u009f\u0005{\u0000\u0000\u009f\u00a0\u0006"+ + "\u0004\u0000\u0000\u00a0\r\u0001\u0000\u0000\u0000\u00a1\u00a2\u0005}"+ + "\u0000\u0000\u00a2\u00a3\u0006\u0005\u0001\u0000\u00a3\u000f\u0001\u0000"+ + "\u0000\u0000\u00a4\u00a5\u0005=\u0000\u0000\u00a5\u0011\u0001\u0000\u0000"+ + "\u0000\u00a6\u00a9\b\u0000\u0000\u0000\u00a7\u00a9\u0003\"\u000f\u0000"+ + "\u00a8\u00a6\u0001\u0000\u0000\u0000\u00a8\u00a7\u0001\u0000\u0000\u0000"+ + "\u00a9\u0013\u0001\u0000\u0000\u0000\u00aa\u00af\u0003 \u000e\u0000\u00ab"+ + "\u00ae\u0003\u001e\r\u0000\u00ac\u00ae\u0005-\u0000\u0000\u00ad\u00ab"+ + "\u0001\u0000\u0000\u0000\u00ad\u00ac\u0001\u0000\u0000\u0000\u00ae\u00b1"+ + "\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0"+ + "\u0001\u0000\u0000\u0000\u00b0\u0015\u0001\u0000\u0000\u0000\u00b1\u00af"+ + "\u0001\u0000\u0000\u0000\u00b2\u00b4\u0007\u0001\u0000\u0000\u00b3\u00b2"+ + "\u0001\u0000\u0000\u0000\u00b4\u00b5\u0001\u0000\u0000\u0000\u00b5\u00b3"+ + "\u0001\u0000\u0000\u0000\u00b5\u00b6\u0001\u0000\u0000\u0000\u00b6\u00b7"+ + "\u0001\u0000\u0000\u0000\u00b7\u00b8\u0006\t\u0002\u0000\u00b8\u0017\u0001"+ + "\u0000\u0000\u0000\u00b9\u00ba\u0005/\u0000\u0000\u00ba\u00bb\u0005*\u0000"+ + "\u0000\u00bb\u00bf\u0001\u0000\u0000\u0000\u00bc\u00be\t\u0000\u0000\u0000"+ + "\u00bd\u00bc\u0001\u0000\u0000\u0000\u00be\u00c1\u0001\u0000\u0000\u0000"+ + "\u00bf\u00c0\u0001\u0000\u0000\u0000\u00bf\u00bd\u0001\u0000\u0000\u0000"+ + "\u00c0\u00c2\u0001\u0000\u0000\u0000\u00c1\u00bf\u0001\u0000\u0000\u0000"+ + "\u00c2\u00c3\u0005*\u0000\u0000\u00c3\u00c4\u0005/\u0000\u0000\u00c4\u00c5"+ + "\u0001\u0000\u0000\u0000\u00c5\u00c6\u0006\n\u0002\u0000\u00c6\u0019\u0001"+ + "\u0000\u0000\u0000\u00c7\u00c8\u0005/\u0000\u0000\u00c8\u00cb\u0005/\u0000"+ + "\u0000\u00c9\u00cb\u0005#\u0000\u0000\u00ca\u00c7\u0001\u0000\u0000\u0000"+ + "\u00ca\u00c9\u0001\u0000\u0000\u0000\u00cb\u00cf\u0001\u0000\u0000\u0000"+ + "\u00cc\u00ce\b\u0002\u0000\u0000\u00cd\u00cc\u0001\u0000\u0000\u0000\u00ce"+ + "\u00d1\u0001\u0000\u0000\u0000\u00cf\u00cd\u0001\u0000\u0000\u0000\u00cf"+ + "\u00d0\u0001\u0000\u0000\u0000\u00d0\u00d3\u0001\u0000\u0000\u0000\u00d1"+ + "\u00cf\u0001\u0000\u0000\u0000\u00d2\u00d4\u0005\r\u0000\u0000\u00d3\u00d2"+ + "\u0001\u0000\u0000\u0000\u00d3\u00d4\u0001\u0000\u0000\u0000\u00d4\u00d6"+ + "\u0001\u0000\u0000\u0000\u00d5\u00d7\u0007\u0003\u0000\u0000\u00d6\u00d5"+ + "\u0001\u0000\u0000\u0000\u00d7\u00d8\u0001\u0000\u0000\u0000\u00d8\u00d9"+ + "\u0006\u000b\u0002\u0000\u00d9\u001b\u0001\u0000\u0000\u0000\u00da\u00db"+ + "\u0005\n\u0000\u0000\u00db\u00dc\u0001\u0000\u0000\u0000\u00dc\u00dd\u0006"+ + "\f\u0002\u0000\u00dd\u001d\u0001\u0000\u0000\u0000\u00de\u00e1\u0003 "+ + "\u000e\u0000\u00df\u00e1\u0007\u0004\u0000\u0000\u00e0\u00de\u0001\u0000"+ + "\u0000\u0000\u00e0\u00df\u0001\u0000\u0000\u0000\u00e1\u001f\u0001\u0000"+ + "\u0000\u0000\u00e2\u00e7\u0007\u0005\u0000\u0000\u00e3\u00e7\b\u0006\u0000"+ + "\u0000\u00e4\u00e5\u0007\u0007\u0000\u0000\u00e5\u00e7\u0007\b\u0000\u0000"+ + "\u00e6\u00e2\u0001\u0000\u0000\u0000\u00e6\u00e3\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e7!\u0001\u0000\u0000\u0000\u00e8"+ + "\u00e9\u0005\\\u0000\u0000\u00e9\u00fb\u0007\t\u0000\u0000\u00ea\u00eb"+ + "\u0005\\\u0000\u0000\u00eb\u00ec\u0003$\u0010\u0000\u00ec\u00ed\u0003"+ + "$\u0010\u0000\u00ed\u00ee\u0003$\u0010\u0000\u00ee\u00ef\u0003$\u0010"+ + "\u0000\u00ef\u00fb\u0001\u0000\u0000\u0000\u00f0\u00f1\u0005\\\u0000\u0000"+ + "\u00f1\u00f2\u0003$\u0010\u0000\u00f2\u00f3\u0003$\u0010\u0000\u00f3\u00f4"+ + "\u0003$\u0010\u0000\u00f4\u00f5\u0003$\u0010\u0000\u00f5\u00f6\u0003$"+ + "\u0010\u0000\u00f6\u00f7\u0003$\u0010\u0000\u00f7\u00f8\u0003$\u0010\u0000"+ + "\u00f8\u00f9\u0003$\u0010\u0000\u00f9\u00fb\u0001\u0000\u0000\u0000\u00fa"+ + "\u00e8\u0001\u0000\u0000\u0000\u00fa\u00ea\u0001\u0000\u0000\u0000\u00fa"+ + "\u00f0\u0001\u0000\u0000\u0000\u00fb#\u0001\u0000\u0000\u0000\u00fc\u00fd"+ + "\u0007\n\u0000\u0000\u00fd%\u0001\u0000\u0000\u0000\u00fe\u0100\u0007"+ + "\u0004\u0000\u0000\u00ff\u00fe\u0001\u0000\u0000\u0000\u0100\u0101\u0001"+ + "\u0000\u0000\u0000\u0101\u00ff\u0001\u0000\u0000\u0000\u0101\u0102\u0001"+ + "\u0000\u0000\u0000\u0102\u0103\u0001\u0000\u0000\u0000\u0103\u0105\u0005"+ + ".\u0000\u0000\u0104\u0106\u0007\u0004\u0000\u0000\u0105\u0104\u0001\u0000"+ + "\u0000\u0000\u0106\u0107\u0001\u0000\u0000\u0000\u0107\u0105\u0001\u0000"+ + "\u0000\u0000\u0107\u0108\u0001\u0000\u0000\u0000\u0108\u010a\u0001\u0000"+ + "\u0000\u0000\u0109\u010b\u0003(\u0012\u0000\u010a\u0109\u0001\u0000\u0000"+ + "\u0000\u010a\u010b\u0001\u0000\u0000\u0000\u010b\u0118\u0001\u0000\u0000"+ + "\u0000\u010c\u010e\u0007\u0004\u0000\u0000\u010d\u010c\u0001\u0000\u0000"+ + "\u0000\u010e\u010f\u0001\u0000\u0000\u0000\u010f\u010d\u0001\u0000\u0000"+ + "\u0000\u010f\u0110\u0001\u0000\u0000\u0000\u0110\u0111\u0001\u0000\u0000"+ + "\u0000\u0111\u0118\u0003(\u0012\u0000\u0112\u0114\u0007\u0004\u0000\u0000"+ + "\u0113\u0112\u0001\u0000\u0000\u0000\u0114\u0115\u0001\u0000\u0000\u0000"+ + "\u0115\u0113\u0001\u0000\u0000\u0000\u0115\u0116\u0001\u0000\u0000\u0000"+ + "\u0116\u0118\u0001\u0000\u0000\u0000\u0117\u00ff\u0001\u0000\u0000\u0000"+ + "\u0117\u010d\u0001\u0000\u0000\u0000\u0117\u0113\u0001\u0000\u0000\u0000"+ + "\u0118\'\u0001\u0000\u0000\u0000\u0119\u011b\u0007\u000b\u0000\u0000\u011a"+ + "\u011c\u0007\f\u0000\u0000\u011b\u011a\u0001\u0000\u0000\u0000\u011b\u011c"+ + "\u0001\u0000\u0000\u0000\u011c\u011e\u0001\u0000\u0000\u0000\u011d\u011f"+ + "\u0007\u0004\u0000\u0000\u011e\u011d\u0001\u0000\u0000\u0000\u011f\u0120"+ + "\u0001\u0000\u0000\u0000\u0120\u011e\u0001\u0000\u0000\u0000\u0120\u0121"+ + "\u0001\u0000\u0000\u0000\u0121)\u0001\u0000\u0000\u0000\u0122\u0123\u0005"+ + "t\u0000\u0000\u0123\u0124\u0005r\u0000\u0000\u0124\u0125\u0005u\u0000"+ + "\u0000\u0125\u012c\u0005e\u0000\u0000\u0126\u0127\u0005f\u0000\u0000\u0127"+ + "\u0128\u0005a\u0000\u0000\u0128\u0129\u0005l\u0000\u0000\u0129\u012a\u0005"+ + "s\u0000\u0000\u012a\u012c\u0005e\u0000\u0000\u012b\u0122\u0001\u0000\u0000"+ + "\u0000\u012b\u0126\u0001\u0000\u0000\u0000\u012c+\u0001\u0000\u0000\u0000"+ + "\u012d\u012e\u0005\"\u0000\u0000\u012e\u012f\u0001\u0000\u0000\u0000\u012f"+ + "\u0130\u0006\u0014\u0003\u0000\u0130-\u0001\u0000\u0000\u0000\u0131\u0132"+ + "\u0005n\u0000\u0000\u0132\u0133\u0005u\u0000\u0000\u0133\u0134\u0005l"+ + "\u0000\u0000\u0134\u0135\u0005l\u0000\u0000\u0135/\u0001\u0000\u0000\u0000"+ + "\u0136\u0137\u0005<\u0000\u0000\u0137\u0138\u0005<\u0000\u0000\u0138\u013a"+ + "\u0001\u0000\u0000\u0000\u0139\u013b\u0005-\u0000\u0000\u013a\u0139\u0001"+ + "\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b\u013c\u0001"+ + "\u0000\u0000\u0000\u013c\u013d\u0006\u0016\u0004\u0000\u013d1\u0001\u0000"+ + "\u0000\u0000\u013e\u013f\u0005+\u0000\u0000\u013f3\u0001\u0000\u0000\u0000"+ + "\u0140\u0141\u0005&\u0000\u0000\u0141\u0142\u0005&\u0000\u0000\u01425"+ + "\u0001\u0000\u0000\u0000\u0143\u0144\u0005=\u0000\u0000\u0144\u0145\u0005"+ + "=\u0000\u0000\u01457\u0001\u0000\u0000\u0000\u0146\u0147\u0005<\u0000"+ + "\u0000\u01479\u0001\u0000\u0000\u0000\u0148\u0149\u0005:\u0000\u0000\u0149"+ + ";\u0001\u0000\u0000\u0000\u014a\u014b\u0005[\u0000\u0000\u014b=\u0001"+ + "\u0000\u0000\u0000\u014c\u014d\u0005(\u0000\u0000\u014d?\u0001\u0000\u0000"+ + "\u0000\u014e\u014f\u0005-\u0000\u0000\u014fA\u0001\u0000\u0000\u0000\u0150"+ + "\u0151\u0005|\u0000\u0000\u0151\u0152\u0005|\u0000\u0000\u0152C\u0001"+ + "\u0000\u0000\u0000\u0153\u0154\u0005!\u0000\u0000\u0154\u0155\u0005=\u0000"+ + "\u0000\u0155E\u0001\u0000\u0000\u0000\u0156\u0157\u0005>\u0000\u0000\u0157"+ + "G\u0001\u0000\u0000\u0000\u0158\u0159\u0005?\u0000\u0000\u0159I\u0001"+ + "\u0000\u0000\u0000\u015a\u015b\u0005]\u0000\u0000\u015bK\u0001\u0000\u0000"+ + "\u0000\u015c\u015d\u0005)\u0000\u0000\u015dM\u0001\u0000\u0000\u0000\u015e"+ + "\u015f\u0005*\u0000\u0000\u015fO\u0001\u0000\u0000\u0000\u0160\u0161\u0005"+ + "!\u0000\u0000\u0161Q\u0001\u0000\u0000\u0000\u0162\u0163\u0005<\u0000"+ + "\u0000\u0163\u0164\u0005=\u0000\u0000\u0164S\u0001\u0000\u0000\u0000\u0165"+ + "\u0166\u0005.\u0000\u0000\u0166U\u0001\u0000\u0000\u0000\u0167\u0168\u0005"+ + "/\u0000\u0000\u0168W\u0001\u0000\u0000\u0000\u0169\u016a\u0005>\u0000"+ + "\u0000\u016a\u016b\u0005=\u0000\u0000\u016bY\u0001\u0000\u0000\u0000\u016c"+ + "\u016d\u0005=\u0000\u0000\u016d\u016e\u0005>\u0000\u0000\u016e[\u0001"+ + "\u0000\u0000\u0000\u016f\u0170\u0005,\u0000\u0000\u0170]\u0001\u0000\u0000"+ + "\u0000\u0171\u0172\u0005%\u0000\u0000\u0172_\u0001\u0000\u0000\u0000\u0173"+ + "\u0174\u0005.\u0000\u0000\u0174\u0175\u0005.\u0000\u0000\u0175\u0176\u0005"+ + ".\u0000\u0000\u0176a\u0001\u0000\u0000\u0000\u0177\u0178\u0005~\u0000"+ + "\u0000\u0178c\u0001\u0000\u0000\u0000\u0179\u017a\u0005$\u0000\u0000\u017a"+ + "\u017b\u0005{\u0000\u0000\u017b\u017c\u0001\u0000\u0000\u0000\u017c\u017d"+ + "\u00060\u0005\u0000\u017d\u017e\u0001\u0000\u0000\u0000\u017e\u017f\u0006"+ + "0\u0006\u0000\u017fe\u0001\u0000\u0000\u0000\u0180\u0182\u0003h2\u0000"+ + "\u0181\u0180\u0001\u0000\u0000\u0000\u0182\u0183\u0001\u0000\u0000\u0000"+ + "\u0183\u0181\u0001\u0000\u0000\u0000\u0183\u0184\u0001\u0000\u0000\u0000"+ + "\u0184g\u0001\u0000\u0000\u0000\u0185\u0190\b\u0000\u0000\u0000\u0186"+ + "\u0187\u0005$\u0000\u0000\u0187\u0190\u0005$\u0000\u0000\u0188\u0189\u0005"+ + "$\u0000\u0000\u0189\u0190\u00042\u0000\u0000\u018a\u018b\u0005%\u0000"+ + "\u0000\u018b\u0190\u0005%\u0000\u0000\u018c\u018d\u0005%\u0000\u0000\u018d"+ + "\u0190\u00042\u0001\u0000\u018e\u0190\u0003\"\u000f\u0000\u018f\u0185"+ + "\u0001\u0000\u0000\u0000\u018f\u0186\u0001\u0000\u0000\u0000\u018f\u0188"+ + "\u0001\u0000\u0000\u0000\u018f\u018a\u0001\u0000\u0000\u0000\u018f\u018c"+ + "\u0001\u0000\u0000\u0000\u018f\u018e\u0001\u0000\u0000\u0000\u0190i\u0001"+ + "\u0000\u0000\u0000\u0191\u0192\u0005\"\u0000\u0000\u0192\u0193\u0001\u0000"+ + "\u0000\u0000\u0193\u0194\u00063\u0007\u0000\u0194\u0195\u00063\b\u0000"+ + "\u0195k\u0001\u0000\u0000\u0000\u0196\u0197\u0005\n\u0000\u0000\u0197"+ + "\u0198\u0001\u0000\u0000\u0000\u0198\u0199\u00064\t\u0000\u0199\u019a"+ + "\u00064\n\u0000\u019am\u0001\u0000\u0000\u0000\u019b\u01a0\u0003 \u000e"+ + "\u0000\u019c\u019f\u0003\u001e\r\u0000\u019d\u019f\u0005-\u0000\u0000"+ + "\u019e\u019c\u0001\u0000\u0000\u0000\u019e\u019d\u0001\u0000\u0000\u0000"+ + "\u019f\u01a2\u0001\u0000\u0000\u0000\u01a0\u019e\u0001\u0000\u0000\u0000"+ + "\u01a0\u01a1\u0001\u0000\u0000\u0000\u01a1\u01a3\u0001\u0000\u0000\u0000"+ + "\u01a2\u01a0\u0001\u0000\u0000\u0000\u01a3\u01a4\u00065\u000b\u0000\u01a4"+ + "\u01a5\u0001\u0000\u0000\u0000\u01a5\u01a6\u00065\f\u0000\u01a6o\u0001"+ + "\u0000\u0000\u0000\u01a7\u01a8\u0005\n\u0000\u0000\u01a8\u01a9\u0001\u0000"+ + "\u0000\u0000\u01a9\u01aa\u00066\t\u0000\u01aaq\u0001\u0000\u0000\u0000"+ + "\u01ab\u01ac\u0005$\u0000\u0000\u01ac\u01ad\u0005{\u0000\u0000\u01ad\u01ae"+ + "\u0001\u0000\u0000\u0000\u01ae\u01af\u00067\r\u0000\u01af\u01b0\u0001"+ + "\u0000\u0000\u0000\u01b0\u01b1\u00067\u000e\u0000\u01b1\u01b2\u00067\u0006"+ + "\u0000\u01b2s\u0001\u0000\u0000\u0000\u01b3\u01b5\u0003v9\u0000\u01b4"+ + "\u01b3\u0001\u0000\u0000\u0000\u01b5\u01b6\u0001\u0000\u0000\u0000\u01b6"+ + "\u01b4\u0001\u0000\u0000\u0000\u01b6\u01b7\u0001\u0000\u0000\u0000\u01b7"+ + "\u01b8\u0001\u0000\u0000\u0000\u01b8\u01b9\u00068\u000f\u0000\u01b9u\u0001"+ + "\u0000\u0000\u0000\u01ba\u01c1\b\r\u0000\u0000\u01bb\u01bc\u0005$\u0000"+ + "\u0000\u01bc\u01c1\b\u000e\u0000\u0000\u01bd\u01be\u0005%\u0000\u0000"+ + "\u01be\u01c1\b\u000e\u0000\u0000\u01bf\u01c1\u0003\"\u000f\u0000\u01c0"+ + "\u01ba\u0001\u0000\u0000\u0000\u01c0\u01bb\u0001\u0000\u0000\u0000\u01c0"+ + "\u01bd\u0001\u0000\u0000\u0000\u01c0\u01bf\u0001\u0000\u0000\u0000\u01c1"+ + "w\u0001\u0000\u0000\u0000$\u0000\u0001\u0002\u0003}\u007f\u008d\u008f"+ + "\u00a8\u00ad\u00af\u00b5\u00bf\u00ca\u00cf\u00d3\u00d6\u00e0\u00e6\u00fa"+ + "\u0101\u0107\u010a\u010f\u0115\u0117\u011b\u0120\u012b\u013a\u0183\u018f"+ + "\u019e\u01a0\u01b6\u01c0\u0010\u0001\u0004\u0000\u0001\u0005\u0001\u0000"+ + "\u0001\u0000\u0005\u0001\u0000\u0005\u0002\u0000\u00010\u0002\u0005\u0000"+ + "\u0000\u0007\u000f\u0000\u0004\u0000\u0000\u0007\f\u0000\u0002\u0003\u0000"+ + "\u00015\u0003\u0007\b\u0000\u00017\u0004\u0007+\u0000\u00018\u0005"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.interp b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.interp index dfee8f1641d..b80f4f5904d 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.interp +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.interp @@ -123,6 +123,7 @@ functionCall arguments index getAttr +legacyIndexAttr splat attrSplat fullSplat @@ -142,4 +143,4 @@ templateInterpolation atn: -[4, 1, 47, 361, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 1, 0, 1, 0, 1, 1, 5, 1, 84, 8, 1, 10, 1, 12, 1, 87, 9, 1, 1, 2, 1, 2, 3, 2, 91, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 5, 4, 99, 8, 4, 10, 4, 12, 4, 102, 9, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 111, 8, 5, 1, 6, 1, 6, 1, 6, 3, 6, 116, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 124, 8, 6, 10, 6, 12, 6, 127, 9, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 140, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 148, 8, 7, 10, 7, 12, 7, 151, 9, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 3, 10, 161, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 167, 8, 11, 10, 11, 12, 11, 170, 9, 11, 1, 11, 3, 11, 173, 8, 11, 3, 11, 175, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 5, 12, 181, 8, 12, 10, 12, 12, 12, 184, 9, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 194, 8, 13, 10, 13, 12, 13, 197, 9, 13, 1, 13, 3, 13, 200, 8, 13, 1, 13, 1, 13, 1, 13, 3, 13, 205, 8, 13, 1, 14, 1, 14, 3, 14, 209, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 216, 8, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 227, 8, 16, 1, 16, 3, 16, 230, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 3, 17, 237, 8, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 3, 20, 250, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 5, 21, 257, 8, 21, 10, 21, 12, 21, 260, 9, 21, 1, 21, 3, 21, 263, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 3, 24, 274, 8, 24, 1, 25, 1, 25, 1, 25, 5, 25, 279, 8, 25, 10, 25, 12, 25, 282, 9, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 5, 26, 289, 8, 26, 10, 26, 12, 26, 292, 9, 26, 1, 27, 1, 27, 3, 27, 296, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 3, 29, 303, 8, 29, 1, 29, 1, 29, 1, 29, 3, 29, 308, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 313, 8, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 325, 8, 34, 10, 34, 12, 34, 328, 9, 34, 4, 34, 330, 8, 34, 11, 34, 12, 34, 331, 1, 34, 1, 34, 1, 34, 5, 34, 337, 8, 34, 10, 34, 12, 34, 340, 9, 34, 1, 34, 3, 34, 343, 8, 34, 1, 35, 1, 35, 3, 35, 347, 8, 35, 1, 36, 1, 36, 1, 37, 1, 37, 3, 37, 353, 8, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 0, 2, 12, 14, 40, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 0, 7, 2, 0, 13, 14, 16, 16, 2, 0, 7, 7, 22, 22, 2, 0, 39, 39, 41, 41, 2, 0, 25, 25, 33, 33, 4, 0, 20, 21, 27, 28, 34, 34, 37, 37, 5, 0, 18, 18, 25, 25, 32, 32, 36, 36, 40, 40, 2, 0, 19, 19, 26, 26, 367, 0, 80, 1, 0, 0, 0, 2, 85, 1, 0, 0, 0, 4, 90, 1, 0, 0, 0, 6, 92, 1, 0, 0, 0, 8, 96, 1, 0, 0, 0, 10, 110, 1, 0, 0, 0, 12, 115, 1, 0, 0, 0, 14, 139, 1, 0, 0, 0, 16, 152, 1, 0, 0, 0, 18, 156, 1, 0, 0, 0, 20, 160, 1, 0, 0, 0, 22, 162, 1, 0, 0, 0, 24, 178, 1, 0, 0, 0, 26, 199, 1, 0, 0, 0, 28, 208, 1, 0, 0, 0, 30, 210, 1, 0, 0, 0, 32, 219, 1, 0, 0, 0, 34, 233, 1, 0, 0, 0, 36, 241, 1, 0, 0, 0, 38, 244, 1, 0, 0, 0, 40, 246, 1, 0, 0, 0, 42, 253, 1, 0, 0, 0, 44, 264, 1, 0, 0, 0, 46, 268, 1, 0, 0, 0, 48, 273, 1, 0, 0, 0, 50, 275, 1, 0, 0, 0, 52, 283, 1, 0, 0, 0, 54, 295, 1, 0, 0, 0, 56, 297, 1, 0, 0, 0, 58, 302, 1, 0, 0, 0, 60, 312, 1, 0, 0, 0, 62, 314, 1, 0, 0, 0, 64, 316, 1, 0, 0, 0, 66, 318, 1, 0, 0, 0, 68, 342, 1, 0, 0, 0, 70, 346, 1, 0, 0, 0, 72, 348, 1, 0, 0, 0, 74, 352, 1, 0, 0, 0, 76, 354, 1, 0, 0, 0, 78, 356, 1, 0, 0, 0, 80, 81, 3, 2, 1, 0, 81, 1, 1, 0, 0, 0, 82, 84, 3, 4, 2, 0, 83, 82, 1, 0, 0, 0, 84, 87, 1, 0, 0, 0, 85, 83, 1, 0, 0, 0, 85, 86, 1, 0, 0, 0, 86, 3, 1, 0, 0, 0, 87, 85, 1, 0, 0, 0, 88, 91, 3, 6, 3, 0, 89, 91, 3, 8, 4, 0, 90, 88, 1, 0, 0, 0, 90, 89, 1, 0, 0, 0, 91, 5, 1, 0, 0, 0, 92, 93, 5, 8, 0, 0, 93, 94, 5, 7, 0, 0, 94, 95, 3, 12, 6, 0, 95, 7, 1, 0, 0, 0, 96, 100, 5, 8, 0, 0, 97, 99, 3, 10, 5, 0, 98, 97, 1, 0, 0, 0, 99, 102, 1, 0, 0, 0, 100, 98, 1, 0, 0, 0, 100, 101, 1, 0, 0, 0, 101, 103, 1, 0, 0, 0, 102, 100, 1, 0, 0, 0, 103, 104, 3, 16, 8, 0, 104, 9, 1, 0, 0, 0, 105, 106, 5, 15, 0, 0, 106, 107, 3, 76, 38, 0, 107, 108, 5, 15, 0, 0, 108, 111, 1, 0, 0, 0, 109, 111, 5, 8, 0, 0, 110, 105, 1, 0, 0, 0, 110, 109, 1, 0, 0, 0, 111, 11, 1, 0, 0, 0, 112, 113, 6, 6, -1, 0, 113, 116, 3, 14, 7, 0, 114, 116, 3, 54, 27, 0, 115, 112, 1, 0, 0, 0, 115, 114, 1, 0, 0, 0, 116, 125, 1, 0, 0, 0, 117, 118, 10, 1, 0, 0, 118, 119, 5, 29, 0, 0, 119, 120, 3, 12, 6, 0, 120, 121, 5, 22, 0, 0, 121, 122, 3, 12, 6, 2, 122, 124, 1, 0, 0, 0, 123, 117, 1, 0, 0, 0, 124, 127, 1, 0, 0, 0, 125, 123, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 13, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 128, 129, 6, 7, -1, 0, 129, 140, 3, 68, 34, 0, 130, 140, 3, 18, 9, 0, 131, 140, 3, 28, 14, 0, 132, 140, 3, 20, 10, 0, 133, 140, 3, 38, 19, 0, 134, 140, 3, 40, 20, 0, 135, 136, 5, 24, 0, 0, 136, 137, 3, 12, 6, 0, 137, 138, 5, 31, 0, 0, 138, 140, 1, 0, 0, 0, 139, 128, 1, 0, 0, 0, 139, 130, 1, 0, 0, 0, 139, 131, 1, 0, 0, 0, 139, 132, 1, 0, 0, 0, 139, 133, 1, 0, 0, 0, 139, 134, 1, 0, 0, 0, 139, 135, 1, 0, 0, 0, 140, 149, 1, 0, 0, 0, 141, 142, 10, 4, 0, 0, 142, 148, 3, 44, 22, 0, 143, 144, 10, 3, 0, 0, 144, 148, 3, 46, 23, 0, 145, 146, 10, 2, 0, 0, 146, 148, 3, 48, 24, 0, 147, 141, 1, 0, 0, 0, 147, 143, 1, 0, 0, 0, 147, 145, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0, 0, 0, 149, 150, 1, 0, 0, 0, 150, 15, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 152, 153, 5, 5, 0, 0, 153, 154, 3, 2, 1, 0, 154, 155, 5, 6, 0, 0, 155, 17, 1, 0, 0, 0, 156, 157, 7, 0, 0, 0, 157, 19, 1, 0, 0, 0, 158, 161, 3, 22, 11, 0, 159, 161, 3, 24, 12, 0, 160, 158, 1, 0, 0, 0, 160, 159, 1, 0, 0, 0, 161, 21, 1, 0, 0, 0, 162, 174, 5, 23, 0, 0, 163, 168, 3, 12, 6, 0, 164, 165, 5, 39, 0, 0, 165, 167, 3, 12, 6, 0, 166, 164, 1, 0, 0, 0, 167, 170, 1, 0, 0, 0, 168, 166, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169, 172, 1, 0, 0, 0, 170, 168, 1, 0, 0, 0, 171, 173, 5, 39, 0, 0, 172, 171, 1, 0, 0, 0, 172, 173, 1, 0, 0, 0, 173, 175, 1, 0, 0, 0, 174, 163, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 30, 0, 0, 177, 23, 1, 0, 0, 0, 178, 182, 5, 5, 0, 0, 179, 181, 3, 26, 13, 0, 180, 179, 1, 0, 0, 0, 181, 184, 1, 0, 0, 0, 182, 180, 1, 0, 0, 0, 182, 183, 1, 0, 0, 0, 183, 185, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 186, 5, 6, 0, 0, 186, 25, 1, 0, 0, 0, 187, 200, 5, 8, 0, 0, 188, 189, 5, 24, 0, 0, 189, 190, 5, 8, 0, 0, 190, 200, 5, 31, 0, 0, 191, 195, 5, 15, 0, 0, 192, 194, 3, 74, 37, 0, 193, 192, 1, 0, 0, 0, 194, 197, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 198, 1, 0, 0, 0, 197, 195, 1, 0, 0, 0, 198, 200, 5, 15, 0, 0, 199, 187, 1, 0, 0, 0, 199, 188, 1, 0, 0, 0, 199, 191, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 202, 7, 1, 0, 0, 202, 204, 3, 12, 6, 0, 203, 205, 5, 39, 0, 0, 204, 203, 1, 0, 0, 0, 204, 205, 1, 0, 0, 0, 205, 27, 1, 0, 0, 0, 206, 209, 3, 30, 15, 0, 207, 209, 3, 32, 16, 0, 208, 206, 1, 0, 0, 0, 208, 207, 1, 0, 0, 0, 209, 29, 1, 0, 0, 0, 210, 211, 5, 2, 0, 0, 211, 212, 3, 34, 17, 0, 212, 213, 5, 22, 0, 0, 213, 215, 3, 12, 6, 0, 214, 216, 3, 36, 18, 0, 215, 214, 1, 0, 0, 0, 215, 216, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 218, 5, 30, 0, 0, 218, 31, 1, 0, 0, 0, 219, 220, 5, 1, 0, 0, 220, 221, 3, 34, 17, 0, 221, 222, 5, 22, 0, 0, 222, 223, 3, 12, 6, 0, 223, 224, 5, 38, 0, 0, 224, 226, 3, 12, 6, 0, 225, 227, 5, 41, 0, 0, 226, 225, 1, 0, 0, 0, 226, 227, 1, 0, 0, 0, 227, 229, 1, 0, 0, 0, 228, 230, 3, 36, 18, 0, 229, 228, 1, 0, 0, 0, 229, 230, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 232, 5, 6, 0, 0, 232, 33, 1, 0, 0, 0, 233, 236, 5, 8, 0, 0, 234, 235, 5, 39, 0, 0, 235, 237, 5, 8, 0, 0, 236, 234, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 5, 4, 0, 0, 239, 240, 3, 12, 6, 0, 240, 35, 1, 0, 0, 0, 241, 242, 5, 3, 0, 0, 242, 243, 3, 12, 6, 0, 243, 37, 1, 0, 0, 0, 244, 245, 5, 8, 0, 0, 245, 39, 1, 0, 0, 0, 246, 247, 5, 8, 0, 0, 247, 249, 5, 24, 0, 0, 248, 250, 3, 42, 21, 0, 249, 248, 1, 0, 0, 0, 249, 250, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 252, 5, 31, 0, 0, 252, 41, 1, 0, 0, 0, 253, 258, 3, 12, 6, 0, 254, 255, 5, 39, 0, 0, 255, 257, 3, 12, 6, 0, 256, 254, 1, 0, 0, 0, 257, 260, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 258, 259, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 261, 263, 7, 2, 0, 0, 262, 261, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 43, 1, 0, 0, 0, 264, 265, 5, 23, 0, 0, 265, 266, 3, 12, 6, 0, 266, 267, 5, 30, 0, 0, 267, 45, 1, 0, 0, 0, 268, 269, 5, 35, 0, 0, 269, 270, 5, 8, 0, 0, 270, 47, 1, 0, 0, 0, 271, 274, 3, 50, 25, 0, 272, 274, 3, 52, 26, 0, 273, 271, 1, 0, 0, 0, 273, 272, 1, 0, 0, 0, 274, 49, 1, 0, 0, 0, 275, 276, 5, 35, 0, 0, 276, 280, 5, 32, 0, 0, 277, 279, 3, 46, 23, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 51, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 284, 5, 23, 0, 0, 284, 285, 5, 32, 0, 0, 285, 290, 5, 30, 0, 0, 286, 289, 3, 46, 23, 0, 287, 289, 3, 44, 22, 0, 288, 286, 1, 0, 0, 0, 288, 287, 1, 0, 0, 0, 289, 292, 1, 0, 0, 0, 290, 288, 1, 0, 0, 0, 290, 291, 1, 0, 0, 0, 291, 53, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 293, 296, 3, 56, 28, 0, 294, 296, 3, 58, 29, 0, 295, 293, 1, 0, 0, 0, 295, 294, 1, 0, 0, 0, 296, 55, 1, 0, 0, 0, 297, 298, 7, 3, 0, 0, 298, 299, 3, 14, 7, 0, 299, 57, 1, 0, 0, 0, 300, 303, 3, 14, 7, 0, 301, 303, 3, 56, 28, 0, 302, 300, 1, 0, 0, 0, 302, 301, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 3, 60, 30, 0, 305, 308, 3, 14, 7, 0, 306, 308, 3, 54, 27, 0, 307, 305, 1, 0, 0, 0, 307, 306, 1, 0, 0, 0, 308, 59, 1, 0, 0, 0, 309, 313, 3, 62, 31, 0, 310, 313, 3, 64, 32, 0, 311, 313, 3, 66, 33, 0, 312, 309, 1, 0, 0, 0, 312, 310, 1, 0, 0, 0, 312, 311, 1, 0, 0, 0, 313, 61, 1, 0, 0, 0, 314, 315, 7, 4, 0, 0, 315, 63, 1, 0, 0, 0, 316, 317, 7, 5, 0, 0, 317, 65, 1, 0, 0, 0, 318, 319, 7, 6, 0, 0, 319, 67, 1, 0, 0, 0, 320, 321, 5, 17, 0, 0, 321, 329, 5, 8, 0, 0, 322, 326, 5, 12, 0, 0, 323, 325, 3, 70, 35, 0, 324, 323, 1, 0, 0, 0, 325, 328, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0, 327, 330, 1, 0, 0, 0, 328, 326, 1, 0, 0, 0, 329, 322, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 329, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 343, 5, 8, 0, 0, 334, 338, 5, 15, 0, 0, 335, 337, 3, 74, 37, 0, 336, 335, 1, 0, 0, 0, 337, 340, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 341, 1, 0, 0, 0, 340, 338, 1, 0, 0, 0, 341, 343, 5, 15, 0, 0, 342, 320, 1, 0, 0, 0, 342, 334, 1, 0, 0, 0, 343, 69, 1, 0, 0, 0, 344, 347, 3, 78, 39, 0, 345, 347, 3, 72, 36, 0, 346, 344, 1, 0, 0, 0, 346, 345, 1, 0, 0, 0, 347, 71, 1, 0, 0, 0, 348, 349, 5, 46, 0, 0, 349, 73, 1, 0, 0, 0, 350, 353, 3, 78, 39, 0, 351, 353, 3, 76, 38, 0, 352, 350, 1, 0, 0, 0, 352, 351, 1, 0, 0, 0, 353, 75, 1, 0, 0, 0, 354, 355, 5, 44, 0, 0, 355, 77, 1, 0, 0, 0, 356, 357, 5, 43, 0, 0, 357, 358, 3, 12, 6, 0, 358, 359, 5, 6, 0, 0, 359, 79, 1, 0, 0, 0, 39, 85, 90, 100, 110, 115, 125, 139, 147, 149, 160, 168, 172, 174, 182, 195, 199, 204, 208, 215, 226, 229, 236, 249, 258, 262, 273, 280, 288, 290, 295, 302, 307, 312, 326, 331, 338, 342, 346, 352] \ No newline at end of file +[4, 1, 47, 369, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 1, 0, 1, 0, 1, 1, 5, 1, 86, 8, 1, 10, 1, 12, 1, 89, 9, 1, 1, 2, 1, 2, 3, 2, 93, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 5, 4, 101, 8, 4, 10, 4, 12, 4, 104, 9, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 113, 8, 5, 1, 6, 1, 6, 1, 6, 3, 6, 118, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 126, 8, 6, 10, 6, 12, 6, 129, 9, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 142, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 152, 8, 7, 10, 7, 12, 7, 155, 9, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 3, 10, 165, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 171, 8, 11, 10, 11, 12, 11, 174, 9, 11, 1, 11, 3, 11, 177, 8, 11, 3, 11, 179, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 5, 12, 185, 8, 12, 10, 12, 12, 12, 188, 9, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 198, 8, 13, 10, 13, 12, 13, 201, 9, 13, 1, 13, 1, 13, 3, 13, 205, 8, 13, 1, 13, 1, 13, 1, 13, 3, 13, 210, 8, 13, 1, 14, 1, 14, 3, 14, 214, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 221, 8, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 232, 8, 16, 1, 16, 3, 16, 235, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 3, 17, 242, 8, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 3, 20, 255, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 5, 21, 262, 8, 21, 10, 21, 12, 21, 265, 9, 21, 1, 21, 3, 21, 268, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 3, 25, 282, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 287, 8, 26, 10, 26, 12, 26, 290, 9, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 5, 27, 297, 8, 27, 10, 27, 12, 27, 300, 9, 27, 1, 28, 1, 28, 3, 28, 304, 8, 28, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 3, 30, 311, 8, 30, 1, 30, 1, 30, 1, 30, 3, 30, 316, 8, 30, 1, 31, 1, 31, 1, 31, 3, 31, 321, 8, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 333, 8, 35, 10, 35, 12, 35, 336, 9, 35, 4, 35, 338, 8, 35, 11, 35, 12, 35, 339, 1, 35, 1, 35, 1, 35, 5, 35, 345, 8, 35, 10, 35, 12, 35, 348, 9, 35, 1, 35, 3, 35, 351, 8, 35, 1, 36, 1, 36, 3, 36, 355, 8, 36, 1, 37, 1, 37, 1, 38, 1, 38, 3, 38, 361, 8, 38, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 0, 2, 12, 14, 41, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 0, 7, 2, 0, 13, 14, 16, 16, 2, 0, 7, 7, 22, 22, 2, 0, 39, 39, 41, 41, 2, 0, 25, 25, 33, 33, 4, 0, 20, 21, 27, 28, 34, 34, 37, 37, 5, 0, 18, 18, 25, 25, 32, 32, 36, 36, 40, 40, 2, 0, 19, 19, 26, 26, 376, 0, 82, 1, 0, 0, 0, 2, 87, 1, 0, 0, 0, 4, 92, 1, 0, 0, 0, 6, 94, 1, 0, 0, 0, 8, 98, 1, 0, 0, 0, 10, 112, 1, 0, 0, 0, 12, 117, 1, 0, 0, 0, 14, 141, 1, 0, 0, 0, 16, 156, 1, 0, 0, 0, 18, 160, 1, 0, 0, 0, 20, 164, 1, 0, 0, 0, 22, 166, 1, 0, 0, 0, 24, 182, 1, 0, 0, 0, 26, 204, 1, 0, 0, 0, 28, 213, 1, 0, 0, 0, 30, 215, 1, 0, 0, 0, 32, 224, 1, 0, 0, 0, 34, 238, 1, 0, 0, 0, 36, 246, 1, 0, 0, 0, 38, 249, 1, 0, 0, 0, 40, 251, 1, 0, 0, 0, 42, 258, 1, 0, 0, 0, 44, 269, 1, 0, 0, 0, 46, 273, 1, 0, 0, 0, 48, 276, 1, 0, 0, 0, 50, 281, 1, 0, 0, 0, 52, 283, 1, 0, 0, 0, 54, 291, 1, 0, 0, 0, 56, 303, 1, 0, 0, 0, 58, 305, 1, 0, 0, 0, 60, 310, 1, 0, 0, 0, 62, 320, 1, 0, 0, 0, 64, 322, 1, 0, 0, 0, 66, 324, 1, 0, 0, 0, 68, 326, 1, 0, 0, 0, 70, 350, 1, 0, 0, 0, 72, 354, 1, 0, 0, 0, 74, 356, 1, 0, 0, 0, 76, 360, 1, 0, 0, 0, 78, 362, 1, 0, 0, 0, 80, 364, 1, 0, 0, 0, 82, 83, 3, 2, 1, 0, 83, 1, 1, 0, 0, 0, 84, 86, 3, 4, 2, 0, 85, 84, 1, 0, 0, 0, 86, 89, 1, 0, 0, 0, 87, 85, 1, 0, 0, 0, 87, 88, 1, 0, 0, 0, 88, 3, 1, 0, 0, 0, 89, 87, 1, 0, 0, 0, 90, 93, 3, 6, 3, 0, 91, 93, 3, 8, 4, 0, 92, 90, 1, 0, 0, 0, 92, 91, 1, 0, 0, 0, 93, 5, 1, 0, 0, 0, 94, 95, 5, 8, 0, 0, 95, 96, 5, 7, 0, 0, 96, 97, 3, 12, 6, 0, 97, 7, 1, 0, 0, 0, 98, 102, 5, 8, 0, 0, 99, 101, 3, 10, 5, 0, 100, 99, 1, 0, 0, 0, 101, 104, 1, 0, 0, 0, 102, 100, 1, 0, 0, 0, 102, 103, 1, 0, 0, 0, 103, 105, 1, 0, 0, 0, 104, 102, 1, 0, 0, 0, 105, 106, 3, 16, 8, 0, 106, 9, 1, 0, 0, 0, 107, 108, 5, 15, 0, 0, 108, 109, 3, 78, 39, 0, 109, 110, 5, 15, 0, 0, 110, 113, 1, 0, 0, 0, 111, 113, 5, 8, 0, 0, 112, 107, 1, 0, 0, 0, 112, 111, 1, 0, 0, 0, 113, 11, 1, 0, 0, 0, 114, 115, 6, 6, -1, 0, 115, 118, 3, 14, 7, 0, 116, 118, 3, 56, 28, 0, 117, 114, 1, 0, 0, 0, 117, 116, 1, 0, 0, 0, 118, 127, 1, 0, 0, 0, 119, 120, 10, 1, 0, 0, 120, 121, 5, 29, 0, 0, 121, 122, 3, 12, 6, 0, 122, 123, 5, 22, 0, 0, 123, 124, 3, 12, 6, 2, 124, 126, 1, 0, 0, 0, 125, 119, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 13, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 130, 131, 6, 7, -1, 0, 131, 142, 3, 70, 35, 0, 132, 142, 3, 18, 9, 0, 133, 142, 3, 28, 14, 0, 134, 142, 3, 20, 10, 0, 135, 142, 3, 38, 19, 0, 136, 142, 3, 40, 20, 0, 137, 138, 5, 24, 0, 0, 138, 139, 3, 12, 6, 0, 139, 140, 5, 31, 0, 0, 140, 142, 1, 0, 0, 0, 141, 130, 1, 0, 0, 0, 141, 132, 1, 0, 0, 0, 141, 133, 1, 0, 0, 0, 141, 134, 1, 0, 0, 0, 141, 135, 1, 0, 0, 0, 141, 136, 1, 0, 0, 0, 141, 137, 1, 0, 0, 0, 142, 153, 1, 0, 0, 0, 143, 144, 10, 5, 0, 0, 144, 152, 3, 44, 22, 0, 145, 146, 10, 4, 0, 0, 146, 152, 3, 46, 23, 0, 147, 148, 10, 3, 0, 0, 148, 152, 3, 48, 24, 0, 149, 150, 10, 2, 0, 0, 150, 152, 3, 50, 25, 0, 151, 143, 1, 0, 0, 0, 151, 145, 1, 0, 0, 0, 151, 147, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 152, 155, 1, 0, 0, 0, 153, 151, 1, 0, 0, 0, 153, 154, 1, 0, 0, 0, 154, 15, 1, 0, 0, 0, 155, 153, 1, 0, 0, 0, 156, 157, 5, 5, 0, 0, 157, 158, 3, 2, 1, 0, 158, 159, 5, 6, 0, 0, 159, 17, 1, 0, 0, 0, 160, 161, 7, 0, 0, 0, 161, 19, 1, 0, 0, 0, 162, 165, 3, 22, 11, 0, 163, 165, 3, 24, 12, 0, 164, 162, 1, 0, 0, 0, 164, 163, 1, 0, 0, 0, 165, 21, 1, 0, 0, 0, 166, 178, 5, 23, 0, 0, 167, 172, 3, 12, 6, 0, 168, 169, 5, 39, 0, 0, 169, 171, 3, 12, 6, 0, 170, 168, 1, 0, 0, 0, 171, 174, 1, 0, 0, 0, 172, 170, 1, 0, 0, 0, 172, 173, 1, 0, 0, 0, 173, 176, 1, 0, 0, 0, 174, 172, 1, 0, 0, 0, 175, 177, 5, 39, 0, 0, 176, 175, 1, 0, 0, 0, 176, 177, 1, 0, 0, 0, 177, 179, 1, 0, 0, 0, 178, 167, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 180, 1, 0, 0, 0, 180, 181, 5, 30, 0, 0, 181, 23, 1, 0, 0, 0, 182, 186, 5, 5, 0, 0, 183, 185, 3, 26, 13, 0, 184, 183, 1, 0, 0, 0, 185, 188, 1, 0, 0, 0, 186, 184, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 189, 1, 0, 0, 0, 188, 186, 1, 0, 0, 0, 189, 190, 5, 6, 0, 0, 190, 25, 1, 0, 0, 0, 191, 205, 5, 8, 0, 0, 192, 193, 5, 24, 0, 0, 193, 194, 5, 8, 0, 0, 194, 205, 5, 31, 0, 0, 195, 199, 5, 15, 0, 0, 196, 198, 3, 76, 38, 0, 197, 196, 1, 0, 0, 0, 198, 201, 1, 0, 0, 0, 199, 197, 1, 0, 0, 0, 199, 200, 1, 0, 0, 0, 200, 202, 1, 0, 0, 0, 201, 199, 1, 0, 0, 0, 202, 205, 5, 15, 0, 0, 203, 205, 3, 12, 6, 0, 204, 191, 1, 0, 0, 0, 204, 192, 1, 0, 0, 0, 204, 195, 1, 0, 0, 0, 204, 203, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 207, 7, 1, 0, 0, 207, 209, 3, 12, 6, 0, 208, 210, 5, 39, 0, 0, 209, 208, 1, 0, 0, 0, 209, 210, 1, 0, 0, 0, 210, 27, 1, 0, 0, 0, 211, 214, 3, 30, 15, 0, 212, 214, 3, 32, 16, 0, 213, 211, 1, 0, 0, 0, 213, 212, 1, 0, 0, 0, 214, 29, 1, 0, 0, 0, 215, 216, 5, 2, 0, 0, 216, 217, 3, 34, 17, 0, 217, 218, 5, 22, 0, 0, 218, 220, 3, 12, 6, 0, 219, 221, 3, 36, 18, 0, 220, 219, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 222, 1, 0, 0, 0, 222, 223, 5, 30, 0, 0, 223, 31, 1, 0, 0, 0, 224, 225, 5, 1, 0, 0, 225, 226, 3, 34, 17, 0, 226, 227, 5, 22, 0, 0, 227, 228, 3, 12, 6, 0, 228, 229, 5, 38, 0, 0, 229, 231, 3, 12, 6, 0, 230, 232, 5, 41, 0, 0, 231, 230, 1, 0, 0, 0, 231, 232, 1, 0, 0, 0, 232, 234, 1, 0, 0, 0, 233, 235, 3, 36, 18, 0, 234, 233, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, 236, 1, 0, 0, 0, 236, 237, 5, 6, 0, 0, 237, 33, 1, 0, 0, 0, 238, 241, 5, 8, 0, 0, 239, 240, 5, 39, 0, 0, 240, 242, 5, 8, 0, 0, 241, 239, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 1, 0, 0, 0, 243, 244, 5, 4, 0, 0, 244, 245, 3, 12, 6, 0, 245, 35, 1, 0, 0, 0, 246, 247, 5, 3, 0, 0, 247, 248, 3, 12, 6, 0, 248, 37, 1, 0, 0, 0, 249, 250, 5, 8, 0, 0, 250, 39, 1, 0, 0, 0, 251, 252, 5, 8, 0, 0, 252, 254, 5, 24, 0, 0, 253, 255, 3, 42, 21, 0, 254, 253, 1, 0, 0, 0, 254, 255, 1, 0, 0, 0, 255, 256, 1, 0, 0, 0, 256, 257, 5, 31, 0, 0, 257, 41, 1, 0, 0, 0, 258, 263, 3, 12, 6, 0, 259, 260, 5, 39, 0, 0, 260, 262, 3, 12, 6, 0, 261, 259, 1, 0, 0, 0, 262, 265, 1, 0, 0, 0, 263, 261, 1, 0, 0, 0, 263, 264, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 266, 268, 7, 2, 0, 0, 267, 266, 1, 0, 0, 0, 267, 268, 1, 0, 0, 0, 268, 43, 1, 0, 0, 0, 269, 270, 5, 23, 0, 0, 270, 271, 3, 12, 6, 0, 271, 272, 5, 30, 0, 0, 272, 45, 1, 0, 0, 0, 273, 274, 5, 35, 0, 0, 274, 275, 5, 8, 0, 0, 275, 47, 1, 0, 0, 0, 276, 277, 5, 35, 0, 0, 277, 278, 5, 13, 0, 0, 278, 49, 1, 0, 0, 0, 279, 282, 3, 52, 26, 0, 280, 282, 3, 54, 27, 0, 281, 279, 1, 0, 0, 0, 281, 280, 1, 0, 0, 0, 282, 51, 1, 0, 0, 0, 283, 284, 5, 35, 0, 0, 284, 288, 5, 32, 0, 0, 285, 287, 3, 46, 23, 0, 286, 285, 1, 0, 0, 0, 287, 290, 1, 0, 0, 0, 288, 286, 1, 0, 0, 0, 288, 289, 1, 0, 0, 0, 289, 53, 1, 0, 0, 0, 290, 288, 1, 0, 0, 0, 291, 292, 5, 23, 0, 0, 292, 293, 5, 32, 0, 0, 293, 298, 5, 30, 0, 0, 294, 297, 3, 46, 23, 0, 295, 297, 3, 44, 22, 0, 296, 294, 1, 0, 0, 0, 296, 295, 1, 0, 0, 0, 297, 300, 1, 0, 0, 0, 298, 296, 1, 0, 0, 0, 298, 299, 1, 0, 0, 0, 299, 55, 1, 0, 0, 0, 300, 298, 1, 0, 0, 0, 301, 304, 3, 58, 29, 0, 302, 304, 3, 60, 30, 0, 303, 301, 1, 0, 0, 0, 303, 302, 1, 0, 0, 0, 304, 57, 1, 0, 0, 0, 305, 306, 7, 3, 0, 0, 306, 307, 3, 14, 7, 0, 307, 59, 1, 0, 0, 0, 308, 311, 3, 14, 7, 0, 309, 311, 3, 58, 29, 0, 310, 308, 1, 0, 0, 0, 310, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 315, 3, 62, 31, 0, 313, 316, 3, 14, 7, 0, 314, 316, 3, 56, 28, 0, 315, 313, 1, 0, 0, 0, 315, 314, 1, 0, 0, 0, 316, 61, 1, 0, 0, 0, 317, 321, 3, 64, 32, 0, 318, 321, 3, 66, 33, 0, 319, 321, 3, 68, 34, 0, 320, 317, 1, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 319, 1, 0, 0, 0, 321, 63, 1, 0, 0, 0, 322, 323, 7, 4, 0, 0, 323, 65, 1, 0, 0, 0, 324, 325, 7, 5, 0, 0, 325, 67, 1, 0, 0, 0, 326, 327, 7, 6, 0, 0, 327, 69, 1, 0, 0, 0, 328, 329, 5, 17, 0, 0, 329, 337, 5, 8, 0, 0, 330, 334, 5, 12, 0, 0, 331, 333, 3, 72, 36, 0, 332, 331, 1, 0, 0, 0, 333, 336, 1, 0, 0, 0, 334, 332, 1, 0, 0, 0, 334, 335, 1, 0, 0, 0, 335, 338, 1, 0, 0, 0, 336, 334, 1, 0, 0, 0, 337, 330, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 337, 1, 0, 0, 0, 339, 340, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 351, 5, 8, 0, 0, 342, 346, 5, 15, 0, 0, 343, 345, 3, 76, 38, 0, 344, 343, 1, 0, 0, 0, 345, 348, 1, 0, 0, 0, 346, 344, 1, 0, 0, 0, 346, 347, 1, 0, 0, 0, 347, 349, 1, 0, 0, 0, 348, 346, 1, 0, 0, 0, 349, 351, 5, 15, 0, 0, 350, 328, 1, 0, 0, 0, 350, 342, 1, 0, 0, 0, 351, 71, 1, 0, 0, 0, 352, 355, 3, 80, 40, 0, 353, 355, 3, 74, 37, 0, 354, 352, 1, 0, 0, 0, 354, 353, 1, 0, 0, 0, 355, 73, 1, 0, 0, 0, 356, 357, 5, 46, 0, 0, 357, 75, 1, 0, 0, 0, 358, 361, 3, 80, 40, 0, 359, 361, 3, 78, 39, 0, 360, 358, 1, 0, 0, 0, 360, 359, 1, 0, 0, 0, 361, 77, 1, 0, 0, 0, 362, 363, 5, 44, 0, 0, 363, 79, 1, 0, 0, 0, 364, 365, 5, 43, 0, 0, 365, 366, 3, 12, 6, 0, 366, 367, 5, 6, 0, 0, 367, 81, 1, 0, 0, 0, 39, 87, 92, 102, 112, 117, 127, 141, 151, 153, 164, 172, 176, 178, 186, 199, 204, 209, 213, 220, 231, 234, 241, 254, 263, 267, 281, 288, 296, 298, 303, 310, 315, 320, 334, 339, 346, 350, 354, 360] \ No newline at end of file diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.java index 94796886905..2827484b3c4 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.hcl.internal.grammar; - -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; - +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class HCLParser extends Parser { @@ -36,34 +32,35 @@ public class HCLParser extends Parser { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - FOR_BRACE=1, FOR_BRACK=2, IF=3, IN=4, LBRACE=5, RBRACE=6, ASSIGN=7, Identifier=8, - WS=9, COMMENT=10, LINE_COMMENT=11, NEWLINE=12, NumericLiteral=13, BooleanLiteral=14, - QUOTE=15, NULL=16, HEREDOC_START=17, PLUS=18, AND=19, EQ=20, LT=21, COLON=22, - LBRACK=23, LPAREN=24, MINUS=25, OR=26, NEQ=27, GT=28, QUESTION=29, RBRACK=30, - RPAREN=31, MUL=32, NOT=33, LEQ=34, DOT=35, DIV=36, GEQ=37, ARROW=38, COMMA=39, - MOD=40, ELLIPSIS=41, TILDE=42, TEMPLATE_INTERPOLATION_START=43, TemplateStringLiteral=44, + FOR_BRACE=1, FOR_BRACK=2, IF=3, IN=4, LBRACE=5, RBRACE=6, ASSIGN=7, Identifier=8, + WS=9, COMMENT=10, LINE_COMMENT=11, NEWLINE=12, NumericLiteral=13, BooleanLiteral=14, + QUOTE=15, NULL=16, HEREDOC_START=17, PLUS=18, AND=19, EQ=20, LT=21, COLON=22, + LBRACK=23, LPAREN=24, MINUS=25, OR=26, NEQ=27, GT=28, QUESTION=29, RBRACK=30, + RPAREN=31, MUL=32, NOT=33, LEQ=34, DOT=35, DIV=36, GEQ=37, ARROW=38, COMMA=39, + MOD=40, ELLIPSIS=41, TILDE=42, TEMPLATE_INTERPOLATION_START=43, TemplateStringLiteral=44, TemplateStringLiteralChar=45, HTemplateLiteral=46, HTemplateLiteralChar=47; public static final int - RULE_configFile = 0, RULE_body = 1, RULE_bodyContent = 2, RULE_attribute = 3, - RULE_block = 4, RULE_blockLabel = 5, RULE_expression = 6, RULE_exprTerm = 7, - RULE_blockExpr = 8, RULE_literalValue = 9, RULE_collectionValue = 10, - RULE_tuple = 11, RULE_object = 12, RULE_objectelem = 13, RULE_forExpr = 14, - RULE_forTupleExpr = 15, RULE_forObjectExpr = 16, RULE_forIntro = 17, RULE_forCond = 18, - RULE_variableExpr = 19, RULE_functionCall = 20, RULE_arguments = 21, RULE_index = 22, - RULE_getAttr = 23, RULE_splat = 24, RULE_attrSplat = 25, RULE_fullSplat = 26, - RULE_operation = 27, RULE_unaryOp = 28, RULE_binaryOp = 29, RULE_binaryOperator = 30, - RULE_compareOperator = 31, RULE_arithmeticOperator = 32, RULE_logicOperator = 33, - RULE_templateExpr = 34, RULE_heredocTemplatePart = 35, RULE_heredocLiteral = 36, - RULE_quotedTemplatePart = 37, RULE_stringLiteral = 38, RULE_templateInterpolation = 39; + RULE_configFile = 0, RULE_body = 1, RULE_bodyContent = 2, RULE_attribute = 3, + RULE_block = 4, RULE_blockLabel = 5, RULE_expression = 6, RULE_exprTerm = 7, + RULE_blockExpr = 8, RULE_literalValue = 9, RULE_collectionValue = 10, + RULE_tuple = 11, RULE_object = 12, RULE_objectelem = 13, RULE_forExpr = 14, + RULE_forTupleExpr = 15, RULE_forObjectExpr = 16, RULE_forIntro = 17, RULE_forCond = 18, + RULE_variableExpr = 19, RULE_functionCall = 20, RULE_arguments = 21, RULE_index = 22, + RULE_getAttr = 23, RULE_legacyIndexAttr = 24, RULE_splat = 25, RULE_attrSplat = 26, + RULE_fullSplat = 27, RULE_operation = 28, RULE_unaryOp = 29, RULE_binaryOp = 30, + RULE_binaryOperator = 31, RULE_compareOperator = 32, RULE_arithmeticOperator = 33, + RULE_logicOperator = 34, RULE_templateExpr = 35, RULE_heredocTemplatePart = 36, + RULE_heredocLiteral = 37, RULE_quotedTemplatePart = 38, RULE_stringLiteral = 39, + RULE_templateInterpolation = 40; private static String[] makeRuleNames() { return new String[] { - "configFile", "body", "bodyContent", "attribute", "block", "blockLabel", - "expression", "exprTerm", "blockExpr", "literalValue", "collectionValue", - "tuple", "object", "objectelem", "forExpr", "forTupleExpr", "forObjectExpr", - "forIntro", "forCond", "variableExpr", "functionCall", "arguments", "index", - "getAttr", "splat", "attrSplat", "fullSplat", "operation", "unaryOp", - "binaryOp", "binaryOperator", "compareOperator", "arithmeticOperator", - "logicOperator", "templateExpr", "heredocTemplatePart", "heredocLiteral", + "configFile", "body", "bodyContent", "attribute", "block", "blockLabel", + "expression", "exprTerm", "blockExpr", "literalValue", "collectionValue", + "tuple", "object", "objectelem", "forExpr", "forTupleExpr", "forObjectExpr", + "forIntro", "forCond", "variableExpr", "functionCall", "arguments", "index", + "getAttr", "legacyIndexAttr", "splat", "attrSplat", "fullSplat", "operation", + "unaryOp", "binaryOp", "binaryOperator", "compareOperator", "arithmeticOperator", + "logicOperator", "templateExpr", "heredocTemplatePart", "heredocLiteral", "quotedTemplatePart", "stringLiteral", "templateInterpolation" }; } @@ -71,23 +68,23 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, null, null, "'if'", "'in'", "'{'", "'}'", "'='", null, null, null, - null, null, null, null, null, "'null'", null, "'+'", "'&&'", "'=='", - "'<'", "':'", "'['", "'('", "'-'", "'||'", "'!='", "'>'", "'?'", "']'", - "')'", "'*'", "'!'", "'<='", "'.'", "'/'", "'>='", "'=>'", "','", "'%'", + null, null, null, "'if'", "'in'", "'{'", "'}'", "'='", null, null, null, + null, null, null, null, null, "'null'", null, "'+'", "'&&'", "'=='", + "'<'", "':'", "'['", "'('", "'-'", "'||'", "'!='", "'>'", "'?'", "']'", + "')'", "'*'", "'!'", "'<='", "'.'", "'/'", "'>='", "'=>'", "','", "'%'", "'...'", "'~'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "FOR_BRACE", "FOR_BRACK", "IF", "IN", "LBRACE", "RBRACE", "ASSIGN", - "Identifier", "WS", "COMMENT", "LINE_COMMENT", "NEWLINE", "NumericLiteral", - "BooleanLiteral", "QUOTE", "NULL", "HEREDOC_START", "PLUS", "AND", "EQ", - "LT", "COLON", "LBRACK", "LPAREN", "MINUS", "OR", "NEQ", "GT", "QUESTION", - "RBRACK", "RPAREN", "MUL", "NOT", "LEQ", "DOT", "DIV", "GEQ", "ARROW", - "COMMA", "MOD", "ELLIPSIS", "TILDE", "TEMPLATE_INTERPOLATION_START", - "TemplateStringLiteral", "TemplateStringLiteralChar", "HTemplateLiteral", + null, "FOR_BRACE", "FOR_BRACK", "IF", "IN", "LBRACE", "RBRACE", "ASSIGN", + "Identifier", "WS", "COMMENT", "LINE_COMMENT", "NEWLINE", "NumericLiteral", + "BooleanLiteral", "QUOTE", "NULL", "HEREDOC_START", "PLUS", "AND", "EQ", + "LT", "COLON", "LBRACK", "LPAREN", "MINUS", "OR", "NEQ", "GT", "QUESTION", + "RBRACK", "RPAREN", "MUL", "NOT", "LEQ", "DOT", "DIV", "GEQ", "ARROW", + "COMMA", "MOD", "ELLIPSIS", "TILDE", "TEMPLATE_INTERPOLATION_START", + "TemplateStringLiteral", "TemplateStringLiteralChar", "HTemplateLiteral", "HTemplateLiteralChar" }; } @@ -172,7 +169,7 @@ public final ConfigFileContext configFile() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(80); + setState(82); body(); } } @@ -221,17 +218,17 @@ public final BodyContext body() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(85); + setState(87); _errHandler.sync(this); _la = _input.LA(1); while (_la==Identifier) { { { - setState(82); + setState(84); bodyContent(); } } - setState(87); + setState(89); _errHandler.sync(this); _la = _input.LA(1); } @@ -279,20 +276,20 @@ public final BodyContentContext bodyContent() throws RecognitionException { BodyContentContext _localctx = new BodyContentContext(_ctx, getState()); enterRule(_localctx, 4, RULE_bodyContent); try { - setState(90); + setState(92); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(88); + setState(90); attribute(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(89); + setState(91); block(); } break; @@ -341,11 +338,11 @@ public final AttributeContext attribute() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(92); + setState(94); match(Identifier); - setState(93); + setState(95); match(ASSIGN); - setState(94); + setState(96); expression(0); } } @@ -398,23 +395,23 @@ public final BlockContext block() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(96); + setState(98); match(Identifier); - setState(100); + setState(102); _errHandler.sync(this); _la = _input.LA(1); while (_la==Identifier || _la==QUOTE) { { { - setState(97); + setState(99); blockLabel(); } } - setState(102); + setState(104); _errHandler.sync(this); _la = _input.LA(1); } - setState(103); + setState(105); blockExpr(); } } @@ -462,24 +459,24 @@ public final BlockLabelContext blockLabel() throws RecognitionException { BlockLabelContext _localctx = new BlockLabelContext(_ctx, getState()); enterRule(_localctx, 10, RULE_blockLabel); try { - setState(110); + setState(112); _errHandler.sync(this); switch (_input.LA(1)) { case QUOTE: enterOuterAlt(_localctx, 1); { - setState(105); + setState(107); match(QUOTE); - setState(106); + setState(108); stringLiteral(); - setState(107); + setState(109); match(QUOTE); } break; case Identifier: enterOuterAlt(_localctx, 2); { - setState(109); + setState(111); match(Identifier); } break; @@ -504,7 +501,7 @@ public ExpressionContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_expression; } - + public ExpressionContext() { } public void copyFrom(ExpressionContext ctx) { super.copyFrom(ctx); @@ -591,7 +588,7 @@ private ExpressionContext expression(int _p) throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(115); + setState(117); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,4,_ctx) ) { case 1: @@ -600,7 +597,7 @@ private ExpressionContext expression(int _p) throws RecognitionException { _ctx = _localctx; _prevctx = _localctx; - setState(113); + setState(115); exprTerm(0); } break; @@ -609,13 +606,13 @@ private ExpressionContext expression(int _p) throws RecognitionException { _localctx = new OperationExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(114); + setState(116); operation(); } break; } _ctx.stop = _input.LT(-1); - setState(125); + setState(127); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,5,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -626,20 +623,20 @@ private ExpressionContext expression(int _p) throws RecognitionException { { _localctx = new ConditionalExpressionContext(new ExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(117); + setState(119); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(118); + setState(120); match(QUESTION); - setState(119); + setState(121); expression(0); - setState(120); + setState(122); match(COLON); - setState(121); + setState(123); expression(2); } - } + } } - setState(127); + setState(129); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,5,_ctx); } @@ -662,7 +659,7 @@ public ExprTermContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_exprTerm; } - + public ExprTermContext() { } public void copyFrom(ExprTermContext ctx) { super.copyFrom(ctx); @@ -820,6 +817,29 @@ public T accept(ParseTreeVisitor visitor) { } } @SuppressWarnings("CheckReturnValue") + public static class LegacyIndexAttributeExpressionContext extends ExprTermContext { + public ExprTermContext exprTerm() { + return getRuleContext(ExprTermContext.class,0); + } + public LegacyIndexAttrContext legacyIndexAttr() { + return getRuleContext(LegacyIndexAttrContext.class,0); + } + public LegacyIndexAttributeExpressionContext(ExprTermContext ctx) { copyFrom(ctx); } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof HCLParserListener ) ((HCLParserListener)listener).enterLegacyIndexAttributeExpression(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof HCLParserListener ) ((HCLParserListener)listener).exitLegacyIndexAttributeExpression(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof HCLParserVisitor ) return ((HCLParserVisitor)visitor).visitLegacyIndexAttributeExpression(this); + else return visitor.visitChildren(this); + } + } + @SuppressWarnings("CheckReturnValue") public static class ForExpressionContext extends ExprTermContext { public ForExprContext forExpr() { return getRuleContext(ForExprContext.class,0); @@ -895,7 +915,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(139); + setState(141); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { case 1: @@ -904,7 +924,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _ctx = _localctx; _prevctx = _localctx; - setState(129); + setState(131); templateExpr(); } break; @@ -913,7 +933,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _localctx = new LiteralExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(130); + setState(132); literalValue(); } break; @@ -922,7 +942,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _localctx = new ForExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(131); + setState(133); forExpr(); } break; @@ -931,7 +951,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _localctx = new CollectionValueExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(132); + setState(134); collectionValue(); } break; @@ -940,7 +960,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _localctx = new VariableExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(133); + setState(135); variableExpr(); } break; @@ -949,7 +969,7 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _localctx = new FunctionCallExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(134); + setState(136); functionCall(); } break; @@ -958,17 +978,17 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { _localctx = new ParentheticalExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(135); + setState(137); match(LPAREN); - setState(136); + setState(138); expression(0); - setState(137); + setState(139); match(RPAREN); } break; } _ctx.stop = _input.LT(-1); - setState(149); + setState(153); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,8,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -976,16 +996,16 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(147); + setState(151); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,7,_ctx) ) { case 1: { _localctx = new IndexAccessExpressionContext(new ExprTermContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_exprTerm); - setState(141); - if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(142); + setState(143); + if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); + setState(144); index(); } break; @@ -993,26 +1013,36 @@ private ExprTermContext exprTerm(int _p) throws RecognitionException { { _localctx = new AttributeAccessExpressionContext(new ExprTermContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_exprTerm); - setState(143); - if (!(precpred(_ctx, 3))) throw new FailedPredicateException(this, "precpred(_ctx, 3)"); - setState(144); + setState(145); + if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); + setState(146); getAttr(); } break; case 3: + { + _localctx = new LegacyIndexAttributeExpressionContext(new ExprTermContext(_parentctx, _parentState)); + pushNewRecursionContext(_localctx, _startState, RULE_exprTerm); + setState(147); + if (!(precpred(_ctx, 3))) throw new FailedPredicateException(this, "precpred(_ctx, 3)"); + setState(148); + legacyIndexAttr(); + } + break; + case 4: { _localctx = new SplatExpressionContext(new ExprTermContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_exprTerm); - setState(145); + setState(149); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(146); + setState(150); splat(); } break; } - } + } } - setState(151); + setState(155); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,8,_ctx); } @@ -1061,11 +1091,11 @@ public final BlockExprContext blockExpr() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(152); + setState(156); match(LBRACE); - setState(153); + setState(157); body(); - setState(154); + setState(158); match(RBRACE); } } @@ -1111,7 +1141,7 @@ public final LiteralValueContext literalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(156); + setState(160); _la = _input.LA(1); if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 90112L) != 0) ) { _errHandler.recoverInline(this); @@ -1165,20 +1195,20 @@ public final CollectionValueContext collectionValue() throws RecognitionExceptio CollectionValueContext _localctx = new CollectionValueContext(_ctx, getState()); enterRule(_localctx, 20, RULE_collectionValue); try { - setState(160); + setState(164); _errHandler.sync(this); switch (_input.LA(1)) { case LBRACK: enterOuterAlt(_localctx, 1); { - setState(158); + setState(162); tuple(); } break; case LBRACE: enterOuterAlt(_localctx, 2); { - setState(159); + setState(163); object(); } break; @@ -1238,39 +1268,39 @@ public final TupleContext tuple() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(162); + setState(166); match(LBRACK); - setState(174); + setState(178); _errHandler.sync(this); _la = _input.LA(1); if (((_la) & ~0x3f) == 0 && ((1L << _la) & 8648909094L) != 0) { { - setState(163); + setState(167); expression(0); - setState(168); + setState(172); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,10,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(164); + setState(168); match(COMMA); - setState(165); + setState(169); expression(0); } - } + } } - setState(170); + setState(174); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,10,_ctx); } - setState(172); + setState(176); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(171); + setState(175); match(COMMA); } } @@ -1278,7 +1308,7 @@ public final TupleContext tuple() throws RecognitionException { } } - setState(176); + setState(180); match(RBRACK); } } @@ -1329,23 +1359,23 @@ public final ObjectContext object() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(178); - match(LBRACE); setState(182); + match(LBRACE); + setState(186); _errHandler.sync(this); _la = _input.LA(1); - while (((_la) & ~0x3f) == 0 && ((1L << _la) & 16810240L) != 0) { + while (((_la) & ~0x3f) == 0 && ((1L << _la) & 8648909094L) != 0) { { { - setState(179); + setState(183); objectelem(); } } - setState(184); + setState(188); _errHandler.sync(this); _la = _input.LA(1); } - setState(185); + setState(189); match(RBRACE); } } @@ -1362,8 +1392,11 @@ public final ObjectContext object() throws RecognitionException { @SuppressWarnings("CheckReturnValue") public static class ObjectelemContext extends ParserRuleContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); + public List expression() { + return getRuleContexts(ExpressionContext.class); + } + public ExpressionContext expression(int i) { + return getRuleContext(ExpressionContext.class,i); } public TerminalNode ASSIGN() { return getToken(HCLParser.ASSIGN, 0); } public TerminalNode COLON() { return getToken(HCLParser.COLON, 0); } @@ -1407,51 +1440,55 @@ public final ObjectelemContext objectelem() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(199); + setState(204); _errHandler.sync(this); - switch (_input.LA(1)) { - case Identifier: + switch ( getInterpreter().adaptivePredict(_input,15,_ctx) ) { + case 1: { - setState(187); + setState(191); match(Identifier); } break; - case LPAREN: + case 2: { - setState(188); + setState(192); match(LPAREN); - setState(189); + setState(193); match(Identifier); - setState(190); + setState(194); match(RPAREN); } break; - case QUOTE: + case 3: { - setState(191); - match(QUOTE); setState(195); + match(QUOTE); + setState(199); _errHandler.sync(this); _la = _input.LA(1); while (_la==TEMPLATE_INTERPOLATION_START || _la==TemplateStringLiteral) { { { - setState(192); + setState(196); quotedTemplatePart(); } } - setState(197); + setState(201); _errHandler.sync(this); _la = _input.LA(1); } - setState(198); + setState(202); match(QUOTE); } break; - default: - throw new NoViableAltException(this); + case 4: + { + setState(203); + expression(0); + } + break; } - setState(201); + setState(206); _la = _input.LA(1); if ( !(_la==ASSIGN || _la==COLON) ) { _errHandler.recoverInline(this); @@ -1461,14 +1498,14 @@ public final ObjectelemContext objectelem() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(202); + setState(207); expression(0); - setState(204); + setState(209); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(203); + setState(208); match(COMMA); } } @@ -1517,20 +1554,20 @@ public final ForExprContext forExpr() throws RecognitionException { ForExprContext _localctx = new ForExprContext(_ctx, getState()); enterRule(_localctx, 28, RULE_forExpr); try { - setState(208); + setState(213); _errHandler.sync(this); switch (_input.LA(1)) { case FOR_BRACK: enterOuterAlt(_localctx, 1); { - setState(206); + setState(211); forTupleExpr(); } break; case FOR_BRACE: enterOuterAlt(_localctx, 2); { - setState(207); + setState(212); forObjectExpr(); } break; @@ -1589,25 +1626,25 @@ public final ForTupleExprContext forTupleExpr() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(210); + setState(215); match(FOR_BRACK); - setState(211); + setState(216); forIntro(); - setState(212); + setState(217); match(COLON); - setState(213); + setState(218); expression(0); - setState(215); + setState(220); _errHandler.sync(this); _la = _input.LA(1); if (_la==IF) { { - setState(214); + setState(219); forCond(); } } - setState(217); + setState(222); match(RBRACK); } } @@ -1667,39 +1704,39 @@ public final ForObjectExprContext forObjectExpr() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(219); + setState(224); match(FOR_BRACE); - setState(220); + setState(225); forIntro(); - setState(221); + setState(226); match(COLON); - setState(222); + setState(227); expression(0); - setState(223); + setState(228); match(ARROW); - setState(224); + setState(229); expression(0); - setState(226); + setState(231); _errHandler.sync(this); _la = _input.LA(1); if (_la==ELLIPSIS) { { - setState(225); + setState(230); match(ELLIPSIS); } } - setState(229); + setState(234); _errHandler.sync(this); _la = _input.LA(1); if (_la==IF) { { - setState(228); + setState(233); forCond(); } } - setState(231); + setState(236); match(RBRACE); } } @@ -1751,23 +1788,23 @@ public final ForIntroContext forIntro() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(233); + setState(238); match(Identifier); - setState(236); + setState(241); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(234); + setState(239); match(COMMA); - setState(235); + setState(240); match(Identifier); } } - setState(238); + setState(243); match(IN); - setState(239); + setState(244); expression(0); } } @@ -1813,9 +1850,9 @@ public final ForCondContext forCond() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(241); + setState(246); match(IF); - setState(242); + setState(247); expression(0); } } @@ -1858,7 +1895,7 @@ public final VariableExprContext variableExpr() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(244); + setState(249); match(Identifier); } } @@ -1907,21 +1944,21 @@ public final FunctionCallContext functionCall() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(246); + setState(251); match(Identifier); - setState(247); + setState(252); match(LPAREN); - setState(249); + setState(254); _errHandler.sync(this); _la = _input.LA(1); if (((_la) & ~0x3f) == 0 && ((1L << _la) & 8648909094L) != 0) { { - setState(248); + setState(253); arguments(); } } - setState(251); + setState(256); match(RPAREN); } } @@ -1976,32 +2013,32 @@ public final ArgumentsContext arguments() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(253); - expression(0); setState(258); + expression(0); + setState(263); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,23,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(254); + setState(259); match(COMMA); - setState(255); + setState(260); expression(0); } - } + } } - setState(260); + setState(265); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,23,_ctx); } - setState(262); + setState(267); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA || _la==ELLIPSIS) { { - setState(261); + setState(266); _la = _input.LA(1); if ( !(_la==COMMA || _la==ELLIPSIS) ) { _errHandler.recoverInline(this); @@ -2059,11 +2096,11 @@ public final IndexContext index() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(264); + setState(269); match(LBRACK); - setState(265); + setState(270); expression(0); - setState(266); + setState(271); match(RBRACK); } } @@ -2107,9 +2144,9 @@ public final GetAttrContext getAttr() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(268); + setState(273); match(DOT); - setState(269); + setState(274); match(Identifier); } } @@ -2124,6 +2161,52 @@ public final GetAttrContext getAttr() throws RecognitionException { return _localctx; } + @SuppressWarnings("CheckReturnValue") + public static class LegacyIndexAttrContext extends ParserRuleContext { + public TerminalNode DOT() { return getToken(HCLParser.DOT, 0); } + public TerminalNode NumericLiteral() { return getToken(HCLParser.NumericLiteral, 0); } + public LegacyIndexAttrContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_legacyIndexAttr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof HCLParserListener ) ((HCLParserListener)listener).enterLegacyIndexAttr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof HCLParserListener ) ((HCLParserListener)listener).exitLegacyIndexAttr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof HCLParserVisitor ) return ((HCLParserVisitor)visitor).visitLegacyIndexAttr(this); + else return visitor.visitChildren(this); + } + } + + public final LegacyIndexAttrContext legacyIndexAttr() throws RecognitionException { + LegacyIndexAttrContext _localctx = new LegacyIndexAttrContext(_ctx, getState()); + enterRule(_localctx, 48, RULE_legacyIndexAttr); + try { + enterOuterAlt(_localctx, 1); + { + setState(276); + match(DOT); + setState(277); + match(NumericLiteral); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + @SuppressWarnings("CheckReturnValue") public static class SplatContext extends ParserRuleContext { public AttrSplatContext attrSplat() { @@ -2153,22 +2236,22 @@ public T accept(ParseTreeVisitor visitor) { public final SplatContext splat() throws RecognitionException { SplatContext _localctx = new SplatContext(_ctx, getState()); - enterRule(_localctx, 48, RULE_splat); + enterRule(_localctx, 50, RULE_splat); try { - setState(273); + setState(281); _errHandler.sync(this); switch (_input.LA(1)) { case DOT: enterOuterAlt(_localctx, 1); { - setState(271); + setState(279); attrSplat(); } break; case LBRACK: enterOuterAlt(_localctx, 2); { - setState(272); + setState(280); fullSplat(); } break; @@ -2218,28 +2301,28 @@ public T accept(ParseTreeVisitor visitor) { public final AttrSplatContext attrSplat() throws RecognitionException { AttrSplatContext _localctx = new AttrSplatContext(_ctx, getState()); - enterRule(_localctx, 50, RULE_attrSplat); + enterRule(_localctx, 52, RULE_attrSplat); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(275); + setState(283); match(DOT); - setState(276); + setState(284); match(MUL); - setState(280); + setState(288); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,26,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(277); + setState(285); getAttr(); } - } + } } - setState(282); + setState(290); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,26,_ctx); } @@ -2294,44 +2377,44 @@ public T accept(ParseTreeVisitor visitor) { public final FullSplatContext fullSplat() throws RecognitionException { FullSplatContext _localctx = new FullSplatContext(_ctx, getState()); - enterRule(_localctx, 52, RULE_fullSplat); + enterRule(_localctx, 54, RULE_fullSplat); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(283); + setState(291); match(LBRACK); - setState(284); + setState(292); match(MUL); - setState(285); + setState(293); match(RBRACK); - setState(290); + setState(298); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,28,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { - setState(288); + setState(296); _errHandler.sync(this); switch (_input.LA(1)) { case DOT: { - setState(286); + setState(294); getAttr(); } break; case LBRACK: { - setState(287); + setState(295); index(); } break; default: throw new NoViableAltException(this); } - } + } } - setState(292); + setState(300); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,28,_ctx); } @@ -2377,22 +2460,22 @@ public T accept(ParseTreeVisitor visitor) { public final OperationContext operation() throws RecognitionException { OperationContext _localctx = new OperationContext(_ctx, getState()); - enterRule(_localctx, 54, RULE_operation); + enterRule(_localctx, 56, RULE_operation); try { - setState(295); + setState(303); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,29,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(293); + setState(301); unaryOp(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(294); + setState(302); binaryOp(); } break; @@ -2437,12 +2520,12 @@ public T accept(ParseTreeVisitor visitor) { public final UnaryOpContext unaryOp() throws RecognitionException { UnaryOpContext _localctx = new UnaryOpContext(_ctx, getState()); - enterRule(_localctx, 56, RULE_unaryOp); + enterRule(_localctx, 58, RULE_unaryOp); int _la; try { enterOuterAlt(_localctx, 1); { - setState(297); + setState(305); _la = _input.LA(1); if ( !(_la==MINUS || _la==NOT) ) { _errHandler.recoverInline(this); @@ -2452,7 +2535,7 @@ public final UnaryOpContext unaryOp() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(298); + setState(306); exprTerm(0); } } @@ -2505,11 +2588,11 @@ public T accept(ParseTreeVisitor visitor) { public final BinaryOpContext binaryOp() throws RecognitionException { BinaryOpContext _localctx = new BinaryOpContext(_ctx, getState()); - enterRule(_localctx, 58, RULE_binaryOp); + enterRule(_localctx, 60, RULE_binaryOp); try { enterOuterAlt(_localctx, 1); { - setState(302); + setState(310); _errHandler.sync(this); switch (_input.LA(1)) { case FOR_BRACE: @@ -2524,34 +2607,34 @@ public final BinaryOpContext binaryOp() throws RecognitionException { case LBRACK: case LPAREN: { - setState(300); + setState(308); exprTerm(0); } break; case MINUS: case NOT: { - setState(301); + setState(309); unaryOp(); } break; default: throw new NoViableAltException(this); } - setState(304); + setState(312); binaryOperator(); - setState(307); + setState(315); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,31,_ctx) ) { case 1: { - setState(305); + setState(313); exprTerm(0); } break; case 2: { - setState(306); + setState(314); operation(); } break; @@ -2601,9 +2684,9 @@ public T accept(ParseTreeVisitor visitor) { public final BinaryOperatorContext binaryOperator() throws RecognitionException { BinaryOperatorContext _localctx = new BinaryOperatorContext(_ctx, getState()); - enterRule(_localctx, 60, RULE_binaryOperator); + enterRule(_localctx, 62, RULE_binaryOperator); try { - setState(312); + setState(320); _errHandler.sync(this); switch (_input.LA(1)) { case EQ: @@ -2614,7 +2697,7 @@ public final BinaryOperatorContext binaryOperator() throws RecognitionException case GEQ: enterOuterAlt(_localctx, 1); { - setState(309); + setState(317); compareOperator(); } break; @@ -2625,7 +2708,7 @@ public final BinaryOperatorContext binaryOperator() throws RecognitionException case MOD: enterOuterAlt(_localctx, 2); { - setState(310); + setState(318); arithmeticOperator(); } break; @@ -2633,7 +2716,7 @@ public final BinaryOperatorContext binaryOperator() throws RecognitionException case OR: enterOuterAlt(_localctx, 3); { - setState(311); + setState(319); logicOperator(); } break; @@ -2681,12 +2764,12 @@ public T accept(ParseTreeVisitor visitor) { public final CompareOperatorContext compareOperator() throws RecognitionException { CompareOperatorContext _localctx = new CompareOperatorContext(_ctx, getState()); - enterRule(_localctx, 62, RULE_compareOperator); + enterRule(_localctx, 64, RULE_compareOperator); int _la; try { enterOuterAlt(_localctx, 1); { - setState(314); + setState(322); _la = _input.LA(1); if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 155024621568L) != 0) ) { _errHandler.recoverInline(this); @@ -2737,12 +2820,12 @@ public T accept(ParseTreeVisitor visitor) { public final ArithmeticOperatorContext arithmeticOperator() throws RecognitionException { ArithmeticOperatorContext _localctx = new ArithmeticOperatorContext(_ctx, getState()); - enterRule(_localctx, 64, RULE_arithmeticOperator); + enterRule(_localctx, 66, RULE_arithmeticOperator); int _la; try { enterOuterAlt(_localctx, 1); { - setState(316); + setState(324); _la = _input.LA(1); if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 1172559888384L) != 0) ) { _errHandler.recoverInline(this); @@ -2790,12 +2873,12 @@ public T accept(ParseTreeVisitor visitor) { public final LogicOperatorContext logicOperator() throws RecognitionException { LogicOperatorContext _localctx = new LogicOperatorContext(_ctx, getState()); - enterRule(_localctx, 66, RULE_logicOperator); + enterRule(_localctx, 68, RULE_logicOperator); int _la; try { enterOuterAlt(_localctx, 1); { - setState(318); + setState(326); _la = _input.LA(1); if ( !(_la==AND || _la==OR) ) { _errHandler.recoverInline(this); @@ -2824,7 +2907,7 @@ public TemplateExprContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_templateExpr; } - + public TemplateExprContext() { } public void copyFrom(TemplateExprContext ctx) { super.copyFrom(ctx); @@ -2892,49 +2975,49 @@ public T accept(ParseTreeVisitor visitor) { public final TemplateExprContext templateExpr() throws RecognitionException { TemplateExprContext _localctx = new TemplateExprContext(_ctx, getState()); - enterRule(_localctx, 68, RULE_templateExpr); + enterRule(_localctx, 70, RULE_templateExpr); int _la; try { - setState(342); + setState(350); _errHandler.sync(this); switch (_input.LA(1)) { case HEREDOC_START: _localctx = new HeredocContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(320); + setState(328); match(HEREDOC_START); - setState(321); - match(Identifier); setState(329); + match(Identifier); + setState(337); _errHandler.sync(this); _la = _input.LA(1); do { { { - setState(322); + setState(330); match(NEWLINE); - setState(326); + setState(334); _errHandler.sync(this); _la = _input.LA(1); while (_la==TEMPLATE_INTERPOLATION_START || _la==HTemplateLiteral) { { { - setState(323); + setState(331); heredocTemplatePart(); } } - setState(328); + setState(336); _errHandler.sync(this); _la = _input.LA(1); } } } - setState(331); + setState(339); _errHandler.sync(this); _la = _input.LA(1); } while ( _la==NEWLINE ); - setState(333); + setState(341); match(Identifier); } break; @@ -2942,23 +3025,23 @@ public final TemplateExprContext templateExpr() throws RecognitionException { _localctx = new QuotedTemplateContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(334); + setState(342); match(QUOTE); - setState(338); + setState(346); _errHandler.sync(this); _la = _input.LA(1); while (_la==TEMPLATE_INTERPOLATION_START || _la==TemplateStringLiteral) { { { - setState(335); + setState(343); quotedTemplatePart(); } } - setState(340); + setState(348); _errHandler.sync(this); _la = _input.LA(1); } - setState(341); + setState(349); match(QUOTE); } break; @@ -3006,22 +3089,22 @@ public T accept(ParseTreeVisitor visitor) { public final HeredocTemplatePartContext heredocTemplatePart() throws RecognitionException { HeredocTemplatePartContext _localctx = new HeredocTemplatePartContext(_ctx, getState()); - enterRule(_localctx, 70, RULE_heredocTemplatePart); + enterRule(_localctx, 72, RULE_heredocTemplatePart); try { - setState(346); + setState(354); _errHandler.sync(this); switch (_input.LA(1)) { case TEMPLATE_INTERPOLATION_START: enterOuterAlt(_localctx, 1); { - setState(344); + setState(352); templateInterpolation(); } break; case HTemplateLiteral: enterOuterAlt(_localctx, 2); { - setState(345); + setState(353); heredocLiteral(); } break; @@ -3064,11 +3147,11 @@ public T accept(ParseTreeVisitor visitor) { public final HeredocLiteralContext heredocLiteral() throws RecognitionException { HeredocLiteralContext _localctx = new HeredocLiteralContext(_ctx, getState()); - enterRule(_localctx, 72, RULE_heredocLiteral); + enterRule(_localctx, 74, RULE_heredocLiteral); try { enterOuterAlt(_localctx, 1); { - setState(348); + setState(356); match(HTemplateLiteral); } } @@ -3112,22 +3195,22 @@ public T accept(ParseTreeVisitor visitor) { public final QuotedTemplatePartContext quotedTemplatePart() throws RecognitionException { QuotedTemplatePartContext _localctx = new QuotedTemplatePartContext(_ctx, getState()); - enterRule(_localctx, 74, RULE_quotedTemplatePart); + enterRule(_localctx, 76, RULE_quotedTemplatePart); try { - setState(352); + setState(360); _errHandler.sync(this); switch (_input.LA(1)) { case TEMPLATE_INTERPOLATION_START: enterOuterAlt(_localctx, 1); { - setState(350); + setState(358); templateInterpolation(); } break; case TemplateStringLiteral: enterOuterAlt(_localctx, 2); { - setState(351); + setState(359); stringLiteral(); } break; @@ -3170,11 +3253,11 @@ public T accept(ParseTreeVisitor visitor) { public final StringLiteralContext stringLiteral() throws RecognitionException { StringLiteralContext _localctx = new StringLiteralContext(_ctx, getState()); - enterRule(_localctx, 76, RULE_stringLiteral); + enterRule(_localctx, 78, RULE_stringLiteral); try { enterOuterAlt(_localctx, 1); { - setState(354); + setState(362); match(TemplateStringLiteral); } } @@ -3217,15 +3300,15 @@ public T accept(ParseTreeVisitor visitor) { public final TemplateInterpolationContext templateInterpolation() throws RecognitionException { TemplateInterpolationContext _localctx = new TemplateInterpolationContext(_ctx, getState()); - enterRule(_localctx, 78, RULE_templateInterpolation); + enterRule(_localctx, 80, RULE_templateInterpolation); try { enterOuterAlt(_localctx, 1); { - setState(356); + setState(364); match(TEMPLATE_INTERPOLATION_START); - setState(357); + setState(365); expression(0); - setState(358); + setState(366); match(RBRACE); } } @@ -3240,8 +3323,8 @@ public final TemplateInterpolationContext templateInterpolation() throws Recogni return _localctx; } - @Override - public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { + @Override + public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { switch (ruleIndex) { case 6: return expression_sempred((ExpressionContext)_localctx, predIndex); @@ -3260,17 +3343,19 @@ private boolean expression_sempred(ExpressionContext _localctx, int predIndex) { private boolean exprTerm_sempred(ExprTermContext _localctx, int predIndex) { switch (predIndex) { case 1: - return precpred(_ctx, 4); + return precpred(_ctx, 5); case 2: - return precpred(_ctx, 3); + return precpred(_ctx, 4); case 3: + return precpred(_ctx, 3); + case 4: return precpred(_ctx, 2); } return true; } public static final String _serializedATN = - "\u0004\u0001/\u0169\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0004\u0001/\u0171\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ "\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b\u0002"+ @@ -3281,223 +3366,229 @@ private boolean exprTerm_sempred(ExprTermContext _localctx, int predIndex) { "\u0002\u0019\u0007\u0019\u0002\u001a\u0007\u001a\u0002\u001b\u0007\u001b"+ "\u0002\u001c\u0007\u001c\u0002\u001d\u0007\u001d\u0002\u001e\u0007\u001e"+ "\u0002\u001f\u0007\u001f\u0002 \u0007 \u0002!\u0007!\u0002\"\u0007\"\u0002"+ - "#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0001"+ - "\u0000\u0001\u0000\u0001\u0001\u0005\u0001T\b\u0001\n\u0001\f\u0001W\t"+ - "\u0001\u0001\u0002\u0001\u0002\u0003\u0002[\b\u0002\u0001\u0003\u0001"+ - "\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0005\u0004c\b"+ - "\u0004\n\u0004\f\u0004f\t\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001"+ - "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005o\b\u0005\u0001"+ - "\u0006\u0001\u0006\u0001\u0006\u0003\u0006t\b\u0006\u0001\u0006\u0001"+ - "\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0005\u0006|\b"+ - "\u0006\n\u0006\f\u0006\u007f\t\u0006\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0003\u0007\u008c\b\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0005\u0007\u0094\b\u0007"+ - "\n\u0007\f\u0007\u0097\t\u0007\u0001\b\u0001\b\u0001\b\u0001\b\u0001\t"+ - "\u0001\t\u0001\n\u0001\n\u0003\n\u00a1\b\n\u0001\u000b\u0001\u000b\u0001"+ - "\u000b\u0001\u000b\u0005\u000b\u00a7\b\u000b\n\u000b\f\u000b\u00aa\t\u000b"+ - "\u0001\u000b\u0003\u000b\u00ad\b\u000b\u0003\u000b\u00af\b\u000b\u0001"+ - "\u000b\u0001\u000b\u0001\f\u0001\f\u0005\f\u00b5\b\f\n\f\f\f\u00b8\t\f"+ - "\u0001\f\u0001\f\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0005"+ - "\r\u00c2\b\r\n\r\f\r\u00c5\t\r\u0001\r\u0003\r\u00c8\b\r\u0001\r\u0001"+ - "\r\u0001\r\u0003\r\u00cd\b\r\u0001\u000e\u0001\u000e\u0003\u000e\u00d1"+ - "\b\u000e\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0003"+ - "\u000f\u00d8\b\u000f\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0001"+ - "\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0003\u0010\u00e3"+ - "\b\u0010\u0001\u0010\u0003\u0010\u00e6\b\u0010\u0001\u0010\u0001\u0010"+ - "\u0001\u0011\u0001\u0011\u0001\u0011\u0003\u0011\u00ed\b\u0011\u0001\u0011"+ - "\u0001\u0011\u0001\u0011\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0013"+ - "\u0001\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0003\u0014\u00fa\b\u0014"+ - "\u0001\u0014\u0001\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0005\u0015"+ - "\u0101\b\u0015\n\u0015\f\u0015\u0104\t\u0015\u0001\u0015\u0003\u0015\u0107"+ - "\b\u0015\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0017\u0001"+ - "\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0003\u0018\u0112\b\u0018\u0001"+ - "\u0019\u0001\u0019\u0001\u0019\u0005\u0019\u0117\b\u0019\n\u0019\f\u0019"+ - "\u011a\t\u0019\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a"+ - "\u0005\u001a\u0121\b\u001a\n\u001a\f\u001a\u0124\t\u001a\u0001\u001b\u0001"+ - "\u001b\u0003\u001b\u0128\b\u001b\u0001\u001c\u0001\u001c\u0001\u001c\u0001"+ - "\u001d\u0001\u001d\u0003\u001d\u012f\b\u001d\u0001\u001d\u0001\u001d\u0001"+ - "\u001d\u0003\u001d\u0134\b\u001d\u0001\u001e\u0001\u001e\u0001\u001e\u0003"+ - "\u001e\u0139\b\u001e\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001!\u0001"+ - "!\u0001\"\u0001\"\u0001\"\u0001\"\u0005\"\u0145\b\"\n\"\f\"\u0148\t\""+ - "\u0004\"\u014a\b\"\u000b\"\f\"\u014b\u0001\"\u0001\"\u0001\"\u0005\"\u0151"+ - "\b\"\n\"\f\"\u0154\t\"\u0001\"\u0003\"\u0157\b\"\u0001#\u0001#\u0003#"+ - "\u015b\b#\u0001$\u0001$\u0001%\u0001%\u0003%\u0161\b%\u0001&\u0001&\u0001"+ - "\'\u0001\'\u0001\'\u0001\'\u0001\'\u0000\u0002\f\u000e(\u0000\u0002\u0004"+ - "\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a\u001c\u001e \""+ - "$&(*,.02468:<>@BDFHJLN\u0000\u0007\u0002\u0000\r\u000e\u0010\u0010\u0002"+ - "\u0000\u0007\u0007\u0016\u0016\u0002\u0000\'\'))\u0002\u0000\u0019\u0019"+ - "!!\u0004\u0000\u0014\u0015\u001b\u001c\"\"%%\u0005\u0000\u0012\u0012\u0019"+ - "\u0019 $$((\u0002\u0000\u0013\u0013\u001a\u001a\u016f\u0000P\u0001\u0000"+ - "\u0000\u0000\u0002U\u0001\u0000\u0000\u0000\u0004Z\u0001\u0000\u0000\u0000"+ - "\u0006\\\u0001\u0000\u0000\u0000\b`\u0001\u0000\u0000\u0000\nn\u0001\u0000"+ - "\u0000\u0000\fs\u0001\u0000\u0000\u0000\u000e\u008b\u0001\u0000\u0000"+ - "\u0000\u0010\u0098\u0001\u0000\u0000\u0000\u0012\u009c\u0001\u0000\u0000"+ - "\u0000\u0014\u00a0\u0001\u0000\u0000\u0000\u0016\u00a2\u0001\u0000\u0000"+ - "\u0000\u0018\u00b2\u0001\u0000\u0000\u0000\u001a\u00c7\u0001\u0000\u0000"+ - "\u0000\u001c\u00d0\u0001\u0000\u0000\u0000\u001e\u00d2\u0001\u0000\u0000"+ - "\u0000 \u00db\u0001\u0000\u0000\u0000\"\u00e9\u0001\u0000\u0000\u0000"+ - "$\u00f1\u0001\u0000\u0000\u0000&\u00f4\u0001\u0000\u0000\u0000(\u00f6"+ - "\u0001\u0000\u0000\u0000*\u00fd\u0001\u0000\u0000\u0000,\u0108\u0001\u0000"+ - "\u0000\u0000.\u010c\u0001\u0000\u0000\u00000\u0111\u0001\u0000\u0000\u0000"+ - "2\u0113\u0001\u0000\u0000\u00004\u011b\u0001\u0000\u0000\u00006\u0127"+ - "\u0001\u0000\u0000\u00008\u0129\u0001\u0000\u0000\u0000:\u012e\u0001\u0000"+ - "\u0000\u0000<\u0138\u0001\u0000\u0000\u0000>\u013a\u0001\u0000\u0000\u0000"+ - "@\u013c\u0001\u0000\u0000\u0000B\u013e\u0001\u0000\u0000\u0000D\u0156"+ - "\u0001\u0000\u0000\u0000F\u015a\u0001\u0000\u0000\u0000H\u015c\u0001\u0000"+ - "\u0000\u0000J\u0160\u0001\u0000\u0000\u0000L\u0162\u0001\u0000\u0000\u0000"+ - "N\u0164\u0001\u0000\u0000\u0000PQ\u0003\u0002\u0001\u0000Q\u0001\u0001"+ - "\u0000\u0000\u0000RT\u0003\u0004\u0002\u0000SR\u0001\u0000\u0000\u0000"+ - "TW\u0001\u0000\u0000\u0000US\u0001\u0000\u0000\u0000UV\u0001\u0000\u0000"+ - "\u0000V\u0003\u0001\u0000\u0000\u0000WU\u0001\u0000\u0000\u0000X[\u0003"+ - "\u0006\u0003\u0000Y[\u0003\b\u0004\u0000ZX\u0001\u0000\u0000\u0000ZY\u0001"+ - "\u0000\u0000\u0000[\u0005\u0001\u0000\u0000\u0000\\]\u0005\b\u0000\u0000"+ - "]^\u0005\u0007\u0000\u0000^_\u0003\f\u0006\u0000_\u0007\u0001\u0000\u0000"+ - "\u0000`d\u0005\b\u0000\u0000ac\u0003\n\u0005\u0000ba\u0001\u0000\u0000"+ - "\u0000cf\u0001\u0000\u0000\u0000db\u0001\u0000\u0000\u0000de\u0001\u0000"+ - "\u0000\u0000eg\u0001\u0000\u0000\u0000fd\u0001\u0000\u0000\u0000gh\u0003"+ - "\u0010\b\u0000h\t\u0001\u0000\u0000\u0000ij\u0005\u000f\u0000\u0000jk"+ - "\u0003L&\u0000kl\u0005\u000f\u0000\u0000lo\u0001\u0000\u0000\u0000mo\u0005"+ - "\b\u0000\u0000ni\u0001\u0000\u0000\u0000nm\u0001\u0000\u0000\u0000o\u000b"+ - "\u0001\u0000\u0000\u0000pq\u0006\u0006\uffff\uffff\u0000qt\u0003\u000e"+ - "\u0007\u0000rt\u00036\u001b\u0000sp\u0001\u0000\u0000\u0000sr\u0001\u0000"+ - "\u0000\u0000t}\u0001\u0000\u0000\u0000uv\n\u0001\u0000\u0000vw\u0005\u001d"+ - "\u0000\u0000wx\u0003\f\u0006\u0000xy\u0005\u0016\u0000\u0000yz\u0003\f"+ - "\u0006\u0002z|\u0001\u0000\u0000\u0000{u\u0001\u0000\u0000\u0000|\u007f"+ - "\u0001\u0000\u0000\u0000}{\u0001\u0000\u0000\u0000}~\u0001\u0000\u0000"+ - "\u0000~\r\u0001\u0000\u0000\u0000\u007f}\u0001\u0000\u0000\u0000\u0080"+ - "\u0081\u0006\u0007\uffff\uffff\u0000\u0081\u008c\u0003D\"\u0000\u0082"+ - "\u008c\u0003\u0012\t\u0000\u0083\u008c\u0003\u001c\u000e\u0000\u0084\u008c"+ - "\u0003\u0014\n\u0000\u0085\u008c\u0003&\u0013\u0000\u0086\u008c\u0003"+ - "(\u0014\u0000\u0087\u0088\u0005\u0018\u0000\u0000\u0088\u0089\u0003\f"+ - "\u0006\u0000\u0089\u008a\u0005\u001f\u0000\u0000\u008a\u008c\u0001\u0000"+ - "\u0000\u0000\u008b\u0080\u0001\u0000\u0000\u0000\u008b\u0082\u0001\u0000"+ - "\u0000\u0000\u008b\u0083\u0001\u0000\u0000\u0000\u008b\u0084\u0001\u0000"+ - "\u0000\u0000\u008b\u0085\u0001\u0000\u0000\u0000\u008b\u0086\u0001\u0000"+ - "\u0000\u0000\u008b\u0087\u0001\u0000\u0000\u0000\u008c\u0095\u0001\u0000"+ - "\u0000\u0000\u008d\u008e\n\u0004\u0000\u0000\u008e\u0094\u0003,\u0016"+ - "\u0000\u008f\u0090\n\u0003\u0000\u0000\u0090\u0094\u0003.\u0017\u0000"+ - "\u0091\u0092\n\u0002\u0000\u0000\u0092\u0094\u00030\u0018\u0000\u0093"+ - "\u008d\u0001\u0000\u0000\u0000\u0093\u008f\u0001\u0000\u0000\u0000\u0093"+ - "\u0091\u0001\u0000\u0000\u0000\u0094\u0097\u0001\u0000\u0000\u0000\u0095"+ - "\u0093\u0001\u0000\u0000\u0000\u0095\u0096\u0001\u0000\u0000\u0000\u0096"+ - "\u000f\u0001\u0000\u0000\u0000\u0097\u0095\u0001\u0000\u0000\u0000\u0098"+ - "\u0099\u0005\u0005\u0000\u0000\u0099\u009a\u0003\u0002\u0001\u0000\u009a"+ - "\u009b\u0005\u0006\u0000\u0000\u009b\u0011\u0001\u0000\u0000\u0000\u009c"+ - "\u009d\u0007\u0000\u0000\u0000\u009d\u0013\u0001\u0000\u0000\u0000\u009e"+ - "\u00a1\u0003\u0016\u000b\u0000\u009f\u00a1\u0003\u0018\f\u0000\u00a0\u009e"+ - "\u0001\u0000\u0000\u0000\u00a0\u009f\u0001\u0000\u0000\u0000\u00a1\u0015"+ - "\u0001\u0000\u0000\u0000\u00a2\u00ae\u0005\u0017\u0000\u0000\u00a3\u00a8"+ - "\u0003\f\u0006\u0000\u00a4\u00a5\u0005\'\u0000\u0000\u00a5\u00a7\u0003"+ - "\f\u0006\u0000\u00a6\u00a4\u0001\u0000\u0000\u0000\u00a7\u00aa\u0001\u0000"+ - "\u0000\u0000\u00a8\u00a6\u0001\u0000\u0000\u0000\u00a8\u00a9\u0001\u0000"+ - "\u0000\u0000\u00a9\u00ac\u0001\u0000\u0000\u0000\u00aa\u00a8\u0001\u0000"+ - "\u0000\u0000\u00ab\u00ad\u0005\'\u0000\u0000\u00ac\u00ab\u0001\u0000\u0000"+ - "\u0000\u00ac\u00ad\u0001\u0000\u0000\u0000\u00ad\u00af\u0001\u0000\u0000"+ - "\u0000\u00ae\u00a3\u0001\u0000\u0000\u0000\u00ae\u00af\u0001\u0000\u0000"+ - "\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u00b1\u0005\u001e\u0000"+ - "\u0000\u00b1\u0017\u0001\u0000\u0000\u0000\u00b2\u00b6\u0005\u0005\u0000"+ - "\u0000\u00b3\u00b5\u0003\u001a\r\u0000\u00b4\u00b3\u0001\u0000\u0000\u0000"+ - "\u00b5\u00b8\u0001\u0000\u0000\u0000\u00b6\u00b4\u0001\u0000\u0000\u0000"+ - "\u00b6\u00b7\u0001\u0000\u0000\u0000\u00b7\u00b9\u0001\u0000\u0000\u0000"+ - "\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u00ba\u0005\u0006\u0000\u0000"+ - "\u00ba\u0019\u0001\u0000\u0000\u0000\u00bb\u00c8\u0005\b\u0000\u0000\u00bc"+ - "\u00bd\u0005\u0018\u0000\u0000\u00bd\u00be\u0005\b\u0000\u0000\u00be\u00c8"+ - "\u0005\u001f\u0000\u0000\u00bf\u00c3\u0005\u000f\u0000\u0000\u00c0\u00c2"+ - "\u0003J%\u0000\u00c1\u00c0\u0001\u0000\u0000\u0000\u00c2\u00c5\u0001\u0000"+ - "\u0000\u0000\u00c3\u00c1\u0001\u0000\u0000\u0000\u00c3\u00c4\u0001\u0000"+ - "\u0000\u0000\u00c4\u00c6\u0001\u0000\u0000\u0000\u00c5\u00c3\u0001\u0000"+ - "\u0000\u0000\u00c6\u00c8\u0005\u000f\u0000\u0000\u00c7\u00bb\u0001\u0000"+ - "\u0000\u0000\u00c7\u00bc\u0001\u0000\u0000\u0000\u00c7\u00bf\u0001\u0000"+ - "\u0000\u0000\u00c8\u00c9\u0001\u0000\u0000\u0000\u00c9\u00ca\u0007\u0001"+ - "\u0000\u0000\u00ca\u00cc\u0003\f\u0006\u0000\u00cb\u00cd\u0005\'\u0000"+ - "\u0000\u00cc\u00cb\u0001\u0000\u0000\u0000\u00cc\u00cd\u0001\u0000\u0000"+ - "\u0000\u00cd\u001b\u0001\u0000\u0000\u0000\u00ce\u00d1\u0003\u001e\u000f"+ - "\u0000\u00cf\u00d1\u0003 \u0010\u0000\u00d0\u00ce\u0001\u0000\u0000\u0000"+ - "\u00d0\u00cf\u0001\u0000\u0000\u0000\u00d1\u001d\u0001\u0000\u0000\u0000"+ - "\u00d2\u00d3\u0005\u0002\u0000\u0000\u00d3\u00d4\u0003\"\u0011\u0000\u00d4"+ - "\u00d5\u0005\u0016\u0000\u0000\u00d5\u00d7\u0003\f\u0006\u0000\u00d6\u00d8"+ - "\u0003$\u0012\u0000\u00d7\u00d6\u0001\u0000\u0000\u0000\u00d7\u00d8\u0001"+ - "\u0000\u0000\u0000\u00d8\u00d9\u0001\u0000\u0000\u0000\u00d9\u00da\u0005"+ - "\u001e\u0000\u0000\u00da\u001f\u0001\u0000\u0000\u0000\u00db\u00dc\u0005"+ - "\u0001\u0000\u0000\u00dc\u00dd\u0003\"\u0011\u0000\u00dd\u00de\u0005\u0016"+ - "\u0000\u0000\u00de\u00df\u0003\f\u0006\u0000\u00df\u00e0\u0005&\u0000"+ - "\u0000\u00e0\u00e2\u0003\f\u0006\u0000\u00e1\u00e3\u0005)\u0000\u0000"+ - "\u00e2\u00e1\u0001\u0000\u0000\u0000\u00e2\u00e3\u0001\u0000\u0000\u0000"+ - "\u00e3\u00e5\u0001\u0000\u0000\u0000\u00e4\u00e6\u0003$\u0012\u0000\u00e5"+ - "\u00e4\u0001\u0000\u0000\u0000\u00e5\u00e6\u0001\u0000\u0000\u0000\u00e6"+ - "\u00e7\u0001\u0000\u0000\u0000\u00e7\u00e8\u0005\u0006\u0000\u0000\u00e8"+ - "!\u0001\u0000\u0000\u0000\u00e9\u00ec\u0005\b\u0000\u0000\u00ea\u00eb"+ - "\u0005\'\u0000\u0000\u00eb\u00ed\u0005\b\u0000\u0000\u00ec\u00ea\u0001"+ - "\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001"+ - "\u0000\u0000\u0000\u00ee\u00ef\u0005\u0004\u0000\u0000\u00ef\u00f0\u0003"+ - "\f\u0006\u0000\u00f0#\u0001\u0000\u0000\u0000\u00f1\u00f2\u0005\u0003"+ - "\u0000\u0000\u00f2\u00f3\u0003\f\u0006\u0000\u00f3%\u0001\u0000\u0000"+ - "\u0000\u00f4\u00f5\u0005\b\u0000\u0000\u00f5\'\u0001\u0000\u0000\u0000"+ - "\u00f6\u00f7\u0005\b\u0000\u0000\u00f7\u00f9\u0005\u0018\u0000\u0000\u00f8"+ - "\u00fa\u0003*\u0015\u0000\u00f9\u00f8\u0001\u0000\u0000\u0000\u00f9\u00fa"+ - "\u0001\u0000\u0000\u0000\u00fa\u00fb\u0001\u0000\u0000\u0000\u00fb\u00fc"+ - "\u0005\u001f\u0000\u0000\u00fc)\u0001\u0000\u0000\u0000\u00fd\u0102\u0003"+ - "\f\u0006\u0000\u00fe\u00ff\u0005\'\u0000\u0000\u00ff\u0101\u0003\f\u0006"+ - "\u0000\u0100\u00fe\u0001\u0000\u0000\u0000\u0101\u0104\u0001\u0000\u0000"+ - "\u0000\u0102\u0100\u0001\u0000\u0000\u0000\u0102\u0103\u0001\u0000\u0000"+ - "\u0000\u0103\u0106\u0001\u0000\u0000\u0000\u0104\u0102\u0001\u0000\u0000"+ - "\u0000\u0105\u0107\u0007\u0002\u0000\u0000\u0106\u0105\u0001\u0000\u0000"+ - "\u0000\u0106\u0107\u0001\u0000\u0000\u0000\u0107+\u0001\u0000\u0000\u0000"+ - "\u0108\u0109\u0005\u0017\u0000\u0000\u0109\u010a\u0003\f\u0006\u0000\u010a"+ - "\u010b\u0005\u001e\u0000\u0000\u010b-\u0001\u0000\u0000\u0000\u010c\u010d"+ - "\u0005#\u0000\u0000\u010d\u010e\u0005\b\u0000\u0000\u010e/\u0001\u0000"+ - "\u0000\u0000\u010f\u0112\u00032\u0019\u0000\u0110\u0112\u00034\u001a\u0000"+ - "\u0111\u010f\u0001\u0000\u0000\u0000\u0111\u0110\u0001\u0000\u0000\u0000"+ - "\u01121\u0001\u0000\u0000\u0000\u0113\u0114\u0005#\u0000\u0000\u0114\u0118"+ - "\u0005 \u0000\u0000\u0115\u0117\u0003.\u0017\u0000\u0116\u0115\u0001\u0000"+ - "\u0000\u0000\u0117\u011a\u0001\u0000\u0000\u0000\u0118\u0116\u0001\u0000"+ - "\u0000\u0000\u0118\u0119\u0001\u0000\u0000\u0000\u01193\u0001\u0000\u0000"+ - "\u0000\u011a\u0118\u0001\u0000\u0000\u0000\u011b\u011c\u0005\u0017\u0000"+ - "\u0000\u011c\u011d\u0005 \u0000\u0000\u011d\u0122\u0005\u001e\u0000\u0000"+ - "\u011e\u0121\u0003.\u0017\u0000\u011f\u0121\u0003,\u0016\u0000\u0120\u011e"+ - "\u0001\u0000\u0000\u0000\u0120\u011f\u0001\u0000\u0000\u0000\u0121\u0124"+ - "\u0001\u0000\u0000\u0000\u0122\u0120\u0001\u0000\u0000\u0000\u0122\u0123"+ - "\u0001\u0000\u0000\u0000\u01235\u0001\u0000\u0000\u0000\u0124\u0122\u0001"+ - "\u0000\u0000\u0000\u0125\u0128\u00038\u001c\u0000\u0126\u0128\u0003:\u001d"+ - "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0126\u0001\u0000\u0000"+ - "\u0000\u01287\u0001\u0000\u0000\u0000\u0129\u012a\u0007\u0003\u0000\u0000"+ - "\u012a\u012b\u0003\u000e\u0007\u0000\u012b9\u0001\u0000\u0000\u0000\u012c"+ - "\u012f\u0003\u000e\u0007\u0000\u012d\u012f\u00038\u001c\u0000\u012e\u012c"+ - "\u0001\u0000\u0000\u0000\u012e\u012d\u0001\u0000\u0000\u0000\u012f\u0130"+ - "\u0001\u0000\u0000\u0000\u0130\u0133\u0003<\u001e\u0000\u0131\u0134\u0003"+ - "\u000e\u0007\u0000\u0132\u0134\u00036\u001b\u0000\u0133\u0131\u0001\u0000"+ - "\u0000\u0000\u0133\u0132\u0001\u0000\u0000\u0000\u0134;\u0001\u0000\u0000"+ - "\u0000\u0135\u0139\u0003>\u001f\u0000\u0136\u0139\u0003@ \u0000\u0137"+ - "\u0139\u0003B!\u0000\u0138\u0135\u0001\u0000\u0000\u0000\u0138\u0136\u0001"+ - "\u0000\u0000\u0000\u0138\u0137\u0001\u0000\u0000\u0000\u0139=\u0001\u0000"+ - "\u0000\u0000\u013a\u013b\u0007\u0004\u0000\u0000\u013b?\u0001\u0000\u0000"+ - "\u0000\u013c\u013d\u0007\u0005\u0000\u0000\u013dA\u0001\u0000\u0000\u0000"+ - "\u013e\u013f\u0007\u0006\u0000\u0000\u013fC\u0001\u0000\u0000\u0000\u0140"+ - "\u0141\u0005\u0011\u0000\u0000\u0141\u0149\u0005\b\u0000\u0000\u0142\u0146"+ - "\u0005\f\u0000\u0000\u0143\u0145\u0003F#\u0000\u0144\u0143\u0001\u0000"+ - "\u0000\u0000\u0145\u0148\u0001\u0000\u0000\u0000\u0146\u0144\u0001\u0000"+ - "\u0000\u0000\u0146\u0147\u0001\u0000\u0000\u0000\u0147\u014a\u0001\u0000"+ - "\u0000\u0000\u0148\u0146\u0001\u0000\u0000\u0000\u0149\u0142\u0001\u0000"+ - "\u0000\u0000\u014a\u014b\u0001\u0000\u0000\u0000\u014b\u0149\u0001\u0000"+ - "\u0000\u0000\u014b\u014c\u0001\u0000\u0000\u0000\u014c\u014d\u0001\u0000"+ - "\u0000\u0000\u014d\u0157\u0005\b\u0000\u0000\u014e\u0152\u0005\u000f\u0000"+ - "\u0000\u014f\u0151\u0003J%\u0000\u0150\u014f\u0001\u0000\u0000\u0000\u0151"+ - "\u0154\u0001\u0000\u0000\u0000\u0152\u0150\u0001\u0000\u0000\u0000\u0152"+ - "\u0153\u0001\u0000\u0000\u0000\u0153\u0155\u0001\u0000\u0000\u0000\u0154"+ - "\u0152\u0001\u0000\u0000\u0000\u0155\u0157\u0005\u000f\u0000\u0000\u0156"+ - "\u0140\u0001\u0000\u0000\u0000\u0156\u014e\u0001\u0000\u0000\u0000\u0157"+ - "E\u0001\u0000\u0000\u0000\u0158\u015b\u0003N\'\u0000\u0159\u015b\u0003"+ - "H$\u0000\u015a\u0158\u0001\u0000\u0000\u0000\u015a\u0159\u0001\u0000\u0000"+ - "\u0000\u015bG\u0001\u0000\u0000\u0000\u015c\u015d\u0005.\u0000\u0000\u015d"+ - "I\u0001\u0000\u0000\u0000\u015e\u0161\u0003N\'\u0000\u015f\u0161\u0003"+ - "L&\u0000\u0160\u015e\u0001\u0000\u0000\u0000\u0160\u015f\u0001\u0000\u0000"+ - "\u0000\u0161K\u0001\u0000\u0000\u0000\u0162\u0163\u0005,\u0000\u0000\u0163"+ - "M\u0001\u0000\u0000\u0000\u0164\u0165\u0005+\u0000\u0000\u0165\u0166\u0003"+ - "\f\u0006\u0000\u0166\u0167\u0005\u0006\u0000\u0000\u0167O\u0001\u0000"+ - "\u0000\u0000\'UZdns}\u008b\u0093\u0095\u00a0\u00a8\u00ac\u00ae\u00b6\u00c3"+ - "\u00c7\u00cc\u00d0\u00d7\u00e2\u00e5\u00ec\u00f9\u0102\u0106\u0111\u0118"+ - "\u0120\u0122\u0127\u012e\u0133\u0138\u0146\u014b\u0152\u0156\u015a\u0160"; + "#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002"+ + "(\u0007(\u0001\u0000\u0001\u0000\u0001\u0001\u0005\u0001V\b\u0001\n\u0001"+ + "\f\u0001Y\t\u0001\u0001\u0002\u0001\u0002\u0003\u0002]\b\u0002\u0001\u0003"+ + "\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0005\u0004"+ + "e\b\u0004\n\u0004\f\u0004h\t\u0004\u0001\u0004\u0001\u0004\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005q\b\u0005"+ + "\u0001\u0006\u0001\u0006\u0001\u0006\u0003\u0006v\b\u0006\u0001\u0006"+ + "\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0005\u0006"+ + "~\b\u0006\n\u0006\f\u0006\u0081\t\u0006\u0001\u0007\u0001\u0007\u0001"+ + "\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+ + "\u0007\u0001\u0007\u0001\u0007\u0003\u0007\u008e\b\u0007\u0001\u0007\u0001"+ + "\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+ + "\u0007\u0005\u0007\u0098\b\u0007\n\u0007\f\u0007\u009b\t\u0007\u0001\b"+ + "\u0001\b\u0001\b\u0001\b\u0001\t\u0001\t\u0001\n\u0001\n\u0003\n\u00a5"+ + "\b\n\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0005\u000b\u00ab"+ + "\b\u000b\n\u000b\f\u000b\u00ae\t\u000b\u0001\u000b\u0003\u000b\u00b1\b"+ + "\u000b\u0003\u000b\u00b3\b\u000b\u0001\u000b\u0001\u000b\u0001\f\u0001"+ + "\f\u0005\f\u00b9\b\f\n\f\f\f\u00bc\t\f\u0001\f\u0001\f\u0001\r\u0001\r"+ + "\u0001\r\u0001\r\u0001\r\u0001\r\u0005\r\u00c6\b\r\n\r\f\r\u00c9\t\r\u0001"+ + "\r\u0001\r\u0003\r\u00cd\b\r\u0001\r\u0001\r\u0001\r\u0003\r\u00d2\b\r"+ + "\u0001\u000e\u0001\u000e\u0003\u000e\u00d6\b\u000e\u0001\u000f\u0001\u000f"+ + "\u0001\u000f\u0001\u000f\u0001\u000f\u0003\u000f\u00dd\b\u000f\u0001\u000f"+ + "\u0001\u000f\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010"+ + "\u0001\u0010\u0001\u0010\u0003\u0010\u00e8\b\u0010\u0001\u0010\u0003\u0010"+ + "\u00eb\b\u0010\u0001\u0010\u0001\u0010\u0001\u0011\u0001\u0011\u0001\u0011"+ + "\u0003\u0011\u00f2\b\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0012"+ + "\u0001\u0012\u0001\u0012\u0001\u0013\u0001\u0013\u0001\u0014\u0001\u0014"+ + "\u0001\u0014\u0003\u0014\u00ff\b\u0014\u0001\u0014\u0001\u0014\u0001\u0015"+ + "\u0001\u0015\u0001\u0015\u0005\u0015\u0106\b\u0015\n\u0015\f\u0015\u0109"+ + "\t\u0015\u0001\u0015\u0003\u0015\u010c\b\u0015\u0001\u0016\u0001\u0016"+ + "\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0018"+ + "\u0001\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0003\u0019\u011a\b\u0019"+ + "\u0001\u001a\u0001\u001a\u0001\u001a\u0005\u001a\u011f\b\u001a\n\u001a"+ + "\f\u001a\u0122\t\u001a\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001b"+ + "\u0001\u001b\u0005\u001b\u0129\b\u001b\n\u001b\f\u001b\u012c\t\u001b\u0001"+ + "\u001c\u0001\u001c\u0003\u001c\u0130\b\u001c\u0001\u001d\u0001\u001d\u0001"+ + "\u001d\u0001\u001e\u0001\u001e\u0003\u001e\u0137\b\u001e\u0001\u001e\u0001"+ + "\u001e\u0001\u001e\u0003\u001e\u013c\b\u001e\u0001\u001f\u0001\u001f\u0001"+ + "\u001f\u0003\u001f\u0141\b\u001f\u0001 \u0001 \u0001!\u0001!\u0001\"\u0001"+ + "\"\u0001#\u0001#\u0001#\u0001#\u0005#\u014d\b#\n#\f#\u0150\t#\u0004#\u0152"+ + "\b#\u000b#\f#\u0153\u0001#\u0001#\u0001#\u0005#\u0159\b#\n#\f#\u015c\t"+ + "#\u0001#\u0003#\u015f\b#\u0001$\u0001$\u0003$\u0163\b$\u0001%\u0001%\u0001"+ + "&\u0001&\u0003&\u0169\b&\u0001\'\u0001\'\u0001(\u0001(\u0001(\u0001(\u0001"+ + "(\u0000\u0002\f\u000e)\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012"+ + "\u0014\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNP\u0000\u0007"+ + "\u0002\u0000\r\u000e\u0010\u0010\u0002\u0000\u0007\u0007\u0016\u0016\u0002"+ + "\u0000\'\'))\u0002\u0000\u0019\u0019!!\u0004\u0000\u0014\u0015\u001b\u001c"+ + "\"\"%%\u0005\u0000\u0012\u0012\u0019\u0019 $$((\u0002\u0000\u0013\u0013"+ + "\u001a\u001a\u0178\u0000R\u0001\u0000\u0000\u0000\u0002W\u0001\u0000\u0000"+ + "\u0000\u0004\\\u0001\u0000\u0000\u0000\u0006^\u0001\u0000\u0000\u0000"+ + "\bb\u0001\u0000\u0000\u0000\np\u0001\u0000\u0000\u0000\fu\u0001\u0000"+ + "\u0000\u0000\u000e\u008d\u0001\u0000\u0000\u0000\u0010\u009c\u0001\u0000"+ + "\u0000\u0000\u0012\u00a0\u0001\u0000\u0000\u0000\u0014\u00a4\u0001\u0000"+ + "\u0000\u0000\u0016\u00a6\u0001\u0000\u0000\u0000\u0018\u00b6\u0001\u0000"+ + "\u0000\u0000\u001a\u00cc\u0001\u0000\u0000\u0000\u001c\u00d5\u0001\u0000"+ + "\u0000\u0000\u001e\u00d7\u0001\u0000\u0000\u0000 \u00e0\u0001\u0000\u0000"+ + "\u0000\"\u00ee\u0001\u0000\u0000\u0000$\u00f6\u0001\u0000\u0000\u0000"+ + "&\u00f9\u0001\u0000\u0000\u0000(\u00fb\u0001\u0000\u0000\u0000*\u0102"+ + "\u0001\u0000\u0000\u0000,\u010d\u0001\u0000\u0000\u0000.\u0111\u0001\u0000"+ + "\u0000\u00000\u0114\u0001\u0000\u0000\u00002\u0119\u0001\u0000\u0000\u0000"+ + "4\u011b\u0001\u0000\u0000\u00006\u0123\u0001\u0000\u0000\u00008\u012f"+ + "\u0001\u0000\u0000\u0000:\u0131\u0001\u0000\u0000\u0000<\u0136\u0001\u0000"+ + "\u0000\u0000>\u0140\u0001\u0000\u0000\u0000@\u0142\u0001\u0000\u0000\u0000"+ + "B\u0144\u0001\u0000\u0000\u0000D\u0146\u0001\u0000\u0000\u0000F\u015e"+ + "\u0001\u0000\u0000\u0000H\u0162\u0001\u0000\u0000\u0000J\u0164\u0001\u0000"+ + "\u0000\u0000L\u0168\u0001\u0000\u0000\u0000N\u016a\u0001\u0000\u0000\u0000"+ + "P\u016c\u0001\u0000\u0000\u0000RS\u0003\u0002\u0001\u0000S\u0001\u0001"+ + "\u0000\u0000\u0000TV\u0003\u0004\u0002\u0000UT\u0001\u0000\u0000\u0000"+ + "VY\u0001\u0000\u0000\u0000WU\u0001\u0000\u0000\u0000WX\u0001\u0000\u0000"+ + "\u0000X\u0003\u0001\u0000\u0000\u0000YW\u0001\u0000\u0000\u0000Z]\u0003"+ + "\u0006\u0003\u0000[]\u0003\b\u0004\u0000\\Z\u0001\u0000\u0000\u0000\\"+ + "[\u0001\u0000\u0000\u0000]\u0005\u0001\u0000\u0000\u0000^_\u0005\b\u0000"+ + "\u0000_`\u0005\u0007\u0000\u0000`a\u0003\f\u0006\u0000a\u0007\u0001\u0000"+ + "\u0000\u0000bf\u0005\b\u0000\u0000ce\u0003\n\u0005\u0000dc\u0001\u0000"+ + "\u0000\u0000eh\u0001\u0000\u0000\u0000fd\u0001\u0000\u0000\u0000fg\u0001"+ + "\u0000\u0000\u0000gi\u0001\u0000\u0000\u0000hf\u0001\u0000\u0000\u0000"+ + "ij\u0003\u0010\b\u0000j\t\u0001\u0000\u0000\u0000kl\u0005\u000f\u0000"+ + "\u0000lm\u0003N\'\u0000mn\u0005\u000f\u0000\u0000nq\u0001\u0000\u0000"+ + "\u0000oq\u0005\b\u0000\u0000pk\u0001\u0000\u0000\u0000po\u0001\u0000\u0000"+ + "\u0000q\u000b\u0001\u0000\u0000\u0000rs\u0006\u0006\uffff\uffff\u0000"+ + "sv\u0003\u000e\u0007\u0000tv\u00038\u001c\u0000ur\u0001\u0000\u0000\u0000"+ + "ut\u0001\u0000\u0000\u0000v\u007f\u0001\u0000\u0000\u0000wx\n\u0001\u0000"+ + "\u0000xy\u0005\u001d\u0000\u0000yz\u0003\f\u0006\u0000z{\u0005\u0016\u0000"+ + "\u0000{|\u0003\f\u0006\u0002|~\u0001\u0000\u0000\u0000}w\u0001\u0000\u0000"+ + "\u0000~\u0081\u0001\u0000\u0000\u0000\u007f}\u0001\u0000\u0000\u0000\u007f"+ + "\u0080\u0001\u0000\u0000\u0000\u0080\r\u0001\u0000\u0000\u0000\u0081\u007f"+ + "\u0001\u0000\u0000\u0000\u0082\u0083\u0006\u0007\uffff\uffff\u0000\u0083"+ + "\u008e\u0003F#\u0000\u0084\u008e\u0003\u0012\t\u0000\u0085\u008e\u0003"+ + "\u001c\u000e\u0000\u0086\u008e\u0003\u0014\n\u0000\u0087\u008e\u0003&"+ + "\u0013\u0000\u0088\u008e\u0003(\u0014\u0000\u0089\u008a\u0005\u0018\u0000"+ + "\u0000\u008a\u008b\u0003\f\u0006\u0000\u008b\u008c\u0005\u001f\u0000\u0000"+ + "\u008c\u008e\u0001\u0000\u0000\u0000\u008d\u0082\u0001\u0000\u0000\u0000"+ + "\u008d\u0084\u0001\u0000\u0000\u0000\u008d\u0085\u0001\u0000\u0000\u0000"+ + "\u008d\u0086\u0001\u0000\u0000\u0000\u008d\u0087\u0001\u0000\u0000\u0000"+ + "\u008d\u0088\u0001\u0000\u0000\u0000\u008d\u0089\u0001\u0000\u0000\u0000"+ + "\u008e\u0099\u0001\u0000\u0000\u0000\u008f\u0090\n\u0005\u0000\u0000\u0090"+ + "\u0098\u0003,\u0016\u0000\u0091\u0092\n\u0004\u0000\u0000\u0092\u0098"+ + "\u0003.\u0017\u0000\u0093\u0094\n\u0003\u0000\u0000\u0094\u0098\u0003"+ + "0\u0018\u0000\u0095\u0096\n\u0002\u0000\u0000\u0096\u0098\u00032\u0019"+ + "\u0000\u0097\u008f\u0001\u0000\u0000\u0000\u0097\u0091\u0001\u0000\u0000"+ + "\u0000\u0097\u0093\u0001\u0000\u0000\u0000\u0097\u0095\u0001\u0000\u0000"+ + "\u0000\u0098\u009b\u0001\u0000\u0000\u0000\u0099\u0097\u0001\u0000\u0000"+ + "\u0000\u0099\u009a\u0001\u0000\u0000\u0000\u009a\u000f\u0001\u0000\u0000"+ + "\u0000\u009b\u0099\u0001\u0000\u0000\u0000\u009c\u009d\u0005\u0005\u0000"+ + "\u0000\u009d\u009e\u0003\u0002\u0001\u0000\u009e\u009f\u0005\u0006\u0000"+ + "\u0000\u009f\u0011\u0001\u0000\u0000\u0000\u00a0\u00a1\u0007\u0000\u0000"+ + "\u0000\u00a1\u0013\u0001\u0000\u0000\u0000\u00a2\u00a5\u0003\u0016\u000b"+ + "\u0000\u00a3\u00a5\u0003\u0018\f\u0000\u00a4\u00a2\u0001\u0000\u0000\u0000"+ + "\u00a4\u00a3\u0001\u0000\u0000\u0000\u00a5\u0015\u0001\u0000\u0000\u0000"+ + "\u00a6\u00b2\u0005\u0017\u0000\u0000\u00a7\u00ac\u0003\f\u0006\u0000\u00a8"+ + "\u00a9\u0005\'\u0000\u0000\u00a9\u00ab\u0003\f\u0006\u0000\u00aa\u00a8"+ + "\u0001\u0000\u0000\u0000\u00ab\u00ae\u0001\u0000\u0000\u0000\u00ac\u00aa"+ + "\u0001\u0000\u0000\u0000\u00ac\u00ad\u0001\u0000\u0000\u0000\u00ad\u00b0"+ + "\u0001\u0000\u0000\u0000\u00ae\u00ac\u0001\u0000\u0000\u0000\u00af\u00b1"+ + "\u0005\'\u0000\u0000\u00b0\u00af\u0001\u0000\u0000\u0000\u00b0\u00b1\u0001"+ + "\u0000\u0000\u0000\u00b1\u00b3\u0001\u0000\u0000\u0000\u00b2\u00a7\u0001"+ + "\u0000\u0000\u0000\u00b2\u00b3\u0001\u0000\u0000\u0000\u00b3\u00b4\u0001"+ + "\u0000\u0000\u0000\u00b4\u00b5\u0005\u001e\u0000\u0000\u00b5\u0017\u0001"+ + "\u0000\u0000\u0000\u00b6\u00ba\u0005\u0005\u0000\u0000\u00b7\u00b9\u0003"+ + "\u001a\r\u0000\u00b8\u00b7\u0001\u0000\u0000\u0000\u00b9\u00bc\u0001\u0000"+ + "\u0000\u0000\u00ba\u00b8\u0001\u0000\u0000\u0000\u00ba\u00bb\u0001\u0000"+ + "\u0000\u0000\u00bb\u00bd\u0001\u0000\u0000\u0000\u00bc\u00ba\u0001\u0000"+ + "\u0000\u0000\u00bd\u00be\u0005\u0006\u0000\u0000\u00be\u0019\u0001\u0000"+ + "\u0000\u0000\u00bf\u00cd\u0005\b\u0000\u0000\u00c0\u00c1\u0005\u0018\u0000"+ + "\u0000\u00c1\u00c2\u0005\b\u0000\u0000\u00c2\u00cd\u0005\u001f\u0000\u0000"+ + "\u00c3\u00c7\u0005\u000f\u0000\u0000\u00c4\u00c6\u0003L&\u0000\u00c5\u00c4"+ + "\u0001\u0000\u0000\u0000\u00c6\u00c9\u0001\u0000\u0000\u0000\u00c7\u00c5"+ + "\u0001\u0000\u0000\u0000\u00c7\u00c8\u0001\u0000\u0000\u0000\u00c8\u00ca"+ + "\u0001\u0000\u0000\u0000\u00c9\u00c7\u0001\u0000\u0000\u0000\u00ca\u00cd"+ + "\u0005\u000f\u0000\u0000\u00cb\u00cd\u0003\f\u0006\u0000\u00cc\u00bf\u0001"+ + "\u0000\u0000\u0000\u00cc\u00c0\u0001\u0000\u0000\u0000\u00cc\u00c3\u0001"+ + "\u0000\u0000\u0000\u00cc\u00cb\u0001\u0000\u0000\u0000\u00cd\u00ce\u0001"+ + "\u0000\u0000\u0000\u00ce\u00cf\u0007\u0001\u0000\u0000\u00cf\u00d1\u0003"+ + "\f\u0006\u0000\u00d0\u00d2\u0005\'\u0000\u0000\u00d1\u00d0\u0001\u0000"+ + "\u0000\u0000\u00d1\u00d2\u0001\u0000\u0000\u0000\u00d2\u001b\u0001\u0000"+ + "\u0000\u0000\u00d3\u00d6\u0003\u001e\u000f\u0000\u00d4\u00d6\u0003 \u0010"+ + "\u0000\u00d5\u00d3\u0001\u0000\u0000\u0000\u00d5\u00d4\u0001\u0000\u0000"+ + "\u0000\u00d6\u001d\u0001\u0000\u0000\u0000\u00d7\u00d8\u0005\u0002\u0000"+ + "\u0000\u00d8\u00d9\u0003\"\u0011\u0000\u00d9\u00da\u0005\u0016\u0000\u0000"+ + "\u00da\u00dc\u0003\f\u0006\u0000\u00db\u00dd\u0003$\u0012\u0000\u00dc"+ + "\u00db\u0001\u0000\u0000\u0000\u00dc\u00dd\u0001\u0000\u0000\u0000\u00dd"+ + "\u00de\u0001\u0000\u0000\u0000\u00de\u00df\u0005\u001e\u0000\u0000\u00df"+ + "\u001f\u0001\u0000\u0000\u0000\u00e0\u00e1\u0005\u0001\u0000\u0000\u00e1"+ + "\u00e2\u0003\"\u0011\u0000\u00e2\u00e3\u0005\u0016\u0000\u0000\u00e3\u00e4"+ + "\u0003\f\u0006\u0000\u00e4\u00e5\u0005&\u0000\u0000\u00e5\u00e7\u0003"+ + "\f\u0006\u0000\u00e6\u00e8\u0005)\u0000\u0000\u00e7\u00e6\u0001\u0000"+ + "\u0000\u0000\u00e7\u00e8\u0001\u0000\u0000\u0000\u00e8\u00ea\u0001\u0000"+ + "\u0000\u0000\u00e9\u00eb\u0003$\u0012\u0000\u00ea\u00e9\u0001\u0000\u0000"+ + "\u0000\u00ea\u00eb\u0001\u0000\u0000\u0000\u00eb\u00ec\u0001\u0000\u0000"+ + "\u0000\u00ec\u00ed\u0005\u0006\u0000\u0000\u00ed!\u0001\u0000\u0000\u0000"+ + "\u00ee\u00f1\u0005\b\u0000\u0000\u00ef\u00f0\u0005\'\u0000\u0000\u00f0"+ + "\u00f2\u0005\b\u0000\u0000\u00f1\u00ef\u0001\u0000\u0000\u0000\u00f1\u00f2"+ + "\u0001\u0000\u0000\u0000\u00f2\u00f3\u0001\u0000\u0000\u0000\u00f3\u00f4"+ + "\u0005\u0004\u0000\u0000\u00f4\u00f5\u0003\f\u0006\u0000\u00f5#\u0001"+ + "\u0000\u0000\u0000\u00f6\u00f7\u0005\u0003\u0000\u0000\u00f7\u00f8\u0003"+ + "\f\u0006\u0000\u00f8%\u0001\u0000\u0000\u0000\u00f9\u00fa\u0005\b\u0000"+ + "\u0000\u00fa\'\u0001\u0000\u0000\u0000\u00fb\u00fc\u0005\b\u0000\u0000"+ + "\u00fc\u00fe\u0005\u0018\u0000\u0000\u00fd\u00ff\u0003*\u0015\u0000\u00fe"+ + "\u00fd\u0001\u0000\u0000\u0000\u00fe\u00ff\u0001\u0000\u0000\u0000\u00ff"+ + "\u0100\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u001f\u0000\u0000\u0101"+ + ")\u0001\u0000\u0000\u0000\u0102\u0107\u0003\f\u0006\u0000\u0103\u0104"+ + "\u0005\'\u0000\u0000\u0104\u0106\u0003\f\u0006\u0000\u0105\u0103\u0001"+ + "\u0000\u0000\u0000\u0106\u0109\u0001\u0000\u0000\u0000\u0107\u0105\u0001"+ + "\u0000\u0000\u0000\u0107\u0108\u0001\u0000\u0000\u0000\u0108\u010b\u0001"+ + "\u0000\u0000\u0000\u0109\u0107\u0001\u0000\u0000\u0000\u010a\u010c\u0007"+ + "\u0002\u0000\u0000\u010b\u010a\u0001\u0000\u0000\u0000\u010b\u010c\u0001"+ + "\u0000\u0000\u0000\u010c+\u0001\u0000\u0000\u0000\u010d\u010e\u0005\u0017"+ + "\u0000\u0000\u010e\u010f\u0003\f\u0006\u0000\u010f\u0110\u0005\u001e\u0000"+ + "\u0000\u0110-\u0001\u0000\u0000\u0000\u0111\u0112\u0005#\u0000\u0000\u0112"+ + "\u0113\u0005\b\u0000\u0000\u0113/\u0001\u0000\u0000\u0000\u0114\u0115"+ + "\u0005#\u0000\u0000\u0115\u0116\u0005\r\u0000\u0000\u01161\u0001\u0000"+ + "\u0000\u0000\u0117\u011a\u00034\u001a\u0000\u0118\u011a\u00036\u001b\u0000"+ + "\u0119\u0117\u0001\u0000\u0000\u0000\u0119\u0118\u0001\u0000\u0000\u0000"+ + "\u011a3\u0001\u0000\u0000\u0000\u011b\u011c\u0005#\u0000\u0000\u011c\u0120"+ + "\u0005 \u0000\u0000\u011d\u011f\u0003.\u0017\u0000\u011e\u011d\u0001\u0000"+ + "\u0000\u0000\u011f\u0122\u0001\u0000\u0000\u0000\u0120\u011e\u0001\u0000"+ + "\u0000\u0000\u0120\u0121\u0001\u0000\u0000\u0000\u01215\u0001\u0000\u0000"+ + "\u0000\u0122\u0120\u0001\u0000\u0000\u0000\u0123\u0124\u0005\u0017\u0000"+ + "\u0000\u0124\u0125\u0005 \u0000\u0000\u0125\u012a\u0005\u001e\u0000\u0000"+ + "\u0126\u0129\u0003.\u0017\u0000\u0127\u0129\u0003,\u0016\u0000\u0128\u0126"+ + "\u0001\u0000\u0000\u0000\u0128\u0127\u0001\u0000\u0000\u0000\u0129\u012c"+ + "\u0001\u0000\u0000\u0000\u012a\u0128\u0001\u0000\u0000\u0000\u012a\u012b"+ + "\u0001\u0000\u0000\u0000\u012b7\u0001\u0000\u0000\u0000\u012c\u012a\u0001"+ + "\u0000\u0000\u0000\u012d\u0130\u0003:\u001d\u0000\u012e\u0130\u0003<\u001e"+ + "\u0000\u012f\u012d\u0001\u0000\u0000\u0000\u012f\u012e\u0001\u0000\u0000"+ + "\u0000\u01309\u0001\u0000\u0000\u0000\u0131\u0132\u0007\u0003\u0000\u0000"+ + "\u0132\u0133\u0003\u000e\u0007\u0000\u0133;\u0001\u0000\u0000\u0000\u0134"+ + "\u0137\u0003\u000e\u0007\u0000\u0135\u0137\u0003:\u001d\u0000\u0136\u0134"+ + "\u0001\u0000\u0000\u0000\u0136\u0135\u0001\u0000\u0000\u0000\u0137\u0138"+ + "\u0001\u0000\u0000\u0000\u0138\u013b\u0003>\u001f\u0000\u0139\u013c\u0003"+ + "\u000e\u0007\u0000\u013a\u013c\u00038\u001c\u0000\u013b\u0139\u0001\u0000"+ + "\u0000\u0000\u013b\u013a\u0001\u0000\u0000\u0000\u013c=\u0001\u0000\u0000"+ + "\u0000\u013d\u0141\u0003@ \u0000\u013e\u0141\u0003B!\u0000\u013f\u0141"+ + "\u0003D\"\u0000\u0140\u013d\u0001\u0000\u0000\u0000\u0140\u013e\u0001"+ + "\u0000\u0000\u0000\u0140\u013f\u0001\u0000\u0000\u0000\u0141?\u0001\u0000"+ + "\u0000\u0000\u0142\u0143\u0007\u0004\u0000\u0000\u0143A\u0001\u0000\u0000"+ + "\u0000\u0144\u0145\u0007\u0005\u0000\u0000\u0145C\u0001\u0000\u0000\u0000"+ + "\u0146\u0147\u0007\u0006\u0000\u0000\u0147E\u0001\u0000\u0000\u0000\u0148"+ + "\u0149\u0005\u0011\u0000\u0000\u0149\u0151\u0005\b\u0000\u0000\u014a\u014e"+ + "\u0005\f\u0000\u0000\u014b\u014d\u0003H$\u0000\u014c\u014b\u0001\u0000"+ + "\u0000\u0000\u014d\u0150\u0001\u0000\u0000\u0000\u014e\u014c\u0001\u0000"+ + "\u0000\u0000\u014e\u014f\u0001\u0000\u0000\u0000\u014f\u0152\u0001\u0000"+ + "\u0000\u0000\u0150\u014e\u0001\u0000\u0000\u0000\u0151\u014a\u0001\u0000"+ + "\u0000\u0000\u0152\u0153\u0001\u0000\u0000\u0000\u0153\u0151\u0001\u0000"+ + "\u0000\u0000\u0153\u0154\u0001\u0000\u0000\u0000\u0154\u0155\u0001\u0000"+ + "\u0000\u0000\u0155\u015f\u0005\b\u0000\u0000\u0156\u015a\u0005\u000f\u0000"+ + "\u0000\u0157\u0159\u0003L&\u0000\u0158\u0157\u0001\u0000\u0000\u0000\u0159"+ + "\u015c\u0001\u0000\u0000\u0000\u015a\u0158\u0001\u0000\u0000\u0000\u015a"+ + "\u015b\u0001\u0000\u0000\u0000\u015b\u015d\u0001\u0000\u0000\u0000\u015c"+ + "\u015a\u0001\u0000\u0000\u0000\u015d\u015f\u0005\u000f\u0000\u0000\u015e"+ + "\u0148\u0001\u0000\u0000\u0000\u015e\u0156\u0001\u0000\u0000\u0000\u015f"+ + "G\u0001\u0000\u0000\u0000\u0160\u0163\u0003P(\u0000\u0161\u0163\u0003"+ + "J%\u0000\u0162\u0160\u0001\u0000\u0000\u0000\u0162\u0161\u0001\u0000\u0000"+ + "\u0000\u0163I\u0001\u0000\u0000\u0000\u0164\u0165\u0005.\u0000\u0000\u0165"+ + "K\u0001\u0000\u0000\u0000\u0166\u0169\u0003P(\u0000\u0167\u0169\u0003"+ + "N\'\u0000\u0168\u0166\u0001\u0000\u0000\u0000\u0168\u0167\u0001\u0000"+ + "\u0000\u0000\u0169M\u0001\u0000\u0000\u0000\u016a\u016b\u0005,\u0000\u0000"+ + "\u016bO\u0001\u0000\u0000\u0000\u016c\u016d\u0005+\u0000\u0000\u016d\u016e"+ + "\u0003\f\u0006\u0000\u016e\u016f\u0005\u0006\u0000\u0000\u016fQ\u0001"+ + "\u0000\u0000\u0000\'W\\fpu\u007f\u008d\u0097\u0099\u00a4\u00ac\u00b0\u00b2"+ + "\u00ba\u00c7\u00cc\u00d1\u00d5\u00dc\u00e7\u00ea\u00f1\u00fe\u0107\u010b"+ + "\u0119\u0120\u0128\u012a\u012f\u0136\u013b\u0140\u014e\u0153\u015a\u015e"+ + "\u0162\u0168"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseListener.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseListener.java index dbdd45be121..0d44480c791 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseListener.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -219,6 +219,18 @@ public class HCLParserBaseListener implements HCLParserListener { *

The default implementation does nothing.

*/ @Override public void exitIndexAccessExpression(HCLParser.IndexAccessExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx) { } /** * {@inheritDoc} * @@ -447,6 +459,18 @@ public class HCLParserBaseListener implements HCLParserListener { *

The default implementation does nothing.

*/ @Override public void exitGetAttr(HCLParser.GetAttrContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLegacyIndexAttr(HCLParser.LegacyIndexAttrContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLegacyIndexAttr(HCLParser.LegacyIndexAttrContext ctx) { } /** * {@inheritDoc} * diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseVisitor.java index fb7e701455a..cc3e91b9973 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserBaseVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,6 +139,13 @@ public class HCLParserBaseVisitor extends AbstractParseTreeVisitor impleme * {@link #visitChildren} on {@code ctx}.

*/ @Override public T visitIndexAccessExpression(HCLParser.IndexAccessExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * @@ -272,6 +279,13 @@ public class HCLParserBaseVisitor extends AbstractParseTreeVisitor impleme * {@link #visitChildren} on {@code ctx}.

*/ @Override public T visitGetAttr(HCLParser.GetAttrContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLegacyIndexAttr(HCLParser.LegacyIndexAttrContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserListener.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserListener.java index fde58c5b522..6725c41ed20 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserListener.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -202,6 +202,18 @@ public interface HCLParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitIndexAccessExpression(HCLParser.IndexAccessExpressionContext ctx); + /** + * Enter a parse tree produced by the {@code LegacyIndexAttributeExpression} + * labeled alternative in {@link HCLParser#exprTerm}. + * @param ctx the parse tree + */ + void enterLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx); + /** + * Exit a parse tree produced by the {@code LegacyIndexAttributeExpression} + * labeled alternative in {@link HCLParser#exprTerm}. + * @param ctx the parse tree + */ + void exitLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx); /** * Enter a parse tree produced by the {@code ForExpression} * labeled alternative in {@link HCLParser#exprTerm}. @@ -398,6 +410,16 @@ public interface HCLParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitGetAttr(HCLParser.GetAttrContext ctx); + /** + * Enter a parse tree produced by {@link HCLParser#legacyIndexAttr}. + * @param ctx the parse tree + */ + void enterLegacyIndexAttr(HCLParser.LegacyIndexAttrContext ctx); + /** + * Exit a parse tree produced by {@link HCLParser#legacyIndexAttr}. + * @param ctx the parse tree + */ + void exitLegacyIndexAttr(HCLParser.LegacyIndexAttrContext ctx); /** * Enter a parse tree produced by {@link HCLParser#splat}. * @param ctx the parse tree diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserVisitor.java index 4d9f7ee6d32..f1a5f11d588 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/HCLParserVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -131,6 +131,13 @@ public interface HCLParserVisitor extends ParseTreeVisitor { * @return the visitor result */ T visitIndexAccessExpression(HCLParser.IndexAccessExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code LegacyIndexAttributeExpression} + * labeled alternative in {@link HCLParser#exprTerm}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLegacyIndexAttributeExpression(HCLParser.LegacyIndexAttributeExpressionContext ctx); /** * Visit a parse tree produced by the {@code ForExpression} * labeled alternative in {@link HCLParser#exprTerm}. @@ -248,6 +255,12 @@ public interface HCLParserVisitor extends ParseTreeVisitor { * @return the visitor result */ T visitGetAttr(HCLParser.GetAttrContext ctx); + /** + * Visit a parse tree produced by {@link HCLParser#legacyIndexAttr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLegacyIndexAttr(HCLParser.LegacyIndexAttrContext ctx); /** * Visit a parse tree produced by {@link HCLParser#splat}. * @param ctx the parse tree diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathLexer.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathLexer.java index e4ff51db26c..987668c563c 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathLexer.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathLexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.hcl.internal.grammar; - +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class JsonPathLexer extends Lexer { @@ -31,11 +32,11 @@ public class JsonPathLexer extends Lexer { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - WS=1, UTF_8_BOM=2, MATCHES_REGEX_OPEN=3, LBRACE=4, RBRACE=5, LBRACK=6, - RBRACK=7, LPAREN=8, RPAREN=9, AT=10, DOT=11, DOT_DOT=12, ROOT=13, WILDCARD=14, - COLON=15, QUESTION=16, CONTAINS=17, Identifier=18, StringLiteral=19, PositiveNumber=20, - NegativeNumber=21, NumericLiteral=22, COMMA=23, TICK=24, QUOTE=25, MATCHES=26, - LOGICAL_OPERATOR=27, AND=28, OR=29, EQUALITY_OPERATOR=30, EQ=31, NE=32, + WS=1, UTF_8_BOM=2, MATCHES_REGEX_OPEN=3, LBRACE=4, RBRACE=5, LBRACK=6, + RBRACK=7, LPAREN=8, RPAREN=9, AT=10, DOT=11, DOT_DOT=12, ROOT=13, WILDCARD=14, + COLON=15, QUESTION=16, CONTAINS=17, Identifier=18, StringLiteral=19, PositiveNumber=20, + NegativeNumber=21, NumericLiteral=22, COMMA=23, TICK=24, QUOTE=25, MATCHES=26, + LOGICAL_OPERATOR=27, AND=28, OR=29, EQUALITY_OPERATOR=30, EQ=31, NE=32, TRUE=33, FALSE=34, NULL=35, MATCHES_REGEX_CLOSE=36, S=37, REGEX=38; public static final int MATCHES_REGEX=1; @@ -49,12 +50,12 @@ public class JsonPathLexer extends Lexer { private static String[] makeRuleNames() { return new String[] { - "WS", "UTF_8_BOM", "MATCHES_REGEX_OPEN", "LBRACE", "RBRACE", "LBRACK", - "RBRACK", "LPAREN", "RPAREN", "AT", "DOT", "DOT_DOT", "ROOT", "WILDCARD", - "COLON", "QUESTION", "CONTAINS", "Identifier", "StringLiteral", "PositiveNumber", - "NegativeNumber", "NumericLiteral", "COMMA", "TICK", "QUOTE", "MATCHES", - "LOGICAL_OPERATOR", "AND", "OR", "EQUALITY_OPERATOR", "EQ", "NE", "TRUE", - "FALSE", "NULL", "ESCAPE_SEQUENCE", "UNICODE", "HEX_DIGIT", "SAFE_CODE_POINT", + "WS", "UTF_8_BOM", "MATCHES_REGEX_OPEN", "LBRACE", "RBRACE", "LBRACK", + "RBRACK", "LPAREN", "RPAREN", "AT", "DOT", "DOT_DOT", "ROOT", "WILDCARD", + "COLON", "QUESTION", "CONTAINS", "Identifier", "StringLiteral", "PositiveNumber", + "NegativeNumber", "NumericLiteral", "COMMA", "TICK", "QUOTE", "MATCHES", + "LOGICAL_OPERATOR", "AND", "OR", "EQUALITY_OPERATOR", "EQ", "NE", "TRUE", + "FALSE", "NULL", "ESCAPE_SEQUENCE", "UNICODE", "HEX_DIGIT", "SAFE_CODE_POINT", "EXPONENT_PART", "MINUS", "MATCHES_REGEX_CLOSE", "S", "REGEX" }; } @@ -62,20 +63,20 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, null, "'\\uFEFF'", null, "'{'", "'}'", "'['", "']'", "'('", "')'", - "'@'", "'.'", "'..'", "'$'", "'*'", "':'", "'?'", "'contains'", null, - null, null, null, null, "','", "'''", "'\"'", "'=~'", null, "'&&'", "'||'", + null, null, "'\\uFEFF'", null, "'{'", "'}'", "'['", "']'", "'('", "')'", + "'@'", "'.'", "'..'", "'$'", "'*'", "':'", "'?'", "'contains'", null, + null, null, null, null, "','", "'''", "'\"'", "'=~'", null, "'&&'", "'||'", null, "'=='", "'!='", "'true'", "'false'", "'null'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "WS", "UTF_8_BOM", "MATCHES_REGEX_OPEN", "LBRACE", "RBRACE", "LBRACK", - "RBRACK", "LPAREN", "RPAREN", "AT", "DOT", "DOT_DOT", "ROOT", "WILDCARD", - "COLON", "QUESTION", "CONTAINS", "Identifier", "StringLiteral", "PositiveNumber", - "NegativeNumber", "NumericLiteral", "COMMA", "TICK", "QUOTE", "MATCHES", - "LOGICAL_OPERATOR", "AND", "OR", "EQUALITY_OPERATOR", "EQ", "NE", "TRUE", + null, "WS", "UTF_8_BOM", "MATCHES_REGEX_OPEN", "LBRACE", "RBRACE", "LBRACK", + "RBRACK", "LPAREN", "RPAREN", "AT", "DOT", "DOT_DOT", "ROOT", "WILDCARD", + "COLON", "QUESTION", "CONTAINS", "Identifier", "StringLiteral", "PositiveNumber", + "NegativeNumber", "NumericLiteral", "COMMA", "TICK", "QUOTE", "MATCHES", + "LOGICAL_OPERATOR", "AND", "OR", "EQUALITY_OPERATOR", "EQ", "NE", "TRUE", "FALSE", "NULL", "MATCHES_REGEX_CLOSE", "S", "REGEX" }; } diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParser.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParser.java index 687e2a8d034..9303ee81d64 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParser.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.hcl.internal.grammar; - -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; - +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class JsonPathParser extends Parser { @@ -36,23 +32,23 @@ public class JsonPathParser extends Parser { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - WS=1, UTF_8_BOM=2, MATCHES_REGEX_OPEN=3, LBRACE=4, RBRACE=5, LBRACK=6, - RBRACK=7, LPAREN=8, RPAREN=9, AT=10, DOT=11, DOT_DOT=12, ROOT=13, WILDCARD=14, - COLON=15, QUESTION=16, CONTAINS=17, Identifier=18, StringLiteral=19, PositiveNumber=20, - NegativeNumber=21, NumericLiteral=22, COMMA=23, TICK=24, QUOTE=25, MATCHES=26, - LOGICAL_OPERATOR=27, AND=28, OR=29, EQUALITY_OPERATOR=30, EQ=31, NE=32, + WS=1, UTF_8_BOM=2, MATCHES_REGEX_OPEN=3, LBRACE=4, RBRACE=5, LBRACK=6, + RBRACK=7, LPAREN=8, RPAREN=9, AT=10, DOT=11, DOT_DOT=12, ROOT=13, WILDCARD=14, + COLON=15, QUESTION=16, CONTAINS=17, Identifier=18, StringLiteral=19, PositiveNumber=20, + NegativeNumber=21, NumericLiteral=22, COMMA=23, TICK=24, QUOTE=25, MATCHES=26, + LOGICAL_OPERATOR=27, AND=28, OR=29, EQUALITY_OPERATOR=30, EQ=31, NE=32, TRUE=33, FALSE=34, NULL=35, MATCHES_REGEX_CLOSE=36, S=37, REGEX=38; public static final int - RULE_jsonPath = 0, RULE_expression = 1, RULE_dotOperator = 2, RULE_recursiveDecent = 3, - RULE_bracketOperator = 4, RULE_filter = 5, RULE_filterExpression = 6, - RULE_binaryExpression = 7, RULE_containsExpression = 8, RULE_regexExpression = 9, - RULE_unaryExpression = 10, RULE_literalExpression = 11, RULE_property = 12, + RULE_jsonPath = 0, RULE_expression = 1, RULE_dotOperator = 2, RULE_recursiveDecent = 3, + RULE_bracketOperator = 4, RULE_filter = 5, RULE_filterExpression = 6, + RULE_binaryExpression = 7, RULE_containsExpression = 8, RULE_regexExpression = 9, + RULE_unaryExpression = 10, RULE_literalExpression = 11, RULE_property = 12, RULE_wildcard = 13, RULE_slice = 14, RULE_start = 15, RULE_end = 16, RULE_indexes = 17; private static String[] makeRuleNames() { return new String[] { - "jsonPath", "expression", "dotOperator", "recursiveDecent", "bracketOperator", - "filter", "filterExpression", "binaryExpression", "containsExpression", - "regexExpression", "unaryExpression", "literalExpression", "property", + "jsonPath", "expression", "dotOperator", "recursiveDecent", "bracketOperator", + "filter", "filterExpression", "binaryExpression", "containsExpression", + "regexExpression", "unaryExpression", "literalExpression", "property", "wildcard", "slice", "start", "end", "indexes" }; } @@ -60,20 +56,20 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, null, "'\\uFEFF'", null, "'{'", "'}'", "'['", "']'", "'('", "')'", - "'@'", "'.'", "'..'", "'$'", "'*'", "':'", "'?'", "'contains'", null, - null, null, null, null, "','", "'''", "'\"'", "'=~'", null, "'&&'", "'||'", + null, null, "'\\uFEFF'", null, "'{'", "'}'", "'['", "']'", "'('", "')'", + "'@'", "'.'", "'..'", "'$'", "'*'", "':'", "'?'", "'contains'", null, + null, null, null, null, "','", "'''", "'\"'", "'=~'", null, "'&&'", "'||'", null, "'=='", "'!='", "'true'", "'false'", "'null'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "WS", "UTF_8_BOM", "MATCHES_REGEX_OPEN", "LBRACE", "RBRACE", "LBRACK", - "RBRACK", "LPAREN", "RPAREN", "AT", "DOT", "DOT_DOT", "ROOT", "WILDCARD", - "COLON", "QUESTION", "CONTAINS", "Identifier", "StringLiteral", "PositiveNumber", - "NegativeNumber", "NumericLiteral", "COMMA", "TICK", "QUOTE", "MATCHES", - "LOGICAL_OPERATOR", "AND", "OR", "EQUALITY_OPERATOR", "EQ", "NE", "TRUE", + null, "WS", "UTF_8_BOM", "MATCHES_REGEX_OPEN", "LBRACE", "RBRACE", "LBRACK", + "RBRACK", "LPAREN", "RPAREN", "AT", "DOT", "DOT_DOT", "ROOT", "WILDCARD", + "COLON", "QUESTION", "CONTAINS", "Identifier", "StringLiteral", "PositiveNumber", + "NegativeNumber", "NumericLiteral", "COMMA", "TICK", "QUOTE", "MATCHES", + "LOGICAL_OPERATOR", "AND", "OR", "EQUALITY_OPERATOR", "EQ", "NE", "TRUE", "FALSE", "NULL", "MATCHES_REGEX_CLOSE", "S", "REGEX" }; } @@ -174,7 +170,7 @@ public final JsonPathContext jsonPath() throws RecognitionException { } } - setState(40); + setState(40); _errHandler.sync(this); _alt = 1; do { @@ -190,7 +186,7 @@ public final JsonPathContext jsonPath() throws RecognitionException { default: throw new NoViableAltException(this); } - setState(42); + setState(42); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,1,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -475,7 +471,7 @@ public final BracketOperatorContext bracketOperator() throws RecognitionExceptio break; case 4: { - setState(63); + setState(63); _errHandler.sync(this); _la = _input.LA(1); do { @@ -485,7 +481,7 @@ public final BracketOperatorContext bracketOperator() throws RecognitionExceptio property(); } } - setState(65); + setState(65); _errHandler.sync(this); _la = _input.LA(1); } while ( _la==Identifier || _la==StringLiteral ); @@ -548,7 +544,7 @@ public final FilterContext filter() throws RecognitionException { match(QUESTION); setState(72); match(LPAREN); - setState(74); + setState(74); _errHandler.sync(this); _la = _input.LA(1); do { @@ -558,7 +554,7 @@ public final FilterContext filter() throws RecognitionException { filterExpression(); } } - setState(76); + setState(76); _errHandler.sync(this); _la = _input.LA(1); } while ( ((_la) & ~0x3f) == 0 && ((1L << _la) & 60137421888L) != 0 ); @@ -853,7 +849,7 @@ private BinaryExpressionContext binaryExpression(int _p) throws RecognitionExcep } break; } - } + } } setState(134); _errHandler.sync(this); @@ -1482,7 +1478,7 @@ public final IndexesContext indexes() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(187); + setState(187); _errHandler.sync(this); _la = _input.LA(1); do { @@ -1492,7 +1488,7 @@ public final IndexesContext indexes() throws RecognitionException { match(PositiveNumber); } } - setState(189); + setState(189); _errHandler.sync(this); _la = _input.LA(1); } while ( _la==PositiveNumber ); @@ -1509,8 +1505,8 @@ public final IndexesContext indexes() throws RecognitionException { return _localctx; } - @Override - public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { + @Override + public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { switch (ruleIndex) { case 7: return binaryExpression_sempred((BinaryExpressionContext)_localctx, predIndex); diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseListener.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseListener.java index 024b82f697e..66acfd555d7 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseListener.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseVisitor.java index b8de4dc8962..7b55bd30391 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserBaseVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserListener.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserListener.java index dac28047fb4..7764c87c616 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserListener.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserVisitor.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserVisitor.java index c4d008aeacf..53fd9927b92 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserVisitor.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/internal/grammar/JsonPathParserVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Hcl.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Hcl.java index 8ee49114969..5d517cc800a 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Hcl.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Hcl.java @@ -19,6 +19,7 @@ import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; +import org.antlr.v4.runtime.tree.TerminalNode; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.hcl.HclParser; @@ -236,6 +237,75 @@ public AttributeAccess withName(HclLeftPadded name) { } } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + class LegacyIndexAttributeAccess implements Expression, Label { + @Nullable + @NonFinal + transient WeakReference padding; + + @With + @EqualsAndHashCode.Include + @Getter + UUID id; + + @With + @Getter + Space prefix; + + @With + @Getter + Markers markers; + + @With + @Getter + HclRightPadded base; + + @With + @Getter + Literal index; + + @Override + public

Hcl acceptHcl(HclVisitor

v, P p) { + return v.visitLegacyIndexAttribute(this, p); + } + + @Override + public String toString() { + return "LegacyIndexAttributeAccess{" + base + "." + index + "}"; + } + + public Padding getPadding() { + Padding p; + if (this.padding == null) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } else { + p = this.padding.get(); + if (p == null || p.t != this) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } + } + return p; + } + + @RequiredArgsConstructor + public static class Padding { + private final LegacyIndexAttributeAccess t; + + public HclRightPadded getBase() { + return t.base; + } + + public LegacyIndexAttributeAccess withBase(HclRightPadded base) { + return t.base == base ? t : new LegacyIndexAttributeAccess(t.id, t.prefix, t.markers, base, t.index); + } + } + } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @RequiredArgsConstructor diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclCoordinates.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclCoordinates.java index 5616b533f28..39327ffbddb 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclCoordinates.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclCoordinates.java @@ -34,7 +34,7 @@ public class HclCoordinates implements Coordinates { Comparator comparator; public boolean isReplacement() { - return Mode.REPLACEMENT.equals(mode); + return Mode.REPLACEMENT == mode; } public enum Mode { diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclRightPadded.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclRightPadded.java index 366b94a2300..3e8c9b9e282 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclRightPadded.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/HclRightPadded.java @@ -50,6 +50,7 @@ public enum Location { FOR_VARIABLE_ARGUMENT(Space.Location.FOR_VARIABLE_SUFFIX), FUNCTION_CALL_ARGUMENT(Space.Location.FUNCTION_CALL_ARGUMENT_SUFFIX), INDEX_POSITION(Space.Location.INDEX_POSITION_SUFFIX), + LEGACY_INDEX_ATTRIBUTE_ACCESS_BASE(Space.Location.LEGACY_INDEX_ATTRIBUTE_ACCESS_BASE), OBJECT_VALUE_ARGUMENT(Space.Location.OBJECT_VALUE_ATTRIBUTE_SUFFIX), PARENTHESES(Space.Location.PARENTHESES_SUFFIX), SPLAT_OPERATOR(Space.Location.SPLAT_OPERATOR_SUFFIX), diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java index 48d5282eb67..cf89a2e683d 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java @@ -156,20 +156,20 @@ public static Space format(String formatting) { for (char c : charArray) { switch (c) { case '#': - if (Comment.Style.LINE_SLASH.equals(inLineSlashOrHashComment)) { + if (Comment.Style.LINE_SLASH == inLineSlashOrHashComment) { + comment.append(c); + } else if (inSingleLineComment) { + comment.append(c); + } else if (inMultiLineComment) { comment.append(c); } else { - if (inSingleLineComment) { - comment.append(c); - } else { - inSingleLineComment = true; - inLineSlashOrHashComment = Comment.Style.LINE_HASH; - comment = new StringBuilder(); - } + inSingleLineComment = true; + inLineSlashOrHashComment = Comment.Style.LINE_HASH; + comment = new StringBuilder(); } break; case '/': - if (Comment.Style.LINE_HASH.equals(inLineSlashOrHashComment)) { + if (Comment.Style.LINE_HASH == inLineSlashOrHashComment) { comment.append(c); } else { if (inSingleLineComment) { @@ -225,6 +225,11 @@ public static Space format(String formatting) { last = c; } + if ((comment.length() > 0)) { + comments.add(new Comment(inLineSlashOrHashComment, comment.toString(), prefix.toString(), Markers.EMPTY)); + prefix = new StringBuilder(); + } + // Shift the whitespace on each comment forward to be a suffix of the comment before it, and the // whitespace on the first comment to be the whitespace of the tree element. The remaining prefix is the suffix // of the last comment. @@ -323,6 +328,8 @@ public enum Location { INDEX_POSITION, INDEX_POSITION_SUFFIX, IDENTIFIER, + LEGACY_INDEX_ATTRIBUTE_ACCESS, + LEGACY_INDEX_ATTRIBUTE_ACCESS_BASE, LITERAL, OBJECT_VALUE, OBJECT_VALUE_ATTRIBUTES, diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclBlockTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclBlockTest.java index fff395d2ac6..295bbac8585 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclBlockTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclBlockTest.java @@ -97,4 +97,37 @@ void oneLineBlock() { ) ); } + + @Test + void providersInModule() { + rewriteRun( + hcl( + """ + module "something" { + source = "./some/other/directory" + + providers = { + aws = aws + aws.dns = aws + } + } + """ + ) + ); + } + + @Test + void expressionOnLeftHandSideOfAMapLiteral() { + rewriteRun( + hcl( + """ + locals { + security_groups_to_create = { + (data.aws_security_group.default.id) : "the_default_one" + } + } + """ + ) + ); + } } diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCollectionValueTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCollectionValueTest.java index 2b877a4cc47..30e60b4f1b9 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCollectionValueTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCollectionValueTest.java @@ -54,4 +54,18 @@ void objectValue() { ) ); } + + @Test + void emptyMapInTwoLines() { + rewriteRun( + hcl( + """ + locals { + known_weird_cases = { + } + } + """ + ) + ); + } } diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCommentTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCommentTest.java index 391964d5319..bf72c18586c 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCommentTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclCommentTest.java @@ -16,7 +16,6 @@ package org.openrewrite.hcl.tree; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -101,7 +100,6 @@ void inLineCommentsNextLineAttribute() { ); } - @ExpectedToFail @Issue("https://github.com/openrewrite/rewrite/issues/4611") @Test void commentAsTheLastLine() { @@ -116,4 +114,113 @@ void commentAsTheLastLine() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite/issues/4611") + @Test + void commentsAsTheFinalLines() { + rewriteRun( + hcl( + """ + locals { + a = 3 + } + # Nice code, right? + # Isn't it? + """ + ) + ); + } + + @Test + void singleLineWithinMultiLineHash() { + rewriteRun( + hcl( + """ + /* + # It's important + */ + locals { + Anwil = "Wloclawek" + } + """ + ) + ); + } + + @Test + void singleLineWithinMultiLineSlash() { + rewriteRun( + hcl( + """ + /* + // It's important + */ + locals { + Anwil = "Wloclawek" + } + """ + ) + ); + } + + @Test + void multilineNotStartingInTheFirstCharacter() { + rewriteRun( + hcl( + """ + /* An indented comment + */ + """ + ) + ); + } + + @Test + void commentedOutLinesInListLiteral() { + rewriteRun( + hcl( + """ + locals { + resources = [ + "arn:aws:s3:::${var.my_precious_bucket}", + "arn:aws:s3:::${var.waste_bucket}", + # "arn:aws:s3:::just-some-bucket/*", + ] + } + """ + ) + ); + } + + @Test + void emptyHashCommentJustBeforeCurlyBraceEnd() { + rewriteRun( + hcl( + """ + locals { + # + } + module "something" { + source = "../else/" + } + """ + ) + ); + } + + @Test + void emptyDoubleSlashCommentJustBeforeCurlyBraceEnd() { + rewriteRun( + hcl( + """ + locals { + // + } + module "something" { + source = "../else/" + } + """ + ) + ); + } } diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclForTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclForTest.java index a6730994b79..211b33fa258 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclForTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclForTest.java @@ -16,6 +16,7 @@ package org.openrewrite.hcl.tree; import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.openrewrite.hcl.Assertions.hcl; @@ -74,4 +75,21 @@ void forEach() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4857") + void commentInAFor() { + rewriteRun( + hcl( + """ + locals { + a = { + # this is some super smart logic here + for i, v in ["a", "b"]: v => i + } + } + """ + ) + ); + } } diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclStringTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclStringTest.java index 0c9262c4ab6..801dc3830d6 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclStringTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclStringTest.java @@ -66,4 +66,17 @@ void trailingDollarSign() { ) ); } + + @Test + void slashesInStrings() { + rewriteRun( + hcl( + """ + locals { + cidr = "192.168.0.0/24" + } + """ + ) + ); + } } diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclVariableExpressionTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclVariableExpressionTest.java index 353d4c43b86..3353166a97b 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclVariableExpressionTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/tree/HclVariableExpressionTest.java @@ -33,4 +33,19 @@ void variableExpression() { ) ); } + + @Test + void legacyIndexOperator() { + rewriteRun( + hcl( + """ + locals { + dns_record = aws_acm_certificate.google_dot_com.0.resource_record_name + with_space_before = a.b .0.d + with_space_after = a.b. 0.d + } + """ + ) + ); + } } diff --git a/rewrite-java-11/build.gradle.kts b/rewrite-java-11/build.gradle.kts index e897cf9f491..091f2016502 100644 --- a/rewrite-java-11/build.gradle.kts +++ b/rewrite-java-11/build.gradle.kts @@ -9,6 +9,7 @@ val javaTck = configurations.create("javaTck") { dependencies { api(project(":rewrite-core")) api(project(":rewrite-java")) + runtimeOnly(project(":rewrite-java-lombok")) compileOnly("org.slf4j:slf4j-api:1.7.+") diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java index 75a6e970757..51103226f9b 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java @@ -18,12 +18,14 @@ import com.sun.tools.javac.comp.*; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.Option; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Timer; +import lombok.Getter; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.objectweb.asm.ClassReader; @@ -43,20 +45,28 @@ import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; +import org.slf4j.LoggerFactory; +import javax.annotation.processing.Processor; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.StandardLocation; import java.io.*; +import java.lang.reflect.Constructor; import java.net.URI; +import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; /** @@ -77,14 +87,16 @@ public class ReloadableJava11Parser implements JavaParser { private final JavaCompiler compiler; private final ResettableLog compilerLog; private final Collection styles; - - private ReloadableJava11Parser(boolean logCompilationWarningsAndErrors, - @Nullable Collection classpath, - Collection classBytesClasspath, - @Nullable Collection dependsOn, - Charset charset, - Collection styles, - JavaTypeCache typeCache) { + private final List annotationProcessors; + + private ReloadableJava11Parser( + boolean logCompilationWarningsAndErrors, + @Nullable Collection classpath, + Collection classBytesClasspath, + @Nullable Collection dependsOn, + Charset charset, + Collection styles, + JavaTypeCache typeCache) { this.classpath = classpath; this.dependsOn = dependsOn; this.styles = styles; @@ -106,6 +118,70 @@ private ReloadableJava11Parser(boolean logCompilationWarningsAndErrors, Options.instance(context).put("-g", "-g"); Options.instance(context).put("-proc", "none"); + // Ensure type attribution continues despite errors in individual files or nodes. + // If an error occurs in a single file or node, type attribution should still proceed + // for all other source files and unaffected nodes within the same file. + Options.instance(context).put("should-stop.ifError", "GENERATE"); + + LOMBOK: + if (System.getenv().getOrDefault("REWRITE_LOMBOK", System.getProperty("rewrite.lombok")) != null && + classpath != null && classpath.stream().anyMatch(it -> it.toString().contains("lombok"))) { + Processor lombokProcessor = null; + try { + // https://projectlombok.org/contributing/lombok-execution-path + List overrideClasspath = new ArrayList<>(); + for (Path part : classpath) { + if (part.toString().contains("lombok")) { + overrideClasspath.add(part.toString()); + } + } + // make sure the rewrite-java-lombok dependency comes first + boolean found = false; + for (int i = 0; i < overrideClasspath.size(); i++) { + if (overrideClasspath.get(i).contains("rewrite-java-lombok")) { + overrideClasspath.add(0, overrideClasspath.remove(i)); + found = true; + } + } + if (!found) { + // try to find `rewrite-java-lombok` using class loader + URL resource = getClass().getClassLoader().getResource("org/openrewrite/java/lombok/OpenRewriteConfigurationKeysLoader.class"); + if (resource != null && resource.getProtocol().equals("jar") && resource.getPath().startsWith("file:")) { + String path = Paths.get(URI.create(resource.getPath().substring(0, resource.getPath().indexOf("!")))).toString(); + overrideClasspath.add(0, path); + } else { + break LOMBOK; + } + } + System.setProperty("shadow.override.lombok", String.join(File.pathSeparator, overrideClasspath)); + + Class shadowLoaderClass = Class.forName("lombok.launch.ShadowClassLoader", true, getClass().getClassLoader()); + Constructor shadowLoaderConstructor = shadowLoaderClass.getDeclaredConstructor( + Class.forName("java.lang.ClassLoader"), + Class.forName("java.lang.String"), + Class.forName("java.lang.String"), + Class.forName("java.util.List"), + Class.forName("java.util.List")); + shadowLoaderConstructor.setAccessible(true); + + ClassLoader lombokShadowLoader = (ClassLoader) shadowLoaderConstructor.newInstance( + getClass().getClassLoader(), + "lombok", + null, + emptyList(), + singletonList("lombok.patcher.Symbols") + ); + lombokProcessor = (Processor) lombokShadowLoader.loadClass("lombok.core.AnnotationProcessor").getDeclaredConstructor().newInstance(); + Options.instance(context).put(Option.PROCESSOR, "lombok.launch.AnnotationProcessorHider$AnnotationProcessor"); + } catch (ReflectiveOperationException ignore) { + // Lombok was not found or could not be initialized + } finally { + annotationProcessors = lombokProcessor != null ? singletonList(lombokProcessor) : emptyList(); + } + } else { + annotationProcessors = emptyList(); + } + // MUST be created ahead of compiler construction new TimedTodo(context); @@ -127,7 +203,7 @@ public void write(char[] cbuf, int off, int len) { if (logCompilationWarningsAndErrors) { String log = new String(Arrays.copyOfRange(cbuf, off, len)); if (!log.isBlank()) { - org.slf4j.LoggerFactory.getLogger(ReloadableJava11Parser.class).warn(log); + LoggerFactory.getLogger(ReloadableJava11Parser.class).warn(log); } } } @@ -191,37 +267,44 @@ LinkedHashMap parseInputsToCompilerAst(Iterable } LinkedHashMap cus = new LinkedHashMap<>(); - acceptedInputs(sourceFiles).forEach(input1 -> { + List inputFileObjects = acceptedInputs(sourceFiles) + .map(input -> new ReloadableJava11ParserInputFileObject(input, ctx)) + .collect(Collectors.toList()); + if (!annotationProcessors.isEmpty()) { + compiler.initProcessAnnotations(annotationProcessors, inputFileObjects, emptyList()); + } + try { + //noinspection unchecked + com.sun.tools.javac.util.List jcCompilationUnits = compiler.parseFiles((List) (List) inputFileObjects); + for (int i = 0; i < inputFileObjects.size(); i++) { + cus.put(inputFileObjects.get(i).getInput(), jcCompilationUnits.get(i)); + } try { - JCTree.JCCompilationUnit jcCompilationUnit = compiler.parse(new ReloadableJava11ParserInputFileObject(input1, ctx)); - cus.put(input1, jcCompilationUnit); - } catch (IllegalStateException e) { - if ("endPosTable already set".equals(e.getMessage())) { - throw new IllegalStateException( - "Call reset() on JavaParser before parsing another set of source files that " + - "have some of the same fully qualified names. Source file [" + - input1.getPath() + "]\n[\n" + StringUtils.readFully(input1.getSource(ctx), getCharset(ctx)) + "\n]", e); + initModules(cus.values()); + enterAll(cus.values()); + + // For some reason this is necessary in JDK 9+, where the internal block counter that + // annotationsBlocked() tests against remains >0 after attribution. + Annotate annotate = Annotate.instance(context); + while (annotate.annotationsBlocked()) { + annotate.unblockAnnotations(); // also flushes once unblocked + } + if (!annotationProcessors.isEmpty()) { + compiler.processAnnotations(jcCompilationUnits, emptyList()); } - throw e; + compiler.attribute(compiler.todo); + } catch (Throwable t) { + // when symbol entering fails on problems like missing types, attribution can often times proceed + // unhindered, but it sometimes cannot (so attribution is always best-effort in the presence of errors) + ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); } - }); - - try { - initModules(cus.values()); - enterAll(cus.values()); - - // For some reason this is necessary in JDK 9+, where the internal block counter that - // annotationsBlocked() tests against remains >0 after attribution. - Annotate annotate = Annotate.instance(context); - while (annotate.annotationsBlocked()) { - annotate.unblockAnnotations(); // also flushes once unblocked + } catch (IllegalStateException e) { + if ("endPosTable already set".equals(e.getMessage())) { + throw new IllegalStateException( + "Call reset() on JavaParser before parsing another set of source files that " + + "have some of the same fully qualified names.", e); } - - compiler.attribute(compiler.todo); - } catch (Throwable t) { - // when symbol entering fails on problems like missing types, attribution can often times proceed - // unhindered, but it sometimes cannot (so attribution is always a BEST EFFORT in the presence of errors) - ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); + throw e; } return cus; } @@ -361,8 +444,7 @@ public String inferBinaryName(Location location, JavaFileObject file) { public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { if (StandardLocation.CLASS_PATH.equals(location)) { Iterable listed = super.list(location, packageName, kinds, recurse); - return Stream.concat( - classByteClasspath.stream() + return Stream.concat(classByteClasspath.stream() .filter(jfo -> jfo.getPackage().equals(packageName)), StreamSupport.stream(listed.spliterator(), false) ).collect(toList()); @@ -373,6 +455,7 @@ public Iterable list(Location location, String packageName, Set< private static class PackageAwareJavaFileObject extends SimpleJavaFileObject { private final String pkg; + @Getter private final String className; private final byte[] classBytes; @@ -406,10 +489,6 @@ public String getPackage() { return pkg; } - public String getClassName() { - return className; - } - @Override public InputStream openInputStream() { return new ByteArrayInputStream(classBytes); diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserInputFileObject.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserInputFileObject.java index 6ff41fecbd5..e117030dc6f 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserInputFileObject.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserInputFileObject.java @@ -111,7 +111,7 @@ public Kind getKind() { @Override public boolean isNameCompatible(String simpleName, Kind kind) { String baseName = simpleName + kind.extension; - return kind.equals(getKind()) && + return kind == getKind() && path.getFileName().toString().equals(baseName); } diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java index 243acd89904..22648604cd2 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java @@ -26,15 +26,19 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; +import org.openrewrite.PrintOutputCapture; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaParsingException; +import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -191,6 +195,16 @@ public J visitAssignment(AssignmentTree node, Space fmt) { typeMapping.type(node)); } + @Override + public J visitErroneous(ErroneousTree node, Space fmt) { + String erroneousNode = source.substring(((JCTree) node).getStartPosition(), ((JCTree) node).getEndPosition(endPosTable)); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode); + } + @Override public J visitBinary(BinaryTree node, Space fmt) { Expression left = convert(node.getLeftOperand()); @@ -314,7 +328,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -328,10 +342,12 @@ public J visitCase(CaseTree node, Space fmt) { node.getExpression() == null ? EMPTY : sourceBefore("case"), singletonList(node.getExpression() == null ? JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)) : - JRightPadded.build(convertOrNull(node.getExpression())) + JRightPadded.build(convert(node.getExpression())) ), Markers.EMPTY ), + null, + null, JContainer.build(sourceBefore(":"), convertStatements(node.getStatements()), Markers.EMPTY), null ); @@ -379,7 +395,7 @@ public J visitClass(ClassTree node, Space fmt) { Markers.EMPTY); JLeftPadded extendings = node.getExtendsClause() == null ? null : - padLeft(sourceBefore("extends"), convertOrNull(node.getExtendsClause())); + padLeft(sourceBefore("extends"), convert(node.getExtendsClause())); JContainer implementings = null; if (node.getImplementsClause() != null && !node.getImplementsClause().isEmpty()) { @@ -407,13 +423,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -422,7 +456,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -628,7 +662,7 @@ public J visitForLoop(ForLoopTree node, Space fmt) { commaDelim.apply(t) ); - JRightPadded condition = convertOrNull(node.getCondition(), semiDelim); + JRightPadded condition = convert(node.getCondition(), semiDelim); if (condition == null) { condition = padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY); } @@ -749,7 +783,7 @@ public J visitLiteral(LiteralTree node, Space fmt) { singletonList(new J.Literal.UnicodeEscape(1, valueSource.substring(3, valueSource.length() - 1))), type); } - } else if (JavaType.Primitive.String.equals(type)) { + } else if (JavaType.Primitive.String == type) { StringBuilder valueSourceWithoutSurrogates = new StringBuilder(); List unicodeEscapes = null; @@ -897,7 +931,7 @@ public J visitMethod(MethodTree node, Space fmt) { } List returnTypeAnnotations = collectAnnotations(annotationPosTable); - TypeTree returnType = convertOrNull(node.getReturnType()); + TypeTree returnType = convert(node.getReturnType()); if (returnType != null && !returnTypeAnnotations.isEmpty()) { returnType = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, returnTypeAnnotations, returnType); @@ -940,7 +974,7 @@ public J visitMethod(MethodTree node, Space fmt) { JContainer.build(sourceBefore("throws"), convertAll(node.getThrows(), commaDelim, noDelim), Markers.EMPTY); - J.Block body = convertOrNull(node.getBody()); + J.Block body = convert(node.getBody()); JLeftPadded defaultValue = node.getDefaultValue() == null ? null : padLeft(sourceBefore("default"), convert(node.getDefaultValue())); @@ -964,9 +998,9 @@ public J visitNewArray(NewArrayTree node, Space fmt) { while (elementType instanceof JCArrayTypeTree) { elementType = ((JCArrayTypeTree) elementType).elemtype; } - typeExpr = convertOrNull(elementType); + typeExpr = convert(elementType); } else { - typeExpr = convertOrNull(jcVarType); + typeExpr = convert(jcVarType); } List nodeDimensions = node.getDimensions(); @@ -979,7 +1013,7 @@ public J visitNewArray(NewArrayTree node, Space fmt) { convert(dim, t -> sourceBefore("]")))); } - while(true) { + while (true) { int beginBracket = indexOfNextNonWhitespace(cursor, source); if (source.charAt(beginBracket) == '[') { int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); @@ -997,7 +1031,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1016,7 +1057,7 @@ public J visitNewClass(NewClassTree node, Space fmt) { } // for enum definitions with anonymous class initializers, endPos of node identifier will be -1 - TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convertOrNull(node.getIdentifier()) : null; + TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convert(node.getIdentifier()) : null; JContainer args; if (positionOfNext("(", '{') > -1) { @@ -1025,8 +1066,8 @@ public J visitNewClass(NewClassTree node, Space fmt) { singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) : convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY); } else { - args = JContainer.empty(); - args = args.withMarkers(args.getMarkers().add(new OmitParentheses(randomId()))); + args = JContainer.empty() + .withMarkers(Markers.build(singletonList(new OmitParentheses(randomId())))); } J.Block body = null; @@ -1124,7 +1165,8 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { @Override public J visitReturn(ReturnTree node, Space fmt) { skip("return"); - return new J.Return(randomId(), fmt, Markers.EMPTY, convertOrNull(node.getExpression())); + Expression expression = convert(node.getExpression()); + return new J.Return(randomId(), fmt, Markers.EMPTY, expression); } @Override @@ -1354,7 +1396,7 @@ private T buildName(String fullyQualifiedName) fullName += "." + part; int endOfPrefix = indexOfNextNonWhitespace(0, part); - Space identFmt = endOfPrefix > 0 ? format(part.substring(0, endOfPrefix)) : EMPTY; + Space identFmt = endOfPrefix > 0 ? format(part, 0, endOfPrefix) : EMPTY; Matcher whitespaceSuffix = whitespaceSuffixPattern.matcher(part); //noinspection ResultOfMethodCallIgnored @@ -1439,6 +1481,22 @@ public J visitUnary(UnaryTree node, Space fmt) { @Override public J visitVariable(VariableTree node, Space fmt) { + JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) node; + if ("".equals(jcVariableDecl.getName().toString())) { + int startPos = jcVariableDecl.getStartPosition(); + int endPos = jcVariableDecl.getEndPosition(endPosTable); + + if (startPos == endPos) { + endPos = startPos + 1; // For cases where the error node is a single character like "/" + } + String erroneousNode = source.substring(startPos, endPos); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode + ); + } return hasFlag(node.getModifiers(), Flags.ENUM) ? visitEnumVariable(node, fmt) : visitVariables(singletonList(node), fmt); // method arguments cannot be multi-declarations @@ -1463,8 +1521,16 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, emptyList(), "var", typeMapping.type(vartype), null); - typeExpr = typeExpr.withMarkers(typeExpr.getMarkers().add(JavaVarKeyword.build())); + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` + typeExpr = new J.Identifier(randomId(), + space, + Markers.build(singletonList(JavaVarKeyword.build())), + emptyList(), + lombokVal ? "val" : "var", + typeMapping.type(vartype), + null); } } else if (vartype instanceof JCArrayTypeTree) { JCExpression elementType = vartype; @@ -1484,6 +1550,10 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm typeExpr = convert(vartype); } + if (typeExpr == null && node.declaredUsingVar()) { + typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.build(singletonList(JavaVarKeyword.build())), emptyList(), "var", typeMapping.type(vartype), null); + } + if (typeExpr != null && !typeExprAnnotations.isEmpty()) { Space prefix = typeExprAnnotations.get(0).getPrefix(); typeExpr = new J.AnnotatedType(randomId(), prefix, Markers.EMPTY, ListUtils.mapFirst(typeExprAnnotations, a -> a.withPrefix(EMPTY)), typeExpr); @@ -1517,7 +1587,7 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm new J.VariableDeclarations.NamedVariable(randomId(), namedVarPrefix, Markers.EMPTY, name, dimensionsAfterName, - n.init != null ? padLeft(sourceBefore("="), convertOrNull(n.init)) : null, + n.init != null ? padLeft(sourceBefore("="), convert(n.init)) : null, (JavaType.Variable) typeMapping.type(n) ), i == nodes.size() - 1 ? EMPTY : sourceBefore(",") @@ -1530,15 +1600,15 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm private List> arrayDimensions() { List> dims = null; - while(true) { + while (true) { int beginBracket = indexOfNextNonWhitespace(cursor, source); if (source.charAt(beginBracket) == '[') { int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); - if(dims == null) { + if (dims == null) { dims = new ArrayList<>(2); } - dims.add(padLeft(format(source.substring(cursor, beginBracket)), - format(source.substring(beginBracket + 1, endBracket)))); + dims.add(padLeft(format(source, cursor, beginBracket), + format(source, beginBracket + 1, endBracket))); cursor = endBracket + 1; } else { break; @@ -1574,7 +1644,7 @@ public J visitWildcard(WildcardTree node, Space fmt) { bound = null; } - return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convertOrNull(wildcard.inner)); + return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convert(wildcard.inner)); } /** @@ -1582,10 +1652,12 @@ public J visitWildcard(WildcardTree node, Space fmt) { * Conversion utilities * -------------- */ - - private J2 convert(Tree t) { + private @Nullable J2 convert(@Nullable Tree t) { + if (t == null) { + return null; + } try { - String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor)); + String prefix = source.substring(cursor, max(cursor, getActualStartPosition((JCTree) t))); cursor += prefix.length(); @SuppressWarnings("unchecked") J2 j = (J2) scan(t, formatWithCommentTree(prefix, (JCTree) t, docCommentTable.getCommentTree((JCTree) t))); return j; @@ -1615,24 +1687,64 @@ private J2 convert(Tree t) { } } - private JRightPadded convert(Tree t, Function suffix) { + private static int getActualStartPosition(JCTree t) { + // The variable's start position in the source is wrongly after lombok's `@val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { + if (t == null) { + return null; + } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); - cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); + int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); + if (idx >= 0) { + rightPadded = (JRightPadded) JRightPadded.build(getErroneous(List.of(rightPadded))); + } + // Cursor hasn't been updated but points at the end of erroneous node already + // This means that error node start position == end position + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the + // same from the JC compiler. + if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { + cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); + } else { + cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + } return rightPadded; } - private long lineNumber(Tree tree) { - return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1; + private J.Erroneous getErroneous(List> converted) { + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + return new J.Erroneous( + org.openrewrite.Tree.randomId(), + EMPTY, + Markers.EMPTY, + p.getOut() + ); } - private @Nullable T convertOrNull(@Nullable Tree t) { - return t == null ? null : convert(t); + private static int findFirstNonWhitespaceChar(String s) { + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + return -1; } - private @Nullable JRightPadded convertOrNull(@Nullable Tree t, Function suffix) { - return t == null ? null : convert(t, suffix); + private long lineNumber(Tree tree) { + return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1; } private List convertAll(List trees) { @@ -1646,13 +1758,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1677,19 +1796,17 @@ private List> convertAll(List tr private Space statementDelim(@Nullable Tree t) { if (t instanceof JCAssert || - t instanceof JCAssign || - t instanceof JCAssignOp || - t instanceof JCBreak || - t instanceof JCContinue || - t instanceof JCDoWhileLoop || - t instanceof JCImport || - t instanceof JCMethodInvocation || - t instanceof JCNewClass || - t instanceof JCReturn || - t instanceof JCThrow || - t instanceof JCUnary || - t instanceof JCExpressionStatement || - t instanceof JCVariableDecl) { + t instanceof JCAssign || + t instanceof JCAssignOp || + t instanceof JCBreak || + t instanceof JCContinue || + t instanceof JCDoWhileLoop || + t instanceof JCImport || + t instanceof JCMethodInvocation || + t instanceof JCNewClass || + t instanceof JCReturn || + t instanceof JCThrow || + t instanceof JCUnary) { return sourceBefore(";"); } @@ -1697,9 +1814,39 @@ private Space statementDelim(@Nullable Tree t) { return statementDelim(((JCLabeledStatement) t).getStatement()); } + if (t instanceof JCExpressionStatement) { + ExpressionTree expTree = ((ExpressionStatementTree) t).getExpression(); + if (expTree instanceof ErroneousTree) { + return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable), ((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList()); + } else { + return sourceBefore(";"); + } + } + + if (t instanceof JCVariableDecl) { + JCTree.JCVariableDecl varTree = (JCTree.JCVariableDecl) t; + if ("".contentEquals(varTree.getName())) { + int start = varTree.vartype.getEndPosition(endPosTable); + int end = varTree.getEndPosition(endPosTable); + String whitespace = source.substring(start, end); + if (whitespace.contains("\n")) { + return EMPTY; + } else { + return Space.build(source.substring(start, end), Collections.emptyList()); + } + } + return sourceBefore(";"); + } + if (t instanceof JCMethodDecl) { JCMethodDecl m = (JCMethodDecl) t; - return sourceBefore(m.body == null || m.defaultValue != null ? ";" : ""); + if (m.body == null || m.defaultValue != null) { + String suffix = source.substring(cursor, positionOfNext(";", null)); + int idx = findFirstNonWhitespaceChar(suffix); + return sourceBefore(idx >= 0 ? "" : ";"); + } else { + return sourceBefore(""); + } } return EMPTY; @@ -1718,12 +1865,19 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { + continue; + } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); } List> converted = new ArrayList<>(treesGroupedByStartPosition.size()); for (List treeGroup : treesGroupedByStartPosition.values()) { if (treeGroup.size() == 1) { + Tree t = treeGroup.get(0); + int startPosition = ((JCTree) t).getStartPosition(); + if (cursor > startPosition) + continue; converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -1743,6 +1897,25 @@ private List> convertStatements(@Nullable List "lombok.val".equals(a.type.toString())); + } + + //noinspection ConstantConditions + return sym != null && ("lombok.val".equals(sym.getQualifiedName().toString()) || sym.getAnnotation(Generated.class) != null); + } + /** * -------------- * Other convenience utilities @@ -1772,9 +1945,10 @@ private Space sourceBefore(String untilDelim, @Nullable Character stop) { cursor += untilDelim.length(); return EMPTY; } - String prefix = source.substring(cursor, delimIndex); - cursor += prefix.length() + untilDelim.length(); // advance past the delimiter - return Space.format(prefix); + + Space space = format(source, cursor, delimIndex); + cursor = delimIndex + untilDelim.length(); // advance past the delimiter + return space; } private JRightPadded padRight(T tree, Space right) { @@ -1801,7 +1975,7 @@ private int positionOfNext(String untilDelim, @Nullable Character stop) { char c2 = source.charAt(delimIndex + 1); switch (c1) { case '/': - switch(c2) { + switch (c2) { case '/': inSingleLineComment = true; delimIndex++; @@ -1813,7 +1987,7 @@ private int positionOfNext(String untilDelim, @Nullable Character stop) { } break; case '*': - if(c2 == '/') { + if (c2 == '/') { inMultiLineComment = false; delimIndex++; continue; @@ -1845,20 +2019,21 @@ private Space whitespace() { if (nextNonWhitespace == cursor) { return EMPTY; } - String prefix = source.substring(cursor, nextNonWhitespace); - cursor += prefix.length(); - return format(prefix); + Space space = format(source, cursor, nextNonWhitespace); + cursor = nextNonWhitespace; + return space; } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts @@ -1879,7 +2054,7 @@ private List listFlags(long flags) { try { // FIXME instanceof probably not right here... return field.get(null) instanceof Long && - field.getName().matches("[A-Z_]+"); + field.getName().matches("[A-Z_]+"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -1917,13 +2092,17 @@ private ReloadableJava11ModifierResults sortedModifiersAndAnnotations(ModifiersT int lastAnnotationPosition = cursor; for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - J.Annotation annotation = convert(annotationPosTable.get(i)); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation.getAnnotationType())) { + continue; + } + J.Annotation annotation = convert(jcAnnotation); if (afterFirstModifier) { currentAnnotations.add(annotation); } else { leadingAnnotations.add(annotation); } - i = cursor -1; + i = cursor - 1; lastAnnotationPosition = cursor; continue; } @@ -2037,7 +2216,11 @@ private List collectAnnotations(Map annotat boolean inMultilineComment = false; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - annotations.add(convert(annotationPosTable.get(i))); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation)) { + continue; + } + annotations.add(convert(jcAnnotation)); i = cursor; continue; } diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java index d0b90bea744..21d4eacbd88 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java @@ -18,24 +18,23 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.JavaTypeMapping; import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.tree.Flag; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @RequiredArgsConstructor @@ -232,8 +231,9 @@ private JavaType generic(Type.TypeVar type, String signature) { private JavaType.FullyQualified classType(Type.ClassType classType, String signature) { Symbol.ClassSymbol sym = (Symbol.ClassSymbol) classType.tsym; Type.ClassType symType = (Type.ClassType) sym.type; + String fqn = sym.flatName().toString(); - JavaType.FullyQualified fq = typeCache.get(sym.flatName().toString()); + JavaType.FullyQualified fq = typeCache.get(fqn); JavaType.Class clazz = (JavaType.Class) (fq instanceof JavaType.Parameterized ? ((JavaType.Parameterized) fq).getType() : fq); if (clazz == null) { if (!sym.completer.isTerminal()) { @@ -243,12 +243,12 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa clazz = new JavaType.Class( null, sym.flags_field, - sym.flatName().toString(), + fqn, getKind(sym), null, null, null, null, null, null, null ); - typeCache.put(sym.flatName().toString(), clazz); + typeCache.put(fqn, clazz); JavaType.FullyQualified supertype = TypeUtils.asFullyQualified(type(symType.supertype_field)); @@ -276,7 +276,7 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa if (elem instanceof Symbol.VarSymbol && (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | Flags.GENERATEDCONSTR | Flags.ANONCONSTR)) == 0) { - if (sym.flatName().toString().equals("java.lang.String") && elem.name.toString().equals("serialPersistentFields")) { + if (fqn.equals("java.lang.String") && elem.name.toString().equals("serialPersistentFields")) { // there is a "serialPersistentFields" member within the String class which is used in normal Java // serialization to customize how the String field is serialized. This field is tripping up Jackson // serialization and is intentionally filtered to prevent errors. @@ -358,12 +358,19 @@ private JavaType.Class.Kind getKind(Symbol.ClassSymbol sym) { return variableType(((JCTree.JCVariableDecl) tree).sym); } else if (tree instanceof JCTree.JCAnnotatedType && ((JCTree.JCAnnotatedType) tree).getUnderlyingType() instanceof JCTree.JCArrayTypeTree) { return annotatedArray((JCTree.JCAnnotatedType) tree); + } else if (tree instanceof JCTree.JCClassDecl) { + symbol = ((JCTree.JCClassDecl) tree).sym; + } else if (tree instanceof JCTree.JCFieldAccess) { + symbol = ((JCTree.JCFieldAccess) tree).sym; } return type(((JCTree) tree).type, symbol); } - private @Nullable JavaType type(Type type, Symbol symbol) { + private @Nullable JavaType type(@Nullable Type type, @Nullable Symbol symbol) { + if (type == null && symbol != null) { + type = symbol.type; + } if (type instanceof Type.MethodType || type instanceof Type.ForAll) { return methodInvocationType(type, symbol); } @@ -453,6 +460,10 @@ public JavaType.Primitive primitive(TypeTag tag) { * @return Method type attribution. */ public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) { + /* + TODO: AttrRecover class does not exist for java 11; there is JCNoType + in the Type.class, so maybe it is possible to retrieve this information... + */ if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) { return null; } @@ -470,12 +481,14 @@ public JavaType.Primitive primitive(TypeTag tag) { return existing; } - List paramNames = null; + String[] paramNames = null; if (!methodSymbol.params().isEmpty()) { - paramNames = new ArrayList<>(methodSymbol.params().size()); - for (Symbol.VarSymbol p : methodSymbol.params()) { + paramNames = new String[methodSymbol.params().size()]; + com.sun.tools.javac.util.List params = methodSymbol.params(); + for (int i = 0; i < params.size(); i++) { + Symbol.VarSymbol p = params.get(i); String s = p.name.toString(); - paramNames.add(s); + paramNames[i] = s; } } @@ -486,13 +499,13 @@ public JavaType.Primitive primitive(TypeTag tag) { methodSymbol.isConstructor() ? "" : methodSymbol.getSimpleName().toString(), null, paramNames, - null, null, null, null + null, null, null, null, null ); typeCache.put(signature, method); JavaType returnType = null; List parameterTypes = null; - List exceptionTypes = null; + List exceptionTypes = null; if (selectType instanceof Type.MethodType) { Type.MethodType methodType = (Type.MethodType) selectType; @@ -512,20 +525,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } else if (selectType instanceof Type.UnknownType) { @@ -563,12 +564,14 @@ public JavaType.Primitive primitive(TypeTag tag) { return existing; } - List paramNames = null; + String[] paramNames = null; if (!methodSymbol.params().isEmpty()) { - paramNames = new ArrayList<>(methodSymbol.params().size()); - for (Symbol.VarSymbol p : methodSymbol.params()) { + paramNames = new String[methodSymbol.params().size()]; + com.sun.tools.javac.util.List params = methodSymbol.params(); + for (int i = 0; i < params.size(); i++) { + Symbol.VarSymbol p = params.get(i); String s = p.name.toString(); - paramNames.add(s); + paramNames[i] = s; } } @@ -586,6 +589,17 @@ public JavaType.Primitive primitive(TypeTag tag) { } } } + + List declaredFormalTypeNames = null; + for (Symbol.TypeVariableSymbol typeParam : methodSymbol.getTypeParameters()) { + if(typeParam.owner == methodSymbol) { + if (declaredFormalTypeNames == null) { + declaredFormalTypeNames = new ArrayList<>(); + } + declaredFormalTypeNames.add(typeParam.name.toString()); + } + } + JavaType.Method method = new JavaType.Method( null, methodSymbol.flags_field, @@ -594,8 +608,8 @@ public JavaType.Primitive primitive(TypeTag tag) { null, paramNames, null, null, null, - // TODO: Figure out the correct thing to put here based on methodSymbol.defaultValue.getValue() - defaultValues + defaultValues, + declaredFormalTypeNames == null ? null : declaredFormalTypeNames.toArray(new String[0]) ); typeCache.put(signature, method); @@ -603,7 +617,7 @@ public JavaType.Primitive primitive(TypeTag tag) { ((Type.ForAll) methodSymbol.type).qtype : methodSymbol.type; - List exceptionTypes = null; + List exceptionTypes = null; Type selectType = methodSymbol.type; if (selectType instanceof Type.ForAll) { @@ -615,20 +629,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } @@ -684,22 +686,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + JavaType.Annotation.ArrayElementValue.from(element, ((Object[]) value)) : + JavaType.Annotation.SingleElementValue.from(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(!list.isEmpty() && list.get(0) instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(!list.isEmpty() && list.get(0) instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java index 8441177d969..72c579ed838 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java @@ -116,6 +116,11 @@ private ReloadableJava17Parser( Options.instance(context).put("-g", "-g"); Options.instance(context).put("-proc", "none"); + // Ensure type attribution continues despite errors in individual files or nodes. + // If an error occurs in a single file or node, type attribution should still proceed + // for all other source files and unaffected nodes within the same file. + Options.instance(context).put("should-stop.ifError", "GENERATE"); + LOMBOK: if (System.getenv().getOrDefault("REWRITE_LOMBOK", System.getProperty("rewrite.lombok")) != null && classpath != null && classpath.stream().anyMatch(it -> it.toString().contains("lombok"))) { diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserInputFileObject.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserInputFileObject.java index 49e01f39290..f4e6faf4c6d 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserInputFileObject.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserInputFileObject.java @@ -111,7 +111,7 @@ public Kind getKind() { @Override public boolean isNameCompatible(String simpleName, Kind kind) { String baseName = simpleName + kind.extension; - return kind.equals(getKind()) && + return kind == getKind() && path.getFileName().toString().equals(baseName); } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java index 25b78628c07..52fefd79c93 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java @@ -19,7 +19,6 @@ import com.sun.source.doctree.DocCommentTree; import com.sun.source.tree.*; import com.sun.source.util.TreePathScanner; -import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.DocCommentTable; @@ -27,16 +26,20 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; +import org.openrewrite.PrintOutputCapture; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaParsingException; +import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -49,7 +52,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.regex.Matcher; @@ -199,6 +201,16 @@ public J visitAssignment(AssignmentTree node, Space fmt) { typeMapping.type(node)); } + @Override + public J visitErroneous(ErroneousTree node, Space fmt) { + String erroneousNode = source.substring(((JCTree) node).getStartPosition(), ((JCTree) node).getEndPosition(endPosTable)); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode); + } + @Override public J visitBinary(BinaryTree node, Space fmt) { Expression left = convert(node.getLeftOperand()); @@ -322,7 +334,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -343,6 +355,8 @@ public J visitCase(CaseTree node, Space fmt) { convertAll(node.getExpressions(), commaDelim, t -> EMPTY), Markers.EMPTY ), + null, + null, JContainer.build( sourceBefore(type == J.Case.Type.Rule ? "->" : ":"), convertStatements(node.getStatements()), @@ -464,13 +478,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -479,7 +511,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -532,7 +564,6 @@ public J visitCompilationUnit(CompilationUnitTree node, Space fmt) { Map annotationPosTable = mapAnnotations(node.getPackageAnnotations(), new HashMap<>(node.getPackageAnnotations().size())); - List packageAnnotations = collectAnnotations(annotationPosTable); J.Package packageDecl = null; @@ -814,7 +845,7 @@ public J visitLiteral(LiteralTree node, Space fmt) { singletonList(new J.Literal.UnicodeEscape(1, valueSource.substring(3, valueSource.length() - 1))), type); } - } else if (JavaType.Primitive.String.equals(type)) { + } else if (JavaType.Primitive.String == type) { StringBuilder valueSourceWithoutSurrogates = new StringBuilder(); List unicodeEscapes = null; @@ -933,11 +964,15 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) { singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) : convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY); - Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : - ((JCIdent) jcSelect).sym; + JavaType.Method methodType; + if (name.getType() instanceof JavaType.Method) { + methodType = (JavaType.Method) name.getType(); + } else { + Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : ((JCIdent) jcSelect).sym; + methodType = typeMapping.methodInvocationType(jcSelect.type, methodSymbol); + } - return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, - typeMapping.methodInvocationType(jcSelect.type, methodSymbol)); + return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, methodType); } @Override @@ -1068,7 +1103,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1215,7 +1257,7 @@ public J visitSwitchExpression(SwitchExpressionTree node, Space fmt) { return new J.SwitchExpression(randomId(), fmt, Markers.EMPTY, convert(node.getExpression()), new J.Block(randomId(), sourceBefore("{"), Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), - convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}"))); + convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}")), typeMapping.type(node)); } @Override @@ -1521,6 +1563,22 @@ public J visitUnary(UnaryTree node, Space fmt) { @Override public J visitVariable(VariableTree node, Space fmt) { + JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) node; + if ("".equals(jcVariableDecl.getName().toString())) { + int startPos = jcVariableDecl.getStartPosition(); + int endPos = jcVariableDecl.getEndPosition(endPosTable); + + if (startPos == endPos) { + endPos = startPos + 1; // For cases where the error node is a single character like "/" + } + String erroneousNode = source.substring(startPos, endPos); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode + ); + } return hasFlag(node.getModifiers(), Flags.ENUM) ? visitEnumVariable(node, fmt) : visitVariables(singletonList(node), fmt); // method arguments cannot be multi-declarations @@ -1545,9 +1603,11 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - boolean lombokVal = isLombokVal(node); + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` typeExpr = new J.Identifier(randomId(), - sourceBefore(lombokVal ? "val" : "var"), + space, Markers.build(singletonList(JavaVarKeyword.build())), emptyList(), lombokVal ? "val" : "var", @@ -1696,8 +1756,8 @@ public J visitWildcard(WildcardTree node, Space fmt) { } private static int getActualStartPosition(JCTree t) { - // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation - if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { + // The variable's start position in the source is wrongly after lombok's `@val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); } return t.getStartPosition(); @@ -1728,15 +1788,53 @@ private void reportJavaParsingException(Throwable ex) { } private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { if (t == null) { return null; } J2 j = convert(t); - JRightPadded rightPadded = new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); - cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); + int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); + if (idx >= 0) { + rightPadded = (JRightPadded) JRightPadded.build(getErroneous(List.of(rightPadded))); + } + // Cursor hasn't been updated but points at the end of erroneous node already + // This means that error node start position == end position + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the + // same from the JC compiler. + if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { + cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); + } else { + cursor(max(endPos(t), cursor)); // if there is a non-empty suffix or a lombok generated method, the cursor can be already moved past it + } return rightPadded; } + private J.Erroneous getErroneous(List> converted) { + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + return new J.Erroneous( + org.openrewrite.Tree.randomId(), + EMPTY, + Markers.EMPTY, + p.getOut() + ); + } + + private static int findFirstNonWhitespaceChar(String s) { + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + return -1; + } + private long lineNumber(Tree tree) { return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1; } @@ -1752,13 +1850,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1783,25 +1888,50 @@ private List> convertAll(List tr private Space statementDelim(@Nullable Tree t) { switch (t.getKind()) { + case CONTINUE: + case RETURN: + case BREAK: case ASSERT: case ASSIGNMENT: - case BREAK: - case CONTINUE: case DO_WHILE_LOOP: case IMPORT: case METHOD_INVOCATION: case NEW_CLASS: - case RETURN: case THROW: + return sourceBefore(";"); case EXPRESSION_STATEMENT: + ExpressionTree expTree = ((ExpressionStatementTree) t).getExpression(); + if (expTree instanceof ErroneousTree) { + return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable), ((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList()); + } else { + return sourceBefore(";"); + } case VARIABLE: + JCTree.JCVariableDecl varTree = (JCTree.JCVariableDecl) t; + if ("".contentEquals(varTree.getName())) { + int start = varTree.vartype.getEndPosition(endPosTable); + int end = varTree.getEndPosition(endPosTable); + String whitespace = source.substring(start, end); + if (whitespace.contains("\n")) { + return EMPTY; + } else { + return Space.build(source.substring(start, end), Collections.emptyList()); + } + } + return sourceBefore(";"); case YIELD: return sourceBefore(";"); case LABELED_STATEMENT: return statementDelim(((JCLabeledStatement) t).getStatement()); case METHOD: JCMethodDecl m = (JCMethodDecl) t; - return sourceBefore(m.body == null || m.defaultValue != null ? ";" : ""); + if (m.body == null || m.defaultValue != null) { + String suffix = source.substring(cursor, positionOfNext(";", null)); + int idx = findFirstNonWhitespaceChar(suffix); + return sourceBefore(idx >= 0 ? "" : ";"); + } else { + return sourceBefore(""); + } default: return t instanceof JCAssignOp || t instanceof JCUnary ? sourceBefore(";") : EMPTY; } @@ -1820,7 +1950,7 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { - if (isLombokGenerated(t)) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { continue; } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); @@ -1829,6 +1959,10 @@ private List> convertStatements(@Nullable List> converted = new ArrayList<>(treesGroupedByStartPosition.size()); for (List treeGroup : treesGroupedByStartPosition.values()) { if (treeGroup.size() == 1) { + Tree t = treeGroup.get(0); + int startPosition = ((JCTree) t).getStartPosition(); + if (cursor > startPosition) + continue; converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -1848,51 +1982,23 @@ private List> convertStatements(@Nullable List "lombok.val".equals(a.type.toString())); } - return isLombokGenerated(sym); - } - private static boolean isLombokGenerated(@Nullable Symbol sym) { - if (sym == null) { - return false; - } - // Lombok val is represented as a @lombok.val on a "final" modifier, neither which appear in source - if ("lombok.val".equals(sym.getQualifiedName().toString())) { - return true; - } - if (sym.getMetadata() == null) { - return false; - } - for (Attribute.Compound a : sym.getDeclarationAttributes()) { - if ("lombok.Generated".equals(a.type.toString())) { - return true; - } - } - return false; + //noinspection ConstantConditions + return sym != null && ("lombok.val".equals(sym.getQualifiedName().toString()) || sym.getAnnotation(Generated.class) != null); } /** @@ -2003,15 +2109,16 @@ private Space whitespace() { return space; } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts @@ -2072,7 +2179,6 @@ private ReloadableJava17ModifierResults sortedModifiersAndAnnotations(ModifiersT for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); - // Skip over lombok's "@val" annotation which does not actually appear in source if (isLombokGenerated(jcAnnotation.getAnnotationType())) { continue; } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java index 7d5ad9da3f6..e25ee76932c 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java @@ -17,25 +17,25 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; +import com.sun.tools.javac.comp.AttrRecover; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.JavaTypeMapping; import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.tree.Flag; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @RequiredArgsConstructor @@ -47,7 +47,7 @@ class ReloadableJava17TypeMapping implements JavaTypeMapping { public JavaType type(com.sun.tools.javac.code.@Nullable Type type) { if (type == null || type instanceof Type.ErrorType || type instanceof Type.PackageType || type instanceof Type.UnknownType || - type instanceof NullType) { + type instanceof NullType) { return JavaType.Class.Unknown.getInstance(); } @@ -258,7 +258,7 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa List interfaces = null; if (symType.interfaces_field != null) { interfaces = new ArrayList<>(symType.interfaces_field.length()); - for (com.sun.tools.javac.code.Type iParam : symType.interfaces_field) { + for (Type iParam : symType.interfaces_field) { JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(iParam)); if (javaType != null) { interfaces.add(javaType); @@ -272,8 +272,8 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa if (sym.members_field != null) { for (Symbol elem : sym.members_field.getSymbols()) { if (elem instanceof Symbol.VarSymbol && - (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | - Flags.GENERATEDCONSTR | Flags.ANONCONSTR)) == 0) { + (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | + Flags.GENERATEDCONSTR | Flags.ANONCONSTR)) == 0) { if (fqn.equals("java.lang.String") && elem.name.toString().equals("serialPersistentFields")) { // there is a "serialPersistentFields" member within the String class which is used in normal Java // serialization to customize how the String field is serialized. This field is tripping up Jackson @@ -286,7 +286,7 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa } fields.add(variableType(elem, clazz)); } else if (elem instanceof Symbol.MethodSymbol && - (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | Flags.ANONCONSTR)) == 0) { + (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | Flags.ANONCONSTR)) == 0) { if (methods == null) { methods = new ArrayList<>(); } @@ -370,7 +370,7 @@ private JavaType.FullyQualified.Kind getKind(Symbol.ClassSymbol sym) { if (type == null && symbol != null) { type = symbol.type; } - if (type instanceof Type.MethodType || type instanceof Type.ForAll) { + if (type instanceof Type.MethodType || type instanceof Type.ForAll || (type instanceof Type.ErrorType && type.getOriginalType() instanceof Type.MethodType)) { return methodInvocationType(type, symbol); } return type(type); @@ -459,6 +459,22 @@ public JavaType.Primitive primitive(TypeTag tag) { * @return Method type attribution. */ public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) { + if (selectType instanceof Type.ErrorType) { + try { + // Ugly reflection solution, because AttrRecover$RecoveryErrorType is private inner class + for (Class targetClass : Class.forName(AttrRecover.class.getCanonicalName()).getDeclaredClasses()) { + if (targetClass.getSimpleName().equals("RecoveryErrorType")) { + Field field = targetClass.getDeclaredField("candidateSymbol"); + field.setAccessible(true); + Symbol originalSymbol = (Symbol) field.get(selectType); + return methodInvocationType(selectType.getOriginalType(), originalSymbol); + } + } + } catch (Exception e) { + // ignore + } + } + if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) { return null; } @@ -494,20 +510,20 @@ public JavaType.Primitive primitive(TypeTag tag) { methodSymbol.isConstructor() ? "" : methodSymbol.getSimpleName().toString(), null, paramNames, - null, null, null, null + null, null, null, null, null ); typeCache.put(signature, method); JavaType returnType = null; List parameterTypes = null; - List exceptionTypes = null; + List exceptionTypes = null; if (selectType instanceof Type.MethodType) { Type.MethodType methodType = (Type.MethodType) selectType; if (!methodType.argtypes.isEmpty()) { parameterTypes = new ArrayList<>(methodType.argtypes.size()); - for (com.sun.tools.javac.code.Type argtype : methodType.argtypes) { + for (Type argtype : methodType.argtypes) { if (argtype != null) { JavaType javaType = type(argtype); parameterTypes.add(javaType); @@ -520,20 +536,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } else if (selectType instanceof Type.UnknownType) { @@ -589,12 +593,23 @@ public JavaType.Primitive primitive(TypeTag tag) { .collect(Collectors.toList()); } else { try { - defaultValues = Collections.singletonList(methodSymbol.getDefaultValue().getValue().toString()); - } catch(UnsupportedOperationException e) { + defaultValues = singletonList(methodSymbol.getDefaultValue().getValue().toString()); + } catch (UnsupportedOperationException e) { // not all Attribute implementations define `getValue()` } } } + + List declaredFormalTypeNames = null; + for (Symbol.TypeVariableSymbol typeParam : methodSymbol.getTypeParameters()) { + if (typeParam.owner == methodSymbol) { + if (declaredFormalTypeNames == null) { + declaredFormalTypeNames = new ArrayList<>(); + } + declaredFormalTypeNames.add(typeParam.name.toString()); + } + } + JavaType.Method method = new JavaType.Method( null, methodSymbol.flags_field, @@ -603,7 +618,8 @@ public JavaType.Primitive primitive(TypeTag tag) { null, paramNames, null, null, null, - defaultValues + defaultValues, + declaredFormalTypeNames == null ? null : declaredFormalTypeNames.toArray(new String[0]) ); typeCache.put(signature, method); @@ -611,7 +627,7 @@ public JavaType.Primitive primitive(TypeTag tag) { ((Type.ForAll) methodSymbol.type).qtype : methodSymbol.type; - List exceptionTypes = null; + List exceptionTypes = null; Type selectType = methodSymbol.type; if (selectType instanceof Type.ForAll) { @@ -623,20 +639,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } @@ -663,7 +667,7 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!mt.argtypes.isEmpty()) { parameterTypes = new ArrayList<>(mt.argtypes.size()); - for (com.sun.tools.javac.code.Type argtype : mt.argtypes) { + for (Type argtype : mt.argtypes) { if (argtype != null) { JavaType javaType = type(argtype); parameterTypes.add(javaType); @@ -692,22 +696,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + JavaType.Annotation.ArrayElementValue.from(element, ((Object[]) value)) : + JavaType.Annotation.SingleElementValue.from(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(!list.isEmpty() && list.get(0) instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(!list.isEmpty() && list.get(0) instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-21/build.gradle.kts b/rewrite-java-21/build.gradle.kts index 143abb855fc..8ab5e091748 100644 --- a/rewrite-java-21/build.gradle.kts +++ b/rewrite-java-21/build.gradle.kts @@ -17,6 +17,7 @@ val javaTck = configurations.create("javaTck") { dependencies { api(project(":rewrite-core")) api(project(":rewrite-java")) + runtimeOnly(project(":rewrite-java-lombok")) compileOnly("org.slf4j:slf4j-api:1.7.+") diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java index 823e2103f10..5b3ac22b69e 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java @@ -21,10 +21,12 @@ import com.sun.tools.javac.comp.Modules; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.Option; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; +import lombok.Getter; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.objectweb.asm.ClassReader; @@ -33,7 +35,6 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.SourceFile; -import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaParsingException; import org.openrewrite.java.internal.JavaTypeCache; @@ -43,20 +44,27 @@ import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; +import org.slf4j.LoggerFactory; +import javax.annotation.processing.Processor; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.StandardLocation; import java.io.*; +import java.lang.reflect.Constructor; import java.net.URI; +import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; /** @@ -77,14 +85,16 @@ public class ReloadableJava21Parser implements JavaParser { private final JavaCompiler compiler; private final ResettableLog compilerLog; private final Collection styles; - - private ReloadableJava21Parser(boolean logCompilationWarningsAndErrors, - @Nullable Collection classpath, - Collection classBytesClasspath, - @Nullable Collection dependsOn, - Charset charset, - Collection styles, - JavaTypeCache typeCache) { + private final List annotationProcessors; + + private ReloadableJava21Parser( + boolean logCompilationWarningsAndErrors, + @Nullable Collection classpath, + Collection classBytesClasspath, + @Nullable Collection dependsOn, + Charset charset, + Collection styles, + JavaTypeCache typeCache) { this.classpath = classpath; this.dependsOn = dependsOn; this.styles = styles; @@ -106,6 +116,70 @@ private ReloadableJava21Parser(boolean logCompilationWarningsAndErrors, Options.instance(context).put("-g", "-g"); Options.instance(context).put("-proc", "none"); + // Ensure type attribution continues despite errors in individual files or nodes. + // If an error occurs in a single file or node, type attribution should still proceed + // for all other source files and unaffected nodes within the same file. + Options.instance(context).put("should-stop.ifError", "GENERATE"); + + LOMBOK: + if (System.getenv().getOrDefault("REWRITE_LOMBOK", System.getProperty("rewrite.lombok")) != null && + classpath != null && classpath.stream().anyMatch(it -> it.toString().contains("lombok"))) { + Processor lombokProcessor = null; + try { + // https://projectlombok.org/contributing/lombok-execution-path + List overrideClasspath = new ArrayList<>(); + for (Path part : classpath) { + if (part.toString().contains("lombok")) { + overrideClasspath.add(part.toString()); + } + } + // make sure the rewrite-java-lombok dependency comes first + boolean found = false; + for (int i = 0; i < overrideClasspath.size(); i++) { + if (overrideClasspath.get(i).contains("rewrite-java-lombok")) { + overrideClasspath.add(0, overrideClasspath.remove(i)); + found = true; + } + } + if (!found) { + // try to find `rewrite-java-lombok` using class loader + URL resource = getClass().getClassLoader().getResource("org/openrewrite/java/lombok/OpenRewriteConfigurationKeysLoader.class"); + if (resource != null && resource.getProtocol().equals("jar") && resource.getPath().startsWith("file:")) { + String path = Paths.get(URI.create(resource.getPath().substring(0, resource.getPath().indexOf("!")))).toString(); + overrideClasspath.add(0, path); + } else { + break LOMBOK; + } + } + System.setProperty("shadow.override.lombok", String.join(File.pathSeparator, overrideClasspath)); + + Class shadowLoaderClass = Class.forName("lombok.launch.ShadowClassLoader", true, getClass().getClassLoader()); + Constructor shadowLoaderConstructor = shadowLoaderClass.getDeclaredConstructor( + Class.forName("java.lang.ClassLoader"), + Class.forName("java.lang.String"), + Class.forName("java.lang.String"), + Class.forName("java.util.List"), + Class.forName("java.util.List")); + shadowLoaderConstructor.setAccessible(true); + + ClassLoader lombokShadowLoader = (ClassLoader) shadowLoaderConstructor.newInstance( + getClass().getClassLoader(), + "lombok", + null, + emptyList(), + singletonList("lombok.patcher.Symbols") + ); + lombokProcessor = (Processor) lombokShadowLoader.loadClass("lombok.core.AnnotationProcessor").getDeclaredConstructor().newInstance(); + Options.instance(context).put(Option.PROCESSOR, "lombok.launch.AnnotationProcessorHider$AnnotationProcessor"); + } catch (ReflectiveOperationException ignore) { + // Lombok was not found or could not be initialized + } finally { + annotationProcessors = lombokProcessor != null ? singletonList(lombokProcessor) : emptyList(); + } + } else { + annotationProcessors = emptyList(); + } + // MUST be created (registered with the context) after pfm and compilerLog compiler = new JavaCompiler(context); @@ -124,7 +198,7 @@ public void write(char[] cbuf, int off, int len) { if (logCompilationWarningsAndErrors) { String log = new String(Arrays.copyOfRange(cbuf, off, len)); if (!log.isBlank()) { - org.slf4j.LoggerFactory.getLogger(ReloadableJava21Parser.class).warn(log); + LoggerFactory.getLogger(ReloadableJava21Parser.class).warn(log); } } } @@ -164,6 +238,7 @@ public Stream parseInputs(Iterable sourceFiles, @Nullable Pat ); J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); + //noinspection DataFlowIssue cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released parsingListener.parsed(input, cu); return requirePrintEqualsInput(cu, input, relativeTo, ctx); @@ -188,39 +263,46 @@ LinkedHashMap parseInputsToCompilerAst(Iterable } LinkedHashMap cus = new LinkedHashMap<>(); - acceptedInputs(sourceFiles).forEach(input1 -> { + List inputFileObjects = acceptedInputs(sourceFiles) + .map(input -> new ReloadableJava21ParserInputFileObject(input, ctx)) + .toList(); + if (!annotationProcessors.isEmpty()) { + compiler.initProcessAnnotations(annotationProcessors, inputFileObjects, emptyList()); + } + try { + //noinspection unchecked + com.sun.tools.javac.util.List jcCompilationUnits = compiler.parseFiles((List) (List) inputFileObjects, true); + for (int i = 0; i < inputFileObjects.size(); i++) { + cus.put(inputFileObjects.get(i).getInput(), jcCompilationUnits.get(i)); + } try { - JCTree.JCCompilationUnit jcCompilationUnit = compiler.parse(new ReloadableJava21ParserInputFileObject(input1, ctx)); - cus.put(input1, jcCompilationUnit); - } catch (IllegalStateException e) { - if ("endPosTable already set".equals(e.getMessage())) { - throw new IllegalStateException( - "Call reset() on JavaParser before parsing another set of source files that " + - "have some of the same fully qualified names. Source file [" + - input1.getPath() + "]\n[\n" + StringUtils.readFully(input1.getSource(ctx), getCharset(ctx)) + "\n]", e); + initModules(cus.values()); + enterAll(cus.values()); + + // For some reason this is necessary in JDK 9+, where the internal block counter that + // annotationsBlocked() tests against remains >0 after attribution. + Annotate annotate = Annotate.instance(context); + while (annotate.annotationsBlocked()) { + annotate.unblockAnnotations(); // also flushes once unblocked } - throw e; + if (!annotationProcessors.isEmpty()) { + compiler.processAnnotations(jcCompilationUnits, emptyList()); + } + compiler.attribute(compiler.todo); + } catch (Throwable t) { + // when symbol entering fails on problems like missing types, attribution can often times proceed + // unhindered, but it sometimes cannot (so attribution is always best-effort in the presence of errors) + ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); } - }); - - try { - initModules(cus.values()); - enterAll(cus.values()); - - // For some reason this is necessary in JDK 9+, where the internal block counter that - // annotationsBlocked() tests against remains >0 after attribution. - Annotate annotate = Annotate.instance(context); - while (annotate.annotationsBlocked()) { - annotate.unblockAnnotations(); // also flushes once unblocked + } catch (IllegalStateException e) { + if ("endPosTable already set".equals(e.getMessage())) { + throw new IllegalStateException( + "Call reset() on JavaParser before parsing another set of source files that " + + "have some of the same fully qualified names.", e); } - - compiler.attribute(compiler.todo); - } catch ( - Throwable t) { - // when symbol entering fails on problems like missing types, attribution can often times proceed - // unhindered, but it sometimes cannot (so attribution is always best-effort in the presence of errors) - ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); + throw e; } + return cus; } @@ -333,9 +415,9 @@ public Iterable list(Location location, String packageName, Set< Iterable listed = super.list(location, packageName, kinds, recurse); return classByteClasspath.isEmpty() ? listed : Stream.concat(classByteClasspath.stream() - .filter(jfo -> jfo.getPackage().equals(packageName)), - StreamSupport.stream(listed.spliterator(), false) - ).collect(toList()); + .filter(jfo -> jfo.getPackage().equals(packageName)), + StreamSupport.stream(listed.spliterator(), false) + ).collect(toList()); } return super.list(location, packageName, kinds, recurse); } @@ -343,6 +425,7 @@ public Iterable list(Location location, String packageName, Set< private static class PackageAwareJavaFileObject extends SimpleJavaFileObject { private final String pkg; + @Getter private final String className; private final byte[] classBytes; @@ -376,10 +459,6 @@ public String getPackage() { return pkg; } - public String getClassName() { - return className; - } - @Override public InputStream openInputStream() { return new ByteArrayInputStream(classBytes); diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java index fbb8065f806..8e05405ad90 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java @@ -111,7 +111,7 @@ public Kind getKind() { @Override public boolean isNameCompatible(String simpleName, Kind kind) { String baseName = simpleName + kind.extension; - return kind.equals(getKind()) && + return kind == getKind() && path.getFileName().toString().equals(baseName); } diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java index a64eeab242f..22d7cfdb3d7 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -26,16 +26,20 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; +import org.openrewrite.PrintOutputCapture; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaParsingException; +import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -48,7 +52,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.regex.Matcher; @@ -198,6 +201,16 @@ public J visitAssignment(AssignmentTree node, Space fmt) { typeMapping.type(node)); } + @Override + public J visitErroneous(ErroneousTree node, Space fmt) { + String erroneousNode = source.substring(((JCTree) node).getStartPosition(), ((JCTree) node).getEndPosition(endPosTable)); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode); + } + @Override public J visitBinary(BinaryTree node, Space fmt) { Expression left = convert(node.getLeftOperand()); @@ -321,7 +334,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -335,13 +348,15 @@ public J visitCase(CaseTree node, Space fmt) { Markers.EMPTY, type, null, + null, JContainer.build( - node.getExpressions().isEmpty() ? EMPTY : sourceBefore("case"), - node.getExpressions().isEmpty() ? + node.getLabels().isEmpty() ? EMPTY : sourceBefore("case"), + node.getLabels().isEmpty() || node.getLabels().getFirst() instanceof DefaultCaseLabelTree ? List.of(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null))) : - convertAll(node.getExpressions(), commaDelim, t -> EMPTY), + convertAll(node.getLabels(), commaDelim, ignored -> node.getGuard() != null ? sourceBefore("when", '-') : EMPTY), Markers.EMPTY ), + convert(node.getGuard()), JContainer.build( sourceBefore(type == J.Case.Type.Rule ? "->" : ":"), convertStatements(node.getStatements()), @@ -379,6 +394,7 @@ public J visitCatch(CatchTree node, Space fmt) { public J visitClass(ClassTree node, Space fmt) { Map annotationPosTable = mapAnnotations(node.getModifiers().getAnnotations(), new HashMap<>(node.getModifiers().getAnnotations().size())); + ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable); List kindAnnotations = collectAnnotations(annotationPosTable); @@ -425,7 +441,7 @@ public J visitClass(ClassTree node, Space fmt) { } JLeftPadded extendings = node.getExtendsClause() == null ? null : - padLeft(sourceBefore("extends"), convertOrNull(node.getExtendsClause())); + padLeft(sourceBefore("extends"), convert(node.getExtendsClause())); JContainer implementings = null; if (node.getImplementsClause() != null && !node.getImplementsClause().isEmpty()) { @@ -462,13 +478,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -477,7 +511,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -688,7 +722,7 @@ public J visitForLoop(ForLoopTree node, Space fmt) { commaDelim.apply(t) ); - JRightPadded condition = convertOrNull(node.getCondition(), semiDelim); + JRightPadded condition = convert(node.getCondition(), semiDelim); if (condition == null) { condition = padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY); } @@ -747,12 +781,26 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { return new J.InstanceOf(randomId(), fmt, Markers.EMPTY, convert(node.getExpression(), t -> sourceBefore("instanceof")), convert(node.getType()), - node.getPattern() instanceof JCBindingPattern b ? - new J.Identifier(randomId(), sourceBefore(b.getVariable().getName().toString()), Markers.EMPTY, emptyList(), b.getVariable().getName().toString(), - type, typeMapping.variableType(b.var.sym)) : null, + getNodePattern(node.getPattern(), type), type); } + private @Nullable J getNodePattern(@Nullable PatternTree pattern, JavaType type) { + if (pattern instanceof JCBindingPattern b) { + return new J.Identifier(randomId(), sourceBefore(b.getVariable().getName().toString()), Markers.EMPTY, emptyList(), b.getVariable().getName().toString(), + type, typeMapping.variableType(b.var.sym)); + } else { + if (pattern == null) { + return null; + } + int saveCursor = cursor; + int endCursor = max(endPos(pattern), cursor); + cursor = endCursor; + return new J.Unknown(randomId(), whitespace(), Markers.EMPTY, new J.Unknown.Source(randomId(), whitespace(), Markers.EMPTY, source.substring(saveCursor, endCursor))); + + } + } + @Override public J visitIntersectionType(IntersectionTypeTree node, Space fmt) { JContainer bounds = node.getBounds().isEmpty() ? null : @@ -811,7 +859,7 @@ public J visitLiteral(LiteralTree node, Space fmt) { singletonList(new J.Literal.UnicodeEscape(1, valueSource.substring(3, valueSource.length() - 1))), type); } - } else if (JavaType.Primitive.String.equals(type)) { + } else if (JavaType.Primitive.String == type) { StringBuilder valueSourceWithoutSurrogates = new StringBuilder(); List unicodeEscapes = null; @@ -930,11 +978,15 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) { singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) : convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY); - Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : - ((JCIdent) jcSelect).sym; + JavaType.Method methodType; + if (name.getType() instanceof JavaType.Method) { + methodType = (JavaType.Method) name.getType(); + } else { + Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : ((JCIdent) jcSelect).sym; + methodType = typeMapping.methodInvocationType(jcSelect.type, methodSymbol); + } - return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, - typeMapping.methodInvocationType(jcSelect.type, methodSymbol)); + return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, methodType); } @Override @@ -943,6 +995,7 @@ public J visitMethod(MethodTree node, Space fmt) { Map annotationPosTable = mapAnnotations(node.getModifiers().getAnnotations(), new HashMap<>(node.getModifiers().getAnnotations().size())); + ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable); J.TypeParameters typeParams; @@ -958,7 +1011,7 @@ public J visitMethod(MethodTree node, Space fmt) { } List returnTypeAnnotations = collectAnnotations(annotationPosTable); - TypeTree returnType = convertOrNull(node.getReturnType()); + TypeTree returnType = convert(node.getReturnType()); if (returnType != null && !returnTypeAnnotations.isEmpty()) { returnType = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, returnTypeAnnotations, returnType); @@ -1005,7 +1058,7 @@ public J visitMethod(MethodTree node, Space fmt) { JContainer.build(sourceBefore("throws"), convertAll(node.getThrows(), commaDelim, noDelim), Markers.EMPTY); - J.Block body = convertOrNull(node.getBody()); + J.Block body = convert(node.getBody()); JLeftPadded defaultValue = node.getDefaultValue() == null ? null : padLeft(sourceBefore("default"), convert(node.getDefaultValue())); @@ -1031,9 +1084,9 @@ public J visitNewArray(NewArrayTree node, Space fmt) { while (elementType instanceof JCArrayTypeTree) { elementType = ((JCArrayTypeTree) elementType).elemtype; } - typeExpr = convertOrNull(elementType); + typeExpr = convert(elementType); } else { - typeExpr = convertOrNull(jcVarType); + typeExpr = convert(jcVarType); } List nodeDimensions = node.getDimensions(); @@ -1064,7 +1117,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1083,7 +1143,7 @@ public J visitNewClass(NewClassTree node, Space fmt) { } // for enum definitions with anonymous class initializers, endPos of node identifier will be -1 - TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convertOrNull(node.getIdentifier()) : null; + TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convert(node.getIdentifier()) : null; JContainer args; if (positionOfNext("(", '{') > -1) { @@ -1192,7 +1252,7 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { @Override public J visitReturn(ReturnTree node, Space fmt) { skip("return"); - Expression expression = convertOrNull(node.getExpression()); + Expression expression = convert(node.getExpression()); return new J.Return(randomId(), fmt, Markers.EMPTY, expression); } @@ -1211,7 +1271,7 @@ public J visitSwitchExpression(SwitchExpressionTree node, Space fmt) { return new J.SwitchExpression(randomId(), fmt, Markers.EMPTY, convert(node.getExpression()), new J.Block(randomId(), sourceBefore("{"), Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), - convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}"))); + convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}")), typeMapping.type(node)); } @Override @@ -1517,6 +1577,22 @@ public J visitUnary(UnaryTree node, Space fmt) { @Override public J visitVariable(VariableTree node, Space fmt) { + JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) node; + if ("".equals(jcVariableDecl.getName().toString())) { + int startPos = jcVariableDecl.getStartPosition(); + int endPos = jcVariableDecl.getEndPosition(endPosTable); + + if (startPos == endPos) { + endPos = startPos + 1; // For cases where the error node is a single character like "/" + } + String erroneousNode = source.substring(startPos, endPos); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode + ); + } return hasFlag(node.getModifiers(), Flags.ENUM) ? visitEnumVariable(node, fmt) : visitVariables(singletonList(node), fmt); // method arguments cannot be multi-declarations @@ -1541,8 +1617,16 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, emptyList(), "var", typeMapping.type(vartype), null); - typeExpr = typeExpr.withMarkers(typeExpr.getMarkers().add(JavaVarKeyword.build())); + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` + typeExpr = new J.Identifier(randomId(), + space, + Markers.build(singletonList(JavaVarKeyword.build())), + emptyList(), + lombokVal ? "val" : "var", + typeMapping.type(vartype), + null); } } else if (vartype instanceof JCArrayTypeTree) { JCExpression elementType = vartype; @@ -1562,6 +1646,10 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm typeExpr = convert(vartype); } + if (typeExpr == null && node.declaredUsingVar()) { + typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.build(singletonList(JavaVarKeyword.build())), emptyList(), "var", typeMapping.type(vartype), null); + } + if (typeExpr != null && !typeExprAnnotations.isEmpty()) { Space prefix = typeExprAnnotations.get(0).getPrefix(); typeExpr = new J.AnnotatedType(randomId(), prefix, Markers.EMPTY, ListUtils.mapFirst(typeExprAnnotations, a -> a.withPrefix(EMPTY)), typeExpr); @@ -1595,7 +1683,7 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm new J.VariableDeclarations.NamedVariable(randomId(), namedVarPrefix, Markers.EMPTY, name, dimensionsAfterName, - n.init != null ? padLeft(sourceBefore("="), convertOrNull(n.init)) : null, + n.init != null ? padLeft(sourceBefore("="), convert(n.init)) : null, (JavaType.Variable) typeMapping.type(n) ), i == nodes.size() - 1 ? EMPTY : sourceBefore(",") @@ -1652,7 +1740,7 @@ public J visitWildcard(WildcardTree node, Space fmt) { bound = null; } - return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convertOrNull(wildcard.inner)); + return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convert(wildcard.inner)); } /** @@ -1660,10 +1748,12 @@ public J visitWildcard(WildcardTree node, Space fmt) { * Conversion utilities * -------------- */ - - private J2 convert(Tree t) { + private @Nullable J2 convert(@Nullable Tree t) { + if (t == null) { + return null; + } try { - String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor)); + String prefix = source.substring(cursor, Math.max(cursor, getActualStartPosition((JCTree) t))); cursor += prefix.length(); // Java 21 and 23 have a different return type from getCommentTree; with reflection we can support both Method getCommentTreeMethod = DocCommentTable.class.getMethod("getCommentTree", JCTree.class); @@ -1679,6 +1769,14 @@ private J2 convert(Tree t) { } } + private static int getActualStartPosition(JCTree t) { + // The variable's start position in the source is wrongly after lombok's `@val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + private void reportJavaParsingException(Throwable ex) { // this SHOULD never happen, but is here simply as a diagnostic measure in the event of unexpected exceptions StringBuilder message = new StringBuilder("Failed to convert for the following cursor stack:"); @@ -1703,24 +1801,56 @@ private void reportJavaParsingException(Throwable ex) { ctx.getOnError().accept(new JavaParsingException(message.toString(), ex)); } - private JRightPadded convert(Tree t, Function suffix) { + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { + if (t == null) { + return null; + } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); - cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); + int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); + if (idx >= 0) { + rightPadded = (JRightPadded) JRightPadded.build(getErroneous(List.of(rightPadded))); + } + // Cursor hasn't been updated but points at the end of erroneous node already + // This means that error node start position == end position + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the + // same from the JC compiler. + if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { + cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); + } else { + cursor(max(endPos(t), cursor)); // if there is a non-empty suffix or a lombok generated method, the cursor can be already moved past it + } return rightPadded; } - private long lineNumber(Tree tree) { - return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1; + private J.Erroneous getErroneous(List> converted) { + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + return new J.Erroneous( + org.openrewrite.Tree.randomId(), + EMPTY, + Markers.EMPTY, + p.getOut() + ); } - private @Nullable T convertOrNull(@Nullable Tree t) { - return t == null ? null : convert(t); + private static int findFirstNonWhitespaceChar(String s) { + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + return -1; } - private @Nullable JRightPadded convertOrNull(@Nullable Tree t, Function suffix) { - return t == null ? null : convert(t, suffix); + private long lineNumber(Tree tree) { + return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1; } private List convertAll(List trees) { @@ -1734,13 +1864,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1765,25 +1902,50 @@ private List> convertAll(List tr private Space statementDelim(@Nullable Tree t) { switch (t.getKind()) { + case CONTINUE: + case RETURN: + case BREAK: case ASSERT: case ASSIGNMENT: - case BREAK: - case CONTINUE: case DO_WHILE_LOOP: case IMPORT: case METHOD_INVOCATION: case NEW_CLASS: - case RETURN: case THROW: + return sourceBefore(";"); case EXPRESSION_STATEMENT: + ExpressionTree expTree = ((ExpressionStatementTree) t).getExpression(); + if (expTree instanceof ErroneousTree) { + return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable), ((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList()); + } else { + return sourceBefore(";"); + } case VARIABLE: + JCTree.JCVariableDecl varTree = (JCTree.JCVariableDecl) t; + if ("".contentEquals(varTree.getName())) { + int start = varTree.vartype.getEndPosition(endPosTable); + int end = varTree.getEndPosition(endPosTable); + String whitespace = source.substring(start, end); + if (whitespace.contains("\n")) { + return EMPTY; + } else { + return Space.build(source.substring(start, end), Collections.emptyList()); + } + } + return sourceBefore(";"); case YIELD: return sourceBefore(";"); case LABELED_STATEMENT: return statementDelim(((JCLabeledStatement) t).getStatement()); case METHOD: JCMethodDecl m = (JCMethodDecl) t; - return sourceBefore(m.body == null || m.defaultValue != null ? ";" : ""); + if (m.body == null || m.defaultValue != null) { + String suffix = source.substring(cursor, positionOfNext(";", null)); + int idx = findFirstNonWhitespaceChar(suffix); + return sourceBefore(idx >= 0 ? "" : ";"); + } else { + return sourceBefore(""); + } default: return t instanceof JCAssignOp || t instanceof JCUnary ? sourceBefore(";") : EMPTY; } @@ -1802,12 +1964,19 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { + continue; + } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); } List> converted = new ArrayList<>(treesGroupedByStartPosition.size()); for (List treeGroup : treesGroupedByStartPosition.values()) { if (treeGroup.size() == 1) { + Tree t = treeGroup.get(0); + int startPosition = ((JCTree) t).getStartPosition(); + if (cursor > startPosition) + continue; converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -1827,6 +1996,25 @@ private List> convertStatements(@Nullable List "lombok.val".equals(a.type.toString())); + } + + //noinspection ConstantConditions + return sym != null && ("lombok.val".equals(sym.getQualifiedName().toString()) || sym.getAnnotation(Generated.class) != null); + } + /** * -------------- * Other convenience utilities @@ -1935,15 +2123,16 @@ private Space whitespace() { return space; } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts @@ -2003,7 +2192,11 @@ private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersT int keywordStartIdx = -1; for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - J.Annotation annotation = convert(annotationPosTable.get(i)); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation.getAnnotationType())) { + continue; + } + J.Annotation annotation = convert(jcAnnotation); if (afterFirstModifier) { currentAnnotations.add(annotation); } else { @@ -2123,7 +2316,11 @@ private List collectAnnotations(Map annotat boolean inMultilineComment = false; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - annotations.add(convert(annotationPosTable.get(i))); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation)) { + continue; + } + annotations.add(convert(jcAnnotation)); i = cursor; continue; } diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java index 10d9b202c6f..2744002ccb6 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java @@ -17,25 +17,26 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; +import com.sun.tools.javac.comp.AttrRecover; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.JavaTypeMapping; import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.tree.Flag; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @RequiredArgsConstructor @@ -47,7 +48,7 @@ class ReloadableJava21TypeMapping implements JavaTypeMapping { public JavaType type(com.sun.tools.javac.code.@Nullable Type type) { if (type == null || type instanceof Type.ErrorType || type instanceof Type.PackageType || type instanceof Type.UnknownType || - type instanceof NullType) { + type instanceof NullType) { return JavaType.Class.Unknown.getInstance(); } @@ -381,7 +382,7 @@ private JavaType.FullyQualified.Kind getKind(Symbol.ClassSymbol sym) { } private @Nullable JavaType type(Type type, Symbol symbol) { - if (type instanceof Type.MethodType || type instanceof Type.ForAll) { + if (type instanceof Type.MethodType || type instanceof Type.ForAll || (type instanceof Type.ErrorType && type.getOriginalType() instanceof Type.MethodType)) { return methodInvocationType(type, symbol); } return type(type); @@ -423,7 +424,7 @@ public JavaType.Primitive primitive(TypeTag tag) { } private JavaType.@Nullable Variable variableType(@Nullable Symbol symbol, - JavaType.@Nullable FullyQualified owner) { + JavaType.@Nullable FullyQualified owner) { if (!(symbol instanceof Symbol.VarSymbol)) { return null; } @@ -470,6 +471,22 @@ public JavaType.Primitive primitive(TypeTag tag) { * @return Method type attribution. */ public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) { + if (selectType instanceof Type.ErrorType) { + try { + // Ugly reflection solution, because AttrRecover$RecoveryErrorType is private inner class + for (Class targetClass : Class.forName(AttrRecover.class.getCanonicalName()).getDeclaredClasses()) { + if (targetClass.getSimpleName().equals("RecoveryErrorType")) { + Field field = targetClass.getDeclaredField("candidateSymbol"); + field.setAccessible(true); + Symbol originalSymbol = (Symbol) field.get(selectType); + return methodInvocationType(selectType.getOriginalType(), originalSymbol); + } + } + } catch (Exception e) { + // ignore + } + } + if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) { return null; } @@ -505,13 +522,13 @@ public JavaType.Primitive primitive(TypeTag tag) { methodSymbol.isConstructor() ? "" : methodSymbol.getSimpleName().toString(), null, paramNames, - null, null, null, null + null, null, null, null, null ); typeCache.put(signature, method); JavaType returnType = null; List parameterTypes = null; - List exceptionTypes = null; + List exceptionTypes = null; if (selectType instanceof Type.MethodType) { Type.MethodType methodType = (Type.MethodType) selectType; @@ -531,20 +548,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } else if (selectType instanceof Type.UnknownType) { @@ -593,19 +598,30 @@ public JavaType.Primitive primitive(TypeTag tag) { } } List defaultValues = null; - if(methodSymbol.getDefaultValue() != null) { - if(methodSymbol.getDefaultValue() instanceof Attribute.Array) { + if (methodSymbol.getDefaultValue() != null) { + if (methodSymbol.getDefaultValue() instanceof Attribute.Array) { defaultValues = ((Attribute.Array) methodSymbol.getDefaultValue()).getValue().stream() .map(attr -> attr.getValue().toString()) .collect(Collectors.toList()); } else { try { defaultValues = Collections.singletonList(methodSymbol.getDefaultValue().getValue().toString()); - } catch(UnsupportedOperationException e) { + } catch (UnsupportedOperationException e) { // not all Attribute implementations define `getValue()` } } } + + List declaredFormalTypeNames = null; + for (Symbol.TypeVariableSymbol typeParam : methodSymbol.getTypeParameters()) { + if(typeParam.owner == methodSymbol) { + if (declaredFormalTypeNames == null) { + declaredFormalTypeNames = new ArrayList<>(); + } + declaredFormalTypeNames.add(typeParam.name.toString()); + } + } + JavaType.Method method = new JavaType.Method( null, methodSymbol.flags_field, @@ -614,7 +630,8 @@ public JavaType.Primitive primitive(TypeTag tag) { null, paramNames, null, null, null, - defaultValues + defaultValues, + declaredFormalTypeNames == null ? null : declaredFormalTypeNames.toArray(new String[0]) ); typeCache.put(signature, method); @@ -622,7 +639,7 @@ public JavaType.Primitive primitive(TypeTag tag) { ((Type.ForAll) methodSymbol.type).qtype : methodSymbol.type; - List exceptionTypes = null; + List exceptionTypes = null; Type selectType = methodSymbol.type; if (selectType instanceof Type.ForAll) { @@ -634,20 +651,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } @@ -703,22 +708,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + JavaType.Annotation.ArrayElementValue.from(element, ((Object[]) value)) : + JavaType.Annotation.SingleElementValue.from(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(!list.isEmpty() && list.getFirst() instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(!list.isEmpty() && list.getFirst() instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-8/build.gradle.kts b/rewrite-java-8/build.gradle.kts index 5f92a5f85dd..e07e976892e 100644 --- a/rewrite-java-8/build.gradle.kts +++ b/rewrite-java-8/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { compileOnly("org.slf4j:slf4j-api:1.7.+") implementation(project(":rewrite-java")) + runtimeOnly(project(":rewrite-java-lombok")) implementation("org.ow2.asm:asm:latest.release") implementation("io.micrometer:micrometer-core:1.9.+") diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/Java8ParserInputFileObject.java b/rewrite-java-8/src/main/java/org/openrewrite/java/Java8ParserInputFileObject.java index 122bdce7957..37e1eb655ae 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/Java8ParserInputFileObject.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/Java8ParserInputFileObject.java @@ -110,7 +110,7 @@ public Kind getKind() { @Override public boolean isNameCompatible(String simpleName, Kind kind) { String baseName = simpleName + kind.extension; - return kind.equals(getKind()) && + return kind == getKind() && path.getFileName().toString().equals(baseName); } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java index 227b6f3609c..73fbc46c6ba 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java @@ -18,12 +18,12 @@ import com.sun.tools.javac.comp.*; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.Option; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; +import lombok.Getter; import org.jspecify.annotations.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -31,7 +31,6 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.SourceFile; -import org.openrewrite.internal.MetricsHelper; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.tree.J; @@ -40,19 +39,25 @@ import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; +import org.slf4j.LoggerFactory; +import javax.annotation.processing.Processor; import javax.tools.*; import java.io.*; +import java.lang.reflect.Constructor; import java.net.URI; +import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static com.sun.tools.javac.util.List.nil; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; class ReloadableJava8Parser implements JavaParser { @@ -70,6 +75,7 @@ class ReloadableJava8Parser implements JavaParser { private final JavaCompiler compiler; private final ResettableLog compilerLog; private final Collection styles; + private final List annotationProcessors; ReloadableJava8Parser(@Nullable Collection classpath, Collection classBytesClasspath, @@ -100,6 +106,70 @@ class ReloadableJava8Parser implements JavaParser { Options.instance(context).put("-g", "-g"); Options.instance(context).put("-proc", "none"); + // Ensure type attribution continues despite errors in individual files or nodes. + // If an error occurs in a single file or node, type attribution should still proceed + // for all other source files and unaffected nodes within the same file. + Options.instance(context).put("should-stop.ifError", "GENERATE"); + + LOMBOK: + if (System.getenv().getOrDefault("REWRITE_LOMBOK", System.getProperty("rewrite.lombok")) != null && + classpath != null && classpath.stream().anyMatch(it -> it.toString().contains("lombok"))) { + Processor lombokProcessor = null; + try { + // https://projectlombok.org/contributing/lombok-execution-path + List overrideClasspath = new ArrayList<>(); + for (Path part : classpath) { + if (part.toString().contains("lombok")) { + overrideClasspath.add(part.toString()); + } + } + // make sure the rewrite-java-lombok dependency comes first + boolean found = false; + for (int i = 0; i < overrideClasspath.size(); i++) { + if (overrideClasspath.get(i).contains("rewrite-java-lombok")) { + overrideClasspath.add(0, overrideClasspath.remove(i)); + found = true; + } + } + if (!found) { + // try to find `rewrite-java-lombok` using class loader + URL resource = getClass().getClassLoader().getResource("org/openrewrite/java/lombok/OpenRewriteConfigurationKeysLoader.class"); + if (resource != null && resource.getProtocol().equals("jar") && resource.getPath().startsWith("file:")) { + String path = Paths.get(URI.create(resource.getPath().substring(0, resource.getPath().indexOf("!")))).toString(); + overrideClasspath.add(0, path); + } else { + break LOMBOK; + } + } + System.setProperty("shadow.override.lombok", String.join(File.pathSeparator, overrideClasspath)); + + Class shadowLoaderClass = Class.forName("lombok.launch.ShadowClassLoader", true, getClass().getClassLoader()); + Constructor shadowLoaderConstructor = shadowLoaderClass.getDeclaredConstructor( + Class.forName("java.lang.ClassLoader"), + Class.forName("java.lang.String"), + Class.forName("java.lang.String"), + Class.forName("java.util.List"), + Class.forName("java.util.List")); + shadowLoaderConstructor.setAccessible(true); + + ClassLoader lombokShadowLoader = (ClassLoader) shadowLoaderConstructor.newInstance( + getClass().getClassLoader(), + "lombok", + null, + emptyList(), + singletonList("lombok.patcher.Symbols") + ); + lombokProcessor = (Processor) lombokShadowLoader.loadClass("lombok.core.AnnotationProcessor").getDeclaredConstructor().newInstance(); + Options.instance(context).put(Option.PROCESSOR, "lombok.launch.AnnotationProcessorHider$AnnotationProcessor"); + } catch (ReflectiveOperationException ignore) { + // Lombok was not found or could not be initialized + } finally { + annotationProcessors = lombokProcessor != null ? singletonList(lombokProcessor) : emptyList(); + } + } else { + annotationProcessors = emptyList(); + } + // MUST be created (registered with the context) after pfm and compilerLog compiler = new JavaCompiler(context); @@ -118,7 +188,7 @@ public void write(char[] cbuf, int off, int len) { if (logCompilationWarningsAndErrors) { String log = new String(Arrays.copyOfRange(cbuf, off, len)); if (!StringUtils.isBlank(log)) { - org.slf4j.LoggerFactory.getLogger(ReloadableJava8Parser.class).warn(log); + LoggerFactory.getLogger(ReloadableJava8Parser.class).warn(log); } } } @@ -138,45 +208,7 @@ public void close() { @Override public Stream parseInputs(Iterable sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - if (classpath != null) { // override classpath - if (context.get(JavaFileManager.class) != pfm) { - throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); - } - - try { - pfm.setLocation(StandardLocation.CLASS_PATH, classpath.stream().map(Path::toFile).collect(toList())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @SuppressWarnings("ConstantConditions") LinkedHashMap cus = acceptedInputs(sourceFiles) - .collect(Collectors.toMap( - Function.identity(), - input -> { - try { - return compiler.parse(new Java8ParserInputFileObject(input, ctx)); - } catch (IllegalStateException e) { - if ("endPosTable already set".equals(e.getMessage())) { - throw new IllegalStateException( - "Call reset() on JavaParser before parsing another set of source files that " + - "have some of the same fully qualified names. Source file [" + - input.getPath() + "]\n[\n" + StringUtils.readFully(input.getSource(ctx), getCharset(ctx)) + "\n]", e); - } - throw e; - } - }, - (e2, e1) -> e1, LinkedHashMap::new)); - - try { - enterAll(cus.values()); - compiler.attribute(new TimedTodo(compiler.todo)); - } catch (Throwable t) { - // when symbol entering fails on problems like missing types, attribution can often times proceed - // unhindered, but it sometimes cannot (so attribution is always a BEST EFFORT in the presence of errors) - ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); - } - + LinkedHashMap cus = parseInputsToCompilerAst(sourceFiles, ctx); return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); parsingListener.startedParsing(input); @@ -190,6 +222,7 @@ public Stream parseInputs(Iterable sourceFiles, @Nullable Pat ctx, context); J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); + //noinspection DataFlowIssue cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released parsingListener.parsed(input, cu); return requirePrintEqualsInput(cu, input, relativeTo, ctx); @@ -200,6 +233,56 @@ public Stream parseInputs(Iterable sourceFiles, @Nullable Pat }); } + LinkedHashMap parseInputsToCompilerAst(Iterable sourceFiles, ExecutionContext ctx) { + if (classpath != null) { // override classpath + if (context.get(JavaFileManager.class) != pfm) { + throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); + } + + try { + pfm.setLocation(StandardLocation.CLASS_PATH, classpath.stream().map(Path::toFile).collect(toList())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + LinkedHashMap cus = new LinkedHashMap<>(); + List inputFileObjects = acceptedInputs(sourceFiles) + .map(input -> new Java8ParserInputFileObject(input, ctx)) + .collect(toList()); + if (!annotationProcessors.isEmpty()) { + compiler.initProcessAnnotations(annotationProcessors); + } + try { + //noinspection unchecked + com.sun.tools.javac.util.List jcCompilationUnits = com.sun.tools.javac.util.List.from( + inputFileObjects.stream() + .map(input -> compiler.parse(input)) + .toArray(JCTree.JCCompilationUnit[]::new)); + for (int i = 0; i < inputFileObjects.size(); i++) { + cus.put(inputFileObjects.get(i).getInput(), jcCompilationUnits.get(i)); + } + try { + enterAll(cus.values()); + JavaCompiler delegate = annotationProcessors.isEmpty() ? compiler : compiler.processAnnotations(jcCompilationUnits, nil()); + delegate.attribute(delegate.todo); + } catch (Throwable t) { + // when symbol entering fails on problems like missing types, attribution can often times proceed + // unhindered, but it sometimes cannot (so attribution is always best-effort in the presence of errors) + ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); + } + } catch (IllegalStateException e) { + if ("endPosTable already set".equals(e.getMessage())) { + throw new IllegalStateException( + "Call reset() on JavaParser before parsing another set of source files that " + + "have some of the same fully qualified names.", e); + } + throw e; + } + + return cus; + } + @Override public ReloadableJava8Parser reset() { typeCache.clear(); @@ -261,35 +344,6 @@ public void reset(Collection uris) { } } - private static class TimedTodo extends Todo { - private final Todo todo; - private Timer.@Nullable Sample sample; - - private TimedTodo(Todo todo) { - super(new Context()); - this.todo = todo; - } - - @Override - public boolean isEmpty() { - if (sample != null) { - sample.stop(MetricsHelper.successTags( - Timer.builder("rewrite.parse") - .description("The time spent by the JDK in type attributing the source file") - .tag("file.type", "Java") - .tag("step", "(2) Type attribution")) - .register(Metrics.globalRegistry)); - } - return todo.isEmpty(); - } - - @Override - public Env remove() { - this.sample = Timer.start(); - return todo.remove(); - } - } - private static class ByteArrayCapableJavacFileManager extends JavacFileManager { private final List classByteClasspath; @@ -318,7 +372,7 @@ public String inferBinaryName(Location location, JavaFileObject file) { @Override public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { - if (StandardLocation.CLASS_PATH.equals(location)) { + if (StandardLocation.CLASS_PATH == location) { Iterable listed = super.list(location, packageName, kinds, recurse); return Stream.concat( classByteClasspath.stream() @@ -332,6 +386,7 @@ public Iterable list(Location location, String packageName, Set< private static class PackageAwareJavaFileObject extends SimpleJavaFileObject { private final String pkg; + @Getter private final String className; private final byte[] classBytes; @@ -365,10 +420,6 @@ public String getPackage() { return pkg; } - public String getClassName() { - return className; - } - @Override public InputStream openInputStream() { return new ByteArrayInputStream(classBytes); diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java index b07b7f18999..5d35ffcc46a 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java @@ -25,14 +25,17 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; +import org.openrewrite.PrintOutputCapture; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -189,6 +192,16 @@ public J visitAssignment(AssignmentTree node, Space fmt) { typeMapping.type(node)); } + @Override + public J visitErroneous(ErroneousTree node, Space fmt) { + String erroneousNode = source.substring(((JCTree) node).getStartPosition(), ((JCTree) node).getEndPosition(endPosTable)); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode); + } + @Override public J visitBinary(BinaryTree node, Space fmt) { Expression left = convert(node.getLeftOperand()); @@ -312,7 +325,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -326,10 +339,12 @@ public J visitCase(CaseTree node, Space fmt) { node.getExpression() == null ? EMPTY : sourceBefore("case"), singletonList(node.getExpression() == null ? JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)) : - JRightPadded.build(convertOrNull(node.getExpression())) + JRightPadded.build(convert(node.getExpression())) ), Markers.EMPTY ), + null, + null, JContainer.build(sourceBefore(":"), convertStatements(node.getStatements()), Markers.EMPTY), null ); @@ -378,7 +393,7 @@ public J visitClass(ClassTree node, Space fmt) { Markers.EMPTY); JLeftPadded extendings = node.getExtendsClause() == null ? null : - padLeft(sourceBefore("extends"), convertOrNull(node.getExtendsClause())); + padLeft(sourceBefore("extends"), convert(node.getExtendsClause())); JContainer implementings = null; if (node.getImplementsClause() != null && !node.getImplementsClause().isEmpty()) { @@ -406,13 +421,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -421,7 +454,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -628,7 +661,7 @@ public J visitForLoop(ForLoopTree node, Space fmt) { commaDelim.apply(t) ); - JRightPadded condition = convertOrNull(node.getCondition(), semiDelim); + JRightPadded condition = convert(node.getCondition(), semiDelim); if (condition == null) { condition = padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY); } @@ -749,7 +782,7 @@ public J visitLiteral(LiteralTree node, Space fmt) { singletonList(new J.Literal.UnicodeEscape(1, valueSource.substring(3, valueSource.length() - 1))), type); } - } else if (JavaType.Primitive.String.equals(type)) { + } else if (JavaType.Primitive.String == type) { StringBuilder valueSourceWithoutSurrogates = new StringBuilder(); List unicodeEscapes = null; @@ -896,7 +929,7 @@ public J visitMethod(MethodTree node, Space fmt) { } List returnTypeAnnotations = collectAnnotations(annotationPosTable); - TypeTree returnType = convertOrNull(node.getReturnType()); + TypeTree returnType = convert(node.getReturnType()); if (returnType != null && !returnTypeAnnotations.isEmpty()) { returnType = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, returnTypeAnnotations, returnType); @@ -939,7 +972,7 @@ public J visitMethod(MethodTree node, Space fmt) { JContainer.build(sourceBefore("throws"), convertAll(node.getThrows(), commaDelim, noDelim), Markers.EMPTY); - J.Block body = convertOrNull(node.getBody()); + J.Block body = convert(node.getBody()); JLeftPadded defaultValue = node.getDefaultValue() == null ? null : padLeft(sourceBefore("default"), convert(node.getDefaultValue())); @@ -963,9 +996,9 @@ public J visitNewArray(NewArrayTree node, Space fmt) { while (elementType instanceof JCArrayTypeTree) { elementType = ((JCArrayTypeTree) elementType).elemtype; } - typeExpr = convertOrNull(elementType); + typeExpr = convert(elementType); } else { - typeExpr = convertOrNull(jcVarType); + typeExpr = convert(jcVarType); } List nodeDimensions = node.getDimensions(); @@ -978,7 +1011,7 @@ public J visitNewArray(NewArrayTree node, Space fmt) { convert(dim, t -> sourceBefore("]")))); } - while(true) { + while (true) { int beginBracket = indexOfNextNonWhitespace(cursor, source); if (source.charAt(beginBracket) == '[') { int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); @@ -996,7 +1029,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1015,7 +1055,7 @@ public J visitNewClass(NewClassTree node, Space fmt) { } // for enum definitions with anonymous class initializers, endPos of node identifier will be -1 - TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convertOrNull(node.getIdentifier()) : null; + TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convert(node.getIdentifier()) : null; JContainer args; if (positionOfNext("(", '{') > -1) { @@ -1123,7 +1163,8 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { @Override public J visitReturn(ReturnTree node, Space fmt) { skip("return"); - return new J.Return(randomId(), fmt, Markers.EMPTY, convertOrNull(node.getExpression())); + Expression expression = convert(node.getExpression()); + return new J.Return(randomId(), fmt, Markers.EMPTY, expression); } @Override @@ -1433,6 +1474,22 @@ public J visitUnary(UnaryTree node, Space fmt) { @Override public J visitVariable(VariableTree node, Space fmt) { + JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) node; + if ("".equals(jcVariableDecl.getName().toString())) { + int startPos = jcVariableDecl.getStartPosition(); + int endPos = jcVariableDecl.getEndPosition(endPosTable); + + if (startPos == endPos) { + endPos = startPos + 1; + } + String erroneousNode = source.substring(startPos, endPos); + return new J.Erroneous( + randomId(), + fmt, + Markers.EMPTY, + erroneousNode + ); + } return hasFlag(node.getModifiers(), Flags.ENUM) ? visitEnumVariable(node, fmt) : visitVariables(singletonList(node), fmt); // method arguments cannot be multi-declarations @@ -1452,6 +1509,17 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm TypeTree typeExpr; if (vartype == null || endPos(vartype) < 0 || vartype instanceof JCErroneous) { typeExpr = null; // this is a lambda parameter with an inferred type expression + } else if (isLombokGenerated(node)) { + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` + typeExpr = new J.Identifier(randomId(), + space, + Markers.build(singletonList(JavaVarKeyword.build())), + emptyList(), + lombokVal ? "val" : "var", + typeMapping.type(vartype), + null); } else if (vartype instanceof JCArrayTypeTree) { JCExpression elementType = vartype; while (elementType instanceof JCArrayTypeTree || elementType instanceof JCAnnotatedType) { @@ -1488,10 +1556,9 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm List> vars = new ArrayList<>(nodes.size()); for (int i = 0; i < nodes.size(); i++) { - VariableTree n = nodes.get(i); + JCVariableDecl n = (JCVariableDecl) nodes.get(i); Space namedVarPrefix = sourceBefore(n.getName().toString()); - JCVariableDecl vd = (JCVariableDecl) n; JavaType type = typeMapping.type(n); J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(), @@ -1504,7 +1571,7 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm new J.VariableDeclarations.NamedVariable(randomId(), namedVarPrefix, Markers.EMPTY, name, dimensionsAfterName, - vd.init != null ? padLeft(sourceBefore("="), convertOrNull(vd.init)) : null, + n.init != null ? padLeft(sourceBefore("="), convert(n.init)) : null, (JavaType.Variable) typeMapping.type(n) ), i == nodes.size() - 1 ? EMPTY : sourceBefore(",") @@ -1561,7 +1628,7 @@ public J visitWildcard(WildcardTree node, Space fmt) { bound = null; } - return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convertOrNull(wildcard.inner)); + return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convert(wildcard.inner)); } /** @@ -1569,10 +1636,12 @@ public J visitWildcard(WildcardTree node, Space fmt) { * Conversion utilities * -------------- */ - - private J2 convert(Tree t) { + private @Nullable J2 convert(@Nullable Tree t) { + if (t == null) { + return null; + } try { - String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor)); + String prefix = source.substring(cursor, Math.max(cursor, getActualStartPosition((JCTree) t))); cursor += prefix.length(); @SuppressWarnings("unchecked") J2 j = (J2) scan(t, formatWithCommentTree(prefix, (JCTree) t, docCommentTable.getCommentTree((JCTree) t))); return j; @@ -1602,14 +1671,65 @@ private J2 convert(Tree t) { } } - private JRightPadded convert(Tree t, Function suffix) { + private static int getActualStartPosition(JCTree t) { + // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + if (t == null) { + return null; + } + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { + if (t == null) { + return null; + } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); - cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); + int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); + if (idx >= 0) { + rightPadded = (JRightPadded) JRightPadded.build(getErroneous(Collections.singletonList(rightPadded))); + } + // Cursor hasn't been updated but points at the end of erroneous node already + // This means that error node start position == end position + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the + // same from the JC compiler. + if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { + cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); + } else { + cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + } return rightPadded; } + private J.Erroneous getErroneous(List> converted) { + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + return new J.Erroneous( + org.openrewrite.Tree.randomId(), + EMPTY, + Markers.EMPTY, + p.getOut() + ); + } + + private static int findFirstNonWhitespaceChar(String s) { + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + return -1; + } + private long lineNumber(Tree tree) { int lineNumber = 1; for (char c : source.substring(0, ((JCTree) tree).getStartPosition()).toCharArray()) { @@ -1620,14 +1740,6 @@ private long lineNumber(Tree tree) { return lineNumber; } - private @Nullable T convertOrNull(@Nullable Tree t) { - return t == null ? null : convert(t); - } - - private @Nullable JRightPadded convertOrNull(@Nullable Tree t, Function suffix) { - return t == null ? null : convert(t, suffix); - } - private List convertAll(List trees) { List converted = new ArrayList<>(trees.size()); for (Tree tree : trees) { @@ -1639,13 +1751,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1670,19 +1789,17 @@ private List> convertAll(List tr private Space statementDelim(@Nullable Tree t) { if (t instanceof JCAssert || - t instanceof JCAssign || - t instanceof JCAssignOp || - t instanceof JCBreak || - t instanceof JCContinue || - t instanceof JCDoWhileLoop || - t instanceof JCImport || - t instanceof JCMethodInvocation || - t instanceof JCNewClass || - t instanceof JCReturn || - t instanceof JCThrow || - t instanceof JCUnary || - t instanceof JCExpressionStatement || - t instanceof JCVariableDecl) { + t instanceof JCAssign || + t instanceof JCAssignOp || + t instanceof JCBreak || + t instanceof JCContinue || + t instanceof JCDoWhileLoop || + t instanceof JCImport || + t instanceof JCMethodInvocation || + t instanceof JCNewClass || + t instanceof JCReturn || + t instanceof JCThrow || + t instanceof JCUnary) { return sourceBefore(";"); } @@ -1690,9 +1807,39 @@ private Space statementDelim(@Nullable Tree t) { return statementDelim(((JCLabeledStatement) t).getStatement()); } + if (t instanceof JCExpressionStatement) { + ExpressionTree expTree = ((ExpressionStatementTree) t).getExpression(); + if (expTree instanceof ErroneousTree) { + return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable), ((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList()); + } else { + return sourceBefore(";"); + } + } + + if (t instanceof JCVariableDecl) { + JCTree.JCVariableDecl varTree = (JCTree.JCVariableDecl) t; + if ("".contentEquals(varTree.getName())) { + int start = varTree.vartype.getEndPosition(endPosTable); + int end = varTree.getEndPosition(endPosTable); + String whitespace = source.substring(start, end); + if (whitespace.contains("\n")) { + return EMPTY; + } else { + return Space.build(source.substring(start, end), Collections.emptyList()); + } + } + return sourceBefore(";"); + } + if (t instanceof JCMethodDecl) { JCMethodDecl m = (JCMethodDecl) t; - return sourceBefore(m.body == null || m.defaultValue != null ? ";" : ""); + if (m.body == null || m.defaultValue != null) { + String suffix = source.substring(cursor, positionOfNext(";", null)); + int idx = findFirstNonWhitespaceChar(suffix); + return sourceBefore(idx >= 0 ? "" : ";"); + } else { + return sourceBefore(""); + } } return EMPTY; @@ -1711,12 +1858,19 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { + continue; + } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); } List> converted = new ArrayList<>(treesGroupedByStartPosition.size()); for (List treeGroup : treesGroupedByStartPosition.values()) { if (treeGroup.size() == 1) { + Tree t = treeGroup.get(0); + int startPosition = ((JCTree) t).getStartPosition(); + if (cursor > startPosition) + continue; converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -1736,6 +1890,32 @@ private List> convertStatements(@Nullable List "@lombok.Generated()".equals(a.toString())); + } + } else if (tree instanceof JCTree.JCClassDecl) { + sym = ((JCClassDecl) tree).sym; + } else if (tree instanceof JCTree.JCVariableDecl) { + sym = ((JCVariableDecl) tree).sym; + return sym != null && sym.getDeclarationAttributes().stream().anyMatch(a -> "lombok.val".equals(a.type.toString()) || "lombok.var".equals(a.type.toString())); + } + + //noinspection ConstantConditions + return sym != null && ( + "lombok.val".equals(sym.getQualifiedName().toString()) || "lombok.var".equals(sym.getQualifiedName().toString()) || + sym.getAnnotation(Generated.class) != null + ); + } + /** * -------------- * Other convenience utilities @@ -1794,7 +1974,7 @@ private int positionOfNext(String untilDelim, @Nullable Character stop) { char c2 = source.charAt(delimIndex + 1); switch (c1) { case '/': - switch(c2) { + switch (c2) { case '/': inSingleLineComment = true; delimIndex++; @@ -1806,7 +1986,7 @@ private int positionOfNext(String untilDelim, @Nullable Character stop) { } break; case '*': - if(c2 == '/') { + if (c2 == '/') { inMultiLineComment = false; delimIndex++; continue; @@ -1843,15 +2023,16 @@ private Space whitespace() { return format(prefix); } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts @@ -1872,7 +2053,7 @@ private List listFlags(long flags) { try { // FIXME instanceof probably not right here... return field.get(null) instanceof Long && - field.getName().matches("[A-Z_]+"); + field.getName().matches("[A-Z_]+"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -1910,13 +2091,17 @@ private Java8ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifie int lastAnnotationPosition = cursor; for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - J.Annotation annotation = convert(annotationPosTable.get(i)); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation.getAnnotationType())) { + continue; + } + J.Annotation annotation = convert(jcAnnotation); if (afterFirstModifier) { currentAnnotations.add(annotation); } else { leadingAnnotations.add(annotation); } - i = cursor -1; + i = cursor - 1; lastAnnotationPosition = cursor; continue; } @@ -2030,7 +2215,11 @@ private List collectAnnotations(Map annotat boolean inMultilineComment = false; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - annotations.add(convert(annotationPosTable.get(i))); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation)) { + continue; + } + annotations.add(convert(jcAnnotation)); i = cursor; continue; } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java index 988b0920206..00a8786479d 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java @@ -18,23 +18,22 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.tree.Flag; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.CONTRAVARIANT; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.COVARIANT; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.INVARIANT; @@ -455,6 +454,10 @@ public JavaType.Primitive primitive(TypeTag tag) { * @return Method type attribution. */ public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) { + /* + TODO: AttrRecover class does not exist for java 8; there is JCNoType + in the Type.class, so maybe it is possible to retrieve this information... + */ if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.ERR || symbol.type instanceof Type.UnknownType) { return null; } @@ -472,12 +475,14 @@ public JavaType.Primitive primitive(TypeTag tag) { return existing; } - List paramNames = null; + String[] paramNames = null; if (!methodSymbol.params().isEmpty()) { - paramNames = new ArrayList<>(methodSymbol.params().size()); - for (Symbol.VarSymbol p : methodSymbol.params()) { + paramNames = new String[methodSymbol.params().size()]; + com.sun.tools.javac.util.List params = methodSymbol.params(); + for (int i = 0; i < params.size(); i++) { + Symbol.VarSymbol p = params.get(i); String s = p.name.toString(); - paramNames.add(s); + paramNames[i] = s; } } @@ -488,13 +493,13 @@ public JavaType.Primitive primitive(TypeTag tag) { methodSymbol.isConstructor() ? "" : methodSymbol.getSimpleName().toString(), null, paramNames, - null, null, null, null + null, null, null, null, null ); typeCache.put(signature, method); JavaType returnType = null; List parameterTypes = null; - List exceptionTypes = null; + List exceptionTypes = null; if (selectType instanceof Type.MethodType) { Type.MethodType methodType = (Type.MethodType) selectType; @@ -514,20 +519,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } else if (selectType instanceof Type.UnknownType) { @@ -566,14 +559,17 @@ public JavaType.Primitive primitive(TypeTag tag) { return existing; } - List paramNames = null; + String[] paramNames = null; if (!methodSymbol.params().isEmpty()) { - paramNames = new ArrayList<>(methodSymbol.params().size()); - for (Symbol.VarSymbol p : methodSymbol.params()) { + paramNames = new String[methodSymbol.params().size()]; + com.sun.tools.javac.util.List params = methodSymbol.params(); + for (int i = 0; i < params.size(); i++) { + Symbol.VarSymbol p = params.get(i); String s = p.name.toString(); - paramNames.add(s); + paramNames[i] = s; } } + List defaultValues = null; if(methodSymbol.getDefaultValue() != null) { if(methodSymbol.getDefaultValue() instanceof Attribute.Array) { @@ -588,6 +584,17 @@ public JavaType.Primitive primitive(TypeTag tag) { } } } + + List declaredFormalTypeNames = null; + for (Symbol.TypeVariableSymbol typeParam : methodSymbol.getTypeParameters()) { + if(typeParam.owner == methodSymbol) { + if (declaredFormalTypeNames == null) { + declaredFormalTypeNames = new ArrayList<>(); + } + declaredFormalTypeNames.add(typeParam.name.toString()); + } + } + JavaType.Method method = new JavaType.Method( null, methodSymbol.flags_field, @@ -596,7 +603,8 @@ public JavaType.Primitive primitive(TypeTag tag) { null, paramNames, null, null, null, - defaultValues + defaultValues, + declaredFormalTypeNames == null ? null : declaredFormalTypeNames.toArray(new String[0]) ); typeCache.put(signature, method); @@ -604,7 +612,7 @@ public JavaType.Primitive primitive(TypeTag tag) { ((Type.ForAll) methodSymbol.type).qtype : methodSymbol.type; - List exceptionTypes = null; + List exceptionTypes = null; Type selectType = methodSymbol.type; if (selectType instanceof Type.ForAll) { @@ -616,20 +624,8 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.thrown.isEmpty()) { exceptionTypes = new ArrayList<>(methodType.thrown.size()); for (Type exceptionType : methodType.thrown) { - JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); - if (javaType == null) { - // if the type cannot be resolved to a class (it might not be on the classpath, or it might have - // been mapped to cyclic) - if (exceptionType instanceof Type.ClassType) { - Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; - javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, - null, null, null, null, null, null, null); - } - } - if (javaType != null) { - // if the exception type is not resolved, it is not added to the list of exceptions - exceptionTypes.add(javaType); - } + JavaType javaType = type(exceptionType); + exceptionTypes.add(javaType); } } } @@ -685,22 +681,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + JavaType.Annotation.ArrayElementValue.from(element, ((Object[]) value)) : + JavaType.Annotation.SingleElementValue.from(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(!list.isEmpty() && list.get(0) instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(!list.isEmpty() && list.get(0) instanceof JavaType ? JavaType.EMPTY_JAVA_TYPE_ARRAY : new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/package-info.java b/rewrite-java-8/src/main/java/org/openrewrite/java/package-info.java new file mode 100644 index 00000000000..e697a3e66c4 --- /dev/null +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java index b629bee724a..b4b04d39c1d 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java @@ -40,7 +40,6 @@ public void handle(AnnotationValues annotationValues, JCTree JCTree.JCIdent ident = (JCTree.JCIdent) assign.getVariable(); String name = ident.getName().toString(); if (name.equals("onConstructor") || name.equals("onConstructor_")) { - // In Java 1.8+ the parameter is `onConstructor_` continue; } } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/BuilderDefaultNoOpHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/BuilderDefaultNoOpHandler.java new file mode 100644 index 00000000000..7424fb33e39 --- /dev/null +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/BuilderDefaultNoOpHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.java.lombok; + +import com.sun.tools.javac.tree.JCTree; +import lombok.Builder; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; + +@HandlerPriority(-1025) +public class BuilderDefaultNoOpHandler extends JavacAnnotationHandler { + @Override + public void handle(AnnotationValues annotationValues, JCTree.JCAnnotation jcAnnotation, JavacNode javacNode) { + } +} diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/BuilderHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/BuilderHandler.java new file mode 100644 index 00000000000..73c8b434f30 --- /dev/null +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/BuilderHandler.java @@ -0,0 +1,124 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.java.lombok; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import lombok.Builder; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.LombokImmutableList; +import lombok.core.LombokNode; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleBuilder; +import lombok.javac.handlers.HandleConstructor; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static lombok.core.AST.Kind.ANNOTATION; + +@HandlerPriority(-1024) +public class BuilderHandler extends JavacAnnotationHandler { + @Override + public void handle(AnnotationValues annotationValues, JCTree.JCAnnotation jcAnnotation, JavacNode javacNode) { + JavacNode parent = javacNode.up(); + if (!(parent.get() instanceof JCTree.JCClassDecl)) { + new HandleBuilder().handle(annotationValues, jcAnnotation, javacNode); + return; + } + Map modifiersRestoreMap = new HashMap<>(); + Map> nodeToChildrenMap = new HashMap<>(); + Field childrenField = null; + try { + childrenField = LombokNode.class.getDeclaredField("children"); + childrenField.setAccessible(true); + // The Lombok handler for the @Builder annotation sets the init expression to null for fields annotated + // with @Builder.Default. Unlike typical Lombok behavior, which either creates new methods or fields, the + // handler for Builder annotation modifies the existing variable declaration. As a result, the AST-to-LST + // converter never encounters the init expression, causing it to behave unexpectedly. + // Additionally, the @Builder.Default annotation generates a private method named $default$, + // which is unlikely to be called in original code. Therefore, it is safe to temporarily remove the + // @Builder.Default annotation during processing. + for (JavacNode fieldNode : HandleConstructor.findAllFields(parent, true)) { + LombokImmutableList children = (LombokImmutableList) childrenField.get(fieldNode); + nodeToChildrenMap.put(fieldNode, children); + LombokImmutableList filtered = children; + for (int i : findBuilderDefaultIndexes(children)) { + filtered = filtered.removeElementAt(i); + } + childrenField.set(fieldNode, filtered); + JCTree.JCVariableDecl fd = (JCTree.JCVariableDecl) fieldNode.get(); + JCTree.JCModifiers modifiers = fd.getModifiers(); + + modifiersRestoreMap.put(modifiers, new FlagAndAnnotations(modifiers.flags, modifiers.annotations)); + modifiers.annotations = removeBuilderDefault(modifiers.annotations); + modifiers.flags = modifiers.flags & (~(1 << 4) /*final mask*/); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // On exception just continue with the original handler + } + new HandleBuilder().handle(annotationValues, jcAnnotation, javacNode); + + // restore values + for (Map.Entry entry : modifiersRestoreMap.entrySet()) { + entry.getKey().flags = entry.getValue().flags; + entry.getKey().annotations = entry.getValue().annotations; + } + for (Map.Entry> entry : nodeToChildrenMap.entrySet()) { + try { + childrenField.set(entry.getKey(), entry.getValue()); + } catch (IllegalAccessException e) { + // Not possible. + } + } + } + + private List removeBuilderDefault(List annotations) { + ListBuffer filteredAnnotations = new ListBuffer<>(); + for (JCTree.JCAnnotation annotation : annotations) { + if (annotation.getAnnotationType().toString().equals("lombok.Builder.Default")) { + continue; + } + filteredAnnotations = filteredAnnotations.append(annotation); + } + return filteredAnnotations.toList(); + } + + private List findBuilderDefaultIndexes(LombokImmutableList nodes) { + ListBuffer indexes = new ListBuffer<>(); + for (int i = 0; i < nodes.size(); i++) { + JavacNode node = nodes.get(i); + if (node.getKind() == ANNOTATION && "lombok.Builder.Default".equals(node.get().type.toString())) { + indexes.add(i); + } + } + return indexes.toList(); + } + + private static class FlagAndAnnotations { + long flags; + List annotations; + + FlagAndAnnotations(long flags, List annotations) { + this.flags = flags; + this.annotations = annotations; + } + } +} diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodNoOpHandler.java similarity index 91% rename from rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodHandler.java rename to rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodNoOpHandler.java index 68b5543da96..dbc6be1dcf4 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodNoOpHandler.java @@ -21,7 +21,7 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; -public class ExtensionMethodHandler extends JavacAnnotationHandler { +public class ExtensionMethodNoOpHandler extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotationValues, JCTree.JCAnnotation jcAnnotation, JavacNode javacNode) { } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperNoOpHandler.java similarity index 93% rename from rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperHandler.java rename to rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperNoOpHandler.java index d1ef2b9fa59..6b7a1be1085 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperNoOpHandler.java @@ -21,7 +21,7 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; -public class HelperHandler extends JavacAnnotationHandler { +public class HelperNoOpHandler extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) { } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedNoOpHandler.java similarity index 93% rename from rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedHandler.java rename to rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedNoOpHandler.java index a9ce02cd671..bd66517d6ff 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedNoOpHandler.java @@ -24,7 +24,7 @@ @SuppressWarnings("SpellCheckingInspection") @HandlerPriority(-512) -public class JacksonizedHandler extends JavacAnnotationHandler { +public class JacksonizedNoOpHandler extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) { } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java index b5942688f46..1acfcfb96a1 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java @@ -38,7 +38,8 @@ public void handle(AnnotationValues annotationValues, JCTree. if (originalArg instanceof JCTree.JCAssign && ((JCTree.JCAssign) originalArg).getVariable() instanceof JCTree.JCIdent) { JCTree.JCAssign assign = (JCTree.JCAssign) originalArg; JCTree.JCIdent ident = (JCTree.JCIdent) assign.getVariable(); - if ("onConstructor".equals(ident.getName().toString())) { + String name = ident.getName().toString(); + if (name.equals("onConstructor") || name.equals("onConstructor_")) { continue; } } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java index e20877acf3d..c376de6bf22 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java @@ -38,7 +38,8 @@ public void handle(AnnotationValues annotationValues, J if (originalArg instanceof JCTree.JCAssign && ((JCTree.JCAssign) originalArg).getVariable() instanceof JCTree.JCIdent) { JCTree.JCAssign assign = (JCTree.JCAssign) originalArg; JCTree.JCIdent ident = (JCTree.JCIdent) assign.getVariable(); - if ("onConstructor".equals(ident.getName().toString())) { + String name = ident.getName().toString(); + if (name.equals("onConstructor") || name.equals("onConstructor_")) { continue; } } diff --git a/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler b/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler index cfecb22a205..b556f06f95e 100644 --- a/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler +++ b/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler @@ -14,11 +14,13 @@ # limitations under the License. # org.openrewrite.java.lombok.AllArgsConstructorHandler +org.openrewrite.java.lombok.BuilderHandler +org.openrewrite.java.lombok.BuilderDefaultNoOpHandler org.openrewrite.java.lombok.CleanupNoOpHandler -org.openrewrite.java.lombok.ExtensionMethodHandler +org.openrewrite.java.lombok.ExtensionMethodNoOpHandler org.openrewrite.java.lombok.GetterHandler -org.openrewrite.java.lombok.HelperHandler -org.openrewrite.java.lombok.JacksonizedHandler +org.openrewrite.java.lombok.HelperNoOpHandler +org.openrewrite.java.lombok.JacksonizedNoOpHandler org.openrewrite.java.lombok.LockedNoOpHandler org.openrewrite.java.lombok.LockedReadNoOpHandler org.openrewrite.java.lombok.LockedWriteNoOpHandler diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java index 9a89ce50b27..92419ead8c5 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java @@ -19,13 +19,20 @@ import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.Issue; +import org.openrewrite.SourceFile; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.MinimumJava21; import org.openrewrite.java.service.AnnotationService; import org.openrewrite.test.RewriteTest; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; @@ -38,7 +45,14 @@ void annotationWithDefaultArgument() { """ @SuppressWarnings("ALL") public class A {} - """ + """, spec -> spec.afterRecipe(cu -> { + J.ClassDeclaration c = cu.getClasses().get(0); + JavaType.Class type = (JavaType.Class) c.getType(); + JavaType.Annotation a = (JavaType.Annotation) type.getAnnotations().get(0); + assertThat(a.getValues()).hasSize(1); + assertThat(a.getValues().get(0).getValue()).isEqualTo(singletonList("ALL")); + } + ) ) ); } @@ -50,7 +64,7 @@ void annotationWithArgument() { """ @SuppressWarnings(value = "ALL") public class A {} - """ + """ ) ); } @@ -62,7 +76,7 @@ void preserveOptionalEmptyParentheses() { """ @Deprecated ( ) public class A {} - """ + """ ) ); } @@ -74,7 +88,7 @@ void newArrayArgument() { """ import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; - + @Target({ FIELD, PARAMETER }) public @interface Annotation {} """ @@ -82,6 +96,21 @@ void newArrayArgument() { ); } + @Test + void newArrayArgumentTrailingComma() { + rewriteRun( + java( + """ + import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.*; + + @Target({ FIELD, PARAMETER , }) + public @interface Annotation {} + """ + ) + ); + } + @SuppressWarnings("FinalMethodInFinalClass") @Test void annotationsInManyLocations() { @@ -144,7 +173,7 @@ void typeParameterAnnotations() { import java.lang.annotation.*; class TypeAnnotationTest { List<@A ? extends @A String> list; - + @Target({ ElementType.FIELD, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) private @interface A { } @@ -215,10 +244,10 @@ public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext p) """ import java.lang.annotation.*; public class TypeAnnotationTest { - + public @Deprecated @A TypeAnnotationTest() { } - + @Target({ ElementType.TYPE, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) private @interface A { } @@ -227,10 +256,10 @@ public class TypeAnnotationTest { """ import java.lang.annotation.*; public class TypeAnnotationTest { - + public @Deprecated TypeAnnotationTest() { } - + @Target({ ElementType.TYPE, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) private @interface A { } @@ -397,4 +426,88 @@ public J.ArrayType visitArrayType(J.ArrayType arrayType, Object o) { ) ); } + + @Test + void recursiveElementValue() { + rewriteRun( + java( + """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + + @Target(ElementType.TYPE) + @A + private @interface A { + A[] value() default @A; + } + + @A({@A, @A(@A)}) + class TypeAnnotationTest { + } + """, spec -> spec.afterRecipe(cu -> { + J.ClassDeclaration c = cu.getClasses().get(1); + JavaType.Class type = (JavaType.Class) c.getType(); + JavaType.Annotation a = (JavaType.Annotation) type.getAnnotations().get(0); + assertThat(a.getValues()).satisfiesExactly( + v -> { + assertThat(v.getElement()).isIn(a.getMethods()); + assertThat((List) (((JavaType.Annotation.ArrayElementValue) v).getValues())).satisfiesExactly( + a1 -> { + assertThat(a1.getType()).isSameAs(a.getType()); + assertThat(a1.getValues()).isEmpty(); + }, + a2 -> { + assertThat(a2.getType()).isSameAs(a.getType()); + assertThat(a2.getValues()).hasSize(1); + } + ); + } + ); + }) + ) + ); + } + + @Test + @MinimumJava21 // Because of `@Deprecated#forRemoval` + void annotationElementValues() { + JavaParser p = JavaParser.fromJavaVersion().build(); + /* + * Using these annotations in core library for testing this feature: + * + * @Deprecated(since="1.2", forRemoval=true) + * public final void stop() + * + * @CallerSensitive + * public ClassLoader getContextClassLoader() { + */ + List sourceFiles = p.parse( + """ + class Test { + public void test() { + Thread.currentThread().stop(); + Thread.currentThread().getContextClassLoader(); + } + } + """ + ).toList(); + J.CompilationUnit cu = (J.CompilationUnit) sourceFiles.get(0); + + J.MethodDeclaration md = (J.MethodDeclaration) cu.getClasses().get(0).getBody().getStatements().get(0); + J.MethodInvocation mi = (J.MethodInvocation) md.getBody().getStatements().get(0); + JavaType.Annotation annotation = (JavaType.Annotation) mi.getMethodType().getAnnotations().get(0); + + // Thread.currentThread().stop(); + assertEquals("java.lang.Deprecated" ,annotation.getType().getFullyQualifiedName()); + assertEquals("since", ((JavaType.Method) annotation.getValues().get(0).getElement()).getName()); + assertEquals("1.2", annotation.getValues().get(0).getValue()); + assertEquals("forRemoval", ((JavaType.Method) annotation.getValues().get(1).getElement()).getName()); + assertEquals(Boolean.TRUE, annotation.getValues().get(1).getValue()); + + // Thread.currentThread().getContextClassLoader(); + mi = (J.MethodInvocation) md.getBody().getStatements().get(1); + annotation = (JavaType.Annotation) mi.getMethodType().getAnnotations().get(0); + assertEquals("jdk.internal.reflect.CallerSensitive" ,annotation.getType().getFullyQualifiedName()); + assertTrue(annotation.getValues().isEmpty()); + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java index 03787bb3816..90dc6a8d5c1 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java @@ -16,7 +16,6 @@ package org.openrewrite.java.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -35,7 +34,7 @@ void enumWithAnnotations() { public enum Test { @Deprecated(since = "now") One, - + @Deprecated(since = "now") Two; } @@ -56,7 +55,7 @@ void foo() {} }; A(int n) {} - + abstract void foo(); } """ @@ -74,7 +73,7 @@ public enum A { @Override void foo() {} }; - + abstract void foo(); } """ @@ -90,10 +89,10 @@ void enumConstructor() { public class Outer { public enum A { A1(1); - + A(int n) {} } - + private static final class ContextFailedToStart { private static Object[] combineArguments(String context, Throwable ex, Object[] arguments) { return new Object[arguments.length + 2]; @@ -126,7 +125,7 @@ void enumWithParameters() { public enum A { ONE(1), TWO(2); - + A(int n) {} } """ @@ -150,7 +149,6 @@ void enumUnnecessarilyTerminatedWithSemicolon() { } @Test - @Disabled("enum A { ONE~~(non-whitespace)~~>, <~~}") void enumValuesTerminatedWithComma() { rewriteRun( java("enum A { ONE, }") @@ -163,4 +161,21 @@ void enumWithEmptyParameters() { java("public enum A { ONE ( ), TWO ( ) }") ); } + + @Test + void enumWithParametersAndTrailingComma() { + rewriteRun( + java( + """ + public enum A { + ONE(1), + TWO(2), + ; + + A(int n) {} + } + """ + ) + ); + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java index 192c592e477..ede1c202240 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java @@ -19,9 +19,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; import org.openrewrite.java.JavaParser; +import org.openrewrite.java.MinimumJava11; +import org.openrewrite.java.MinimumJava17; +import org.openrewrite.java.search.FindMissingTypes; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.TypeValidation; @@ -33,7 +34,6 @@ import static org.openrewrite.java.Assertions.java; @SuppressWarnings({"CaughtExceptionImmediatelyRethrown", "LombokGetterMayBeUsed", "LombokSetterMayBeUsed", "DefaultAnnotationParam", "NotNullFieldNotInitialized", "ProtectedMemberInFinalClass", "WriteOnlyObject", "ConcatenationWithEmptyString"}) -@EnabledOnJre(JRE.JAVA_17) class LombokTest implements RewriteTest { @BeforeAll @@ -118,6 +118,54 @@ void test() { ); } + @Test + void builderWithDefault() { + rewriteRun( + java( + """ + import lombok.Builder; + + @Builder + class A { + @Builder.Default boolean b = false; + @Builder.Default int n = 0; + @Builder.Default String s = "Hello, Anshuman!"; + + void test() { + A x = A.builder().n(1).b(true).s("foo").build(); + A y = A.builder().n(1).b(true).build(); + A z = A.builder().n(1).build(); + } + } + """ + ) + ); + } + + @Test + void builderWithDefaultAndFinal() { + rewriteRun( + java( + """ + import lombok.Builder; + + @Builder + class A { + @Builder.Default private final boolean b = false; + @Builder.Default public final int n = 0; + @Builder.Default protected final String s = "Hello, Anshuman!"; + + void test() { + A x = A.builder().n(1).b(true).s("foo").build(); + A y = A.builder().n(1).b(true).build(); + A z = A.builder().n(1).build(); + } + } + """ + ) + ); + } + @Test void tostring() { rewriteRun( @@ -199,6 +247,12 @@ public class ConstructorExample { public static class NoArgsExample { @NonNull private String field; } + + public void test() { + ConstructorExample x = ConstructorExample.of("desc"); + ConstructorExample y = new ConstructorExample<>("1L"); + ConstructorExample.NoArgsExample z = new ConstructorExample.NoArgsExample(); + } } """ ) @@ -320,7 +374,57 @@ public void foo() { } @Test + void gett() { + rewriteRun( + java( + """ + import lombok.Getter; + + public class WithExample { + @Getter int age; + + public WithExample(int age) { + this.age = age; + } + + void test() { + int x = getAge(); + } + } + """ + ) + ); + } + + //TODO fix for Java 8 and 11 + @Test + @MinimumJava17 void with() { + rewriteRun( + java( + """ + import lombok.With; + + public class WithExample { + @With int age; + + public WithExample(int age) { + this.age = age; + } + + void test() { + WithExample x = withAge("name", 23); + } + } + """ + ) + ); + } + + //TODO fix for Java 8 and 11 + @Test + @MinimumJava17 + void withWithParams() { rewriteRun( java( """ @@ -336,6 +440,42 @@ public WithExample(@NonNull String name, int age) { this.name = name; this.age = age; } + + static void test() { + WithExample x = new WithExample("old name", 22); + x.withName("name", 23); + } + } + """ + ) + ); + } + + //TODO fix for Java 8 and 11 + @Test + @MinimumJava17 + void withOnClass() { + rewriteRun( + java( + """ + import lombok.AccessLevel; + import lombok.NonNull; + import lombok.With; + + @With + public class WithExample { + private final String name; + private final int age; + + public WithExample(String name, int age) { + this.name = name; + this.age = age; + } + + void test() { + WithExample x = new WithExample("old name", 22); + x.withName("name", 23); + } } """ ) @@ -388,7 +528,7 @@ public class SingularExample { } @Test - void jul() { + void log() { rewriteRun( java( """ @@ -402,7 +542,24 @@ class A { Map map; void m() { log.info("string = " + string); - log.info(() -> "map = %s".formatted(map)); + log.info(() -> String.format("map = %s", map)); + } + } + """ + ) + ); + } + + @Test + void var() { + rewriteRun( + java( + """ + import lombok.var; + + class Test { + void test() { + var s = "foo"; } } """ @@ -647,6 +804,7 @@ public void both(String s) { } @Test + @MinimumJava11 void jacksonized() { rewriteRun( spec -> spec.parser(JavaParser.fromJavaVersion().classpath("jackson-annotations", "lombok")), @@ -676,6 +834,78 @@ void standardException() { @StandardException public class ExampleException extends Exception { + public void test() { + new ExampleException("message"); + new ExampleException(new RuntimeException("message")); + new ExampleException("message", new RuntimeException("message")); + } + } + """ + ) + ); + } + + @Test + @MinimumJava11 + void onConstructor() { + rewriteRun( + java( + """ + public @interface Inject {} + public @interface Id {} + public @interface Column { String name(); } + public @interface Max { long value(); } + """ + ), + java( + """ + import lombok.AllArgsConstructor; + import lombok.Getter; + import lombok.Setter; + + @AllArgsConstructor(onConstructor_=@Inject) + public class OnXExample { + @Getter(onMethod_={@Id, @Column(name="unique-id")}) + @Setter(onParam_=@Max(10000)) + private long unid; + + public void test() { + OnXExample x = new OnXExample(1L); + x.setUnid(2L); + System.out.println(x.getUnid()); + } + } + """ + ) + ); + } + + @Test + @MinimumJava11 + void onConstructorNoArgs() { + rewriteRun( + java( + """ + public @interface Inject {} + """ + ), + java( + """ + import lombok.NoArgsConstructor; + import lombok.NonNull; + import lombok.RequiredArgsConstructor; + + import javax.inject.Inject; + + @NoArgsConstructor(onConstructor_ = @Inject) + @RequiredArgsConstructor(onConstructor_ = @Inject) + public class OnXExample { + @NonNull private Long unid; + + public void test() { + new OnXExample(); + new OnXExample(1L); + } } """ ) @@ -689,32 +919,38 @@ public class ExampleException extends Exception { @SuppressWarnings("MismatchedReadAndWriteOfArray") @Nested class LessSupported { + /* + java 8 cannot figure out all type checking: + - When the @AllArgsConstructorHandler, @NoArgsConstructorHandler and @NoArgsConstructorHandler annotations are + used with the `onConstructor_` param, Lombok does not call the JavacAnnotationHandlers. + - The @Jacksonized annotation does somehow turns into `ClassDeclaration->CompilationUni` error + */ + @Test - void extensionMethod() { + // TODO: Find solution and remove this test + void jacksonizedForJava8() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), + spec -> spec + .parser(JavaParser.fromJavaVersion().classpath("jackson-annotations", "lombok")) + .typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { + assert o instanceof FindMissingTypes.MissingTypeResult; + FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; + // Using the @Jacksonized annotation in java 8 just breaks it all + return result.getPath().startsWith("ClassDeclaration->CompilationUnit") || + result.getPath().startsWith("Identifier->Annotation")|| + result.getPath().startsWith("Identifier->ParameterizedType"); + }).build()), java( """ - import lombok.experimental.ExtensionMethod; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import lombok.Builder; + import lombok.extern.jackson.Jacksonized; - @ExtensionMethod({java.util.Arrays.class, Extensions.class}) - public class ExtensionMethodExample { - public String test() { - int[] intArray = {5, 3, 8, 2}; - intArray.sort(); - String iAmNull = null; - return iAmNull.or("hELlO, WORlD!".toTitleCase()); - } - } - - class Extensions { - public static T or(T obj, T ifNull) { - return obj != null ? obj : ifNull; - } - public static String toTitleCase(String in) { - if (in.isEmpty()) return in; - return "" + Character.toTitleCase(in.charAt(0)) + in.substring(1).toLowerCase(); - } + @Jacksonized + @Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public class JacksonExample { + private List strings; } """ ) @@ -722,26 +958,41 @@ public static String toTitleCase(String in) { } @Test - void onConstructor() { + // TODO: Find solution and remove this test + void onConstructorForJava8() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), + spec -> spec.typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { + assert o instanceof FindMissingTypes.MissingTypeResult; + FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; + // The AllArgsConstructorHandler, GetterHandler and SetterHandler do not run at all for java 8, + // so no generated constructors and methods, thus no types. + return result.getPath().startsWith("NewClass->") || result.getPath().startsWith("MethodInvocation->"); + }).build()), + java( + """ + public @interface Inject {} + public @interface Id {} + public @interface Column { String name(); } + public @interface Max { long value(); } + """ + ), java( """ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; - import javax.inject.Inject; - import javax.persistence.Id; - import javax.persistence.Column; - import javax.validation.constraints.Max; - - // @__ missing attribution - @AllArgsConstructor(onConstructor=@__(@Inject)) + @AllArgsConstructor(onConstructor_=@Inject) public class OnXExample { - @Getter(onMethod_={@Id, @Column(name="unique-id")}) //JDK8 - @Setter(onParam_=@Max(10000)) //JDK8 + @Getter(onMethod_={@Id, @Column(name="unique-id")}) + @Setter(onParam_=@Max(10000)) private long unid; + + public void test() { + OnXExample x = new OnXExample(1L); + x.setUnid(2L); + System.out.println(x.getUnid()); + } } """ ) @@ -749,21 +1000,72 @@ public class OnXExample { } @Test - void onConstructorNoArgs() { + // TODO: Find solution and remove this test + void onConstructorNoArgsForJava8() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), + spec -> spec.typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { + assert o instanceof FindMissingTypes.MissingTypeResult; + FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; + // The NoArgsConstructor and RequiredArgsConstructor do not run at all for java 8, + // so no generated constructors, thus no types. + return result.getPath().startsWith("NewClass->"); + }).build()), + java( + """ + public @interface Inject {} + public @interface Ignore {} // somehow we need this, to prevent `ClassDeclaration->CompilationUnit` errors + """ + ), java( """ import lombok.NoArgsConstructor; - import lombok.Getter; - import lombok.RequiredArgsConstructor;import lombok.Setter; + import lombok.NonNull; + import lombok.RequiredArgsConstructor; import javax.inject.Inject; - // @__ missing attribution - @NoArgsConstructor(onConstructor = @__(@Inject)) - @RequiredArgsConstructor(onConstructor_ = @__(@Inject)) + + @NoArgsConstructor(onConstructor_=@Inject) + @RequiredArgsConstructor(onConstructor_=@Inject) public class OnXExample { - private long unid; + @NonNull private Long unid; + + public void test() { + new OnXExample(); + new OnXExample(1L); + } + } + """ + ) + ); + } + + + @Test + void extensionMethod() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), + java( + """ + import lombok.experimental.ExtensionMethod; + + @ExtensionMethod({java.util.Arrays.class, Extensions.class}) + public class ExtensionMethodExample { + public String test() { + int[] intArray = {5, 3, 8, 2}; + intArray.sort(); + String iAmNull = null; + return iAmNull.or("hELlO, WORlD!".toTitleCase()); + } + } + + class Extensions { + public static T or(T obj, T ifNull) { + return obj != null ? obj : ifNull; + } + public static String toTitleCase(String in) { + if (in.isEmpty()) return in; + return "" + Character.toTitleCase(in.charAt(0)) + in.substring(1).toLowerCase(); + } } """ ) diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java index 48c0972ce7e..e504de17cb2 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.java.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; @@ -50,7 +49,6 @@ class Test { } @Test - @Disabled("int[] n = new int[] { 0, 1, 2~~(non-whitespace)~~>, <~~};") void initializersWithTrailingComma() { rewriteRun( java( diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ParserTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ParserTest.java new file mode 100644 index 00000000000..065ef0c22f9 --- /dev/null +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ParserTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; +import org.openrewrite.SourceFile; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RewriteTest; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +class ParserTest implements RewriteTest { + + @Test + @Issue("https://github.com/openrewrite/rewrite/pull/4914") + void parseString() throws IOException { + // path needs to be resolvable from `rewrite-java-8` etc. + Path targetFile = Paths.get("../rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ParserTest.java"); + @SuppressWarnings("SimplifyStreamApiCallChains") List ignore = JavaParser.fromJavaVersion() + .build() + .parse(new String(Files.readAllBytes(targetFile))) + .collect(Collectors.toList()); + } +} diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/RecordPatternMatchingTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/RecordPatternMatchingTest.java new file mode 100644 index 00000000000..6af88fe84f1 --- /dev/null +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/RecordPatternMatchingTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.java.MinimumJava21; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; + +import static org.openrewrite.java.Assertions.java; + +@MinimumJava21 +class RecordPatternMatchingTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.typeValidationOptions(TypeValidation.all().unknown(false)); + } + + @Test + void shouldParseJava21PatternMatchForRecords() { + rewriteRun( + java( + //language=java + """ + record Point(int x, int y) {} + class Test { + void printSum(Object obj) { + if (obj instanceof Point(int x, int y)) { + System.out.println(x+y); + } + } + } + """ + )); + } + + @Test + void shouldParseJava21NestedPatternMatchForRecords() { + rewriteRun( + java( + //language=java + """ + record Point(int x, int y) {} + enum Color { RED, GREEN, BLUE } + record ColoredPoint(Point p, Color c) {} + record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} + class Test { + void printColorOfUpperLeftPoint(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint(Point p, Color c), + ColoredPoint lr)) { + System.out.println(c); + } + } + } + """ + )); + } + +} diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchPatternMatchingTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchPatternMatchingTest.java new file mode 100644 index 00000000000..30d89070966 --- /dev/null +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchPatternMatchingTest.java @@ -0,0 +1,155 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.java.MinimumJava21; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +@MinimumJava21 +class SwitchPatternMatchingTest implements RewriteTest { + + @Test + void shouldParseJava21PatternSwitch() { + rewriteRun( + java( + //language=java + """ + class Test { + String formatterPatternSwitch(Object obj) { + return switch (obj) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + default -> obj.toString(); + }; + } + } + """ + )); + } + + @Test + void shouldSupportParsingNullSwitch() { + rewriteRun( + java( + //language=java + """ + class Test { + void fooBarWithNull(String s) { + switch (s) { + case null -> System.out.println("Oops"); + case "Foo", "Bar" -> System.out.println("Great"); + default -> System.out.println("Ok"); + } + } + } + """ + )); + } + + @Test + void shouldParseJava21EnumSupportInSwitch() { + rewriteRun( + java( + //language=java + """ + enum Coin { HEADS, TAILS } + + class Test { + void switchEnum(Coin c) { + switch (c) { + case HEADS -> System.out.println("Heads"); + case Coin.TAILS -> System.out.println("Tails"); + } + } + } + """ + ) + ); + } + + @Test + void shouldParseJava21ImprovedEnumSupportInSwitch() { + rewriteRun( + java( + //language=java + """ + sealed interface I permits Foo, Bar {} + public enum Foo implements I { A, B } + final class Bar implements I {} + + class Test { + void switchEnumExtendedType(I c) { + switch (c) { + case Foo.A -> System.out.println("It's Foo A"); + case Foo.B -> System.out.println("It's Foo B"); + case Bar b -> System.out.println("It's Bar"); + } + } + } + """ + )); + } + + @Test + void shouldParseJava21SwitchWithRelaxedTypeRestrictions() { + rewriteRun( + java( + //language=java + """ + record Point(int i, int j) {} + enum Color { RED, GREEN, BLUE; } + + class Test { + void typeTester(Object obj) { + switch (obj) { + case null -> System.out.println("null"); + case String s -> System.out.println("String"); + case Color c -> System.out.println("Color: " + c.toString()); + case Point p -> System.out.println("Record class: " + p.toString()); + case int[] ia -> System.out.println("Array of ints of length" + ia.length); + default -> System.out.println("Something else"); + } + } + } + """ + )); + } + + @Test + void shouldParseJava21SwitchWithSpecialCases() { + rewriteRun( + java( + //language=java + """ + class Test { + void integerTester(Integer i) { + switch (i) { + case -1, 1 -> System.out.println("special"); + case Integer j when (j - 1) > -1 -> System.out.println("pos"); + case Integer j -> System.out.println("others"); + } + } + } + """ + )); + } + +} diff --git a/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeGoat.java b/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeGoat.java index a32c6df44c4..64a1d90452f 100644 --- a/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeGoat.java +++ b/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeGoat.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java; +import java.io.FileNotFoundException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +27,9 @@ public abstract class JavaTypeGoat & C> { public static final PT parameterizedField = new PT() { }; + public static Double PI; + public static double PI_PRIMITIVE; + public static abstract class InheritedJavaTypeGoat & C> extends JavaTypeGoat { public InheritedJavaTypeGoat() { super(); @@ -73,6 +77,9 @@ public static class TypeB {} public abstract & C> U genericIntersection(U n); public abstract T genericT(T n); // remove after signatures are common. public abstract & Intersection> void recursiveIntersection(U n); + public abstract T nameShadow(T t); + public abstract void throwsException() throws FileNotFoundException; + public abstract void throwsGenericException() throws T, InterruptedException; } interface C { diff --git a/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java b/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java index 591d897bfc3..2ba9ad4b339 100644 --- a/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java +++ b/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java @@ -25,9 +25,7 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.CONTRAVARIANT; -import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.COVARIANT; -import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.INVARIANT; +import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; /** * Based on type attribution mappings of [JavaTypeGoat]. @@ -53,6 +51,43 @@ default JavaType firstMethodParameter(String methodName) { return methodType(methodName).getParameterTypes().get(0); } + @Test + default void declaredFields() { + JavaType.Parameterized goat = goatType(); + assertThat(goat.getMembers().stream().filter(m -> m.getName().equals("parameterizedField"))).anySatisfy(field -> + assertThat(field).isInstanceOfSatisfying(JavaType.Variable.class, variable -> + assertThat(variable.getType()).isInstanceOfSatisfying(JavaType.Parameterized.class, param -> + assertThat(param.getTypeParameters()).hasOnlyElementsOfType(JavaType.FullyQualified.class) + ) + ) + ); + } + + @Test + default void declaringTypeRefersToParameterizedClass() { + JavaType.FullyQualified declaringType = methodType("nameShadow").getDeclaringType(); + assertThat(declaringType.getTypeParameters()) + .describedAs("If it points to the raw class, " + + "method level name shadowing of generic " + + "type variables cannot be detected.") + .isNotEmpty(); + } + + @Test + default void shadowedDeclaredFormalTypeParameters() { + // this method overrides the class definition of the type variable T + assertThat(methodType("nameShadow").getDeclaredFormalTypeNames()) + .containsExactly("T"); + + // this method provides an unshadowed definition of U + assertThat(methodType("genericUnbounded").getDeclaredFormalTypeNames()) + .containsExactly("U"); + + // this method uses the definition of T from the class level + assertThat(methodType("genericT").getDeclaredFormalTypeNames()) + .isEmpty(); + } + @Test default void javaLangObjectHasNoSupertype() { assertThat(goatType().getSupertype().getSupertype()).isNull(); @@ -110,6 +145,11 @@ default void generic() { assertThat(generic.getName()).isEqualTo("?"); assertThat(generic.getVariance()).isEqualTo(COVARIANT); assertThat(TypeUtils.asFullyQualified(generic.getBounds().get(0)).getFullyQualifiedName()).isEqualTo("org.openrewrite.java.C"); + + generic = (JavaType.GenericTypeVariable) TypeUtils.asParameterized(methodType("generic").getReturnType()).getTypeParameters().get(0); + assertThat(generic.getName()).isEqualTo("?"); + assertThat(generic.getVariance()).isEqualTo(COVARIANT); + assertThat(TypeUtils.asFullyQualified(generic.getBounds().get(0)).getFullyQualifiedName()).isEqualTo("org.openrewrite.java.C"); } @Test @@ -221,14 +261,18 @@ default void enumTypeB() { } @Test - default void ignoreSourceRetentionAnnotations() { + default void includeSourceRetentionAnnotations() { JavaType.Parameterized goat = goatType(); - assertThat(goat.getAnnotations().size()).isEqualTo(1); - assertThat(goat.getAnnotations().get(0).getClassName()).isEqualTo("AnnotationWithRuntimeRetention"); + assertThat(goat.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention"), + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithSourceRetention") + ); JavaType.Method clazzMethod = methodType("clazz"); - assertThat(clazzMethod.getAnnotations().size()).isEqualTo(1); - assertThat(clazzMethod.getAnnotations().get(0).getClassName()).isEqualTo("AnnotationWithRuntimeRetention"); + assertThat(clazzMethod.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention"), + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithSourceRetention") + ); } @Issue("https://github.com/openrewrite/rewrite/issues/1367") @@ -237,4 +281,18 @@ default void recursiveIntersection() { JavaType.GenericTypeVariable clazz = TypeUtils.asGeneric(firstMethodParameter("recursiveIntersection")); assertThat(clazz.toString()).isEqualTo("Generic{U extends org.openrewrite.java.JavaTypeGoat$Extension & org.openrewrite.java.Intersection}"); } + + @Test + default void throwsGenericExceptions() { + JavaType.Method method = methodType("throwsGenericException"); + JavaType ex = method.getThrownExceptions().get(0); + + assertThat(ex).isInstanceOf(JavaType.GenericTypeVariable.class); + + JavaType.GenericTypeVariable generic = (JavaType.GenericTypeVariable) ex; + assertThat(generic.getName()).isEqualTo("T"); + assertThat(generic.getVariance()).isEqualTo(COVARIANT); + assertThat(TypeUtils.asFullyQualified(generic.getBounds().get(0)) + .getFullyQualifiedName()).isEqualTo("java.io.FileNotFoundException"); + } } diff --git a/rewrite-java-test/src/main/resources/JavaTypeGoat.java b/rewrite-java-test/src/main/resources/JavaTypeGoat.java index 61953059cab..047fa6ada5e 100644 --- a/rewrite-java-test/src/main/resources/JavaTypeGoat.java +++ b/rewrite-java-test/src/main/resources/JavaTypeGoat.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java; +import java.io.FileNotFoundException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +27,9 @@ public abstract class JavaTypeGoat & C> { public static final PT parameterizedField = new PT() { }; + public static Double PI; + public static double PI_PRIMITIVE; + public static abstract class InheritedJavaTypeGoat & C> extends JavaTypeGoat { public InheritedJavaTypeGoat() { super(); @@ -73,6 +77,9 @@ public static class TypeB {} public abstract & C> U genericIntersection(U n); public abstract T genericT(T n); // remove after signatures are common. public abstract & Intersection> void recursiveIntersection(U n); + public abstract T nameShadow(T t); + public abstract void throwsException() throws FileNotFoundException; + public abstract void throwsGenericException() throws T, InterruptedException; } interface C { diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java index e5b6c404e5f..96a4a160793 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java @@ -37,9 +37,7 @@ import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; -import static org.openrewrite.java.Assertions.addTypesToSourceSet; -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.java.Assertions.srcMainJava; +import static org.openrewrite.java.Assertions.*; import static org.openrewrite.test.RewriteTest.toRecipe; @SuppressWarnings("rawtypes") @@ -113,7 +111,7 @@ void dontDuplicateImports() { """ import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus.Series; - + class A {} """ ) @@ -139,7 +137,7 @@ class A {} import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - + class A {} """ ) @@ -155,16 +153,16 @@ void dontDuplicateImports3() { """ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; - + import java.util.List; class A {} """, """ import static org.junit.jupiter.api.Assertions.*; - + import java.util.List; - + class A {} """ ) @@ -178,7 +176,7 @@ void dontImportYourself() { java( """ package com.myorg; - + class A { } """ @@ -186,6 +184,84 @@ class A { ); } + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/540") + @Test + void forceImportNonJavaLangRecord() { + // Add import for a class named `Record`, even within the same package, to avoid conflicts with java.lang.Record + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("com.acme.bank.Record", null, false))) + .parser(JavaParser.fromJavaVersion().dependsOn("package com.acme.bank; public class Record {}")), + //language=java + java( + """ + package com.acme.bank; + + class Foo { + } + """, + """ + package com.acme.bank; + + import com.acme.bank.Record; + + class Foo { + } + """, + spec -> spec.markers(javaVersion(11)) + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/540") + @Test + void forceImportNonJavaLangRecordFromWildcardImport() { + // Add import for a class named `Record`, even within the same package, to avoid conflicts with java.lang.Record + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("com.acme.bank.Record", null, false))) + .parser(JavaParser.fromJavaVersion().dependsOn("package com.acme.bank; public class Record {}")), + //language=java + java( + """ + package com.acme.bank; + + import com.acme.bank.*; + + class Foo { + } + """, + """ + package com.acme.bank; + + import com.acme.bank.*; + + import com.acme.bank.Record; + + class Foo { + } + """, + spec -> spec.markers(javaVersion(11)) + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/540") + @Test + void notForceImportJavaRecord() { + // Do not add import for java.lang.Record by default + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.lang.Record", null, false))), + //language=java + java( + """ + package com.acme.bank; + + class Foo { + } + """, + spec -> spec.markers(javaVersion(11)) + ) + ); + } @Test void dontImportJavaLang() { rewriteRun( @@ -241,7 +317,7 @@ void dontImportFromSamePackage() { java( """ package com.myorg; - + class B { } """ @@ -249,7 +325,7 @@ class B { java( """ package com.myorg; - + class A { } """ @@ -309,7 +385,7 @@ void addNamedImport() { java("class A {}", """ import java.util.List; - + class A {} """ ) @@ -323,7 +399,7 @@ void doNotAddImportIfNotReferenced() { java( """ package a; - + class A {} """ ) @@ -337,22 +413,22 @@ void addImportInsertsNewMiddleBlock() { java( """ package a; - + import com.sun.naming.*; - + import static java.util.Collections.*; - + class A {} """, """ package a; - + import com.sun.naming.*; - + import java.util.List; - + import static java.util.Collections.*; - + class A {} """ ) @@ -366,14 +442,14 @@ void addFirstImport() { java( """ package a; - + class A {} """, """ package a; - + import java.util.List; - + class A {} """ ) @@ -385,22 +461,22 @@ class A {} void addImportIfReferenced() { rewriteRun( spec -> spec.recipe(toRecipe(() -> - new JavaIsoVisitor<>() { - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); - maybeAddImport("java.math.BigDecimal"); - maybeAddImport("java.math.RoundingMode"); - return JavaTemplate.builder("BigDecimal d = BigDecimal.valueOf(1).setScale(1, RoundingMode.HALF_EVEN);") - .imports("java.math.BigDecimal", "java.math.RoundingMode") - .build() - .apply( - updateCursor(c), - c.getBody().getCoordinates().lastStatement() - ); - } - } - ).withMaxCycles(1)), + new JavaIsoVisitor<>() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); + maybeAddImport("java.math.BigDecimal"); + maybeAddImport("java.math.RoundingMode"); + return JavaTemplate.builder("BigDecimal d = BigDecimal.valueOf(1).setScale(1, RoundingMode.HALF_EVEN);") + .imports("java.math.BigDecimal", "java.math.RoundingMode") + .build() + .apply( + updateCursor(c), + c.getBody().getCoordinates().lastStatement() + ); + } + } + ).withMaxCycles(1)), java( """ package a; @@ -410,10 +486,10 @@ class A { """, """ package a; - + import java.math.BigDecimal; import java.math.RoundingMode; - + class A { BigDecimal d = BigDecimal.valueOf(1).setScale(1, RoundingMode.HALF_EVEN); } @@ -429,7 +505,7 @@ void doNotAddWildcardImportIfNotReferenced() { java( """ package a; - + class A {} """ ) @@ -443,7 +519,7 @@ void lastImportWhenFirstClassDeclarationHasJavadoc() { java( """ import java.util.List; - + /** * My type */ @@ -451,9 +527,9 @@ class A {} """, """ import java.util.List; - + import static java.util.Collections.*; - + /** * My type */ @@ -474,9 +550,9 @@ class A {} """, """ package a; - + import java.util.List; - + class A {} """ ) @@ -516,18 +592,18 @@ public class B {} java( """ package a; - + import c.C0; import c.c.C1; import c.c.c.C2; - + class A {} """, String.format(""" package a; - + %s - + class A {} """, expectedImports.stream().map(i -> "import " + i + ";").collect(Collectors.joining("\n")) @@ -549,7 +625,7 @@ void doNotAddImportIfAlreadyExists() { java( """ package a; - + import java.util.List; class A {} """ @@ -564,7 +640,7 @@ void doNotAddImportIfCoveredByStarImport() { java( """ package a; - + import java.util.*; class A {} """ @@ -595,17 +671,17 @@ void addNamedImportIfStarStaticImportExists() { java( """ package a; - + import static java.util.List.*; class A {} """, """ package a; - + import java.util.List; - + import static java.util.List.*; - + class A {} """ ) @@ -623,9 +699,9 @@ class A {} """, """ import java.util.*; - + import static java.util.Collections.emptyList; - + class A {} """ ) @@ -640,7 +716,7 @@ void addStaticImportForUnreferencedField() { java( """ package mycompany; - + public class Type { public static String FIELD; } @@ -650,7 +726,7 @@ public class Type { "class A {}", """ import static mycompany.Type.FIELD; - + class A {} """ ) @@ -683,17 +759,17 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex java( """ public class A { - + } """, """ import java.time.temporal.ChronoUnit; - + import static java.time.temporal.ChronoUnit.MILLIS; - + public class A { ChronoUnit unit = MILLIS; - + } """ ) @@ -708,9 +784,9 @@ void dontAddImportToStaticFieldWithNamespaceConflict() { java( """ package a; - + import java.time.temporal.ChronoUnit; - + class A { static final int MILLIS = 1; ChronoUnit unit = ChronoUnit.MILLIS; @@ -727,7 +803,7 @@ void dontAddStaticWildcardImportIfNotReferenced() { java( """ package a; - + class A {} """ ) @@ -749,9 +825,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu java( """ package a; - + import java.util.List; - + class A { public A() { List list = java.util.Collections.emptyList(); @@ -760,11 +836,11 @@ public A() { """, """ package a; - + import java.util.List; - + import static java.util.Collections.emptyList; - + class A { public A() { List list = emptyList(); @@ -775,6 +851,49 @@ public A() { ); } + @Test + void addNamedStaticImportWhenReferenced2() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { + method = super.visitMethodDeclaration(method, executionContext); + method = JavaTemplate.builder("List list = new ArrayList<>();") + .imports("java.util.ArrayList", "java.util.List") + .staticImports("java.util.Calendar.Builder") + .build() + .apply(getCursor(), method.getBody().getCoordinates().firstStatement()); + maybeAddImport("java.util.ArrayList"); + maybeAddImport("java.util.List"); + maybeAddImport("java.util.Calendar", "Builder"); + return method; + } + }).withMaxCycles(1)), + java( + """ + import static java.util.Calendar.Builder; + + class A { + public A() { + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import static java.util.Calendar.Builder; + + class A { + public A() { + List list = new ArrayList<>(); + } + } + """ + ) + ); + } + @Test void doNotAddNamedStaticImportIfNotReferenced() { rewriteRun( @@ -782,7 +901,7 @@ void doNotAddNamedStaticImportIfNotReferenced() { java( """ package a; - + class A {} """ ) @@ -807,9 +926,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu java( """ package a; - + import java.util.List; - + class A { public A() { List list = java.util.Collections.emptyList(); @@ -818,11 +937,11 @@ public A() { """, """ package a; - + import java.util.List; - + import static java.util.Collections.*; - + class A { public A() { List list = emptyList(); @@ -883,14 +1002,14 @@ public class C { """ import foo.B; import foo.C; - + import java.util.Collections; import java.util.List; import java.util.HashSet; import java.util.HashMap; import java.util.Map; import java.util.Set; - + class A { B b = new B(); C c = new C(); @@ -903,7 +1022,7 @@ class A { """ import foo.B; import foo.C; - + import java.util.*; class A { @@ -928,15 +1047,15 @@ void addImportWhenDuplicatesExist() { """ import javax.ws.rs.Path; import javax.ws.rs.Path; - + class A {} """, """ import org.springframework.http.MediaType; - + import javax.ws.rs.Path; import javax.ws.rs.Path; - + class A {} """ ) @@ -952,15 +1071,15 @@ void unorderedImportsWithNewBlock() { """ import org.foo.B; import org.foo.A; - + class A {} """, """ import org.foo.B; import org.foo.A; - + import java.time.Duration; - + class A {} """ ) @@ -981,7 +1100,7 @@ void doNotFoldNormalImportWithNamespaceConflict() { import java.util.Collections; import java.util.Map; import java.util.Set; - + @SuppressWarnings("ALL") class Test { List list; @@ -994,7 +1113,7 @@ class Test { import java.util.List; import java.util.Map; import java.util.Set; - + @SuppressWarnings("ALL") class Test { List list; @@ -1136,7 +1255,7 @@ void noImportLayout() { """, """ import java.util.List; - + import static java.util.Collections.*; """ ) @@ -1154,9 +1273,9 @@ class A {} """.replace("\n", "\r\n"), """ package a; - + import java.util.List; - + class A {} """.replace("\n", "\r\n") ) @@ -1170,17 +1289,17 @@ void crlfNewLinesWithPreviousImports() { java( """ package a; - + import java.util.Set; - + class A {} """.replace("\n", "\r\n"), """ package a; - + import java.util.List; import java.util.Set; - + class A {} """.replace("\n", "\r\n") ) @@ -1194,13 +1313,13 @@ void crlfNewLinesWithPreviousImportsNoPackage() { java( """ import java.util.Set; - + class A {} """.replace("\n", "\r\n"), """ import java.util.List; import java.util.Set; - + class A {} """.replace("\n", "\r\n") ) @@ -1214,13 +1333,13 @@ void crlfNewLinesWithPreviousImportsNoClass() { java( """ package a; - + import java.util.Arrays; import java.util.Set; """.replace("\n", "\r\n"), """ package a; - + import java.util.Arrays; import java.util.List; import java.util.Set; @@ -1269,10 +1388,10 @@ void crlfNewLinesInComments() { * limitations under the License. */ """.replace("\n", "\r\n") + - """ - import java.util.Arrays; - import java.util.Set; - """, + """ + import java.util.Arrays; + import java.util.Set; + """, """ /* * Copyright 2023 the original author or authors. @@ -1305,31 +1424,31 @@ void crlfNewLinesInJavadoc() { """ import java.util.Arrays; import java.util.Set; - + """ + - """ - /** - * Copyright 2023 the original author or authors. - *

- * Licensed 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. - */ - """.replace("\n", "\r\n") + - "class Foo {}", + """ + /** + * Copyright 2023 the original author or authors. + *

+ * Licensed 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. + */ + """.replace("\n", "\r\n") + + "class Foo {}", """ import java.util.Arrays; import java.util.List; import java.util.Set; - + /** * Copyright 2023 the original author or authors. *

diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java index ac1b1bc8117..fa9c34deb28 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java @@ -15,8 +15,11 @@ */ package org.openrewrite.java; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; import static org.openrewrite.java.Assertions.java; @@ -25,7 +28,7 @@ class AddOrUpdateAnnotationAttributeTest implements RewriteTest { @Test void addValueAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null, null)), java( """ package org.example; @@ -56,7 +59,7 @@ public class A { @Test void addValueAttributeClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null, null)), java( """ package org.example; @@ -87,7 +90,7 @@ public class A { @Test void addValueAttributeFullyQualifiedClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "java.math.BigDecimal.class", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "java.math.BigDecimal.class", null, null, null)), java( """ package org.example; @@ -118,7 +121,7 @@ public class A { @Test void updateValueAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null, null)), java( """ package org.example; @@ -150,7 +153,7 @@ public class A { @Test void updateValueAttributeClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null, null)), java( """ package org.example; @@ -182,7 +185,7 @@ public class A { @Test void removeValueAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null, null)), java( """ package org.example; @@ -214,7 +217,7 @@ public class A { @Test void removeValueAttributeClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null, null)), java( """ package org.example; @@ -245,7 +248,7 @@ public class A { @Test void addNamedAttribute() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null)), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null, null)), java( """ package org.junit; @@ -282,7 +285,7 @@ void foo() { @Test void replaceAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null, null)), java( """ package org.junit; @@ -319,7 +322,7 @@ void foo() { @Test void removeAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", null, null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", null, null, null, null)), java( """ package org.junit; @@ -356,7 +359,7 @@ void foo() { @Test void preserveExistingAttributes() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null, null)), java( """ package org.junit; @@ -394,7 +397,7 @@ void foo() { @Test void implicitValueToExplicitValue() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null)), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null, null)), java( """ package org.junit; @@ -431,7 +434,7 @@ void foo() { @Test void implicitValueToExplicitValueClass() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null)), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null, null)), java( """ package org.junit; @@ -469,7 +472,7 @@ void foo() { @Test void dontChangeWhenSetToAddOnly() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", true, false)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, true, false)), java( """ package org.junit; @@ -500,6 +503,7 @@ void arrayInAnnotationAttribute() { "org.example.Foo", "array", "newTest", + null, false, false)), java( @@ -536,6 +540,7 @@ void arrayInputMoreThanOneInAnnotationAttribute() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, false)), java( @@ -572,6 +577,7 @@ void addArrayInputInAnnotationAttribute() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, false)), java( @@ -608,6 +614,7 @@ void addArrayInputInAnnotationAttributeWithAppendTrue() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, true)), java( @@ -644,6 +651,7 @@ void addArrayInputInAnnotationAttributeEmptyBraces() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, null)), java( @@ -681,6 +689,7 @@ void removeArrayInputInAnnotationAttribute() { "array", null, null, + null, false)), java( """ @@ -717,6 +726,7 @@ void addOtherAttributeInArrayAnnotation() { "string", "test", null, + null, false)), java( """ @@ -753,6 +763,7 @@ void appendSingleValueToExistingArrayAttribute() { "org.example.Foo", "array", "b", + null, false, true)), java( @@ -789,6 +800,7 @@ void appendMultipleValuesToExistingArrayAttribute() { "org.example.Foo", "array", "b,c", + null, false, true)), java( @@ -825,6 +837,7 @@ void appendMultipleValuesToExistingArrayAttributeWithOverlap() { "org.example.Foo", "array", "b,c", + null, false, true)), java( @@ -861,6 +874,7 @@ void appendMultipleValuesToExistingArrayAttributeNonSet() { "org.example.Foo", "array", "b,c", + null, true, true)), java( @@ -892,4 +906,424 @@ public class A { ) ); } + + @Test + void updateFieldAccessAttribute() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", "value", "hello", null, false, null)), + java( + """ + package org.example; + + public class Const { + public static final String HI = "hi"; + } + """), + java( + """ + package org.example; + public @interface Foo { + String value() default ""; + } + """ + ), + java( + """ + import org.example.Foo; + import org.example.Const; + + @Foo(value = Const.HI) + public class A { + } + """, + """ + import org.example.Foo; + import org.example.Const; + + @Foo(value = "hello") + public class A { + } + """ + ) + ); + } + + @Test + void addAttributeToNestedAnnotationArray() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Bar", + "attribute", + "", + null, + null, + false)), + java( + """ + package org.example; + public @interface Foo { + Bar[] array() default {}; + } + """ + ), + java( + """ + package org.example; + public @interface Bar { + String attribute() default ""; + } + """ + ), + java( + """ + import org.example.Foo; + import org.example.Bar; + + @Foo(array = { @Bar() }) + public class A { + } + """, + """ + import org.example.Foo; + import org.example.Bar; + + @Foo(array = { @Bar(attribute = "") }) + public class A { + } + """ + ) + ); + } + + @Nested + class OnMatch { + @Test + void matchValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", "goodbye", null, null)), + java( + """ + package org.example; + public @interface Foo { + String value() default ""; + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo("goodbye") + public class A { + } + """, + """ + import org.example.Foo; + + @Foo("hello") + public class A { + } + """ + ) + ); + } + + @Test + void matchEnumValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Values.TWO", "Values.ONE", null, null)), + java( + """ + package org.example; + public @interface Foo { + Values value() default ""; + } + public enum Values {ONE, TWO} + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(Values.ONE) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(Values.TWO) + public class A { + } + """ + ) + ); + } + + @Test + void matchValueInArray() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "hello", + "goodbye", + null, + true)), + java( + """ + package org.example; + public @interface Foo { + String[] value() default ""; + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo({"goodbye", "hi"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo({"hi", "hello"}) + public class A { + } + """ + ) + ); + } + + @Test + void noMatchValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", "hi", null, null)), + java( + """ + package org.example; + public @interface Foo { + String value() default ""; + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo("goodbye") + public class A { + } + """ + ) + ); + } + + @Test + void matchClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", "Long.class", null, null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(Long.class) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(Integer.class) + public class A { + } + """ + ) + ); + } + + @Test + void nomatchClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", "Double.class", null, null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(Long.class) + public class A { + } + """ + ) + ); + } + } + + @Nested + class AsValueAttribute { + + @Language("java") + private static final String FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE = """ + package org.example; + public @interface Foo { + String[] value() default {}; + } + """; + + @Test + void implicitWithNullAttributeName() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo({"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo({"a", "b"}) + public class A { + } + """ + ) + ); + } + + @Test + void implicitWithAttributeNameValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(value = {"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(value = {"a", "b"}) + public class A { + } + """ + ) + ); + } + + @Test + void explicitWithNullAttributeName() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(value = {"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(value = {"a", "b"}) + public class A { + } + """ + ) + ); + } + + @Test + void explicitWithAttributeNameValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + "value", + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(value = {"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(value = {"a", "b"}) + public class A { + } + """ + ) + ); + } + } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java index 39ca3ed049e..5419f7c5be6 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java @@ -32,6 +32,7 @@ import static org.openrewrite.java.Assertions.java; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.xml.Assertions.xml; +import static org.openrewrite.yaml.Assertions.yaml; @SuppressWarnings("ConstantConditions") class ChangePackageTest implements RewriteTest { @@ -430,11 +431,11 @@ class A { } """, """ - import org.openrewrite.test.other.Test; - class A { - Test test = null; - } - """, + import org.openrewrite.test.other.Test; + class A { + Test test = null; + } + """, spec -> spec.afterRecipe(cu -> { assertThat(cu.findType("org.openrewrite.other.Test")).isEmpty(); assertThat(cu.findType("org.openrewrite.test.other.Test")).isNotEmpty(); @@ -488,11 +489,11 @@ class A { } """, """ - import org.openrewrite.test.other.Test; - class A { - Test test = null; - } - """, + import org.openrewrite.test.other.Test; + class A { + Test test = null; + } + """, spec -> spec.afterRecipe(cu -> { assertThat(cu.findType("org.openrewrite.other.Test")).isEmpty(); assertThat(cu.findType("org.openrewrite.test.other.Test")).isNotEmpty(); @@ -546,11 +547,11 @@ class A { } """, """ - import org.openrewrite.test.other.Test; - class A { - Test test = null; - } - """, + import org.openrewrite.test.other.Test; + class A { + Test test = null; + } + """, spec -> spec.afterRecipe(cu -> { assertThat(cu.findType("org.openrewrite.other.Test")).isEmpty(); assertThat(cu.findType("org.openrewrite.test.other.Test")).isNotEmpty(); @@ -705,7 +706,7 @@ void method() {} void annotationArgument() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -757,7 +758,7 @@ void method() {} void annotationArgumentNamed() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -807,7 +808,7 @@ void method() {} void annotationArgumentFullyQualified() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -855,7 +856,7 @@ void method() {} void annotationArgumentNamedFullyQualified() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -1439,7 +1440,7 @@ void staticImport() { java( """ import static org.openrewrite.Test.stat; - + public class B { public void test() { stat(); @@ -1448,7 +1449,7 @@ public void test() { """, """ import static org.openrewrite.test.Test.stat; - + public class B { public void test() { stat(); @@ -1724,7 +1725,6 @@ void changePackageInSpringXml() { """ ) ); - } @Test @@ -1741,7 +1741,30 @@ void changeTypeInPropertiesFile() { a.property=java.cool.String b.property=java.cool.test.String c.property=String - """, spec -> spec.path("application.properties")) + """, + spec -> spec.path("application.properties")) + ); + } + + @Test + void changePackageInYaml() { + rewriteRun( + spec -> spec.recipe(new ChangePackage("java.lang", "java.cool", true)), + yaml( + """ + root: + a: java.lang.String + b: java.lang.test.String + c: String + """, + """ + root: + a: java.cool.String + b: java.cool.test.String + c: String + """, + spec -> spec.path("application.yaml") + ) ); } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index 487e56e4a8e..c453a2246fb 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -31,6 +31,7 @@ import static org.openrewrite.java.Assertions.java; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.xml.Assertions.xml; +import static org.openrewrite.yaml.Assertions.yaml; @SuppressWarnings("ConstantConditions") class ChangeTypeTest implements RewriteTest { @@ -207,7 +208,7 @@ class Test { class Test { List p; - List p2; + java.util.List p2; java.util.List p3; } """ @@ -2071,12 +2072,150 @@ void changeTypeInPropertiesFile() { properties( """ a.property=java.lang.String + c.property=java.lang.StringBuilder b.property=String """, """ a.property=java.lang.Integer + c.property=java.lang.StringBuilder b.property=String """, spec -> spec.path("application.properties")) ); } + + @Test + void changeTypeInYaml() { + rewriteRun( + spec -> spec.recipe(new ChangeType("java.lang.String", "java.lang.Integer", true)), + yaml( + """ + root: + a: java.lang.String + b: java.lang.StringBuilder + c: java.lang.test.String + d: String + """, + """ + root: + a: java.lang.Integer + b: java.lang.StringBuilder + c: java.lang.test.String + d: String + """, + spec -> spec.path("application.yaml") + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4773") + void noRenameOfTypeWithMatchingPrefix() { + rewriteRun( + spec -> spec.recipe(new ChangeType("org.codehaus.jackson.annotate.JsonIgnoreProperties", "com.fasterxml.jackson.annotation.JsonIgnoreProperties", false)) + .parser(JavaParser.fromJavaVersion() + .dependsOn( + """ + package org.codehaus.jackson.annotate; + public @interface JsonIgnoreProperties { + boolean ignoreUnknown() default false; + } + """, + """ + package org.codehaus.jackson.annotate; + public @interface JsonIgnore { + } + """ + ) + ), + java( + """ + import org.codehaus.jackson.annotate.JsonIgnore; + import org.codehaus.jackson.annotate.JsonIgnoreProperties; + + @JsonIgnoreProperties(ignoreUnknown = true) + public class myClass { + @JsonIgnore + public boolean isDirty() { + return false; + } + } + """, + """ + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import org.codehaus.jackson.annotate.JsonIgnore; + + @JsonIgnoreProperties(ignoreUnknown = true) + public class myClass { + @JsonIgnore + public boolean isDirty() { + return false; + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4764") + void changeTypeOfInnerClass() { + rewriteRun( + spec -> spec.recipe(new ChangeType("foo.A$Builder", "bar.A$Builder", true)) + .parser(JavaParser.fromJavaVersion().dependsOn( + """ + package foo; + + public class A { + public A.Builder builder() { + return new A.Builder(); + } + + public static class Builder { + public A build() { + return new A(); + } + } + } + """, + """ + package bar; + + public class A { + public A.Builder builder() { + return new A.Builder(); + } + + public static class Builder { + public A build() { + return new A(); + } + } + } + """ + ) + ), + java( + """ + import foo.A; + import foo.A.Builder; + + class Test { + A test() { + A.Builder b = A.builder(); + return b.build(); + } + } + """, """ + import foo.A; + + class Test { + A test() { + bar.A.Builder b = A.builder(); + return b.build(); + } + } + """ + ) + ); + } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java index 03c6a38ee89..25a75622a02 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java @@ -25,18 +25,22 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; import org.openrewrite.SourceFile; +import org.openrewrite.java.search.FindCompileErrors; import org.openrewrite.java.tree.J; import org.openrewrite.test.RewriteTest; import java.io.IOException; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.TypeValidation.all; /** * @author Alex Boyko @@ -46,6 +50,7 @@ class JavaParserTest implements RewriteTest { @Test void incompleteAssignment() { rewriteRun( + spec -> spec.typeValidationOptions(all().erroneous(false)), java( """ @Deprecated(since=) @@ -105,6 +110,16 @@ void dependenciesFromResources(@TempDir Path temp) throws Exception { "directory contains guava-30.0-jre.jar which has the same prefix"); } + @Test + void getParserClasspathDownloadCreateRequiredFolder(@TempDir Path temp) throws Exception { + Path updatedTemp = Path.of(temp.toString(), "someFolder"); + assertThat(updatedTemp.toFile().exists()).isFalse(); + JavaParserExecutionContextView ctx = JavaParserExecutionContextView.view(new InMemoryExecutionContext()); + ctx.setParserClasspathDownloadTarget(updatedTemp.toFile()); + ctx.getParserClasspathDownloadTarget(); + assertThat(updatedTemp.toFile().exists()).isTrue(); + } + @Test @Issue("https://github.com/openrewrite/rewrite/issues/3222") void parseFromByteArray() { @@ -166,6 +181,132 @@ void moduleInfo() { assertFalse(JavaParser.fromJavaVersion().build().accept(Path.of("src/main/java/foo/module-info.java"))); } + @ParameterizedTest + //language=java + @ValueSource(strings = { + """ + package com.example.demo; + class FooBar { + public void test() { + ownerR + } + } + """, + """ + package com.example.demo; + class FooBar { + public void test(int num string msg) { + String a; this.ownerR + System.out.println(); + } + } + """, + """ + package com.example.demo; + class FooBar { + public void test(int num string s, int b) { + String a; this.ownerR + System.out.println(); + } + } + """, + """ + package com.example.demo; + class FooBar { + public void test(int num) { + String a; this.ownerR // comment + System.out.println(); + } + } + """, + """ + package com.example.demo; + class FooBar { + public void test(int num) { + // comment + this.ownerR + } + } + """, + """ + package com.example.demo; + class FooBar { + public void test(int param ) { + this.ownerR + // comment + } + } + """ + }) + void erroneousExpressionStatements(@Language("java") String source) { + rewriteRun( + spec -> spec.typeValidationOptions(all().erroneous(false)), + java(source) + ); + } + + @Test + void erroneousVariableDeclarations() { + rewriteRun( + spec -> spec.recipe(new FindCompileErrors()) + .typeValidationOptions(all().erroneous(false)), + java( + """ + package com.example.demo; + class Foo { + /pet + public void test() { + } + } + """, + """ + package com.example.demo; + class Foo { + /*~~>*///*~~>*/pet + public void test() { + } + } + """ + ), + java( + """ + package com.example.demo; + class Bar { + pet + public void test() { + } + } + """, + """ + package com.example.demo; + class Bar { + /*~~>*/pet + public void test() { + } + } + """ + ), + java( + """ + package com.example.demo; + class Baz { + -pet + public void test() { + } + } + """, + """ + package com.example.demo; + class Baz { + /*~~>*/-/*~~>*/pet + public void test() { + } + } + """ + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite/pull/4624") void shouldParseComments() { @@ -214,4 +355,22 @@ class A { ) ); } + + @Test + void filterArtifacts() { + List classpath = List.of( + URI.create("file:/.m2/repository/com/google/guava/guava-24.1.1/com_google_guava_guava-24.1.1.jar"), + URI.create("file:/.m2/repository/org/threeten/threeten-extra-1.5.0/org_threeten_threeten_extra-1.5.0.jar"), + URI.create("file:/.m2/repository/com/amazonaws/aws-java-sdk-s3-1.11.546/com_amazonaws_aws_java_sdk_s3-1.11.546.jar"), + URI.create("file:/.m2/repository/org/openrewrite/rewrite-java/8.41.1/rewrite-java-8.41.1.jar") + ); + assertThat(JavaParser.filterArtifacts("threeten-extra", classpath)) + .containsOnly(Paths.get("/.m2/repository/org/threeten/threeten-extra-1.5.0/org_threeten_threeten_extra-1.5.0.jar")); + assertThat(JavaParser.filterArtifacts("guava", classpath)) + .containsOnly(Paths.get("/.m2/repository/com/google/guava/guava-24.1.1/com_google_guava_guava-24.1.1.jar")); + assertThat(JavaParser.filterArtifacts("aws-java-sdk-s3", classpath)) + .containsOnly(Paths.get("/.m2/repository/com/amazonaws/aws-java-sdk-s3-1.11.546/com_amazonaws_aws_java_sdk_s3-1.11.546.jar")); + assertThat(JavaParser.filterArtifacts("rewrite-java", classpath)) + .containsOnly(Paths.get("/.m2/repository/org/openrewrite/rewrite-java/8.41.1/rewrite-java-8.41.1.jar")); + } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java index 25e1e314f6f..cc2041f7236 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java @@ -72,7 +72,7 @@ void assignmentWithinIfPredicate() { @Override public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ctx) { if ((assignment.getAssignment() instanceof J.Literal) && - ((J.Literal) assignment.getAssignment()).getValue().equals(1)) { + ((J.Literal) assignment.getAssignment()).getValue().equals(1)) { return JavaTemplate.builder("value = 0") .contextSensitive() .build() @@ -1351,7 +1351,7 @@ void replaceMethodArgumentsInIfStatementWithoutBraces() { public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); if (new MethodMatcher("Foo bar(..)").matches(mi) && - mi.getArguments().get(0) instanceof J.Binary) { + mi.getArguments().get(0) instanceof J.Binary) { return JavaTemplate.builder("\"Hello, {}\", \"World!\"") .contextSensitive() .build() @@ -1382,4 +1382,47 @@ void foo(boolean condition) { ) ); } + + @Test + void replaceVariableDeclarationWithFinalVar() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + J.VariableDeclarations vd = super.visitVariableDeclarations(multiVariable, ctx); + if (TypeUtils.isString(vd.getType()) && "String".equals(((J.Identifier) vd.getTypeExpression()).getSimpleName())) { + JavaCoordinates coordinates = vd.getCoordinates().replace(); + return JavaTemplate.builder("final var #{}") + .contextSensitive() + .doBeforeParseTemplate(System.out::println) + .build() + .apply(getCursor(), coordinates, new Object[]{vd.getVariables().get(0).getSimpleName()}); + } + return vd; + } + })), + java( + """ + import java.util.List; + import java.util.ArrayList; + + class A { + void bar(List lst) { + for (String s : lst) {} + } + } + """, + """ + import java.util.List; + import java.util.ArrayList; + + class A { + void bar(List lst) { + for (final var s : lst) {} + } + } + """ + ) + ); + } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest6Test.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest6Test.java index b6dd28e6764..4b8cdb9d754 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest6Test.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest6Test.java @@ -255,7 +255,9 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex })).afterRecipe(run -> { J.CompilationUnit cu = (J.CompilationUnit) run.getChangeset().getAllResults().get(0).getAfter(); J.MethodDeclaration testMethodDecl = (J.MethodDeclaration) cu.getClasses().get(0).getBody().getStatements().get(0); - assertThat(testMethodDecl.getMethodType().getThrownExceptions().stream().map(JavaType.FullyQualified::getFullyQualifiedName)) + assertThat(testMethodDecl.getMethodType().getThrownExceptions().stream() + .map(JavaType.FullyQualified.class::cast) + .map(JavaType.FullyQualified::getFullyQualifiedName)) .containsExactly("java.lang.Exception"); }), java( diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java index 8c3e7fc9e22..a906fb84249 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; +import static org.openrewrite.test.TypeValidation.all; class JavaVisitorTest implements RewriteTest { @@ -116,20 +117,58 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex }) ), java( - """ - class A { - public void method1() { - } - - @Deprecated - public String myMethod() { - return "hello"; - } - - public void method2() { - } - } - """) + """ + class A { + public void method1() { + } + + @Deprecated + public String myMethod() { + return "hello"; + } + + public void method2() { + } + } + """ + ) + ); + } + + @Test + void javaVisitorHandlesErroneousNodes() { + rewriteRun( + spec -> spec + .expectedCyclesThatMakeChanges(2) + .recipes( + toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext p) { + if (method.getSimpleName().equals("test")) { + return JavaTemplate.builder("Exception").contextSensitive().build() + .apply(getCursor(), method.getCoordinates().replaceThrows()); + } + return method; + } + }) + ) + .typeValidationOptions(all().erroneous(false)), + java( + """ + class A { + void test() { + owner + } + } + """, + """ + class A { + void test() throws Exception { + owner + } + } + """ + ) ); } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java index 6b8d5b62381..b5d90458706 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java @@ -64,7 +64,7 @@ public class A { @DocumentExample @Test - void foo() { + void skipMissingTypeAttribution() { rewriteRun( spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( @@ -733,4 +733,53 @@ void foo(String a) { rewriteRun(java(beforeJava, template.formatted(after))); } } + + @Issue("https://github.com/openrewrite/rewrite-feature-flags/issues/40") + @Test + void simplifyStringLiteralEqualsStringLiteral() { + rewriteRun( + java( + """ + class A { + { + String foo = "foo"; + if ("foo".equals("foo")) {} + if (foo.equals(foo)) {} + if (foo.equals("foo")) {} + if ("foo".equals(foo)) {} + if ("foo".equals("bar")) {} + } + } + """, + """ + class A { + { + String foo = "foo"; + if (true) {} + if (true) {} + if (foo.equals("foo")) {} + if ("foo".equals(foo)) {} + if (false) {} + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4821") + @Test + void stringComparisonInBinary() { + rewriteRun( + java( + """ + class A { + private boolean notNullAndNotEqual(String one, String other) { + return one != null && !one.equals(other); + } + } + """ + ) + ); + } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 2bb6531f15c..070b5b96b03 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -173,6 +173,31 @@ private void firstArgOnNewLine( ); } + @Test + void firstParameterNameConflictWithReturnTypeAndMethodName() { + rewriteRun( + tabsAndIndents(style -> style.withMethodDeclarationParameters(new TabsAndIndentsStyle.MethodDeclarationParameters(true))), + java( + """ + class Test { + private String first(String first, + int times, + String third) { + } + } + """, + """ + class Test { + private String first(String first, + int times, + String third) { + } + } + """ + ) + ); + } + @Test void alignMethodDeclarationParamsWhenContinuationIndentUsingTabs() { rewriteRun( @@ -194,7 +219,7 @@ class Foo { ); } - // https://rules.sonarsource.com/java/tag/confusing/RSPEC-S3973 + // https://rules.sonarsource.com/java/tag/confusing/RSPEC-3973 @DocumentExample @SuppressWarnings("SuspiciousIndentAfterControlStatement") @Test diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindTypesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindTypesTest.java index d85bd134a47..321f560b763 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindTypesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindTypesTest.java @@ -47,6 +47,10 @@ public static void stat() {} @Test void simpleName() { rewriteRun( + spec -> spec.dataTable(TypeUses.Row.class, rows -> assertThat(rows) + .containsExactly( + new TypeUses.Row("B.java", "A1", "a.A1") + )), java( """ import a.A1; diff --git a/rewrite-java/build.gradle.kts b/rewrite-java/build.gradle.kts index 3af7ee05274..5b4d42c7351 100644 --- a/rewrite-java/build.gradle.kts +++ b/rewrite-java/build.gradle.kts @@ -13,9 +13,9 @@ tasks.register("generateAntlrSources") { mainClass.set("org.antlr.v4.Tool") args = listOf( - "-o", "src/main/java/org/openrewrite/java/internal/grammar", - "-package", "org.openrewrite.java.internal.grammar", - "-visitor" + "-o", "src/main/java/org/openrewrite/java/internal/grammar", + "-package", "org.openrewrite.java.internal.grammar", + "-visitor" ) + fileTree("src/main/antlr").matching { include("**/*.g4") }.map { it.path } classpath = antlrGeneration diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java index 3feff736d1c..2e23df27adb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java @@ -105,9 +105,13 @@ public AddImport(@Nullable String packageName, String typeName, @Nullable String return cu; } - // No need to add imports if the class to import is in java.lang, or if the classes are within the same package - if (("java.lang".equals(packageName) && StringUtils.isBlank(member)) || (cu.getPackageDeclaration() != null && - packageName.equals(cu.getPackageDeclaration().getExpression().printTrimmed(getCursor())))) { + // No need to add imports if the class to import is in java.lang + if ("java.lang".equals(packageName) && StringUtils.isBlank(member)) { + return cu; + } + // Nor if the classes are within the same package + if (!"Record".equals(typeName) && cu.getPackageDeclaration() != null && + packageName.equals(cu.getPackageDeclaration().getExpression().printTrimmed(getCursor()))) { return cu; } @@ -115,14 +119,14 @@ public AddImport(@Nullable String packageName, String typeName, @Nullable String return cu; } - if (cu.getImports().stream().anyMatch(i -> { + if (!"Record".equals(typeName) && cu.getImports().stream().anyMatch(i -> { String ending = i.getQualid().getSimpleName(); if (member == null) { return !i.isStatic() && i.getPackageName().equals(packageName) && - (ending.equals(typeName) || "*".equals(ending)); + (ending.equals(typeName) || "*".equals(ending)); } return i.isStatic() && i.getTypeName().equals(fullyQualifiedName) && - (ending.equals(member) || "*".equals(ending)); + (ending.equals(member) || "*".equals(ending)); })) { return cu; } @@ -133,24 +137,22 @@ public AddImport(@Nullable String packageName, String typeName, @Nullable String new JLeftPadded<>(member == null ? Space.EMPTY : Space.SINGLE_SPACE, member != null, Markers.EMPTY), TypeTree.build(fullyQualifiedName + - (member == null ? "" : "." + member)).withPrefix(Space.SINGLE_SPACE), + (member == null ? "" : "." + member)).withPrefix(Space.SINGLE_SPACE), null); List> imports = new ArrayList<>(cu.getPadding().getImports()); - if (imports.isEmpty() && !cu.getClasses().isEmpty()) { - if (cu.getPackageDeclaration() == null) { - // leave javadocs on the class and move other comments up to the import - // (which could include license headers and the like) - Space firstClassPrefix = cu.getClasses().get(0).getPrefix(); - importToAdd = importToAdd.withPrefix(firstClassPrefix - .withComments(ListUtils.map(firstClassPrefix.getComments(), comment -> comment instanceof Javadoc ? null : comment)) - .withWhitespace("")); - - cu = cu.withClasses(ListUtils.mapFirst(cu.getClasses(), clazz -> - clazz.withComments(ListUtils.map(clazz.getComments(), comment -> comment instanceof Javadoc ? comment : null)) - )); - } + if (imports.isEmpty() && !cu.getClasses().isEmpty() && cu.getPackageDeclaration() == null) { + // leave javadocs on the class and move other comments up to the import + // (which could include license headers and the like) + Space firstClassPrefix = cu.getClasses().get(0).getPrefix(); + importToAdd = importToAdd.withPrefix(firstClassPrefix + .withComments(ListUtils.map(firstClassPrefix.getComments(), comment -> comment instanceof Javadoc ? null : comment)) + .withWhitespace("")); + + cu = cu.withClasses(ListUtils.mapFirst(cu.getClasses(), clazz -> + clazz.withComments(ListUtils.map(clazz.getComments(), comment -> comment instanceof Javadoc ? comment : null)) + )); } ImportLayoutStyle layoutStyle = Optional.ofNullable(((SourceFile) cu).getStyle(ImportLayoutStyle.class)) @@ -214,7 +216,7 @@ private boolean hasReference(JavaSourceFile compilationUnit) { //Non-static imports, we just look for field accesses. for (NameTree t : FindTypes.find(compilationUnit, fullyQualifiedName)) { if ((!(t instanceof J.FieldAccess) || !((J.FieldAccess) t).isFullyQualifiedClassReference(fullyQualifiedName)) && - isTypeReference(t)) { + isTypeReference(t)) { return true; } } @@ -226,7 +228,7 @@ private boolean hasReference(JavaSourceFile compilationUnit) { if (invocation instanceof J.MethodInvocation) { J.MethodInvocation mi = (J.MethodInvocation) invocation; if (mi.getSelect() == null && - ("*".equals(member) || mi.getName().getSimpleName().equals(member))) { + ("*".equals(member) || mi.getName().getSimpleName().equals(member))) { return true; } } @@ -239,11 +241,18 @@ private boolean hasReference(JavaSourceFile compilationUnit) { } private class FindStaticFieldAccess extends JavaIsoVisitor> { + private boolean checkIsOfClassType(@Nullable JavaType type, String fullyQualifiedName) { + if (isOfClassType(type, fullyQualifiedName)) { + return true; + } + return type instanceof JavaType.Class && isOfClassType(((JavaType.Class) type).getOwningClass(), fullyQualifiedName); + } + @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, AtomicReference found) { // If the type isn't used there's no need to proceed further for (JavaType.Variable varType : cu.getTypesInUse().getVariables()) { - if (varType.getName().equals(member) && isOfClassType(varType.getType(), fullyQualifiedName)) { + if (checkIsOfClassType(varType.getType(), fullyQualifiedName)) { return super.visitCompilationUnit(cu, found); } } @@ -253,8 +262,9 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, AtomicRefere @Override public J.Identifier visitIdentifier(J.Identifier identifier, AtomicReference found) { assert getCursor().getParent() != null; - if (identifier.getSimpleName().equals(member) && isOfClassType(identifier.getType(), fullyQualifiedName) && - !(getCursor().getParent().firstEnclosingOrThrow(J.class) instanceof J.FieldAccess)) { + if (identifier.getSimpleName().equals(member) && + checkIsOfClassType(identifier.getType(), fullyQualifiedName) && + !(getCursor().getParent().firstEnclosingOrThrow(J.class) instanceof J.FieldAccess)) { found.set(true); } return identifier; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java b/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java index be098009e16..2f7a8858857 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java @@ -18,21 +18,25 @@ import lombok.EqualsAndHashCode; import lombok.Value; import lombok.With; +import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import static java.util.Objects.requireNonNull; import static org.openrewrite.Tree.randomId; @Value @@ -46,7 +50,7 @@ public String getDisplayName() { @Override public String getDescription() { return "Some annotations accept arguments. This recipe sets an existing argument to the specified value, " + - "or adds the argument if it is not already set."; + "or adds the argument if it is not already set."; } @Option(displayName = "Annotation type", @@ -67,6 +71,12 @@ public String getDescription() { @Nullable String attributeValue; + @Option(displayName = "Old Attribute value", + description = "The current value of the attribute, this can be used to filter where the change is applied. Set to `null` for wildcard behavior.", + example = "400") + @Nullable + String oldAttributeValue; + @Option(displayName = "Add only", description = "When set to `true` will not change existing annotation attribute values.") @Nullable @@ -74,9 +84,9 @@ public String getDescription() { @Option(displayName = "Append array", description = "If the attribute is an array, setting this option to `true` will append the value(s). " + - "In conjunction with `addOnly`, it is possible to control duplicates: " + - "`addOnly=true`, always append. " + - "`addOnly=false`, only append if the value is not already present.") + "In conjunction with `addOnly`, it is possible to control duplicates: " + + "`addOnly=true`, always append. " + + "`addOnly=false`, only append if the value is not already present.") @Nullable Boolean appendArray; @@ -85,15 +95,15 @@ public TreeVisitor getVisitor() { return Preconditions.check(new UsesType<>(annotationType, false), new JavaIsoVisitor() { @Override public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { - J.Annotation original = a; + J.Annotation original = super.visitAnnotation(a, ctx); if (!TypeUtils.isOfClassType(a.getType(), annotationType)) { - return a; + return original; } String newAttributeValue = maybeQuoteStringArgument(attributeName, attributeValue, a); List currentArgs = a.getArguments(); if (currentArgs == null || currentArgs.isEmpty() || currentArgs.get(0) instanceof J.Empty) { - if (newAttributeValue == null) { + if (newAttributeValue == null || oldAttributeValue != null) { return a; } @@ -104,8 +114,8 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { .apply(getCursor(), a.getCoordinates().replaceArguments(), newAttributeValue); } else { String newAttributeValueResult = newAttributeValue; - if (((JavaType.FullyQualified) Objects.requireNonNull(a.getAnnotationType().getType())).getMethods().stream().anyMatch(method -> method.getReturnType().toString().equals("java.lang.String[]"))) { - String attributeValueCleanedUp = attributeValue.replaceAll("\\s+","").replaceAll("[\\s+{}\"]",""); + if (((JavaType.FullyQualified) requireNonNull(a.getAnnotationType().getType())).getMethods().stream().anyMatch(method -> method.getReturnType().toString().equals("java.lang.String[]"))) { + String attributeValueCleanedUp = attributeValue.replaceAll("\\s+", "").replaceAll("[\\s+{}\"]", ""); List attributeList = Arrays.asList(attributeValueCleanedUp.contains(",") ? attributeValueCleanedUp.split(",") : new String[]{attributeValueCleanedUp}); newAttributeValueResult = attributeList.stream() .map(String::valueOf) @@ -124,9 +134,13 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (it instanceof J.Assignment) { J.Assignment as = (J.Assignment) it; J.Identifier var = (J.Identifier) as.getVariable(); - if (attributeName == null || !attributeName.equals(var.getSimpleName())) { + if (attributeName == null && !"value".equals(var.getSimpleName())) { + return it; + } + if (attributeName != null && !attributeName.equals(var.getSimpleName())) { return it; } + foundOrSetAttributeWithDesiredValue.set(true); if (newAttributeValue == null) { @@ -134,8 +148,8 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { } if (as.getAssignment() instanceof J.NewArray) { - List jLiteralList = ((J.NewArray) as.getAssignment()).getInitializer(); - String attributeValueCleanedUp = attributeValue.replaceAll("\\s+","").replaceAll("[\\s+{}\"]",""); + List jLiteralList = requireNonNull(((J.NewArray) as.getAssignment()).getInitializer()); + String attributeValueCleanedUp = attributeValue.replaceAll("\\s+", "").replaceAll("[\\s+{}\"]", ""); List attributeList = Arrays.asList(attributeValueCleanedUp.contains(",") ? attributeValueCleanedUp.split(",") : new String[]{attributeValueCleanedUp}); if (as.getMarkers().findFirst(AlreadyAppended.class).filter(ap -> ap.getValues().equals(newAttributeValue)).isPresent()) { @@ -149,36 +163,47 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (Boolean.FALSE.equals(addOnly) && attributeValIsAlreadyPresent(jLiteralList, newAttributeListValue)) { continue; } + if (oldAttributeValue != null && !oldAttributeValue.equals(attrListValues)) { + continue; + } changed = true; - jLiteralList.add(new J.Literal(randomId(), Space.EMPTY, Markers.EMPTY, newAttributeListValue, newAttributeListValue, null, JavaType.Primitive.String)); + Expression e = requireNonNull(((J.Annotation) JavaTemplate.builder(newAttributeListValue) + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments()).get(0); + jLiteralList.add(e); } return changed ? as.withAssignment(((J.NewArray) as.getAssignment()).withInitializer(jLiteralList)) .withMarkers(as.getMarkers().add(new AlreadyAppended(randomId(), newAttributeValue))) : as; } int m = 0; - for (int i = 0; i< Objects.requireNonNull(jLiteralList).size(); i++){ - if (i >= attributeList.size()){ + for (int i = 0; i < requireNonNull(jLiteralList).size(); i++) { + if (i >= attributeList.size()) { jLiteralList.remove(i); i--; continue; } String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(i), finalA); - if (jLiteralList.size() == i+1){ - m = i+1; + if (jLiteralList.size() == i + 1) { + m = i + 1; } - if (newAttributeListValue != null && newAttributeListValue.equals(((J.Literal) jLiteralList.get(i)).getValueSource()) || Boolean.TRUE.equals(addOnly)) { + if (newAttributeListValue.equals(((J.Literal) jLiteralList.get(i)).getValueSource()) || Boolean.TRUE.equals(addOnly)) { + continue; + } + if (oldAttributeValue != null && !oldAttributeValue.equals(attributeList.get(i))) { continue; } jLiteralList.set(i, ((J.Literal) jLiteralList.get(i)).withValue(newAttributeListValue).withValueSource(newAttributeListValue).withPrefix(jLiteralList.get(i).getPrefix())); } - if (jLiteralList.size() < attributeList.size() || Boolean.TRUE.equals(addOnly)){ - if (Boolean.TRUE.equals(addOnly)){ + if (jLiteralList.size() < attributeList.size() || Boolean.TRUE.equals(addOnly)) { + if (Boolean.TRUE.equals(addOnly)) { m = 0; } - for (int j = m; j < attributeList.size(); j++){ + for (int j = m; j < attributeList.size(); j++) { String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(j), finalA); jLiteralList.add(j, new J.Literal(randomId(), jLiteralList.get(j - 1).getPrefix(), Markers.EMPTY, newAttributeListValue, newAttributeListValue, null, JavaType.Primitive.String)); } @@ -186,11 +211,24 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { return as.withAssignment(((J.NewArray) as.getAssignment()).withInitializer(jLiteralList)); } else { - J.Literal value = (J.Literal) as.getAssignment(); - if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { - return it; + Expression exp = as.getAssignment(); + if (exp instanceof J.Literal) { + J.Literal value = (J.Literal) exp; + if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { + return it; + } + if (!valueMatches(value, oldAttributeValue)) { + return it; + } + return as.withAssignment(value.withValue(newAttributeValue).withValueSource(newAttributeValue)); + } else if (exp instanceof J.FieldAccess) { + if (Boolean.TRUE.equals(addOnly)) { + return it; + } + int index = finalA.getArguments().indexOf(as); + as = (J.Assignment) ((J.Annotation) JavaTemplate.apply("#{} = #{}", getCursor(), as.getCoordinates().replace(), var.getSimpleName(), newAttributeValue)).getArguments().get(index); + return as; } - return as.withAssignment(value.withValue(newAttributeValue).withValueSource(newAttributeValue)); } } else if (it instanceof J.Literal) { // The only way anything except an assignment can appear is if there's an implicit assignment to "value" @@ -203,6 +241,9 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { return it; } + if (!valueMatches(value, oldAttributeValue)) { + return it; + } return ((J.Literal) it).withValue(newAttributeValue).withValueSource(newAttributeValue); } else { // Make the attribute name explicit, before we add the new value below @@ -217,6 +258,9 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { // The only way anything except an assignment can appear is if there's an implicit assignment to "value" if (attributeName == null || "value".equals(attributeName)) { foundOrSetAttributeWithDesiredValue.set(true); + if (!valueMatches(it, oldAttributeValue)) { + return it; + } if (newAttributeValue == null) { return null; } @@ -236,33 +280,132 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { .apply(getCursor(), finalA.getCoordinates().replaceArguments(), it)) .getArguments().get(0); } + } else if (it instanceof J.NewArray) { + if (it.getMarkers().findFirst(AlreadyAppended.class).filter(ap -> ap.getValues().equals(newAttributeValue)).isPresent()) { + return it; + } + + if (newAttributeValue == null) { + return null; + } + + J.NewArray arrayValue = (J.NewArray) it; + List jLiteralList = requireNonNull(arrayValue.getInitializer()); + String attributeValueCleanedUp = attributeValue.replaceAll("\\s+", "").replaceAll("[\\s+{}\"]", ""); + List attributeList = Arrays.asList(attributeValueCleanedUp.contains(",") ? attributeValueCleanedUp.split(",") : new String[]{attributeValueCleanedUp}); + + if (Boolean.TRUE.equals(appendArray)) { + boolean changed = false; + for (String attrListValues : attributeList) { + String newAttributeListValue = maybeQuoteStringArgument(attributeName, attrListValues, finalA); + if (Boolean.FALSE.equals(addOnly) && attributeValIsAlreadyPresent(jLiteralList, newAttributeListValue)) { + continue; + } + changed = true; + + Expression e = requireNonNull(((J.Annotation) JavaTemplate.builder(newAttributeListValue) + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments()).get(0); + jLiteralList.add(e); + } + if (oldAttributeValue != null) { // remove old value from array + jLiteralList = ListUtils.map(jLiteralList, val -> valueMatches(val, oldAttributeValue) ? null : val); + } + + return changed ? arrayValue.withInitializer(jLiteralList) + .withMarkers(it.getMarkers().add(new AlreadyAppended(randomId(), newAttributeValue))) : it; + } + int m = 0; + for (int i = 0; i < requireNonNull(jLiteralList).size(); i++) { + if (i >= attributeList.size()) { + jLiteralList.remove(i); + i--; + continue; + } + + String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(i), finalA); + if (jLiteralList.size() == i + 1) { + m = i + 1; + } + + if (newAttributeListValue.equals(((J.Literal) jLiteralList.get(i)).getValueSource()) || Boolean.TRUE.equals(addOnly)) { + continue; + } + if (oldAttributeValue != null && !oldAttributeValue.equals(newAttributeListValue)) { + continue; + } + + jLiteralList.set(i, ((J.Literal) jLiteralList.get(i)).withValue(newAttributeListValue).withValueSource(newAttributeListValue).withPrefix(jLiteralList.get(i).getPrefix())); + } + if (jLiteralList.size() < attributeList.size() || Boolean.TRUE.equals(addOnly)) { + if (Boolean.TRUE.equals(addOnly)) { + m = 0; + } + for (int j = m; j < attributeList.size(); j++) { + String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(j), finalA); + + Expression e = requireNonNull(((J.Annotation) JavaTemplate.builder(newAttributeListValue) + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments()).get(0); + jLiteralList.add(j, e); + } + } + + return arrayValue.withInitializer(jLiteralList); } return it; }); + if (newArgs != currentArgs) { a = a.withArguments(newArgs); } - if (foundOrSetAttributeWithDesiredValue.get()) { - a = maybeAutoFormat(original, a, ctx); - return a; + if (!foundOrSetAttributeWithDesiredValue.get() && !attributeValIsAlreadyPresent(newArgs, newAttributeValue)) { + // There was no existing value to update, so add a new value into the argument list + String effectiveName = (attributeName == null) ? "value" : attributeName; + //noinspection ConstantConditions + J.Assignment as = (J.Assignment) ((J.Annotation) JavaTemplate.builder("#{} = #{}") + .contextSensitive() + .build() + .apply(getCursor(), a.getCoordinates().replaceArguments(), effectiveName, newAttributeValue)) + .getArguments().get(0); + a = a.withArguments(ListUtils.concat(as, a.getArguments())); } - // There was no existing value to update, so add a new value into the argument list - String effectiveName = (attributeName == null) ? "value" : attributeName; - //noinspection ConstantConditions - J.Assignment as = (J.Assignment) ((J.Annotation) JavaTemplate.builder("#{} = #{}") - .contextSensitive() - .build() - .apply(getCursor(), a.getCoordinates().replaceArguments(), effectiveName, newAttributeValue)) - .getArguments().get(0); - a = a.withArguments(ListUtils.concat(as, a.getArguments())); - a = maybeAutoFormat(original, a, ctx); } - + a = maybeAutoFormat(original, a, ctx); return a; } }); } + private static boolean valueMatches(@Nullable Expression expression, @Nullable String oldAttributeValue) { + if (expression == null) { + return oldAttributeValue == null; + } + if (oldAttributeValue == null) { // null means wildcard + return true; + } else if (expression instanceof J.Literal) { + return oldAttributeValue.equals(((J.Literal) expression).getValue()); + } else if (expression instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) expression; + String currentValue = ((J.Identifier) fa.getTarget()).getSimpleName() + "." + fa.getSimpleName(); + return oldAttributeValue.equals(currentValue); + } else if (expression instanceof J.Identifier) { // class names, static variables .. + if (oldAttributeValue.endsWith(".class")) { + String className = TypeUtils.toString(requireNonNull(expression.getType())) + ".class"; + return className.endsWith(oldAttributeValue); + } else { + return oldAttributeValue.equals(((J.Identifier) expression).getSimpleName()); + } + } else { + throw new IllegalArgumentException("Unexpected expression type: " + expression.getClass()); + } + } + + @Contract("_, null, _ -> null; _, !null, _ -> !null") private static @Nullable String maybeQuoteStringArgument(@Nullable String attributeName, @Nullable String attributeValue, J.Annotation annotation) { if ((attributeValue != null) && attributeIsString(attributeName, annotation)) { return "\"" + attributeValue + "\""; @@ -284,7 +427,10 @@ private static boolean attributeIsString(@Nullable String attributeName, J.Annot return false; } - private static boolean attributeValIsAlreadyPresent(List expression, @Nullable String attributeValue) { + private static boolean attributeValIsAlreadyPresent(@Nullable List expression, @Nullable String attributeValue) { + if (expression == null) { + return attributeValue == null; + } for (Expression e : expression) { if (e instanceof J.Literal) { J.Literal literal = (J.Literal) e; @@ -292,6 +438,9 @@ private static boolean attributeValIsAlreadyPresent(List expression, return true; } } + if (e instanceof J.NewArray) { + return attributeValIsAlreadyPresent(((J.NewArray) e).getInitializer(), attributeValue); + } } return false; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AnnotationMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/AnnotationMatcher.java index e37d088fe21..94b138738d1 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AnnotationMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AnnotationMatcher.java @@ -47,7 +47,7 @@ * {@literal @}java.lang.SuppressWarnings - Matches java.lang.SuppressWarnings with no parameters. * {@literal @}myhttp.Get(serviceName="payments", path="recentPayments") - Matches references to myhttp.Get where the parameters are also matched. * {@literal @}myhttp.Get(path="recentPayments", serviceName="payments") - Exactly the same results from the previous example, order of parameters does not matter. - * {@literal @}java.lang.SuppressWarnings("deprecation") - Matches java.langSuppressWarning with a single parameter. + * {@literal @}java.lang.SuppressWarnings("deprecation") - Matches java.langSuppressWarning with a parameter "deprecation", values in array initializer match as well. * {@literal @}org.junit.runner.RunWith(org.junit.runners.JUnit4.class) - Matches JUnit4's @RunWith(JUnit4.class) * */ @@ -76,8 +76,8 @@ public AnnotationMatcher(String signature) { public boolean matches(J.Annotation annotation) { return matchesAnnotationName(annotation) && - matchesSingleParameter(annotation) && - matchesNamedParameters(annotation); + matchesSingleParameter(annotation) && + matchesNamedParameters(annotation); } private boolean matchesAnnotationName(J.Annotation annotation) { @@ -99,7 +99,7 @@ private boolean matchesAnnotationOrMetaAnnotation(JavaType.@Nullable FullyQualif seenAnnotations = new HashSet<>(); } if (seenAnnotations.add(annotation.getFullyQualifiedName()) && - matchesAnnotationOrMetaAnnotation(annotation, seenAnnotations)) { + matchesAnnotationOrMetaAnnotation(annotation, seenAnnotations)) { return true; } } @@ -168,10 +168,16 @@ private boolean argumentValueMatches(String matchOnArgumentName, Expression arg, } if (arg instanceof J.NewArray) { J.NewArray na = (J.NewArray) arg; - if (na.getInitializer() == null || na.getInitializer().size() != 1) { + if (na.getInitializer() == null) { return false; } - return argumentValueMatches("value", na.getInitializer().get(0), matchText); + // recursively check each initializer of the array initializer + for (Expression expression : na.getInitializer()) { + if (argumentValueMatches(matchOnArgumentName, expression, matchText)) { + return true; + } + } + return false; } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java index 19142844e33..4d2196083ca 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java @@ -38,6 +38,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import static java.util.stream.Collectors.joining; import static org.openrewrite.test.SourceSpecs.dir; @SuppressWarnings("unused") @@ -58,6 +59,36 @@ static void customizeExecutionContext(ExecutionContext ctx) { public static SourceFile validateTypes(SourceFile source, TypeValidation typeValidation) { if (source instanceof JavaSourceFile) { assertValidTypes(typeValidation, (JavaSourceFile) source); + if (typeValidation.erroneous()) { + List allErroneous = new JavaIsoVisitor>() { + @Override + public J.Erroneous visitErroneous(J.Erroneous erroneous, List list) { + J.Erroneous err = super.visitErroneous(erroneous, list); + list.add(err); + return err; + } + }.reduce(source, new ArrayList<>()); + if (!allErroneous.isEmpty()) { + throw new IllegalStateException("LST contains erroneous nodes\n" + allErroneous.stream() + .map(J.Erroneous::getText) + .collect(joining("\n\n"))); + } + } + if (typeValidation.unknown()) { + List allUnknown = new JavaIsoVisitor>() { + @Override + public J.Unknown visitUnknown(J.Unknown unknown, List list) { + J.Unknown err = super.visitUnknown(unknown, list); + list.add(err); + return err; + } + }.reduce(source, new ArrayList<>()); + if (!allUnknown.isEmpty()) { + throw new IllegalStateException("LST contains erroneous nodes\n" + allUnknown.stream() + .map(unknown -> unknown.getSource().getText()) + .collect(joining("\n\n"))); + } + } } return source; } @@ -84,11 +115,12 @@ private static void assertValidTypes(TypeValidation typeValidation, J sf) { return true; } }) + .filter(missingType -> !typeValidation.allowMissingType().apply(missingType)) .collect(Collectors.toList()); if (!missingTypeResults.isEmpty()) { String missingTypes = missingTypeResults.stream() .map(v -> v.getPath() + "\n" + v.getPrintedTree()) - .collect(Collectors.joining("\n\n")); + .collect(joining("\n\n")); throw new IllegalStateException( "LST contains missing or invalid type information\n" + missingTypes + "\nhttps://docs.openrewrite.org/reference/faq#im-seeing-lst-contains-missing-or-invalid-type-information-in-my-recipe-unit-tests-how-to-resolve"); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java new file mode 100644 index 00000000000..6cf60bfc3e8 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.java; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Markers; + +import static java.util.Collections.emptyList; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ChangeMethodInvocationReturnType extends Recipe { + + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find matching method declarations/invocations.", + example = "org.mockito.Matchers anyVararg()") + String methodPattern; + + @Option(displayName = "New method invocation return type", + description = "The fully qualified new return type of method invocation.", + example = "long") + String newReturnType; + + @Override + public String getDisplayName() { + return "Change method invocation return type"; + } + + @Override + public String getDescription() { + return "Changes the return type of a method invocation."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + private final MethodMatcher methodMatcher = new MethodMatcher(methodPattern, false); + + private boolean methodUpdated; + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + JavaType.Method type = m.getMethodType(); + if (methodMatcher.matches(method) && type != null && !newReturnType.equals(type.getReturnType().toString())) { + type = type.withReturnType(JavaType.buildType(newReturnType)); + m = m.withMethodType(type); + if (m.getName().getType() != null) { + m = m.withName(m.getName().withType(type)); + } + methodUpdated = true; + } + return m; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + methodUpdated = false; + JavaType.FullyQualified originalType = multiVariable.getTypeAsFullyQualified(); + J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx); + + if (methodUpdated) { + JavaType newType = JavaType.buildType(newReturnType); + JavaType.FullyQualified newFieldType = TypeUtils.asFullyQualified(newType); + + maybeAddImport(newFieldType); + maybeRemoveImport(originalType); + + mv = mv.withTypeExpression(mv.getTypeExpression() == null ? + null : + new J.Identifier(mv.getTypeExpression().getId(), + mv.getTypeExpression().getPrefix(), + Markers.EMPTY, + emptyList(), + newReturnType.substring(newReturnType.lastIndexOf('.') + 1), + newType, + null + ) + ); + + mv = mv.withVariables(ListUtils.map(mv.getVariables(), var -> { + JavaType.FullyQualified varType = TypeUtils.asFullyQualified(var.getType()); + if (varType != null && !varType.equals(newType)) { + return var.withType(newType).withName(var.getName().withType(newType)); + } + return var; + })); + } + + return mv; + } + }; + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeStaticFieldToMethod.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeStaticFieldToMethod.java index d4c278783cc..6155a3b433d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeStaticFieldToMethod.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeStaticFieldToMethod.java @@ -90,9 +90,9 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct @Override public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { if (getCursor().firstEnclosing(J.Import.class) == null && - TypeUtils.isOfClassType(fieldAccess.getTarget().getType(), oldClassName) && - fieldAccess.getSimpleName().equals(oldFieldName)) { - return useNewMethod(fieldAccess); + TypeUtils.isOfClassType(fieldAccess.getTarget().getType(), oldClassName) && + fieldAccess.getSimpleName().equals(oldFieldName)) { + return useNewMethod(fieldAccess, fieldAccess.getCoordinates().replace()); } return super.visitFieldAccess(fieldAccess, ctx); } @@ -101,43 +101,18 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { JavaType.Variable varType = ident.getFieldType(); if (varType != null && - TypeUtils.isOfClassType(varType.getOwner(), oldClassName) && - varType.getName().equals(oldFieldName)) { - return useNewMethod(ident); + TypeUtils.isOfClassType(varType.getOwner(), oldClassName) && + varType.getName().equals(oldFieldName)) { + return useNewMethod(ident, ident.getCoordinates().replace()); } return ident; } - private J useNewMethod(TypeTree tree) { + private J useNewMethod(TypeTree tree, JavaCoordinates coordinates) { String newClass = newClassName == null ? oldClassName : newClassName; - maybeRemoveImport(oldClassName); maybeAddImport(newClass); - - Cursor statementCursor = getCursor().dropParentUntil(Statement.class::isInstance); - Statement statement = statementCursor.getValue(); - J applied = makeNewMethod(newClass).apply(statementCursor, statement.getCoordinates().replace()); - - J.MethodInvocation method = null; - if (applied instanceof J.Block) { - J.Block block = (J.Block) applied; - method = block.getStatements().get(0).withPrefix(tree.getPrefix()); - } else if (applied instanceof J.NewArray) { - J.NewArray newArray = (J.NewArray) applied; - method = (J.MethodInvocation) newArray.getInitializer().get(0); - } - if (method == null || method.getMethodType() == null) { - throw new IllegalArgumentException("Error while changing a static field to a method. The generated template using a the new class [" + - newClass + "] and the method [" + newMethodName + "] resulted in a null method type."); - } - if (tree.getType() != null) { - JavaType.Method mt = method.getMethodType().withReturnType(tree.getType()); - method = method.withMethodType(mt); - if (method.getName().getType() != null) { - method = method.withName(method.getName().withType(mt)); - } - } - return method; + return makeNewMethod(newClass).apply(getCursor(), coordinates); } @NonNull @@ -145,7 +120,8 @@ private JavaTemplate makeNewMethod(String newClass) { String packageName = StringUtils.substringBeforeLast(newClass, "."); String simpleClassName = StringUtils.substringAfterLast(newClass, "."); - String methodInvocationTemplate = "{" + simpleClassName + (newTarget != null ? "." + newTarget + "." : ".") + newMethodName + "();}"; + + String methodInvocationTemplate = simpleClassName + (newTarget != null ? "." + newTarget + "." : ".") + newMethodName + "()"; @Language("java") String methodStub; if (newTarget == null) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 7dfbb7fed68..8cdb8cf4865 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -114,7 +114,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { SourceFileWithReferences.References references = sourceFile.getReferences(); TypeMatcher matcher = new TypeMatcher(oldFullyQualifiedTypeName); Map matches = new HashMap<>(); - for (Reference ref : references.findMatches(matcher, Reference.Kind.TYPE)) { + for (Reference ref : references.findMatches(matcher)) { matches.put(ref.getTree(), ref); } return new ReferenceChangeTypeVisitor(matches, matcher.createRenamer(newFullyQualifiedTypeName)).visit(tree, ctx, requireNonNull(getCursor().getParent())); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java index b15951d90e8..99234db3a4e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java @@ -364,4 +364,10 @@ public J.Wildcard visitWildcard(J.Wildcard wildcard, P p) { public J.Yield visitYield(J.Yield yield, P p) { return (J.Yield) super.visitYield(yield, p); } + + @Override + public J.Erroneous visitErroneous(J.Erroneous erroneous, P p) { + return (J.Erroneous) super.visitErroneous(erroneous, p); + } + } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 98913cb04a7..1aca352e5df 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -16,34 +16,29 @@ package org.openrewrite.java; import io.github.classgraph.ClassGraph; -import io.github.classgraph.Resource; -import io.github.classgraph.ResourceList; -import io.github.classgraph.ScanResult; import org.intellij.lang.annotations.Language; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.internal.parser.JavaParserClasspathLoader; +import org.openrewrite.java.internal.parser.RewriteClasspathJarClasspathLoader; +import org.openrewrite.java.internal.parser.TypeTable; import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.J; import org.openrewrite.style.NamedStyles; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.io.ByteArrayInputStream; import java.net.URI; import java.nio.charset.Charset; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.function.Function; -import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; -import static java.util.Objects.requireNonNull; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @@ -74,26 +69,11 @@ static List dependenciesFromClasspath(String... artifactNames) { List artifacts = new ArrayList<>(artifactNames.length); List missingArtifactNames = new ArrayList<>(artifactNames.length); for (String artifactName : artifactNames) { - Pattern jarPattern = Pattern.compile(artifactName + "(?:" + "-.*?" + ")?" + "\\.jar$"); - // In a multi-project IDE classpath, some classpath entries aren't jars - Pattern explodedPattern = Pattern.compile("/" + artifactName + "/"); - boolean lacking = true; - for (URI cpEntry : runtimeClasspath) { - if (!"file".equals(cpEntry.getScheme())) { - // exclude any `jar` entries which could result from `Bundle-ClassPath` in `MANIFEST.MF` - continue; - } - String cpEntryString = cpEntry.toString(); - Path path = Paths.get(cpEntry); - if (jarPattern.matcher(cpEntryString).find() || - explodedPattern.matcher(cpEntryString).find() && path.toFile().isDirectory()) { - artifacts.add(path); - lacking = false; - // Do not break because jarPattern matches "foo-bar-1.0.jar" and "foo-1.0.jar" to "foo" - } - } - if (lacking) { + List matchedArtifacts = filterArtifacts(artifactName, runtimeClasspath); + if (matchedArtifacts.isEmpty()) { missingArtifactNames.add(artifactName); + } else { + artifacts.addAll(matchedArtifacts); } } @@ -107,96 +87,65 @@ static List dependenciesFromClasspath(String... artifactNames) { return artifacts; } + /** + * Filters the classpath entries to find paths that match the given artifact name. + * + * @param artifactName The artifact name to search for. + * @param runtimeClasspath The list of classpath URIs to search within. + * @return List of Paths that match the artifact name. + */ + // VisibleForTesting + static List filterArtifacts(String artifactName, List runtimeClasspath) { + List artifacts = new ArrayList<>(); + // Bazel automatically replaces '-' with '_' when generating jar files. + String normalizedArtifactName = artifactName.replace('-', '_'); + Pattern jarPattern = Pattern.compile(String.format("(%s|%s)(?:-.*?)?\\.jar$", artifactName, normalizedArtifactName)); + // In a multi-project IDE classpath, some classpath entries aren't jars + Pattern explodedPattern = Pattern.compile("/" + artifactName + "/"); + for (URI cpEntry : runtimeClasspath) { + if (!"file".equals(cpEntry.getScheme())) { + // exclude any `jar` entries which could result from `Bundle-ClassPath` in `MANIFEST.MF` + continue; + } + String cpEntryString = cpEntry.toString(); + Path path = Paths.get(cpEntry); + if (jarPattern.matcher(cpEntryString).find() || + explodedPattern.matcher(cpEntryString).find() && path.toFile().isDirectory()) { + artifacts.add(path); + // Do not break because jarPattern matches "foo-bar-1.0.jar" and "foo-1.0.jar" to "foo" + } + } + return artifacts; + } + static List dependenciesFromResources(ExecutionContext ctx, String... artifactNamesWithVersions) { if (artifactNamesWithVersions.length == 0) { - return Collections.emptyList(); + return emptyList(); } List artifacts = new ArrayList<>(artifactNamesWithVersions.length); - Set missingArtifactNames = new LinkedHashSet<>(artifactNamesWithVersions.length); - missingArtifactNames.addAll(Arrays.asList(artifactNamesWithVersions)); - File resourceTarget = JavaParserExecutionContextView.view(ctx) - .getParserClasspathDownloadTarget(); - - Class caller; - try { - // StackWalker is only available in Java 15+, but right now we only use classloader isolated - // recipe instances in Java 17 environments, so we can safely use StackWalker there. - Class options = Class.forName("java.lang.StackWalker$Option"); - Object retainOption = options.getDeclaredField("RETAIN_CLASS_REFERENCE").get(null); - - Class walkerClass = Class.forName("java.lang.StackWalker"); - Method getInstance = walkerClass.getDeclaredMethod("getInstance", options); - Object walker = getInstance.invoke(null, retainOption); - Method getDeclaringClass = Class.forName("java.lang.StackWalker$StackFrame").getDeclaredMethod("getDeclaringClass"); - caller = (Class) walkerClass.getMethod("walk", Function.class).invoke(walker, (Function, Object>) s -> s - .map(f -> { - try { - return (Class) getDeclaringClass.invoke(f); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - }) - // Drop anything before the parser or builder class, as well as those classes themselves - .filter(new Predicate>() { - boolean parserOrBuilderFound = false; - - @Override - public boolean test(Class c1) { - if (c1.getName().equals(JavaParser.class.getName()) || - c1.getName().equals(Builder.class.getName())) { - parserOrBuilderFound = true; - return false; - } - return parserOrBuilderFound; - } - }) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Unable to find caller of JavaParser.dependenciesFromResources(..)"))); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | - NoSuchMethodException | InvocationTargetException e) { - caller = JavaParser.class; - } - - try (ScanResult result = new ClassGraph().acceptPaths("META-INF/rewrite/classpath") - .addClassLoader(caller.getClassLoader()) - .scan()) { - ResourceList resources = result.getResourcesWithExtension(".jar"); - - for (String artifactName : new ArrayList<>(missingArtifactNames)) { - Pattern jarPattern = Pattern.compile(artifactName + "-?.*\\.jar$"); - for (Resource resource : resources) { - if (jarPattern.matcher(resource.getPath()).find()) { - try { - Path artifact = resourceTarget.toPath().resolve(Paths.get(resource.getPath()).getFileName()); - if (!Files.exists(artifact)) { - try { - InputStream resourceAsStream = requireNonNull( - caller.getResourceAsStream("/" + resource.getPath()), - caller.getCanonicalName() + " resource not found: " + resource.getPath()); - Files.copy(resourceAsStream, artifact); - } catch (FileAlreadyExistsException ignore) { - // can happen when tests run in parallel, for example - } - } - missingArtifactNames.remove(artifactName); - artifacts.add(artifact); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - break; + Set missingArtifactNames = new LinkedHashSet<>(Arrays.asList(artifactNamesWithVersions)); + + try (RewriteClasspathJarClasspathLoader rewriteClasspathJarClasspathLoader = new RewriteClasspathJarClasspathLoader(ctx)) { + List loaders = new ArrayList<>(2); + Optional.ofNullable(TypeTable.fromClasspath(ctx, missingArtifactNames)).ifPresent(loaders::add); + loaders.add(rewriteClasspathJarClasspathLoader); + + for (JavaParserClasspathLoader loader : loaders) { + for (String missingArtifactName : new ArrayList<>(missingArtifactNames)) { + Path located = loader.load(missingArtifactName); + if (located != null) { + artifacts.add(located); + missingArtifactNames.remove(missingArtifactName); } } } + } - if (!missingArtifactNames.isEmpty()) { - throw new IllegalArgumentException( - "Unable to find classpath resource dependencies beginning with: " + - missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ", "", ".\n")) + - "The caller is of type " + caller.getName() + ".\n" + - "The resources resolvable from the caller's classpath are: " + - resources.stream().map(Resource::getPath).sorted().collect(joining(", ")) - ); - } + if (!missingArtifactNames.isEmpty()) { + throw new IllegalArgumentException( + "Unable to find classpath resource dependencies beginning with: " + + missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ", "", ".\n")) + ); } return artifacts; @@ -308,9 +257,9 @@ default boolean accept(Path path) { @SuppressWarnings("unchecked") abstract class Builder

> extends Parser.Builder { - protected Collection classpath = Collections.emptyList(); - protected Collection artifactNames = Collections.emptyList(); - protected Collection classBytesClasspath = Collections.emptyList(); + protected Collection classpath = emptyList(); + protected Collection artifactNames = emptyList(); + protected Collection classBytesClasspath = emptyList(); protected JavaTypeCache javaTypeCache = new JavaTypeCache(); @Nullable @@ -353,7 +302,7 @@ public B dependsOn(@Language("java") String... inputsAsStrings) { } public B classpath(Collection classpath) { - this.artifactNames = Collections.emptyList(); + this.artifactNames = emptyList(); this.classpath = classpath; return (B) this; } @@ -372,13 +321,13 @@ public B addClasspathEntry(Path entry) { public B classpath(String... artifactNames) { this.artifactNames = Arrays.asList(artifactNames); - this.classpath = Collections.emptyList(); + this.classpath = emptyList(); return (B) this; } @SuppressWarnings({"UnusedReturnValue", "unused"}) public B classpathFromResources(ExecutionContext ctx, String... classpath) { - this.artifactNames = Collections.emptyList(); + this.artifactNames = emptyList(); this.classpath = dependenciesFromResources(ctx, classpath); return (B) this; } @@ -399,7 +348,7 @@ protected Collection resolvedClasspath() { if (!artifactNames.isEmpty()) { classpath = new ArrayList<>(classpath); classpath.addAll(JavaParser.dependenciesFromClasspath(artifactNames.toArray(new String[0]))); - artifactNames = Collections.emptyList(); + artifactNames = emptyList(); } return classpath; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParserExecutionContextView.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParserExecutionContextView.java index 884104913ab..266ee8928a8 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParserExecutionContextView.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParserExecutionContextView.java @@ -45,11 +45,10 @@ public JavaParserExecutionContextView setParserClasspathDownloadTarget(File fold public File getParserClasspathDownloadTarget() { File target = getMessage(PARSER_CLASSPATH_DOWNLOAD_LOCATION); if (target == null) { - File defaultTarget = new File(System.getProperty("user.home") + "/.rewrite/classpath"); - if (!defaultTarget.mkdirs() && !defaultTarget.exists()) { - throw new UncheckedIOException(new IOException("Failed to create directory " + defaultTarget.getAbsolutePath())); - } - return defaultTarget; + target = new File(System.getProperty("user.home") + "/.rewrite/classpath"); + } + if (!target.mkdirs() && !target.exists()) { + throw new UncheckedIOException(new IOException("Failed to create directory " + target.getAbsolutePath())); } return target; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index f6b8081eca3..38e6fc9715b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -18,8 +18,10 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.PrintOutputCapture; +import org.openrewrite.Tree; import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.java.tree.J.*; import org.openrewrite.marker.Marker; @@ -27,6 +29,7 @@ import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.UnaryOperator; public class JavaPrinter

extends JavaVisitor> { @@ -92,6 +95,16 @@ protected void visitRightPadded(@Nullable JRightPadded rightPadded, } } + @Override + public M visitMarker(Marker marker, PrintOutputCapture

p) { + if (marker instanceof TrailingComma) { + p.append(','); + visitSpace(((TrailingComma) marker).getSuffix(), Space.Location.TRAILING_COMMA_SUFFIX, p); + } + //noinspection unchecked + return (M) marker; + } + @Override public J visitModifier(Modifier mod, PrintOutputCapture

p) { visit(mod.getAnnotations(), p); @@ -414,7 +427,8 @@ protected void printStatementTerminator(Statement s, PrintOutputCapture

p) { } if (s instanceof MethodDeclaration && ((MethodDeclaration) s).getBody() == null) { - p.append(';'); + if (!hasError(s)) + p.append(';'); return; } @@ -428,8 +442,8 @@ protected void printStatementTerminator(Statement s, PrintOutputCapture

p) { getCursor() .dropParentUntil( c -> c instanceof Switch || - c instanceof SwitchExpression || - c == Cursor.ROOT_VALUE + c instanceof SwitchExpression || + c == Cursor.ROOT_VALUE ) .getValue(); if (aSwitch instanceof SwitchExpression) { @@ -445,6 +459,19 @@ protected void printStatementTerminator(Statement s, PrintOutputCapture

p) { } } + private static boolean hasError(Tree tree) { + AtomicBoolean isError = new AtomicBoolean(false); + new JavaIsoVisitor() { + + @Override + public Erroneous visitErroneous(Erroneous erroneous, AtomicBoolean atomicBoolean) { + atomicBoolean.set(true); + return erroneous; + } + }.visit(tree, isError); + return isError.get(); + } + @Override public J visitBreak(Break breakStatement, PrintOutputCapture

p) { beforeSyntax(breakStatement, Space.Location.BREAK_PREFIX, p); @@ -457,11 +484,15 @@ public J visitBreak(Break breakStatement, PrintOutputCapture

p) { @Override public J visitCase(Case case_, PrintOutputCapture

p) { beforeSyntax(case_, Space.Location.CASE_PREFIX, p); - Expression elem = case_.getExpressions().get(0); + J elem = case_.getCaseLabels().get(0); if (!(elem instanceof Identifier) || !((Identifier) elem).getSimpleName().equals("default")) { p.append("case"); } - visitContainer("", case_.getPadding().getExpressions(), JContainer.Location.CASE_EXPRESSION, ",", "", p); + visitContainer("", case_.getPadding().getCaseLabels(), JContainer.Location.CASE_LABEL, ",", "", p); + if (case_.getGuard() != null) { + p.append("when"); + visit(case_.getGuard(), p); + } visitSpace(case_.getPadding().getStatements().getBefore(), Space.Location.CASE, p); p.append(case_.getType() == Case.Type.Statement ? ":" : "->"); visitStatements(case_.getPadding().getStatements().getPadding() @@ -521,7 +552,7 @@ public J visitClassDeclaration(ClassDeclaration classDecl, PrintOutputCapture

visitContainer("<", classDecl.getPadding().getTypeParameters(), JContainer.Location.TYPE_PARAMETERS, ",", ">", p); visitContainer("(", classDecl.getPadding().getPrimaryConstructor(), JContainer.Location.RECORD_STATE_VECTOR, ",", ")", p); visitLeftPadded("extends", classDecl.getPadding().getExtends(), JLeftPadded.Location.EXTENDS, p); - visitContainer(classDecl.getKind().equals(ClassDeclaration.Kind.Type.Interface) ? "extends" : "implements", + visitContainer(classDecl.getKind() == ClassDeclaration.Kind.Type.Interface ? "extends" : "implements", classDecl.getPadding().getImplements(), JContainer.Location.IMPLEMENTS, ",", null, p); visitContainer("permits", classDecl.getPadding().getPermits(), JContainer.Location.PERMITS, ",", null, p); visit(classDecl.getBody(), p); @@ -1196,6 +1227,14 @@ public J visitYield(Yield yield, PrintOutputCapture

p) { return yield; } + @Override + public J visitErroneous(Erroneous error, PrintOutputCapture

p) { + beforeSyntax(error, Space.Location.ERRONEOUS, p); + p.append(error.getText()); + afterSyntax(error, p); + return error; + } + private static final UnaryOperator JAVA_MARKER_WRAPPER = out -> "/*~~" + out + (out.isEmpty() ? "" : "~~") + ">*/"; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java index f06a3501ec4..c87b5d74f84 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java @@ -69,6 +69,8 @@ public JavaType visitNonNull(JavaType javaType, P p) { if (javaType instanceof JavaType.Array) { javaType = visitArray((JavaType.Array) javaType, p); + } else if (javaType instanceof JavaType.Annotation) { + javaType = visitAnnotation((JavaType.Annotation) javaType, p); } else if (javaType instanceof JavaType.Class) { javaType = visitClass((JavaType.Class) javaType, p); } else if (javaType instanceof JavaType.GenericTypeVariable) { @@ -105,6 +107,12 @@ public JavaType visitMultiCatch(JavaType.MultiCatch multiCatch, P p) { return multiCatch.withThrowableTypes(ListUtils.map(multiCatch.getThrowableTypes(), tt -> visit(tt, p))); } + public JavaType visitAnnotation(JavaType.Annotation annotation, P p) { + JavaType.Annotation a = annotation; + a = a.withType((JavaType.FullyQualified) visit(a.getType(), p)); + return a; + } + public JavaType visitArray(JavaType.Array array, P p) { JavaType.Array a = array; a = a.withElemType(visit(a.getElemType(), p)); @@ -148,7 +156,7 @@ public JavaType visitMethod(JavaType.Method method, P p) { m = m.withDeclaringType((JavaType.FullyQualified) visit(m.getDeclaringType(), p)); m = m.withReturnType(visit(m.getReturnType(), p)); m = m.withParameterTypes(ListUtils.map(m.getParameterTypes(), pt -> visit(pt, p))); - m = m.withThrownExceptions(ListUtils.map(m.getThrownExceptions(), t -> (JavaType.FullyQualified) visit(t, p))); + m = m.withThrownExceptions(ListUtils.map(m.getThrownExceptions(), t -> visit(t, p))); m = m.withAnnotations(ListUtils.map(m.getAnnotations(), a -> (JavaType.FullyQualified) visit(a, p))); return m; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index fe218390396..8f8e05bb1bd 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -423,7 +423,8 @@ public J visitCase(J.Case case_, P p) { } else { c = (J.Case) temp; } - c = c.getPadding().withExpressions(visitContainer(c.getPadding().getExpressions(), JContainer.Location.CASE_EXPRESSION, p)); + c = c.getPadding().withCaseLabels(visitContainer(c.getPadding().getCaseLabels(), JContainer.Location.CASE_LABEL, p)); + c = c.withGuard(visitAndCast(c.getGuard(), p)); c = c.getPadding().withBody(visitRightPadded(c.getPadding().getBody(), JRightPadded.Location.CASE_BODY, p)); c = c.getPadding().withStatements(visitContainer(c.getPadding().getStatements(), JContainer.Location.CASE, p)); return c; @@ -1406,7 +1407,7 @@ public J visitYield(J.Yield yield, P p) { } public @Nullable JContainer visitContainer(@Nullable JContainer container, - JContainer.Location loc, P p) { + JContainer.Location loc, P p) { if (container == null) { //noinspection ConstantConditions return null; @@ -1423,6 +1424,13 @@ public J visitYield(J.Yield yield, P p) { JContainer.build(before, js, container.getMarkers()); } + public J visitErroneous(J.Erroneous erroneous, P p) { + J.Erroneous u = erroneous; + u = u.withPrefix(visitSpace(u.getPrefix(), Space.Location.ERRONEOUS, p)); + u = u.withMarkers(visitMarkers(u.getMarkers(), p)); + return u; + } + /** * Check if a child AST element is in the same lexical scope as that of the AST element associated with the base * cursor. (i.e.: Are the variables and declarations visible in the base scope also visible to the child AST @@ -1456,7 +1464,7 @@ protected boolean isInSameNameScope(Cursor base, Cursor child) { Object childScope = it.next(); if (childScope instanceof J.ClassDeclaration) { J.ClassDeclaration childClass = (J.ClassDeclaration) childScope; - if (!(childClass.getKind().equals(J.ClassDeclaration.Kind.Type.Class)) || + if (childClass.getKind() != J.ClassDeclaration.Kind.Type.Class || childClass.hasModifier(J.Modifier.Type.Static)) { //Short circuit the search if a terminating element is encountered. return false; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/PackageMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/PackageMatcher.java index 9c5f91105dc..55a93b768ab 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/PackageMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/PackageMatcher.java @@ -38,7 +38,7 @@ public PackageMatcher(@Nullable String targetPackage, boolean recursive) { @Override public boolean matchesReference(Reference reference) { - if (reference.getKind().equals(Reference.Kind.TYPE) || reference.getKind().equals(Reference.Kind.PACKAGE)) { + if (reference.getKind() == Reference.Kind.TYPE || reference.getKind() == Reference.Kind.PACKAGE) { String recursivePackageNamePrefix = targetPackage + "."; if (reference.getValue().equals(targetPackage) || recursive && reference.getValue().startsWith(recursivePackageNamePrefix)) { return true; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java index 7cf86789aeb..4682c999ffa 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java @@ -69,26 +69,32 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) return j; } - private @Nullable J removeMethods(Expression expression, int depth, boolean isLambdaBody, Stack selectAfter) { - if (!(expression instanceof J.MethodInvocation)) { + private @Nullable J removeMethods(@Nullable Expression expression, int depth, boolean isLambdaBody, Stack selectAfter) { + if (!(expression instanceof J.MethodInvocation) || ((J.MethodInvocation) expression).getMethodType() == null) { return expression; } - boolean isStatement = isStatement(); J.MethodInvocation m = (J.MethodInvocation) expression; + boolean isStatic = m.getMethodType().hasFlags(Flag.Static); - if (m.getMethodType() == null || m.getSelect() == null) { + if (isStatic && !isStatementInParentBlock(m)) { return expression; } + boolean isStatement = isStatement(); + if (matchers.entrySet().stream().anyMatch(entry -> matches(m, entry.getKey(), entry.getValue()))) { - boolean hasSameReturnType = TypeUtils.isAssignableTo(m.getMethodType().getReturnType(), m.getSelect().getType()); - boolean removable = (isStatement && depth == 0) || hasSameReturnType; + boolean hasSameReturnType = m.getSelect() != null && TypeUtils.isAssignableTo(m.getMethodType().getReturnType(), m.getSelect().getType()); + boolean removable = ((isStatement || isStatic) && depth == 0) || hasSameReturnType; if (!removable) { return expression; } - if (m.getSelect() instanceof J.Identifier || m.getSelect() instanceof J.NewClass) { + maybeRemoveImport(m.getMethodType().getDeclaringType()); + + if (m.getSelect() == null) { + return null; + } else if (m.getSelect() instanceof J.Identifier || m.getSelect() instanceof J.NewClass) { boolean keepSelect = depth != 0; if (keepSelect) { selectAfter.add(getSelectAfter(m)); @@ -131,6 +137,11 @@ private boolean isStatement() { ).getValue() instanceof J.Block; } + private boolean isStatementInParentBlock(Statement method) { + J.Block parentBlock = getCursor().firstEnclosing(J.Block.class); + return parentBlock == null || parentBlock.getStatements().contains(method); + } + private boolean isLambdaBody() { if (getCursor().getParent() == null) { return false; @@ -241,7 +252,7 @@ public J.Block visitBlock(J.Block block, ExecutionContext ctx) { @Value @With - static class ToBeRemoved implements Marker { + private static class ToBeRemoved implements Marker { UUID id; static J2 withMarker(J2 j) { return j.withMarkers(j.getMarkers().addIfAbsent(new ToBeRemoved(randomId()))); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstant.java b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstant.java index eedef216d95..b8f7f001e14 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstant.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstant.java @@ -19,6 +19,7 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; @@ -53,7 +54,7 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new JavaVisitor() { + JavaVisitor replacementVisitor = new JavaVisitor() { J.@Nullable Literal literal; @Override @@ -117,5 +118,9 @@ private J.Literal buildLiteral() { return literal; } }; + return Preconditions.check( + new UsesType<>(owningType, true), + replacementVisitor + ); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java new file mode 100644 index 00000000000..64bea17ed38 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.java; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ReplaceStringLiteralValue extends Recipe { + + @Option(displayName = "Old literal `String` value", + description = "The `String` value to replace.", + example = "apple") + String oldLiteralValue; + + @Option(displayName = "New literal `String` value", + description = "The `String` value to replace with.", + example = "orange") + String newLiteralValue; + + @JsonCreator + public ReplaceStringLiteralValue(@JsonProperty("oldStringValue") String oldStringValue, @JsonProperty("newStringValue") String newStringValue) { + this.oldLiteralValue = oldStringValue; + this.newLiteralValue = newStringValue; + } + + @Override + public String getDisplayName() { + return "Replace `String` literal"; + } + + @Override + public String getDescription() { + return "Replace the value of a complete `String` literal."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + @Override + public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) { + J.Literal lit = super.visitLiteral(literal, ctx); + if (lit.getType() == JavaType.Primitive.String && + oldLiteralValue.equals(lit.getValue())) { + return lit + .withValue(newLiteralValue) + .withValueSource('"' + newLiteralValue + '"'); + } + return lit; + } + }; + } + +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java index 1cf2f2ff53f..4d4f0e4133b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java @@ -112,12 +112,11 @@ private static boolean isPlainIdentifier(MethodSignatureParser.TargetTypePattern @Override public boolean matchesReference(Reference reference) { - return reference.getKind().equals(Reference.Kind.TYPE) && matchesTargetTypeName(reference.getValue()); + return reference.getKind() == Reference.Kind.TYPE && matchesTargetTypeName(reference.getValue()); } @Override public Reference.Renamer createRenamer(String newName) { return reference -> newName; } - } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java index 8eee53b3624..a28e6547241 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java @@ -69,7 +69,7 @@ public JavaType visitMethod(JavaType.Method method, P p) { (JavaType.FullyQualified) visit(method.getDeclaringType(), p), visit(method.getReturnType(), p), mapInPlace(method.getParameterTypes().toArray(EMPTY_JAVA_TYPE_ARRAY), pt -> visit(pt, p)), - mapInPlace(method.getThrownExceptions().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), t -> (JavaType.FullyQualified) visit(t, p)), + mapInPlace(method.getThrownExceptions().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p)), mapInPlace(method.getAnnotations().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), a -> (JavaType.FullyQualified) visit(a, p)) ); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/VariableNameUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/VariableNameUtils.java index e9a880d0600..fc4f1ddc048 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/VariableNameUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/VariableNameUtils.java @@ -50,7 +50,7 @@ public static String generateVariableName(String baseName, Cursor scope, Generat Set namesInScope = findNamesInScope(scope); // Generate a new name to prevent namespace shadowing. String newName = baseName; - if (GenerationStrategy.INCREMENT_NUMBER.equals(strategy)) { + if (GenerationStrategy.INCREMENT_NUMBER == strategy) { StringBuilder postFix = new StringBuilder(); char[] charArray = baseName.toCharArray(); for (int i = charArray.length - 1; i >= 0; i--) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index 742fcca4a04..d1efe641755 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -240,6 +240,7 @@ private J.Binary.Type maybeNegate(J.Binary.Type operator) { } private final MethodMatcher isEmpty = new MethodMatcher("java.lang.String isEmpty()"); + private final MethodMatcher equals = new MethodMatcher("java.lang.String equals(java.lang.Object)"); @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { @@ -250,6 +251,13 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext execu select instanceof J.Literal && select.getType() == JavaType.Primitive.String) { return booleanLiteral(method, J.Literal.isLiteralValue(select, "")); + } else if (equals.matches(asMethod)) { + Expression arg = asMethod.getArguments().get(0); + if (arg instanceof J.Literal && select instanceof J.Literal) { + return booleanLiteral(method, ((J.Literal) select).getValue().equals(((J.Literal) arg).getValue())); + } else if (SemanticallyEqual.areEqual(select, arg)) { + return booleanLiteral(method, true); + } } return j; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java index 1e6f9e72aae..a67c26f7ff5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java @@ -24,6 +24,8 @@ import org.openrewrite.java.tree.*; import org.openrewrite.style.GeneralFormatStyle; +import static org.openrewrite.format.LineBreaks.normalizeNewLines; + public class NormalizeLineBreaksVisitor

extends JavaIsoVisitor

{ @Nullable private final Tree stopAfter; @@ -66,22 +68,6 @@ public Space visitSpace(Space space, Space.Location loc, P p) { })); } - private static String normalizeNewLines(String text, boolean useCrlf) { - if (!text.contains("\n")) { - return text; - } - StringBuilder normalized = new StringBuilder(); - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); - if (useCrlf && c == '\n' && (i == 0 || text.charAt(i - 1) != '\r')) { - normalized.append('\r').append('\n'); - } else if (useCrlf || c != '\r') { - normalized.append(c); - } - } - return normalized.toString(); - } - @Override public J postVisit(J tree, P p) { if (stopAfter != null && stopAfter.isScope(tree)) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index d333c9c04d9..33c977107f7 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -17,8 +17,11 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; +import org.openrewrite.PrintOutputCapture; import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.RecipeRunException; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.style.TabsAndIndentsStyle; @@ -26,6 +29,7 @@ import java.util.Iterator; import java.util.List; +import java.util.concurrent.CancellationException; public class TabsAndIndentsVisitor

extends JavaIsoVisitor

{ @Nullable @@ -143,18 +147,18 @@ public Space visitSpace(Space space, Space.Location loc, P p) { // block spaces are always aligned to their parent Object value = getCursor().getValue(); - boolean alignBlockPrefixToParent = loc.equals(Space.Location.BLOCK_PREFIX) && space.getWhitespace().contains("\n") && + boolean alignBlockPrefixToParent = loc == Space.Location.BLOCK_PREFIX && space.getWhitespace().contains("\n") && // ignore init blocks. (value instanceof J.Block && !(getCursor().getParentTreeCursor().getValue() instanceof J.Block)); - boolean alignBlockToParent = loc.equals(Space.Location.BLOCK_END) || - loc.equals(Space.Location.NEW_ARRAY_INITIALIZER_SUFFIX) || - loc.equals(Space.Location.CATCH_PREFIX) || - loc.equals(Space.Location.TRY_FINALLY) || - loc.equals(Space.Location.ELSE_PREFIX); + boolean alignBlockToParent = loc == Space.Location.BLOCK_END || + loc == Space.Location.NEW_ARRAY_INITIALIZER_SUFFIX || + loc == Space.Location.CATCH_PREFIX || + loc == Space.Location.TRY_FINALLY || + loc == Space.Location.ELSE_PREFIX; - if ((loc.equals(Space.Location.EXTENDS) && space.getWhitespace().contains("\n")) || - Space.Location.EXTENDS.equals(getCursor().getParent().getMessage("lastLocation"))) { + if ((loc == Space.Location.EXTENDS && space.getWhitespace().contains("\n")) || + Space.Location.EXTENDS == getCursor().getParent().getMessage("lastLocation")) { indentType = IndentType.CONTINUATION_INDENT; } @@ -226,30 +230,27 @@ public Space visitSpace(Space space, Space.Location loc, P p) { } JContainer container = getCursor().getParentOrThrow().getValue(); List elements = container.getElements(); - J firstArg = elements.iterator().next(); J lastArg = elements.get(elements.size() - 1); - if (style.getMethodDeclarationParameters().getAlignWhenMultiple()) { + if (elements.size() > 1 && style.getMethodDeclarationParameters().getAlignWhenMultiple()) { J.MethodDeclaration method = getCursor().firstEnclosing(J.MethodDeclaration.class); if (method != null) { - int alignTo; - if (firstArg.getPrefix().getLastWhitespace().contains("\n")) { - alignTo = getLengthOfWhitespace(firstArg.getPrefix().getLastWhitespace()); + int alignTo = computeFirstParameterColumn(method); + if (alignTo != -1) { + getCursor().getParentOrThrow().putMessage("lastIndent", alignTo - style.getContinuationIndent()); + elem = visitAndCast(elem, p); + getCursor().getParentOrThrow().putMessage("lastIndent", indent); + after = indentTo(right.getAfter(), t == lastArg ? indent : alignTo, loc.getAfterLocation()); } else { - String source = method.print(getCursor()); - int firstArgIndex = source.indexOf(firstArg.print(getCursor())); - int lineBreakIndex = source.lastIndexOf('\n', firstArgIndex); - alignTo = (firstArgIndex - (lineBreakIndex == -1 ? 0 : lineBreakIndex)) - 1; + after = right.getAfter(); } - getCursor().getParentOrThrow().putMessage("lastIndent", alignTo - style.getContinuationIndent()); - elem = visitAndCast(elem, p); - getCursor().getParentOrThrow().putMessage("lastIndent", indent); - after = indentTo(right.getAfter(), t == lastArg ? indent : alignTo, loc.getAfterLocation()); } else { after = right.getAfter(); } - } else { + } else if (elements.size() > 1) { elem = visitAndCast(elem, p); after = indentTo(right.getAfter(), t == lastArg ? indent : style.getContinuationIndent(), loc.getAfterLocation()); + } else { + after = right.getAfter(); } break; } @@ -353,8 +354,43 @@ public Space visitSpace(Space space, Space.Location loc, P p) { return (after == right.getAfter() && t == right.getElement()) ? right : new JRightPadded<>(t, after, right.getMarkers()); } + private int computeFirstParameterColumn(J.MethodDeclaration method) { + List> arguments = method.getPadding().getParameters().getPadding().getElements(); + J firstArg = arguments.isEmpty() ? null : arguments.get(0).getElement(); + if (firstArg == null || firstArg instanceof J.Empty) { + return -1; + } + + if (firstArg.getPrefix().getLastWhitespace().contains("\n")) { + return getLengthOfWhitespace(firstArg.getPrefix().getLastWhitespace()); + } else { + TreeVisitor>> printer = method.printer(getCursor()); + PrintOutputCapture> capture = new PrintOutputCapture>(printer) { + @Override + public PrintOutputCapture> append(@Nullable String text) { + if (getContext().getCursor().getValue() == firstArg) { + throw new CancellationException(); + } + return super.append(text); + } + }; + try { + printer.visit(method, capture, getCursor().getParentOrThrow()); + throw new IllegalStateException(); + } catch (RecipeRunException e) { + String out = capture.getOut(); + int lineBreakIndex = out.lastIndexOf('\n'); + return (out.length() - (lineBreakIndex == -1 ? 0 : lineBreakIndex)) - 1; + } + } + } + @Override - public JContainer visitContainer(JContainer container, JContainer.Location loc, P p) { + public @Nullable JContainer visitContainer(@Nullable JContainer container, JContainer.Location loc, P p) { + if (container == null) { + return null; + } + setCursor(new Cursor(getCursor(), container)); Space before; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java index a7fb142280a..d379b047055 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java @@ -153,7 +153,7 @@ private JavaType.FullyQualified classTypeWithoutParameters(Class clazz) { annotations = new ArrayList<>(clazz.getDeclaredAnnotations().length); for (Annotation a : clazz.getDeclaredAnnotations()) { JavaType.FullyQualified type = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(type); + annotations.add(new JavaType.Annotation(type, emptyList())); } } @@ -298,11 +298,11 @@ private JavaType.Variable field(Field field) { annotations = new ArrayList<>(field.getDeclaredAnnotations().length); for (Annotation a : field.getDeclaredAnnotations()) { JavaType.FullyQualified type = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(type); + annotations.add(new JavaType.Annotation(type, emptyList())); } } - mappedVariable.unsafeSet(type(field.getDeclaringClass()), type(field.getType()), annotations); + mappedVariable.unsafeSet(type(field.getDeclaringClass()), type(field.getGenericType()), annotations); return mappedVariable; } @@ -341,11 +341,11 @@ private JavaType.Method method(Constructor method, JavaType.FullyQualified de "", null, paramNames, - null, null, null, null + null, null, null, null, null ); typeCache.put(signature, mappedMethod); - List thrownExceptions = null; + List thrownExceptions = null; if (method.getExceptionTypes().length > 0) { thrownExceptions = new ArrayList<>(method.getExceptionTypes().length); for (Class e : method.getExceptionTypes()) { @@ -359,7 +359,7 @@ private JavaType.Method method(Constructor method, JavaType.FullyQualified de annotations = new ArrayList<>(method.getDeclaredAnnotations().length); for (Annotation a : method.getDeclaredAnnotations()) { JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(fullyQualified); + annotations.add(new JavaType.Annotation(fullyQualified, emptyList())); } } @@ -439,6 +439,17 @@ private JavaType.Method method(Method method, JavaType.FullyQualified declaringT defaultValues = Collections.singletonList(method.getDefaultValue().toString()); } } + + List declaredFormalTypeNames = null; + for (TypeVariable typeVariable : method.getTypeParameters()) { + if (typeVariable.getGenericDeclaration() == method) { + if (declaredFormalTypeNames == null) { + declaredFormalTypeNames = new ArrayList<>(); + } + declaredFormalTypeNames.add(typeVariable.getName()); + } + } + JavaType.Method mappedMethod = new JavaType.Method( null, method.getModifiers(), @@ -447,16 +458,18 @@ private JavaType.Method method(Method method, JavaType.FullyQualified declaringT null, paramNames, null, null, null, - defaultValues + defaultValues, + declaredFormalTypeNames == null ? + null : + declaredFormalTypeNames.toArray(new String[0]) ); typeCache.put(signature, mappedMethod); - List thrownExceptions = null; + List thrownExceptions = null; if (method.getExceptionTypes().length > 0) { thrownExceptions = new ArrayList<>(method.getExceptionTypes().length); - for (Class e : method.getExceptionTypes()) { - JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) type(e); - thrownExceptions.add(fullyQualified); + for (Type e : method.getGenericExceptionTypes()) { + thrownExceptions.add(type(e)); } } @@ -465,20 +478,20 @@ private JavaType.Method method(Method method, JavaType.FullyQualified declaringT annotations = new ArrayList<>(method.getDeclaredAnnotations().length); for (Annotation a : method.getDeclaredAnnotations()) { JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(fullyQualified); + annotations.add(new JavaType.Annotation(fullyQualified, emptyList())); } } List parameterTypes = emptyList(); if (method.getParameters().length > 0) { parameterTypes = new ArrayList<>(method.getParameters().length); - for (Parameter parameter : method.getParameters()) { - Type parameterizedType = parameter.getParameterizedType(); - parameterTypes.add(type(parameterizedType == null ? parameter.getType() : parameterizedType)); + for (Type parameter : method.getGenericParameterTypes()) { + parameterTypes.add(type(parameter)); } } - mappedMethod.unsafeSet(declaringType, type(method.getReturnType()), parameterTypes, thrownExceptions, annotations); + JavaType returnType = type(method.getGenericReturnType()); + mappedMethod.unsafeSet(declaringType, returnType, parameterTypes, thrownExceptions, annotations); return mappedMethod; } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/JavaParserCaller.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/JavaParserCaller.java new file mode 100644 index 00000000000..a8b64fade99 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/JavaParserCaller.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.internal.parser; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * This class is used to find the caller of {@link JavaParser#dependenciesFromResources(ExecutionContext, String...)}, + * which is used to load classpath resources for {@link JavaParser}. + */ +class JavaParserCaller { + private JavaParserCaller() { + } + + public static Class findCaller() { + Class caller; + try { + // StackWalker is only available in Java 15+, but right now we only use classloader isolated + // recipe instances in Java 17 environments, so we can safely use StackWalker there. + Class options = Class.forName("java.lang.StackWalker$Option"); + Object retainOption = options.getDeclaredField("RETAIN_CLASS_REFERENCE").get(null); + + Class walkerClass = Class.forName("java.lang.StackWalker"); + Method getInstance = walkerClass.getDeclaredMethod("getInstance", options); + Object walker = getInstance.invoke(null, retainOption); + Method getDeclaringClass = Class.forName("java.lang.StackWalker$StackFrame").getDeclaredMethod("getDeclaringClass"); + caller = (Class) walkerClass.getMethod("walk", Function.class).invoke(walker, (Function, Object>) s -> s + .map(f -> { + try { + return (Class) getDeclaringClass.invoke(f); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + }) + // Drop anything before the parser or builder class, as well as those classes themselves + .filter(new Predicate>() { + boolean parserOrBuilderFound = false; + + @Override + public boolean test(Class c1) { + if (c1.getName().equals(JavaParser.class.getName()) || + c1.getName().equals(JavaParser.Builder.class.getName())) { + parserOrBuilderFound = true; + return false; + } + return parserOrBuilderFound; + } + }) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Unable to find caller of JavaParser.dependenciesFromResources(..)"))); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | + NoSuchMethodException | InvocationTargetException e) { + caller = JavaParser.class; + } + return caller; + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/JavaParserClasspathLoader.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/JavaParserClasspathLoader.java new file mode 100644 index 00000000000..39ca37f99a8 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/JavaParserClasspathLoader.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.internal.parser; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; + +import java.nio.file.Path; + +/** + * Prepares classpath resources for use by {@link JavaParser}. + */ +public interface JavaParserClasspathLoader { + + /** + * Load a classpath resource. + * + * @param artifactName A descriptor for the classpath resource to load. + * @return The path a JAR or classes directory that is suitable for use + * as a classpath entry in a compilation step. + */ + @Nullable + Path load(String artifactName); +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/RewriteClasspathJarClasspathLoader.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/RewriteClasspathJarClasspathLoader.java new file mode 100644 index 00000000000..19c6db1db76 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/RewriteClasspathJarClasspathLoader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.internal.parser; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.Resource; +import io.github.classgraph.ResourceList; +import io.github.classgraph.ScanResult; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaParserExecutionContextView; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; +import static org.openrewrite.java.internal.parser.JavaParserCaller.findCaller; + +/** + * As of 6.1.0, we are using {@link TypeTable} to include {@link JavaParser} dependencies + * as resources in the classpath, because including the whole JAR caused bloat in the size of recipe JARs + * and would often get blocked by security scanners that weren't able to recognize that these JAR resources + * were only used to parse templats and never executed and so didn't represent a security threat. + *

+ * Provided that the type table concept works well, this class and the technique it enables + * will be removed in a future release. + */ +public class RewriteClasspathJarClasspathLoader implements JavaParserClasspathLoader, AutoCloseable { + private final Class caller = findCaller(); + private final ScanResult result; + private final ResourceList resources; + private final ExecutionContext ctx; + + public RewriteClasspathJarClasspathLoader(ExecutionContext ctx) { + this.ctx = ctx; + result = new ClassGraph().acceptPaths("META-INF/rewrite/classpath").scan(); + resources = result.getResourcesWithExtension(".jar"); + } + + @Override + public @Nullable Path load(String artifactName) { + Pattern jarPattern = Pattern.compile(artifactName + "-?.*\\.jar$"); + for (Resource resource : resources) { + if (jarPattern.matcher(resource.getPath()).find()) { + try { + Path artifact = getJarsFolder(ctx).resolve(Paths.get(resource.getPath()).getFileName()); + if (!Files.exists(artifact)) { + try { + InputStream resourceAsStream = requireNonNull( + caller.getResourceAsStream("/" + resource.getPath()), + caller.getCanonicalName() + " resource not found: " + resource.getPath()); + Files.copy(resourceAsStream, artifact); + } catch (FileAlreadyExistsException ignore) { + // can happen when tests run in parallel, for example + } + } + return artifact; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + return null; + } + + return null; + + } + + /** + * The /.jars folder will contain JARs that were packed into /META-INF/rewrite/classpath as + * part of the original integration to pack parser classpath resources into recipe JARs. Those + * JARs existed in /META-INF/rewrite/classpath with just file names of the form + * [ARTIFACT]-[VERSION].jar, and so there was always the possibility of collision on JARs + * with the same artifact name and different group IDs with this mechanism. As a result, we'll + * continue to write those JARs out to the /jars folder, distinct from the way we write out + * parser classpath resources from {@link TypeTable} going forward, so that there is no possibility + * that we pick up a JAR written from /META-INF/rewrite/classpath that has a more accurate GAV + * coordinate fully specified in a type table. + * + * @param ctx An execution context from which we can determine the root of the JARs folder + * @return The path to the /.jars folder + */ + private static Path getJarsFolder(ExecutionContext ctx) { + Path jarsFolder = JavaParserExecutionContextView.view(ctx) + .getParserClasspathDownloadTarget().toPath().resolve(".jars"); + if (!Files.exists(jarsFolder) && !jarsFolder.toFile().mkdirs()) { + throw new UncheckedIOException(new IOException("Failed to create directory " + jarsFolder)); + } + return jarsFolder; + } + + @Override + public void close() { + resources.close(); + result.close(); + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java new file mode 100644 index 00000000000..3dcb031f123 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java @@ -0,0 +1,432 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.internal.parser; + +import lombok.RequiredArgsConstructor; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.objectweb.asm.*; +import org.objectweb.asm.util.CheckClassAdapter; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Incubating; +import org.openrewrite.java.JavaParserExecutionContextView; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +import static java.util.Objects.requireNonNull; +import static org.objectweb.asm.ClassReader.SKIP_CODE; +import static org.objectweb.asm.Opcodes.V1_8; +import static org.openrewrite.java.internal.parser.JavaParserCaller.findCaller; + +/** + * Type tables are written as a TSV file with the following columns: + *

    + *
  • groupId
  • + *
  • artifactId
  • + *
  • version
  • + *
  • classAccess
  • + *
  • className
  • + *
  • classSignature
  • + *
  • classSuperclassSignature
  • + *
  • classSuperinterfaceSignatures[]
  • + *
  • access
  • + *
  • memberName
  • + *
  • descriptor
  • + *
  • signature
  • + *
  • parameterNames
  • + *
  • exceptions[]
  • + *
+ *

+ * Descriptor and signature are in JVMS 4.3 format. + * Because these type tables could get fairly large, the format is optimized for fast record access. TSV was chosen over CSV for its + * simplicity and for the reasons cited here. + *

+ * There is of course a lot of duplication in the class and GAV columns, but compression cuts down on + * the disk impact of that and the value is an overall single table representation. + *

+ * To read a compressed type table file (which is compressed with zlib), the following command can be used: + * printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" |cat - types.tsv.zip |gzip -dc + * It prepends the gzip magic header onto the zlib data and used gzip to decompress. + */ +@Incubating(since = "8.44.0") +@Value +public class TypeTable implements JavaParserClasspathLoader { + /** + * Verifies that the bytecodes written out for the types represented in a type table + * will not be invalid and therefore rejected by the JVM verifier when used in a compilation + * step. + */ + public static final String VERIFY_CLASS_WRITING = "org.openrewrite.java.TypeTableClassWritingVerification"; + + public static final String DEFAULT_RESOURCE_PATH = "META-INF/rewrite/classpath.tsv.zip"; + + private static final Map classesDirByArtifact = new LinkedHashMap<>(); + + public static @Nullable TypeTable fromClasspath(ExecutionContext ctx, Collection artifactNames) { + try (InputStream is = findCaller().getClassLoader().getResourceAsStream(DEFAULT_RESOURCE_PATH)) { + return is == null ? null : new TypeTable(ctx, is, artifactNames); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public TypeTable(ExecutionContext ctx, InputStream is, Collection artifactNames) { + try (InputStream inflate = new InflaterInputStream(is)) { + new Reader(ctx).read(inflate, artifactsNotYetWritten(artifactNames)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static Collection artifactsNotYetWritten(Collection artifactNames) { + Collection notWritten = new ArrayList<>(artifactNames); + for (String artifactName : artifactNames) { + for (GroupArtifactVersion groupArtifactVersion : classesDirByArtifact.keySet()) { + if (Pattern.compile(artifactName + ".*") + .matcher(groupArtifactVersion.getArtifactId() + "-" + groupArtifactVersion.getVersion()) + .matches()) { + notWritten.remove(artifactName); + } + } + } + return notWritten; + } + + /** + * Reads a type table from the classpath, and writes classes directories to disk for matching artifact names. + */ + @RequiredArgsConstructor + static class Reader { + private final ExecutionContext ctx; + private @Nullable GroupArtifactVersion gav; + private final Map> membersByClassName = new HashMap<>(); + + public void read(InputStream is, Collection artifactNames) throws IOException { + Set artifactNamePatterns = artifactNames.stream() + .map(name -> Pattern.compile(name + ".*")) + .collect(Collectors.toSet()); + + try (BufferedReader in = new BufferedReader(new InputStreamReader(is))) { + in.lines().skip(1).forEach(line -> { + String[] fields = line.split("\t", -1); + GroupArtifactVersion rowGav = new GroupArtifactVersion(fields[0], fields[1], fields[2]); + if (!rowGav.equals(gav)) { + writeClassesDir(); + } + + String artifactVersion = fields[1] + "-" + fields[2]; + + for (Pattern artifactNamePattern : artifactNamePatterns) { + if (artifactNamePattern.matcher(artifactVersion).matches()) { + gav = rowGav; + break; + } + } + + if (gav != null) { + Member member = new Member( + new ClassDefinition( + Integer.parseInt(fields[3]), + fields[4], + fields[5].isEmpty() ? null : fields[5], + fields[6].isEmpty() ? null : fields[6], + fields[7].isEmpty() ? null : fields[7].split("\\|") + ), + Integer.parseInt(fields[8]), + fields[9], + fields[10], + fields[11].isEmpty() ? null : fields[11], + fields[12].isEmpty() ? null : fields[12].split("\\|") + ); + membersByClassName + .computeIfAbsent(member.getClassDefinition(), cd -> new ArrayList<>()) + .add(member); + } + }); + } + writeClassesDir(); + } + + private void writeClassesDir() { + if (gav == null) { + return; + } + + Path classesDir = getClassesDir(ctx, gav); + classesDirByArtifact.put(gav, classesDir); + + membersByClassName.forEach((classDef, members) -> { + Path classFile = classesDir.resolve(classDef.getName() + ".class"); + if (!Files.exists(classFile.getParent()) && !classFile.getParent().toFile().mkdirs()) { + throw new UncheckedIOException(new IOException("Failed to create directory " + classesDir.getParent())); + } + + ClassWriter cw = new ClassWriter(0); + ClassVisitor classWriter = ctx.getMessage(VERIFY_CLASS_WRITING, false) ? + cw : new CheckClassAdapter(cw); + + classWriter.visit( + V1_8, + classDef.getAccess(), + classDef.getName(), + classDef.getSignature(), + classDef.getSuperclassSignature(), + classDef.getSuperinterfaceSignatures() + ); + + Set innerClasses = new HashSet<>(); + for (ClassDefinition innerClassDef : membersByClassName.keySet()) { + if (innerClassDef.getName().contains("$")) { + innerClasses.add(innerClassDef); + } + } + + for (ClassDefinition innerClass : innerClasses) { + classWriter.visitInnerClass( + innerClass.getName(), + classDef.getName(), + innerClass.getName().substring(innerClass.getName().lastIndexOf('$') + 1), + innerClass.getAccess() + ); + } + + for (Member member : members) { + if (member.getDescriptor().contains("(")) { + classWriter + .visitMethod( + member.getAccess(), + member.getName(), + member.getDescriptor(), + member.getSignature(), + member.getExceptions() + ) + .visitEnd(); + } else { + classWriter + .visitField( + member.getAccess(), + member.getName(), + member.getDescriptor(), + member.getSignature(), + null + ) + .visitEnd(); + } + } + + try { + Files.write(classFile, cw.toByteArray()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + + gav = null; + } + } + + private static Path getClassesDir(ExecutionContext ctx, GroupArtifactVersion gav) { + Path jarsFolder = JavaParserExecutionContextView.view(ctx) + .getParserClasspathDownloadTarget().toPath().resolve(".tt"); + if (!Files.exists(jarsFolder) && !jarsFolder.toFile().mkdirs()) { + throw new UncheckedIOException(new IOException("Failed to create directory " + jarsFolder)); + } + + Path classesDir = jarsFolder; + for (String g : gav.getGroupId().split("\\.")) { + classesDir = classesDir.resolve(g); + } + classesDir = classesDir.resolve(gav.getArtifactId()).resolve(gav.getVersion()); + + if (!Files.exists(classesDir) && !classesDir.toFile().mkdirs()) { + throw new UncheckedIOException(new IOException("Failed to create directory " + classesDir)); + } + + return classesDir; + } + + public static Writer newWriter(OutputStream out) { + return new Writer(out); + } + + @Override + public @Nullable Path load(String artifactName) { + for (Map.Entry gavAndClassesDir : classesDirByArtifact.entrySet()) { + GroupArtifactVersion gav = gavAndClassesDir.getKey(); + if (Pattern.compile(artifactName + ".*") + .matcher(gav.getArtifactId() + "-" + gav.getVersion()) + .matches()) { + return gavAndClassesDir.getValue(); + } + } + return null; + } + + public static class Writer implements AutoCloseable { + private final PrintStream out; + private final DeflaterOutputStream deflater; + + public Writer(OutputStream out) { + this.deflater = new DeflaterOutputStream(out); + this.out = new PrintStream(deflater); + this.out.println("groupId\tartifactId\tversion\tclassAccess\tclassName\tclassSignature\tclassSuperclassSignature\tclassSuperinterfaceSignatures\taccess\tname\tdescriptor\tsignature\tparameterNames\texceptions"); + } + + public Jar jar(String groupId, String artifactId, String version) { + return new Jar(groupId, artifactId, version); + } + + @Override + public void close() throws IOException { + deflater.flush(); + out.close(); + } + + @Value + public class Jar { + String groupId; + String artifactId; + String version; + + public void write(Path jar) { + try (JarFile jarFile = new JarFile(jar.toFile())) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + try (InputStream inputStream = jarFile.getInputStream(entry)) { + new ClassReader(inputStream).accept(new ClassVisitor(Opcodes.ASM9) { + @Nullable + ClassDefinition classDefinition; + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + classDefinition = classDefinition(access, name, signature, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + requireNonNull(classDefinition).writeField(access, name, descriptor, signature); + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + requireNonNull(classDefinition).writeMethod(access, name, descriptor, signature, null, exceptions); + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + }, SKIP_CODE); + } + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public ClassDefinition classDefinition(int access, String name, @Nullable String signature, + String superclassName, String @Nullable [] superinterfaceSignatures) { + return new ClassDefinition(this, access, name, signature, superclassName, superinterfaceSignatures); + } + } + + @Value + public class ClassDefinition { + Jar jar; + int classAccess; + String className; + + @Nullable + String classSignature; + + String classSuperclassName; + String @Nullable [] classSuperinterfaceSignatures; + + public void writeMethod(int access, String name, String descriptor, + @Nullable String signature, + String @Nullable [] parameterNames, + String @Nullable [] exceptions) { + if (((Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC) & access) == 0) { + out.printf( + "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s%n", + jar.groupId, jar.artifactId, jar.version, + classAccess, className, + classSignature == null ? "" : classSignature, + classSuperclassName, + classSuperinterfaceSignatures == null ? "" : String.join("|", classSuperinterfaceSignatures), + access, name, descriptor, + signature == null ? "" : signature, + parameterNames == null ? "" : String.join("|", parameterNames), + exceptions == null ? "" : String.join("|", exceptions) + ); + } + } + + public void writeField(int access, String name, String descriptor, + @Nullable String signature) { + if (((Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC) & access) == 0) { + // Fits into the same table structure + writeMethod(access, name, descriptor, signature, null, null); + } + } + } + } + + @Value + private static class GroupArtifactVersion { + String groupId; + String artifactId; + String version; + } + + @Value + private static class ClassDefinition { + int access; + String name; + + @Nullable + String signature; + + @Nullable + String superclassSignature; + + String @Nullable [] superinterfaceSignatures; + } + + @Value + private static class Member { + ClassDefinition classDefinition; + int access; + String name; + String descriptor; + + @Nullable + String signature; + + String @Nullable [] exceptions; + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/package-info.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/package-info.java new file mode 100644 index 00000000000..69e4e77650a --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +package org.openrewrite.java.internal.parser; + +import org.jspecify.annotations.NullMarked; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index 3be2c6e2b0b..d8715d558d5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -25,6 +25,7 @@ import org.openrewrite.Cursor; import org.openrewrite.SourceFile; import org.openrewrite.Tree; +import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.*; @@ -60,7 +61,7 @@ public String template(Cursor cursor, String template, Space.Location location, // for CoordinateBuilder.MethodDeclaration#replaceBody() if (cursor.getValue() instanceof J.MethodDeclaration && - location.equals(Space.Location.BLOCK_PREFIX)) { + location == Space.Location.BLOCK_PREFIX) { J.MethodDeclaration method = cursor.getValue(); J.MethodDeclaration m = method.withBody(null).withLeadingAnnotations(emptyList()).withPrefix(Space.EMPTY); before.insert(0, m.printTrimmed(cursor.getParentOrThrow()).trim() + '{'); @@ -308,7 +309,12 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin Optional arg = annotation.getArguments().stream().filter(a -> a == prior).findFirst(); if (arg.isPresent()) { StringBuilder beforeBuffer = new StringBuilder(); - beforeBuffer.append('@').append(((JavaType.Class) annotation.getType()).getFullyQualifiedName()).append('('); + String name = annotation.getType() instanceof JavaType.Class ? + ((JavaType.Class) annotation.getType()).getFullyQualifiedName() : + annotation.getType() instanceof JavaType.FullyQualified ? + ((JavaType.FullyQualified) annotation.getType()).getFullyQualifiedName() : + annotation.getSimpleName(); + beforeBuffer.append('@').append(name).append('('); before.insert(0, beforeBuffer); after.append(')').append('\n'); @@ -409,7 +415,7 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin } else if (j instanceof J.ForEachLoop.Control) { J.ForEachLoop.Control c = (J.ForEachLoop.Control) j; if (referToSameElement(prior, c.getVariable())) { - after.append(" = /*" + STOP_COMMENT + "/*").append(c.getIterable().printTrimmed(cursor)); + after.append(" = /*" + STOP_COMMENT + "*/").append(c.getIterable().printTrimmed(cursor)); } else if (referToSameElement(prior, c.getIterable())) { before.insert(0, "Object __b" + cursor.getPathAsStream().count() + "__ ="); after.append(";"); @@ -463,16 +469,18 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin // If prior is a type parameter, wrap in __M__.anyT() // For anything else, ignore the invocation J.MethodInvocation m = (J.MethodInvocation) j; + J firstEnclosing = cursor.getParentOrThrow().firstEnclosing(J.class); if (m.getArguments().stream().anyMatch(arg -> referToSameElement(prior, arg))) { before.insert(0, "__M__.any("); - if (cursor.getParentOrThrow().firstEnclosing(J.class) instanceof J.Block) { + if (firstEnclosing instanceof J.Block || firstEnclosing instanceof J.Case || + firstEnclosing instanceof J.If || firstEnclosing instanceof J.If.Else) { after.append(");"); } else { after.append(")"); } } else if (m.getTypeParameters() != null && m.getTypeParameters().stream().anyMatch(tp -> referToSameElement(prior, tp))) { before.insert(0, "__M__.anyT<"); - if (cursor.getParentOrThrow().firstEnclosing(J.class) instanceof J.Block) { + if (firstEnclosing instanceof J.Block || firstEnclosing instanceof J.Case) { after.append(">();"); } else { after.append(">()"); @@ -481,7 +489,7 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin List comments = new ArrayList<>(1); comments.add(new TextComment(true, STOP_COMMENT, "", Markers.EMPTY)); after.append(".").append(m.withSelect(null).withComments(comments).printTrimmed(cursor.getParentOrThrow())); - if (cursor.getParentOrThrow().firstEnclosing(J.class) instanceof J.Block) { + if (firstEnclosing instanceof J.Block || firstEnclosing instanceof J.Case) { after.append(";"); } } @@ -561,6 +569,8 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin before.insert(0, ev.getName()); } else if (j instanceof J.EnumValueSet) { after.append(";"); + } else if (j instanceof J.Case) { + after.append(";"); } contextTemplate(next(cursor), j, before, after, insertionPoint, REPLACEMENT); } @@ -814,6 +824,20 @@ public J visitMethodInvocation(J.MethodInvocation method, Integer integer) { } return mi; } + + @Override + public J visitVariableDeclarations(J.VariableDeclarations multiVariable, Integer integer) { + List variables = multiVariable.getVariables(); + for (J.VariableDeclarations.NamedVariable variable : variables) { + J.VariableDeclarations.NamedVariable.Padding padding = variable.getPadding(); + if (padding.getInitializer() != null && stopCommentExists(padding.getInitializer().getBefore().getComments())) { + // Split the variable declarations at the variable with the `STOP_COMMENT` & trim off initializer + List vars = variables.subList(0, variables.indexOf(variable) + 1); + return multiVariable.withVariables(ListUtils.mapLast(vars, v -> v.withInitializer(null))); + } + } + return super.visitVariableDeclarations(multiVariable, integer); + } } } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java index cfd9b9ea548..a3c8e1fd7ac 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java @@ -55,7 +55,7 @@ public TreeVisitor getMixin() { return new JavaVisitor() { @Override public J visitAnnotation(J.Annotation annotation, Integer integer) { - if (loc.equals(ANNOTATION_PREFIX) && mode.equals(JavaCoordinates.Mode.REPLACEMENT) && + if (loc == ANNOTATION_PREFIX && mode == JavaCoordinates.Mode.REPLACEMENT && annotation.isScope(insertionPoint)) { List gen = substitutions.unsubstitute(templateParser.parseAnnotations(getCursor(), substitutedTemplate)); if (gen.isEmpty()) { @@ -64,7 +64,7 @@ public J visitAnnotation(J.Annotation annotation, Integer integer) { "\nUse JavaTemplate.Builder.doBeforeParseTemplate() to see what stub is being generated and include it in any bug report."); } return gen.get(0).withPrefix(annotation.getPrefix()); - } else if (loc.equals(ANNOTATION_ARGUMENTS) && mode.equals(JavaCoordinates.Mode.REPLACEMENT) && + } else if (loc == ANNOTATION_ARGUMENTS && mode == JavaCoordinates.Mode.REPLACEMENT && annotation.isScope(insertionPoint)) { List gen = substitutions.unsubstitute(templateParser.parseAnnotations(getCursor(), "@Example(" + substitutedTemplate + ")")); return annotation.withArguments(gen.get(0).getArguments()); @@ -144,7 +144,7 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, Integer p) { case ANNOTATIONS: { List gen = substitutions.unsubstitute(templateParser.parseAnnotations(getCursor(), substitutedTemplate)); J.ClassDeclaration c = classDecl; - if (mode.equals(JavaCoordinates.Mode.REPLACEMENT)) { + if (mode == JavaCoordinates.Mode.REPLACEMENT) { c = c.withLeadingAnnotations(gen); if (c.getTypeParameters() != null) { c = c.withTypeParameters(ListUtils.map(c.getTypeParameters(), tp -> tp.withAnnotations(emptyList()))); @@ -176,7 +176,7 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, Integer p) { .collect(toList()); J.ClassDeclaration c = classDecl; - if (mode.equals(JavaCoordinates.Mode.REPLACEMENT)) { + if (mode == JavaCoordinates.Mode.REPLACEMENT) { c = c.withImplements(implementings); //noinspection ConstantConditions c = c.getPadding().withImplements(c.getPadding().getImplements().withBefore(Space.EMPTY)); @@ -202,8 +202,8 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, Integer p) { @Override public J visitExpression(Expression expression, Integer p) { - if ((loc.equals(EXPRESSION_PREFIX) || - loc.equals(STATEMENT_PREFIX) && expression instanceof Statement) && + if ((loc == EXPRESSION_PREFIX || + loc == STATEMENT_PREFIX && expression instanceof Statement) && expression.isScope(insertionPoint)) { return autoFormat(substitutions.unsubstitute(templateParser.parseExpression( getCursor(), @@ -216,13 +216,13 @@ public J visitExpression(Expression expression, Integer p) { @Override public J visitFieldAccess(J.FieldAccess fa, Integer p) { - if (loc.equals(FIELD_ACCESS_PREFIX) && fa.isScope(insertionPoint)) { + if (loc == FIELD_ACCESS_PREFIX && fa.isScope(insertionPoint)) { return autoFormat(substitutions.unsubstitute(templateParser.parseExpression( getCursor(), substitutedTemplate, loc)) .withPrefix(fa.getPrefix()), p); - } else if (loc.equals(STATEMENT_PREFIX) && fa.isScope(insertionPoint)) { + } else if (loc == STATEMENT_PREFIX && fa.isScope(insertionPoint)) { // NOTE: while `J.FieldAccess` inherits from `Statement` they can only ever be used as expressions return autoFormat(substitutions.unsubstitute(templateParser.parseExpression( getCursor(), @@ -236,7 +236,7 @@ public J visitFieldAccess(J.FieldAccess fa, Integer p) { @Override public J visitIdentifier(J.Identifier ident, Integer p) { // ONLY for backwards compatibility, otherwise the same as expression replacement - if (loc.equals(IDENTIFIER_PREFIX) && ident.isScope(insertionPoint)) { + if (loc == IDENTIFIER_PREFIX && ident.isScope(insertionPoint)) { return autoFormat(substitutions.unsubstitute(templateParser.parseExpression( getCursor(), substitutedTemplate, @@ -248,7 +248,7 @@ public J visitIdentifier(J.Identifier ident, Integer p) { @Override public J visitLambda(J.Lambda lambda, Integer p) { - if (loc.equals(LAMBDA_PARAMETERS_PREFIX) && lambda.getParameters().isScope(insertionPoint)) { + if (loc == LAMBDA_PARAMETERS_PREFIX && lambda.getParameters().isScope(insertionPoint)) { return lambda.withParameters(substitutions.unsubstitute(templateParser.parseLambdaParameters(getCursor(), substitutedTemplate))); } return maybeReplaceStatement(lambda, J.class, 0); @@ -261,7 +261,7 @@ public J visitMethodDeclaration(J.MethodDeclaration method, Integer p) { case ANNOTATIONS: { List gen = substitutions.unsubstitute(templateParser.parseAnnotations(getCursor(), substitutedTemplate)); J.MethodDeclaration m = method; - if (mode.equals(JavaCoordinates.Mode.REPLACEMENT)) { + if (mode == JavaCoordinates.Mode.REPLACEMENT) { m = method.withLeadingAnnotations(gen); if (m.getTypeParameters() != null) { m = m.withTypeParameters(ListUtils.map(m.getTypeParameters(), tp -> tp.withAnnotations(emptyList()))); @@ -354,11 +354,11 @@ public J visitMethodDeclaration(J.MethodDeclaration method, Integer p) { // Update method type information to reflect the new checked exceptions JavaType.Method type = m.getMethodType(); if (type != null) { - List newThrows = new ArrayList<>(); + List newThrows = new ArrayList<>(); List throws_ = (m.getThrows() == null) ? emptyList() : m.getThrows(); for (NameTree t : throws_) { J.Identifier exceptionIdent = (J.Identifier) t; - newThrows.add((JavaType.FullyQualified) exceptionIdent.getType()); + newThrows.add(exceptionIdent.getType()); } type = type.withThrownExceptions(newThrows); } @@ -384,9 +384,9 @@ public J visitMethodDeclaration(J.MethodDeclaration method, Integer p) { @Override public J visitMethodInvocation(J.MethodInvocation method, Integer integer) { - if ((loc.equals(METHOD_INVOCATION_ARGUMENTS) || loc.equals(METHOD_INVOCATION_NAME)) && method.isScope(insertionPoint)) { + if ((loc == METHOD_INVOCATION_ARGUMENTS || loc == METHOD_INVOCATION_NAME) && method.isScope(insertionPoint)) { J.MethodInvocation m; - if (loc.equals(METHOD_INVOCATION_ARGUMENTS)) { + if (loc == METHOD_INVOCATION_ARGUMENTS) { m = substitutions.unsubstitute(templateParser.parseMethodArguments(getCursor(), substitutedTemplate, loc)); m = autoFormat(m, 0); m = method.withArguments(m.getArguments()).withMethodType(m.getMethodType()); @@ -433,7 +433,7 @@ public J visitNewClass(J.NewClass newClass, Integer p) { @Override public J visitPackage(J.Package pkg, Integer integer) { - if (loc.equals(PACKAGE_PREFIX) && pkg.isScope(insertionPoint)) { + if (loc == PACKAGE_PREFIX && pkg.isScope(insertionPoint)) { return pkg.withExpression(substitutions.unsubstitute(templateParser.parsePackage(getCursor(), substitutedTemplate))); } return super.visitPackage(pkg, integer); @@ -445,8 +445,8 @@ public J visitStatement(Statement statement, Integer p) { } private J3 maybeReplaceStatement(Statement statement, Class expected, Integer p) { - if (loc.equals(STATEMENT_PREFIX) && statement.isScope(insertionPoint)) { - if (mode.equals(JavaCoordinates.Mode.REPLACEMENT)) { + if (loc == STATEMENT_PREFIX && statement.isScope(insertionPoint)) { + if (mode == JavaCoordinates.Mode.REPLACEMENT) { List gen = substitutions.unsubstitute(templateParser.parseBlockStatements(getCursor(), expected, substitutedTemplate, loc, mode)); if (gen.size() != 1) { @@ -481,7 +481,7 @@ public J visitVariableDeclarations(J.VariableDeclarations multiVariable, Integer if (loc == ANNOTATIONS) { J.VariableDeclarations v = multiVariable; final List gen = substitutions.unsubstitute(templateParser.parseAnnotations(getCursor(), substitutedTemplate)); - if (mode.equals(JavaCoordinates.Mode.REPLACEMENT)) { + if (mode == JavaCoordinates.Mode.REPLACEMENT) { v = v.withLeadingAnnotations(gen); if (v.getTypeExpression() instanceof J.AnnotatedType) { v = v.withTypeExpression(((J.AnnotatedType) v.getTypeExpression()).getTypeExpression()); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java index 16a9a68438c..6331cc78d69 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java @@ -146,7 +146,7 @@ private String substituteTypedPattern(String key, int index, TemplateParameterPa String fqn = getTypeName(type); JavaType.Primitive primitive = JavaType.Primitive.fromKeyword(fqn); - s = primitive == null || primitive.equals(JavaType.Primitive.String) ? + s = primitive == null || primitive == JavaType.Primitive.String ? newObjectParameter(fqn, index) : newPrimitiveParameter(fqn, index); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java index 847f84fdc87..100279d44dc 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java @@ -80,7 +80,7 @@ public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ct } } - AddOrUpdateAnnotationAttribute addOrUpdateAnnotationAttribute = new AddOrUpdateAnnotationAttribute(ORG_OPENREWRITE_OPTION, "example", "TODO Provide a usage example for the docs", true, false); + AddOrUpdateAnnotationAttribute addOrUpdateAnnotationAttribute = new AddOrUpdateAnnotationAttribute(ORG_OPENREWRITE_OPTION, "example", "TODO Provide a usage example for the docs", null, true, false); return (J.Annotation) addOrUpdateAnnotationAttribute.getVisitor().visitNonNull(an, ctx, getCursor().getParent()); } }); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindCompileErrors.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindCompileErrors.java new file mode 100644 index 00000000000..21fd93439c2 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindCompileErrors.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.java.search; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.table.CompileErrors; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +public class FindCompileErrors extends Recipe { + + transient CompileErrors report = new CompileErrors(this); + + @Override + public String getDisplayName() { + return "Find compile errors"; + } + + @Override + public String getDescription() { + return "Compile errors result in a particular LST structure that can be searched for."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + @Override + public J.Erroneous visitErroneous(J.Erroneous erroneous, ExecutionContext ctx) { + J.CompilationUnit cu = getCursor().firstEnclosing(J.CompilationUnit.class); + String sourceFile = cu != null ? cu.getSourcePath().toString() : "Unknown source file"; + String code = erroneous.print(getCursor()); + report.insertRow(ctx, new CompileErrors.Row( + sourceFile, + code + )); + return SearchResult.found(erroneous); + } + }; + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindTypes.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindTypes.java index 3b9c7f4d254..7a0dff047c5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindTypes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindTypes.java @@ -27,7 +27,6 @@ import org.openrewrite.java.table.TypeUses; import org.openrewrite.java.tree.*; import org.openrewrite.marker.SearchResult; -import org.openrewrite.trait.Reference; import org.openrewrite.trait.Trait; import java.util.HashSet; @@ -80,7 +79,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { SourceFileWithReferences sourceFile = (SourceFileWithReferences) tree; SourceFileWithReferences.References references = sourceFile.getReferences(); TypeMatcher matcher = new TypeMatcher(fullyQualifiedTypeName); - Set matches = references.findMatches(matcher, Reference.Kind.TYPE).stream().map(Trait::getTree).collect(Collectors.toSet()); + Set matches = references.findMatches(matcher).stream().map(Trait::getTree).collect(Collectors.toSet()); return new ReferenceVisitor(matches).visit(tree, ctx); } return tree; @@ -206,11 +205,14 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { private J2 found(J2 j, ExecutionContext ctx) { JavaType.FullyQualified fqn = TypeUtils.asFullyQualified(j.getType()); - typeUses.insertRow(ctx, new TypeUses.Row( - getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath().toString(), - j.printTrimmed(getCursor().getParentTreeCursor()), - fqn == null ? j.getType().toString() : fqn.getFullyQualifiedName() - )); + if (!j.getMarkers().findFirst(SearchResult.class).isPresent()) { + // Avoid double-counting results in the data table + typeUses.insertRow(ctx, new TypeUses.Row( + getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath().toString(), + j.printTrimmed(getCursor().getParentTreeCursor()), + fqn == null ? j.getType().toString() : fqn.getFullyQualifiedName() + )); + } return SearchResult.found(j); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java b/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java index 6260ae2ec1a..3618a95f763 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java @@ -366,7 +366,13 @@ public J.Case visitCase(J.Case _case, J j) { J.Case compareTo = (J.Case) j; this.visitList(_case.getStatements(), compareTo.getStatements()); visit(_case.getBody(), compareTo.getBody()); - this.visitList(_case.getExpressions(), compareTo.getExpressions()); + this.visitList(_case.getCaseLabels(), compareTo.getCaseLabels()); + if (_case.getGuard() != null && compareTo.getGuard() != null) { + visit(_case.getGuard(), compareTo.getGuard()); + } else if (nullMissMatch(_case.getGuard(), compareTo.getGuard())) { + isEqual.set(false); + return _case; + } } return _case; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java index 2e701f37491..4044dfcbc78 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java @@ -123,7 +123,7 @@ public boolean isAcceptable(SourceFile sourceFile, P p) { SourceFileWithReferences sourceFile = (SourceFileWithReferences) tree; SourceFileWithReferences.References references = sourceFile.getReferences(); TypeMatcher matcher = typeMatcher != null ? typeMatcher : new TypeMatcher(fullyQualifiedType); - for (Reference ignored : references.findMatches(matcher, Reference.Kind.TYPE)) { + for (Reference ignored : references.findMatches(matcher)) { return SearchResult.found(sourceFile); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/JavaNamingService.java b/rewrite-java/src/main/java/org/openrewrite/java/service/JavaNamingService.java index 5f2e84e9c04..f424ce8d9de 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/service/JavaNamingService.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/JavaNamingService.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2024 the original author or authors. *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java index 34a3a10abf5..d545053d8b8 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java @@ -577,7 +577,7 @@ public ImportLayoutStyle getImportLayoutStyle() { List countOfBlocksInStaticGroups = new ArrayList<>(); for (Block block : longestBlocks) { - if (BlockType.ImportStatic.equals(block.type)) { + if (BlockType.ImportStatic == block.type) { staticBlocks.add(block); countOfBlocksInStaticGroups.add(0); countOfBlocksInStaticGroups.set(staticCountPos, countOfBlocksInStaticGroups.get(staticCountPos) + 1); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/table/CompileErrors.java b/rewrite-java/src/main/java/org/openrewrite/java/table/CompileErrors.java new file mode 100644 index 00000000000..ff39951e875 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/table/CompileErrors.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.java.table; + +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class CompileErrors extends DataTable { + public CompileErrors(Recipe recipe) { + super(recipe, + "Compile errors", + "The source code of compile errors."); + } + + @Value + public static class Row { + @Column(displayName = "Source file", + description = "The source file that the method call occurred in.") + String sourceFile; + + @Column(displayName = "Source", + description = "The source code of the type use.") + String code; + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 7afe8698797..6ff5ff63cac 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -19,6 +19,7 @@ import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.openrewrite.*; @@ -1027,14 +1028,36 @@ public Case withPattern(@Nullable Expression pattern) { return withExpressions(ListUtils.mapFirst(getExpressions(), first -> pattern)); } - JContainer expressions; - + /** + * @deprecated As of Java 21 this is referred to as case labels and can be broader than just Expressions. + * Use {@link #getCaseLabels} and {@link #withCaseLabels(List)} instead. + */ + @Deprecated public List getExpressions() { - return expressions.getElements(); + return caseLabels.getElements().stream().filter(Expression.class::isInstance).map(Expression.class::cast).collect(toList()); } + /** + * @deprecated As of Java 21 this is referred to as case labels and can be broader than just Expressions. + * Use {@link #getCaseLabels} and {@link #withCaseLabels(List)} instead. + */ public Case withExpressions(List expressions) { - return getPadding().withExpressions(requireNonNull(JContainer.withElementsNullable(this.expressions, expressions))); + if (caseLabels.getElements().stream().allMatch(Expression.class::isInstance)) { + //noinspection unchecked + return getPadding().withCaseLabels(requireNonNull(JContainer.withElementsNullable(this.caseLabels, (List) (List) expressions))); + } else { + throw new IllegalStateException("caseLabels contains an entry that is not an Expression, use withCaseLabels instead."); + } + } + + JContainer caseLabels; + + public List getCaseLabels() { + return caseLabels.getElements(); + } + + public Case withCaseLabels(List caseLabels) { + return getPadding().withCaseLabels(requireNonNull(JContainer.withElementsNullable(this.caseLabels, caseLabels))); } /** @@ -1067,21 +1090,37 @@ public Case withBody(J body) { return getPadding().withBody(JRightPadded.withElement(this.body, body)); } + @Nullable + @Getter + @With + Expression guard; + @JsonCreator - public Case(UUID id, Space prefix, Markers markers, Type type, @Deprecated @Nullable Expression pattern, JContainer expressions, JContainer statements, @Nullable JRightPadded body) { + public Case(UUID id, Space prefix, Markers markers, Type type, @Deprecated @Nullable Expression pattern, @Nullable JContainer expressions, @Nullable JContainer caseLabels, @Nullable Expression guard, JContainer statements, @Nullable JRightPadded body) { this.id = id; this.prefix = prefix; this.markers = markers; this.type = type; if (pattern != null) { - this.expressions = requireNonNull(JContainer.withElementsNullable(null, singletonList(pattern))); + this.caseLabels = requireNonNull(JContainer.withElementsNullable(null, singletonList(pattern))); + } else if (expressions != null) { + this.caseLabels = JContainer.build(expressions.getBefore(), expressions.getElements().stream().map(J.class::cast).map(JRightPadded::build).collect(toList()), expressions.getMarkers()); + } else if (caseLabels != null) { + this.caseLabels = caseLabels; } else { - this.expressions = expressions; + this.caseLabels = JContainer.empty(); } + this.guard = guard; this.statements = statements; this.body = body; } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2025-05-01") + public Case(UUID id, Space prefix, Markers markers, Type type, JContainer expressions, JContainer statements, @Nullable JRightPadded body) { + this(id, prefix, markers, type, null, expressions, null, null, statements, body); + } + @Override public

J acceptJava(JavaVisitor

v, P p) { return v.visitCase(this, p); @@ -1127,7 +1166,7 @@ public static class Padding { } public Case withBody(@Nullable JRightPadded body) { - return t.body == body ? t : new Case(t.id, t.prefix, t.markers, t.type, null, t.expressions, t.statements, body); + return t.body == body ? t : new Case(t.id, t.prefix, t.markers, t.type, null, null, t.caseLabels, t.guard, t.statements, body); } public JContainer getStatements() { @@ -1135,15 +1174,38 @@ public JContainer getStatements() { } public Case withStatements(JContainer statements) { - return t.statements == statements ? t : new Case(t.id, t.prefix, t.markers, t.type, null, t.expressions, statements, t.body); + return t.statements == statements ? t : new Case(t.id, t.prefix, t.markers, t.type, null, null, t.caseLabels, t.guard, statements, t.body); } + /** + * @deprecated As of Java 21 this is referred to as case labels and can be broader than just Expressions. + * Use {@link #getCaseLabels} and {@link #withCaseLabels(JContainer)} instead. + */ + @Deprecated public JContainer getExpressions() { - return t.expressions; + return JContainer.build(t.caseLabels.getBefore(), t.caseLabels.getElements().stream().filter(Expression.class::isInstance).map(Expression.class::cast).map(JRightPadded::build).collect(toList()), t.caseLabels.getMarkers()); } + /** + * @deprecated As of Java 21 this is referred to as case labels and can be broader than just Expressions. + * Use {@link #getCaseLabels} and {@link #withCaseLabels(JContainer)} instead. + */ + @Deprecated public Case withExpressions(JContainer expressions) { - return t.expressions == expressions ? t : new Case(t.id, t.prefix, t.markers, t.type, null, expressions, t.statements, t.body); + if (t.getExpressions() == expressions) { + return t; + } else if (t.caseLabels.getElements().stream().allMatch(Expression.class::isInstance)) { + return new Case(t.id, t.prefix, t.markers, t.type, null, expressions, null, t.guard, t.statements, t.body); + } + throw new IllegalStateException("caseLabels contains an entry that is not an Expression, use withCaseLabels instead."); + } + + public JContainer getCaseLabels() { + return t.caseLabels; + } + + public Case withCaseLabels(JContainer caseLabels) { + return t.caseLabels == caseLabels ? t : new Case(t.id, t.prefix, t.markers, t.type, null, null, caseLabels, t.guard, t.statements, t.body); } } } @@ -1979,10 +2041,14 @@ public List getSideEffects() { } public boolean isFullyQualifiedClassReference(String className) { - if (!className.contains(".")) { + if (getName().getFieldType() == null && getName().getType() instanceof JavaType.FullyQualified && + !(getName().getType() instanceof JavaType.Unknown) && + TypeUtils.fullyQualifiedNamesAreEqual(((JavaType.FullyQualified) getName().getType()).getFullyQualifiedName(), className)) { + return true; + } else if (!className.contains(".")) { return false; } - return isFullyQualifiedClassReference(this, className, className.length()); + return isFullyQualifiedClassReference(this, TypeUtils.toFullyQualifiedName(className), className.length()); } private boolean isFullyQualifiedClassReference(J.FieldAccess fieldAccess, String className, int prevDotIndex) { @@ -1991,7 +2057,7 @@ private boolean isFullyQualifiedClassReference(J.FieldAccess fieldAccess, String return false; } String simpleName = fieldAccess.getName().getSimpleName(); - if (!simpleName.regionMatches(0, className, dotIndex + 1, simpleName.length())) { + if (!simpleName.regionMatches(0, className, dotIndex + 1, Math.max(simpleName.length(), prevDotIndex - dotIndex - 1))) { return false; } if (fieldAccess.getTarget() instanceof J.FieldAccess) { @@ -4058,7 +4124,7 @@ public String toString() { /** * These types are sorted in order of their recommended appearance in a list of modifiers, as defined in the - * JLS. + * JLS. */ public enum Type { Default, @@ -5038,6 +5104,7 @@ public CoordinateBuilder.Statement getCoordinates() { @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @Data + @RequiredArgsConstructor final class SwitchExpression implements J, Expression, TypedTree { @With @EqualsAndHashCode.Include @@ -5055,26 +5122,24 @@ final class SwitchExpression implements J, Expression, TypedTree { @With Block cases; - @Override - @Transient - public @Nullable JavaType getType() { - return new JavaVisitor>() { + @With + @Nullable + @Getter + JavaType type; + + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2025-05-01") + public SwitchExpression(UUID id, Space prefix, Markers markers, ControlParentheses selector, Block cases) { + this(id, prefix, markers, selector, cases, new JavaVisitor>() { @Override - public J visitBlock(Block block, AtomicReference javaType) { + public J visitBlock(Block block, AtomicReference<@Nullable JavaType> javaType) { if (!block.getStatements().isEmpty()) { Case caze = (Case) block.getStatements().get(0); - javaType.set(caze.getExpressions().get(0).getType()); + javaType.set(caze.getExpressions().isEmpty() ? null : caze.getExpressions().get(0).getType()); } return block; } - }.reduce(this, new AtomicReference<>()).get(); - } - - @Override - public T withType(@Nullable JavaType type) { - // a switch expression's type is driven by its case statements - //noinspection unchecked - return (T) this; + }.reduce(cases, new AtomicReference<>()).get()); } @Override @@ -6264,4 +6329,52 @@ public

J acceptJava(JavaVisitor

v, P p) { } } } + + /** + * A node that represents an erroneous element. + */ + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @Data + @With + final class Erroneous implements Statement, Expression { + @With + @EqualsAndHashCode.Include + UUID id; + + @With + Space prefix; + + @With + Markers markers; + + @With + String text; + + @Override + public

J acceptJava(JavaVisitor

v, P p) { + return v.visitErroneous(this, p); + } + + @Override + public @Nullable JavaType getType() { + return JavaType.Unknown.getInstance(); + } + + @Override + public T withType(@Nullable JavaType type) { + return (T) this; + } + + @Override + @Transient + public CoordinateBuilder.Statement getCoordinates() { + return new CoordinateBuilder.Statement(this); + } + + @Override + public String toString() { + return withPrefix(Space.EMPTY).printTrimmed(new JavaPrinter<>()); + } + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JContainer.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JContainer.java index a299f98da67..c03505a05d2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JContainer.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JContainer.java @@ -102,6 +102,7 @@ public enum Location { ANNOTATION_ARGUMENTS(Space.Location.ANNOTATION_ARGUMENTS, JRightPadded.Location.ANNOTATION_ARGUMENT), CASE(Space.Location.CASE, JRightPadded.Location.CASE), CASE_EXPRESSION(Space.Location.CASE_EXPRESSION, JRightPadded.Location.CASE_EXPRESSION), + CASE_LABEL(Space.Location.CASE_LABEL, JRightPadded.Location.CASE_LABEL), IMPLEMENTS(Space.Location.IMPLEMENTS, JRightPadded.Location.IMPLEMENTS), PERMITS(Space.Location.PERMITS, JRightPadded.Location.PERMITS), LANGUAGE_EXTENSION(Space.Location.LANGUAGE_EXTENSION, JRightPadded.Location.LANGUAGE_EXTENSION), diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JRightPadded.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JRightPadded.java index 78c65a04a14..b90a9abc19d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JRightPadded.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JRightPadded.java @@ -47,6 +47,7 @@ public enum Location { BLOCK_STATEMENT(Space.Location.BLOCK_STATEMENT_SUFFIX), CASE(Space.Location.CASE_SUFFIX), CASE_EXPRESSION(Space.Location.CASE_EXPRESSION), + CASE_LABEL(Space.Location.CASE_LABEL), CASE_BODY(Space.Location.CASE_BODY), CATCH_ALTERNATIVE(Space.Location.CATCH_ALTERNATIVE_SUFFIX), DIMENSION(Space.Location.DIMENSION_SUFFIX), diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaCoordinates.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaCoordinates.java index 04bf5e5e1f5..482bf3cfe43 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaCoordinates.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaCoordinates.java @@ -34,7 +34,7 @@ public class JavaCoordinates implements Coordinates { Comparator comparator; public boolean isReplacement() { - return Mode.REPLACEMENT.equals(mode); + return Mode.REPLACEMENT == mode; } /** diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 112861469e8..c62550a5d93 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -15,10 +15,11 @@ */ package org.openrewrite.java.tree; -import com.fasterxml.jackson.annotation.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.With; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import org.jspecify.annotations.Nullable; @@ -45,6 +46,7 @@ public interface JavaType { Method[] EMPTY_METHOD_ARRAY = new Method[0]; String[] EMPTY_STRING_ARRAY = new String[0]; JavaType[] EMPTY_JAVA_TYPE_ARRAY = new JavaType[0]; + Annotation.ElementValue[] EMPTY_ANNOTATION_VALUE_ARRAY = new Annotation.ElementValue[0]; // TODO: To be removed with OpenRewrite 9 default @Nullable Integer getManagedReference() { @@ -102,7 +104,7 @@ public MultiCatch(@Nullable List throwableTypes) { this.throwableTypes = arrayOrNullIfEmpty(throwableTypes, EMPTY_JAVA_TYPE_ARRAY); } - MultiCatch(JavaType @Nullable[] throwableTypes) { + MultiCatch(JavaType @Nullable [] throwableTypes) { this.throwableTypes = nullIfEmpty(throwableTypes); } @@ -110,7 +112,7 @@ public MultiCatch(@Nullable List throwableTypes) { MultiCatch() { } - private JavaType[] throwableTypes; + private JavaType @Nullable [] throwableTypes; public List getThrowableTypes() { if (throwableTypes == null) { @@ -143,7 +145,7 @@ public Intersection(@Nullable List bounds) { this.bounds = arrayOrNullIfEmpty(bounds, EMPTY_JAVA_TYPE_ARRAY); } - Intersection(JavaType @Nullable[] throwableTypes) { + Intersection(JavaType @Nullable [] throwableTypes) { this.bounds = nullIfEmpty(throwableTypes); } @@ -151,7 +153,7 @@ public Intersection(@Nullable List bounds) { Intersection() { } - private JavaType[] bounds; + private JavaType @Nullable [] bounds; public List getBounds() { if (bounds == null) { @@ -372,7 +374,7 @@ class Class extends FullyQualified { Kind kind; @NonFinal - JavaType @Nullable[] typeParameters; + JavaType @Nullable [] typeParameters; @With @Nullable @@ -384,9 +386,8 @@ class Class extends FullyQualified { @NonFinal FullyQualified owningClass; - @NonFinal - FullyQualified @Nullable[] annotations; + FullyQualified @Nullable [] annotations; public Class(@Nullable Integer managedReference, long flagsBitMap, String fullyQualifiedName, Kind kind, @Nullable List typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, @@ -408,9 +409,9 @@ public Class(@Nullable Integer managedReference, long flagsBitMap, String fullyQ } Class(@Nullable Integer managedReference, long flagsBitMap, String fullyQualifiedName, - Kind kind, JavaType @Nullable[] typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, - FullyQualified @Nullable[] annotations, FullyQualified @Nullable[] interfaces, - Variable @Nullable[] members, Method @Nullable[] methods) { + Kind kind, JavaType @Nullable [] typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, + FullyQualified @Nullable [] annotations, FullyQualified @Nullable [] interfaces, + Variable @Nullable [] members, Method @Nullable [] methods) { this.managedReference = managedReference; this.flagsBitMap = flagsBitMap & Flag.VALID_CLASS_FLAGS; this.fullyQualifiedName = fullyQualifiedName; @@ -445,7 +446,7 @@ public Class withAnnotations(@Nullable List annotations) { @NonFinal - FullyQualified @Nullable[] interfaces; + FullyQualified @Nullable [] interfaces; @Override public List getInterfaces() { @@ -463,7 +464,7 @@ public Class withInterfaces(@Nullable List interfaces) { @NonFinal - Variable @Nullable[] members; + Variable @Nullable [] members; @Override public List getMembers() { @@ -481,7 +482,7 @@ public Class withMembers(@Nullable List members) { @NonFinal - Method @Nullable[] methods; + Method @Nullable [] methods; @Override public List getMethods() { @@ -554,9 +555,9 @@ public Class unsafeSet(@Nullable List typeParameters, @Nullable FullyQ return this; } - public Class unsafeSet(JavaType @Nullable[] typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, - FullyQualified @Nullable[] annotations, FullyQualified @Nullable[] interfaces, - Variable @Nullable[] members, Method @Nullable[] methods) { + public Class unsafeSet(JavaType @Nullable [] typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, + FullyQualified @Nullable [] annotations, FullyQualified @Nullable [] interfaces, + Variable @Nullable [] members, Method @Nullable [] methods) { //noinspection DuplicatedCode this.typeParameters = ListUtils.nullIfEmpty(typeParameters); this.supertype = supertype; @@ -626,6 +627,152 @@ public static ShallowClass build(String fullyQualifiedName) { } } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + class Annotation extends FullyQualified { + + @Getter + @With + final FullyQualified type; + + final ElementValue @Nullable [] values; + + public Annotation(FullyQualified type, List values) { + this(type, arrayOrNullIfEmpty(values, EMPTY_ANNOTATION_VALUE_ARRAY)); + } + + Annotation(FullyQualified type, ElementValue @Nullable [] values) { + this.type = type; + this.values = nullIfEmpty(values); + } + + public List getValues() { + return values == null ? emptyList() : Arrays.asList(values); + } + + public Annotation withValues(@Nullable List values) { + ElementValue[] valuesArray = arrayOrNullIfEmpty(values, EMPTY_ANNOTATION_VALUE_ARRAY); + if (Arrays.equals(valuesArray, this.values)) { + return this; + } + return new Annotation(type, valuesArray); + } + + @Override + public String getFullyQualifiedName() { + return type.getFullyQualifiedName(); + } + + @Override + public FullyQualified withFullyQualifiedName(String fullyQualifiedName) { + return withType(type.withFullyQualifiedName(fullyQualifiedName)); + } + + @Override + public List getAnnotations() { + return type.getAnnotations(); + } + + @Override + public boolean hasFlags(Flag... test) { + return type.hasFlags(test); + } + + @Override + public Set getFlags() { + return type.getFlags(); + } + + @Override + public List getInterfaces() { + return type.getInterfaces(); + } + + @Override + public Kind getKind() { + return type.getKind(); + } + + @Override + public List getMembers() { + return type.getMembers(); + } + + @Override + public List getMethods() { + return type.getMethods(); + } + + @Override + public List getTypeParameters() { + return type.getTypeParameters(); + } + + @Override + public @Nullable FullyQualified getOwningClass() { + return type.getOwningClass(); + } + + @Override + public @Nullable FullyQualified getSupertype() { + return type.getSupertype(); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@c", include = JsonTypeInfo.As.PROPERTY) + public interface ElementValue { + JavaType getElement(); + + Object getValue(); + } + + @Value + public static class SingleElementValue implements ElementValue { + JavaType element; + + @Nullable + Object constantValue; + + @Nullable + JavaType referenceValue; + + public static SingleElementValue from(JavaType element, Object value) { + if (value instanceof JavaType) { + return new SingleElementValue(element, null, (JavaType) value); + } else { + return new SingleElementValue(element, value, null); + } + } + + @Override + public Object getValue() { + return constantValue != null ? constantValue : referenceValue; + } + } + + @Value + public static class ArrayElementValue implements ElementValue { + JavaType element; + Object @Nullable [] constantValues; + JavaType @Nullable [] referenceValues; + + public static ArrayElementValue from(JavaType element, Object[] values) { + if (values.length > 0 && values[0] instanceof JavaType) { + return new ArrayElementValue(element, null, (JavaType[]) values); + } else { + return new ArrayElementValue(element, values, null); + } + } + + @Override + public Object getValue() { + return getValues(); + } + + public List getValues() { + return Arrays.asList(constantValues != null ? constantValues : referenceValues); + } + } + } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) class Parameterized extends FullyQualified { @Getter @@ -639,7 +786,7 @@ class Parameterized extends FullyQualified { FullyQualified type; @NonFinal - JavaType @Nullable[] typeParameters; + JavaType @Nullable [] typeParameters; public Parameterized(@Nullable Integer managedReference, @Nullable FullyQualified type, @Nullable List typeParameters) { @@ -651,7 +798,7 @@ public Parameterized(@Nullable Integer managedReference, @Nullable FullyQualifie } Parameterized(@Nullable Integer managedReference, @Nullable FullyQualified type, - JavaType @Nullable[] typeParameters) { + JavaType @Nullable [] typeParameters) { this.managedReference = managedReference; this.type = unknownIfNull(type); this.typeParameters = nullIfEmpty(typeParameters); @@ -691,7 +838,7 @@ public Parameterized unsafeSet(@Nullable FullyQualified type, @Nullable List bounds) { this( @@ -802,7 +949,7 @@ public GenericTypeVariable(@Nullable Integer managedReference, String name, Vari ); } - GenericTypeVariable(@Nullable Integer managedReference, String name, Variance variance, JavaType @Nullable[] bounds) { + GenericTypeVariable(@Nullable Integer managedReference, String name, Variance variance, JavaType @Nullable [] bounds) { this.managedReference = managedReference; this.name = name; this.variance = variance; @@ -838,7 +985,7 @@ public GenericTypeVariable unsafeSet(String name, Variance variance, @Nullable L return this; } - public GenericTypeVariable unsafeSet(String name, Variance variance, JavaType @Nullable[] bounds) { + public GenericTypeVariable unsafeSet(String name, Variance variance, JavaType @Nullable [] bounds) { this.name = name; this.variance = variance; this.bounds = ListUtils.nullIfEmpty(bounds); @@ -879,9 +1026,9 @@ class Array implements JavaType { @NonFinal - FullyQualified @Nullable[] annotations; + FullyQualified @Nullable [] annotations; - public Array(@Nullable Integer managedReference, @Nullable JavaType elemType, FullyQualified @Nullable[] annotations) { + public Array(@Nullable Integer managedReference, @Nullable JavaType elemType, FullyQualified @Nullable [] annotations) { this.managedReference = managedReference; this.elemType = unknownIfNull(elemType); this.annotations = ListUtils.nullIfEmpty(annotations); @@ -913,7 +1060,7 @@ public Array unsafeSetManagedReference(Integer id) { return this; } - public Array unsafeSet(JavaType elemType, FullyQualified @Nullable[] annotations) { + public Array unsafeSet(JavaType elemType, FullyQualified @Nullable [] annotations) { this.elemType = unknownIfNull(elemType); this.annotations = ListUtils.nullIfEmpty(annotations); return this; @@ -1097,36 +1244,72 @@ class Method implements JavaType { @NonFinal JavaType returnType; - @NonFinal - String @Nullable[] parameterNames; + String @Nullable [] parameterNames; @NonFinal - JavaType @Nullable[] parameterTypes; + JavaType @Nullable [] parameterTypes; @NonFinal - FullyQualified @Nullable[] thrownExceptions; + JavaType @Nullable [] thrownExceptions; @NonFinal - FullyQualified @Nullable[] annotations; + FullyQualified @Nullable [] annotations; @Incubating(since = "7.34.0") @Nullable @NonFinal List defaultValue; + /** + * The names of the generic type variables declared by this method. These names + * will occur as the name in a {@link GenericTypeVariable} parameter, + * return type, or thrown exception declaration elsewhere in the method definition. We + * keep track of the names of those variables that were defined by this method in order + * to be able to distinguish them from generic type variables, potentially of the same + * name, defined on a containing class. Some examples: + *

+ *

+         * {@code
+         * interface Test {
+         *    U m1();   // [U] used as return type
+         *    void m2(U); // [U] used as parameter
+         *    void m3() throws U; // [U]
+         *
+         *   T m4(T);      // ∅ since T refers to the class definition of T
+         *    T m3(T);  // [T] since it shadows the class definition of T
+         *    U m4(T);  // [U] but not T because T refers to the class
+         * }
+         * }
+         * 
+ */ + @With + @NonFinal + String @Nullable [] declaredFormalTypeNames; + + @Deprecated public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable FullyQualified declaringType, String name, @Nullable JavaType returnType, @Nullable List parameterNames, - @Nullable List parameterTypes, @Nullable List thrownExceptions, + @Nullable List parameterTypes, @Nullable List thrownExceptions, @Nullable List annotations) { this(managedReference, flagsBitMap, declaringType, name, returnType, parameterNames, parameterTypes, thrownExceptions, annotations, null); } + @Deprecated public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable FullyQualified declaringType, String name, @Nullable JavaType returnType, @Nullable List parameterNames, - @Nullable List parameterTypes, @Nullable List thrownExceptions, + @Nullable List parameterTypes, @Nullable List thrownExceptions, @Nullable List annotations, @Nullable List defaultValue) { + this(managedReference, flagsBitMap, declaringType, name, returnType, parameterNames, parameterTypes, + thrownExceptions, annotations, defaultValue, null); + } + + public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable FullyQualified declaringType, String name, + @Nullable JavaType returnType, @Nullable List parameterNames, + @Nullable List parameterTypes, @Nullable List thrownExceptions, + @Nullable List annotations, @Nullable List defaultValue, + @Nullable List declaredFormalTypeNames) { this( managedReference, flagsBitMap, @@ -1137,14 +1320,16 @@ public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable Fu arrayOrNullIfEmpty(parameterTypes, EMPTY_JAVA_TYPE_ARRAY), arrayOrNullIfEmpty(thrownExceptions, EMPTY_FULLY_QUALIFIED_ARRAY), arrayOrNullIfEmpty(annotations, EMPTY_FULLY_QUALIFIED_ARRAY), - defaultValue + defaultValue, + arrayOrNullIfEmpty(declaredFormalTypeNames, EMPTY_STRING_ARRAY) ); } public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable FullyQualified declaringType, String name, - @Nullable JavaType returnType, String @Nullable[] parameterNames, - JavaType @Nullable[] parameterTypes, FullyQualified @Nullable[] thrownExceptions, - FullyQualified @Nullable[] annotations, @Nullable List defaultValue) { + @Nullable JavaType returnType, String @Nullable [] parameterNames, + JavaType @Nullable [] parameterTypes, JavaType @Nullable [] thrownExceptions, + FullyQualified @Nullable [] annotations, @Nullable List defaultValue, + String @Nullable [] declaredFormalTypeNames) { this.managedReference = managedReference; this.flagsBitMap = flagsBitMap & Flag.VALID_FLAGS; this.declaringType = unknownIfNull(declaringType); @@ -1155,6 +1340,7 @@ public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable Fu this.thrownExceptions = nullIfEmpty(thrownExceptions); this.annotations = nullIfEmpty(annotations); this.defaultValue = nullIfEmpty(defaultValue); + this.declaredFormalTypeNames = nullIfEmpty(declaredFormalTypeNames); } @JsonCreator @@ -1170,26 +1356,27 @@ public Method unsafeSetManagedReference(Integer id) { public Method unsafeSet(@Nullable FullyQualified declaringType, @Nullable JavaType returnType, @Nullable List parameterTypes, - @Nullable List thrownExceptions, + @Nullable List thrownExceptions, @Nullable List annotations) { this.declaringType = unknownIfNull(declaringType); this.returnType = unknownIfNull(returnType); this.parameterTypes = arrayOrNullIfEmpty(parameterTypes, EMPTY_JAVA_TYPE_ARRAY); - this.thrownExceptions = arrayOrNullIfEmpty(thrownExceptions, EMPTY_FULLY_QUALIFIED_ARRAY); + this.thrownExceptions = arrayOrNullIfEmpty(thrownExceptions, EMPTY_JAVA_TYPE_ARRAY); this.annotations = arrayOrNullIfEmpty(annotations, EMPTY_FULLY_QUALIFIED_ARRAY); return this; } public Method unsafeSet(@Nullable FullyQualified declaringType, @Nullable JavaType returnType, - JavaType @Nullable[] parameterTypes, - FullyQualified @Nullable[] thrownExceptions, - FullyQualified @Nullable[] annotations) { + JavaType @Nullable [] parameterTypes, + JavaType @Nullable [] thrownExceptions, + FullyQualified @Nullable [] annotations) { this.declaringType = unknownIfNull(declaringType); this.returnType = unknownIfNull(returnType); this.parameterTypes = ListUtils.nullIfEmpty(parameterTypes); this.thrownExceptions = ListUtils.nullIfEmpty(thrownExceptions); this.annotations = ListUtils.nullIfEmpty(annotations); + this.declaredFormalTypeNames = ListUtils.nullIfEmpty(declaredFormalTypeNames); return this; } @@ -1289,13 +1476,18 @@ public List getParameterNames() { return parameterNames == null ? emptyList() : Arrays.asList(parameterNames); } + public List getDeclaredFormalTypeNames() { + return declaredFormalTypeNames == null ? emptyList() : Arrays.asList(declaredFormalTypeNames); + } + public Method withParameterNames(@Nullable List parameterNames) { String[] parameterNamesArray = arrayOrNullIfEmpty(parameterNames, EMPTY_STRING_ARRAY); if (Arrays.equals(parameterNamesArray, this.parameterNames)) { return this; } return new Method(this.managedReference, this.flagsBitMap, this.declaringType, this.name, this.returnType, - parameterNamesArray, this.parameterTypes, this.thrownExceptions, this.annotations, this.defaultValue); + parameterNamesArray, this.parameterTypes, this.thrownExceptions, this.annotations, this.defaultValue, + this.declaredFormalTypeNames); } public List getParameterTypes() { @@ -1308,20 +1500,22 @@ public Method withParameterTypes(@Nullable List parameterTypes) { return this; } return new Method(this.managedReference, this.flagsBitMap, this.declaringType, this.name, this.returnType, - this.parameterNames, parameterTypesArray, this.thrownExceptions, this.annotations, this.defaultValue); + this.parameterNames, parameterTypesArray, this.thrownExceptions, this.annotations, this.defaultValue, + this.declaredFormalTypeNames); } - public List getThrownExceptions() { + public List getThrownExceptions() { return thrownExceptions == null ? emptyList() : Arrays.asList(thrownExceptions); } - public Method withThrownExceptions(@Nullable List thrownExceptions) { - FullyQualified[] thrownExceptionsArray = arrayOrNullIfEmpty(thrownExceptions, EMPTY_FULLY_QUALIFIED_ARRAY); + public Method withThrownExceptions(@Nullable List thrownExceptions) { + JavaType[] thrownExceptionsArray = arrayOrNullIfEmpty(thrownExceptions, EMPTY_JAVA_TYPE_ARRAY); if (Arrays.equals(thrownExceptionsArray, this.thrownExceptions)) { return this; } return new Method(this.managedReference, this.flagsBitMap, this.declaringType, this.name, this.returnType, - this.parameterNames, this.parameterTypes, thrownExceptionsArray, this.annotations, this.defaultValue); + this.parameterNames, this.parameterTypes, thrownExceptionsArray, this.annotations, this.defaultValue, + this.declaredFormalTypeNames); } public List getAnnotations() { @@ -1334,7 +1528,8 @@ public Method withAnnotations(@Nullable List annotations) { return this; } return new Method(this.managedReference, this.flagsBitMap, this.declaringType, this.name, this.returnType, - this.parameterNames, this.parameterTypes, this.thrownExceptions, annotationsArray, this.defaultValue); + this.parameterNames, this.parameterTypes, this.thrownExceptions, annotationsArray, this.defaultValue, + this.declaredFormalTypeNames); } public boolean hasFlags(Flag... test) { @@ -1393,7 +1588,7 @@ class Variable implements JavaType { JavaType type; @NonFinal - FullyQualified @Nullable[] annotations; + FullyQualified @Nullable [] annotations; public Variable(@Nullable Integer managedReference, long flagsBitMap, String name, @Nullable JavaType owner, @Nullable JavaType type, @Nullable List annotations) { @@ -1408,7 +1603,7 @@ public Variable(@Nullable Integer managedReference, long flagsBitMap, String nam } Variable(@Nullable Integer managedReference, long flagsBitMap, String name, @Nullable JavaType owner, - @Nullable JavaType type, FullyQualified @Nullable[] annotations) { + @Nullable JavaType type, FullyQualified @Nullable [] annotations) { this.managedReference = managedReference; this.flagsBitMap = flagsBitMap & Flag.VALID_FLAGS; this.name = name; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index 129a0eee381..a18deff4ce0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -286,31 +286,35 @@ public String toString() { printedWs.append(spaces[(i - lastNewline) % 10]); } else if (c == '\t') { printedWs.append(tabs[(i - lastNewline) % 10]); + } else { + // should never happen (probably a bug in the parser) + printedWs.append(c); } } } + String whitespaces = printedWs.toString(); return "Space(" + - "comments=<" + (comments.size() == 1 ? "1 comment" : comments.size() + " comments") + - ">, whitespace='" + printedWs + "')"; + "comments=<" + (comments.size() == 1 ? "1 comment" : comments.size() + " comments") + ">, " + + "whitespace=" + (whitespaces.isEmpty() ? "" : "'" + whitespaces + "'") + ")"; } public enum Location { - ANY, ANNOTATED_TYPE_PREFIX, + ANNOTATIONS, ANNOTATION_ARGUMENTS, ANNOTATION_ARGUMENT_SUFFIX, - ANNOTATIONS, ANNOTATION_PREFIX, + ANY, ARRAY_ACCESS_PREFIX, ARRAY_INDEX_SUFFIX, ARRAY_TYPE_PREFIX, - ASSERT_PREFIX, ASSERT_DETAIL, ASSERT_DETAIL_PREFIX, + ASSERT_PREFIX, ASSIGNMENT, - ASSIGNMENT_OPERATION_PREFIX, ASSIGNMENT_OPERATION_OPERATOR, + ASSIGNMENT_OPERATION_PREFIX, ASSIGNMENT_PREFIX, BINARY_OPERATOR, BINARY_PREFIX, @@ -319,9 +323,10 @@ public enum Location { BLOCK_STATEMENT_SUFFIX, BREAK_PREFIX, CASE, - CASE_PREFIX, CASE_BODY, CASE_EXPRESSION, + CASE_LABEL, + CASE_PREFIX, CASE_SUFFIX, CATCH_ALTERNATIVE_SUFFIX, CATCH_PREFIX, @@ -331,8 +336,8 @@ public enum Location { COMPILATION_UNIT_PREFIX, CONTINUE_PREFIX, CONTROL_PARENTHESES_PREFIX, - DIMENSION_PREFIX, DIMENSION, + DIMENSION_PREFIX, DIMENSION_SUFFIX, DO_WHILE_PREFIX, ELSE_PREFIX, @@ -340,6 +345,7 @@ public enum Location { ENUM_VALUE_PREFIX, ENUM_VALUE_SET_PREFIX, ENUM_VALUE_SUFFIX, + ERRONEOUS, EXPRESSION_PREFIX, EXTENDS, FIELD_ACCESS_NAME, @@ -359,9 +365,8 @@ public enum Location { IF_PREFIX, IF_THEN_SUFFIX, IMPLEMENTS, - IMPORT_ALIAS_PREFIX, - PERMITS, IMPLEMENTS_SUFFIX, + IMPORT_ALIAS_PREFIX, IMPORT_PREFIX, IMPORT_SUFFIX, INSTANCEOF_PREFIX, @@ -378,9 +383,9 @@ public enum Location { MEMBER_REFERENCE_CONTAINING, MEMBER_REFERENCE_NAME, MEMBER_REFERENCE_PREFIX, + METHOD_DECLARATION_DEFAULT_VALUE, METHOD_DECLARATION_PARAMETERS, METHOD_DECLARATION_PARAMETER_SUFFIX, - METHOD_DECLARATION_DEFAULT_VALUE, METHOD_DECLARATION_PREFIX, METHOD_INVOCATION_ARGUMENTS, METHOD_INVOCATION_ARGUMENT_SUFFIX, @@ -405,6 +410,7 @@ public enum Location { PARAMETERIZED_TYPE_PREFIX, PARENTHESES_PREFIX, PARENTHESES_SUFFIX, + PERMITS, PERMITS_SUFFIX, PRIMITIVE_PREFIX, RECORD_STATE_VECTOR, @@ -413,8 +419,8 @@ public enum Location { STATEMENT_PREFIX, STATIC_IMPORT, STATIC_INIT_SUFFIX, - SWITCH_PREFIX, SWITCH_EXPRESSION_PREFIX, + SWITCH_PREFIX, SYNCHRONIZED_PREFIX, TERNARY_FALSE, TERNARY_PREFIX, @@ -422,6 +428,7 @@ public enum Location { THROWS, THROWS_SUFFIX, THROW_PREFIX, + TRAILING_COMMA_SUFFIX, TRY_FINALLY, TRY_PREFIX, TRY_RESOURCE, diff --git a/rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java b/rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java new file mode 100644 index 00000000000..339e1777c28 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ChangeMethodInvocationReturnTypeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ChangeMethodInvocationReturnType("java.lang.Integer parseInt(String)", "long")); + } + + @Test + @DocumentExample + void replaceVariableAssignment() { + rewriteRun( + //language=java + java( + """ + class Foo { + void bar() { + int one = Integer.parseInt("1"); + } + } + """, + """ + class Foo { + void bar() { + long one = Integer.parseInt("1"); + } + } + """ + ) + ); + } + + @Test + void shouldOnlyChangeTargetMethodAssignments() { + rewriteRun( + //language=java + java( + """ + class Foo { + void bar() { + int zero = Integer.valueOf("0"); + int one = Integer.parseInt("1"); + int two = Integer.valueOf("2"); + } + } + """, + """ + class Foo { + void bar() { + int zero = Integer.valueOf("0"); + long one = Integer.parseInt("1"); + int two = Integer.valueOf("2"); + } + } + """ + ) + ); + } + + @Test + void replaceVariableAssignmentFullyQualified() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodInvocationReturnType("bar.Bar bar()", "java.math.BigInteger")) + .parser(JavaParser.fromJavaVersion() + //language=java + .dependsOn( + """ + package bar; + public class Bar { + public static Integer bar() { + return null; + } + } + """ + ) + ), + //language=java + java( + """ + import bar.Bar; + class Foo { + void foo() { + Integer one = Bar.bar(); + } + } + """, + """ + import bar.Bar; + + import java.math.BigInteger; + + class Foo { + void foo() { + BigInteger one = Bar.bar(); + } + } + """ + ) + ); + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/RemoveMethodInvocationsVisitorTest.java b/rewrite-java/src/test/java/org/openrewrite/java/RemoveMethodInvocationsVisitorTest.java index c94f455eb16..279e70c1a8b 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/RemoveMethodInvocationsVisitorTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/RemoveMethodInvocationsVisitorTest.java @@ -15,13 +15,14 @@ */ package org.openrewrite.java; +import java.util.List; + import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.DocumentExample; import org.openrewrite.Recipe; import org.openrewrite.test.RewriteTest; -import java.util.List; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; @@ -172,7 +173,6 @@ void method() { } @Test - @ExpectedToFail void removeWithoutSelect() { rewriteRun( spec -> spec.recipe(createRemoveMethodsRecipe("Test foo()")), @@ -492,4 +492,140 @@ public void method() { ) ); } + + @Test + void removeStaticMethodFromImport() { + rewriteRun( + spec -> spec.recipe(createRemoveMethodsRecipe("org.junit.jupiter.api.Assertions assertTrue(..)")), + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertTrue; + + class Test { + void method() { + assertTrue(true); + } + } + """, + """ + class Test { + void method() { + } + } + """ + ) + ); + } + + @Test + void keepStaticMethodFromImportWithAssignment() { + rewriteRun( + spec -> spec.recipe(createRemoveMethodsRecipe("java.util.Collections emptyList()")), + // language=java + java( + """ + import java.util.List; + + import static java.util.Collections.emptyList; + + class Test { + void method() { + List emptyList = emptyList(); + emptyList.isEmpty(); + } + } + """ + ) + ); + } + + @Test + void keepStaticMethodFromImportClassField() { + rewriteRun( + spec -> spec.recipe(createRemoveMethodsRecipe("java.util.Collections emptyList()")), + // language=java + java( + """ + import java.util.List; + + import static java.util.Collections.emptyList; + + class Test { + List emptyList = emptyList(); + void method() { + emptyList.isEmpty(); + } + } + """ + ) + ); + } + + @Test + void removeStaticMethod() { + rewriteRun( + spec -> spec.recipe(createRemoveMethodsRecipe("org.junit.jupiter.api.Assertions assertTrue(..)")), + // language=java + java( + """ + import org.junit.jupiter.api.Assertions; + + class Test { + void method() { + Assertions.assertTrue(true); + } + } + """, + """ + class Test { + void method() { + } + } + """ + ) + ); + } + + @Test + void keepStaticMethodWithAssignment() { + rewriteRun( + spec -> spec.recipe(createRemoveMethodsRecipe("java.util.Collections emptyList()")), + // language=java + java( + """ + import java.util.List; + import java.util.Collections; + + class Test { + void method() { + List emptyList = Collections.emptyList(); + emptyList.size(); + } + } + """ + ) + ); + } + + @Test + void keepStaticMethodClassField() { + rewriteRun( + spec -> spec.recipe(createRemoveMethodsRecipe("java.util.Collections emptyList()")), + // language=java + java( + """ + import java.util.List; + import java.util.Collections; + + class Test { + List emptyList = Collections.emptyList(); + void method() { + emptyList.size(); + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java b/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java new file mode 100644 index 00000000000..e40fd0f9f93 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ReplaceStringLiteralValueTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ReplaceStringLiteralValue("apple", "orange")); + } + + @DocumentExample + @Test + void replaceAppleWithOrange() { + rewriteRun( + java( + """ + class Test { + String s = "apple"; + } + """, + """ + class Test { + String s = "orange"; + } + """ + ) + ); + } + @Test + void doNotReplacePineapply() { + rewriteRun( + java( + """ + class Test { + // We only match the full String literal value + String s = "pineapple"; + } + """ + ) + ); + } + +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java b/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java index e858ccbceef..06d20326f58 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java @@ -22,6 +22,8 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; +import static org.assertj.core.api.Assertions.assertThat; + @SuppressWarnings("ConstantConditions") class JavaReflectionTypeMappingTest implements JavaTypeMappingTest { JavaReflectionTypeMapping typeMapping = new JavaReflectionTypeMapping(new JavaTypeCache()); @@ -56,4 +58,20 @@ public void enumTypeA() { @Override public void enumTypeB() { } + + @Test + @Override + // The JavaReflectionTypeMapping cannot include source retention annotations + public void includeSourceRetentionAnnotations() { + JavaType.Parameterized goat = goatType(); + assertThat(goat.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention") + ); + + JavaType.Method clazzMethod = methodType("clazz"); + assertThat(clazzMethod.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention") + ); + } + } diff --git a/rewrite-java/src/test/java/org/openrewrite/java/internal/parser/TypeTableTest.java b/rewrite-java/src/test/java/org/openrewrite/java/internal/parser/TypeTableTest.java new file mode 100644 index 00000000000..9af0b44f1e6 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/internal/parser/TypeTableTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.java.internal.parser; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaParserExecutionContextView; +import org.openrewrite.test.RewriteTest; + +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static io.micrometer.core.instrument.util.DoubleFormat.decimalOrNan; +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.java; + +class TypeTableTest implements RewriteTest { + Path tsv; + ExecutionContext ctx = new InMemoryExecutionContext(); + + @TempDir + Path temp; + + @BeforeEach + void before() { + ctx.putMessage(TypeTable.VERIFY_CLASS_WRITING, true); + JavaParserExecutionContextView.view(ctx).setParserClasspathDownloadTarget(temp.toFile()); + tsv = temp.resolve("types.tsv.zip"); + System.out.println(tsv); + } + + /** + * Snappy isn't optimal for compression, but is excellent for portability since it + * requires no native libraries or JNI. + * + * @throws IOException If unable to write. + */ + @Test + void writeAllRuntimeClasspathJars() throws IOException { + try (TypeTable.Writer writer = TypeTable.newWriter(Files.newOutputStream(tsv))) { + long jarsSize = 0; + for (Path classpath : JavaParser.runtimeClasspath()) { + jarsSize += writeJar(classpath, writer); + } + System.out.println("Total size of table " + humanReadableByteCount(Files.size(tsv))); + System.out.println("Total size of jars " + humanReadableByteCount(jarsSize)); + } + } + + @Disabled + @Test + void writeAllMavenLocal() throws IOException { + Path m2Repo = Paths.get(System.getProperty("user.home"), ".m2", "repository"); + try (TypeTable.Writer writer = TypeTable.newWriter(Files.newOutputStream(tsv))) { + AtomicLong jarsSize = new AtomicLong(); + AtomicLong jarCount = new AtomicLong(); + Files.walkFileTree(m2Repo, new SimpleFileVisitor<>() { + @SneakyThrows + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (file.toString().endsWith(".jar")) { + jarsSize.addAndGet(writeJar(file, writer)); + if (jarCount.incrementAndGet() > 500) { + return FileVisitResult.TERMINATE; + } + } + return FileVisitResult.CONTINUE; + } + }); + System.out.println("Total size of table " + humanReadableByteCount(Files.size(tsv))); + System.out.println("Total size of jars " + humanReadableByteCount(jarsSize.get())); + } + } + + @Test + void writeReadMicrometer() throws IOException { + try (TypeTable.Writer writer = TypeTable.newWriter(Files.newOutputStream(tsv))) { + for (Path classpath : JavaParser.runtimeClasspath()) { + if (classpath.toFile().getName().contains("micrometer")) { + writeJar(classpath, writer); + } + } + } + + TypeTable table = new TypeTable(ctx, Files.newInputStream(tsv), List.of("micrometer")); + Path micrometerClassesDir = table.load("micrometer"); + + assertThat(micrometerClassesDir).isNotNull(); + + // Demonstrate that the bytecode we wrote for the classes in this + // JAR is sufficient for the compiler to type attribute code that depends + // on them. + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion() + .classpath(List.of(micrometerClassesDir))), + java( + """ + import io.micrometer.core.instrument.Metrics; + import io.micrometer.core.instrument.Timer; + + class Test { + Timer timer = Metrics.timer("my.timer"); + } + """ + ) + ); + } + + private static long writeJar(Path classpath, TypeTable.Writer writer) throws IOException { + String fileName = classpath.toFile().getName(); + if (fileName.endsWith(".jar")) { + String[] artifactVersion = fileName.replaceAll(".jar$", "") + .split("-(?=\\d)"); + if (artifactVersion.length > 1) { + writer + .jar("unknown", artifactVersion[0], artifactVersion[1]) + .write(classpath); + System.out.println(" Wrote " + artifactVersion[0] + ":" + artifactVersion[1]); + } + return Files.size(classpath); + } + return 0; + } + + private String humanReadableByteCount(double bytes) { + int unit = 1024; + if (bytes < unit || Double.isNaN(bytes)) { + return decimalOrNan(bytes) + " B"; + } + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = "KMGTPE".charAt(exp - 1) + "i"; + return decimalOrNan(bytes / Math.pow(unit, exp)) + " " + pre + "B"; + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java b/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java new file mode 100644 index 00000000000..36fc57b9637 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.java.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.table.CompileErrors; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.TypeValidation.all; + +class FindCompileErrorsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipes(new FindCompileErrors()); + } + + @DocumentExample + @Test + void javaVisitorHandlesErroneousNodes() { + rewriteRun( + spec -> spec.dataTable(CompileErrors.Row.class, + rows -> assertThat(rows).singleElement().satisfies(row -> { + assertThat(row.getSourceFile()).isEqualTo("A.java"); + assertThat(row.getCode()).isEqualTo("\n owner"); + })).typeValidationOptions(all().erroneous(false)), + java( + """ + class A { + void test() { + owner + } + } + """, + """ + class A { + void test() { + /*~~>*/owner + } + } + """ + ) + ); + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/trait/AnnotatedTest.java b/rewrite-java/src/test/java/org/openrewrite/java/trait/AnnotatedTest.java index 4f55f333154..e928803d389 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/trait/AnnotatedTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/trait/AnnotatedTest.java @@ -62,4 +62,44 @@ class Test { ) ); } + + @Test + void checkOnArray() { + rewriteRun( + spec -> + spec.recipe(RewriteTest.toRecipe(() -> + annotated("@Example(other=\"World\")") + .asVisitor(a -> SearchResult.found(a.getTree())) + )), + java( + //language=java + """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface Example { + String[] other; + } + """ + ), + java( + //language=java + """ + @Example(other = {"Hello", "World"}) + class Test { + } + """, + //language=java + """ + /*~~>*/@Example(other = {"Hello", "World"}) + class Test { + } + """ + ) + ); + } } diff --git a/rewrite-json/src/main/java/org/openrewrite/json/AddKeyValue.java b/rewrite-json/src/main/java/org/openrewrite/json/AddKeyValue.java new file mode 100644 index 00000000000..4fc66417797 --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/AddKeyValue.java @@ -0,0 +1,135 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.json; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.intellij.lang.annotations.Language; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.json.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.Collections; +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.openrewrite.Tree.randomId; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AddKeyValue extends Recipe { + + @Option(displayName = "Key path", + description = "A JsonPath expression to locate the *parent* JSON entry.", + example = "'$.subjects.*' or '$.' or '$.x[1].y.*' etc.") + String keyPath; + + @Option(displayName = "Key", + description = "The key to create.", + example = "myKey") + String key; + + @Option(displayName = "Value", + description = "The value to add to the document at the specified key. Can be of any type representing JSON value." + + " String values should be quoted to be inserted as Strings.", + example = "`\"myValue\"` or `{\"a\": 1}` or `[ 123 ]`") + @Language("Json") + String value; + + @Option(displayName = "Prepend", + required = false, + description = "If set to `true` the value will be added to the beginning of the object") + boolean prepend; + + @Override + public String getDisplayName() { + return "Add value to JSON Object"; + } + + @Override + public String getDescription() { + return "Adds a `value` at the specified `keyPath` with the specified `key`, if the key doesn't already exist."; + } + + + @Override + public TreeVisitor getVisitor() { + return new JsonIsoVisitor() { + private final JsonPathMatcher pathMatcher = new JsonPathMatcher(keyPath); + + @Override + public Json.JsonObject visitObject(Json.JsonObject obj, ExecutionContext ctx) { + obj = super.visitObject(obj, ctx); + + if (pathMatcher.matches(getCursor()) && objectDoesNotContainKey(obj, key)) { + List originalMembers = obj.getMembers(); + boolean jsonIsEmpty = originalMembers.isEmpty() || originalMembers.get(0) instanceof Json.Empty; + Space space = jsonIsEmpty || prepend ? originalMembers.get(0).getPrefix() : Space.build("\n", emptyList()); + + Json newMember = new Json.Member(randomId(), space, Markers.EMPTY, rightPaddedKey(), parsedValue()); + + if (jsonIsEmpty) { + return autoFormat(obj.withMembers(Collections.singletonList(newMember)), ctx, getCursor().getParent()); + } + + List newMembers = prepend ? + ListUtils.concat(newMember, originalMembers) : + ListUtils.concat(originalMembers, newMember); + return autoFormat(obj.withMembers(newMembers), ctx, getCursor().getParent()); + } + return obj; + } + + private JsonValue parsedValue() { + Json.Document parsedDoc = (Json.Document) JsonParser.builder().build() + .parse(value.trim()).findFirst().get(); + JsonValue value = parsedDoc.getValue(); + return value.withPrefix(value.getPrefix().withWhitespace(" ")); + } + + private JsonRightPadded rightPaddedKey() { + return new JsonRightPadded<>( + new Json.Literal(randomId(), Space.EMPTY, Markers.EMPTY, "\"" + key + "\"", key), + Space.EMPTY, Markers.EMPTY + ); + } + + private boolean objectDoesNotContainKey(Json.JsonObject obj, String key) { + for (Json member : obj.getMembers()) { + if (member instanceof Json.Member) { + if (keyMatches(((Json.Member) member).getKey(), key)) { + return false; + } + } + } + return true; + } + + private boolean keyMatches(JsonKey jsonKey, String key) { + if (jsonKey instanceof Json.Literal) { + return key.equals(((Json.Literal) jsonKey).getValue()); + } else if (jsonKey instanceof Json.Identifier) { + return key.equals(((Json.Identifier) jsonKey).getName()); + } + return false; + } + }; + } +} diff --git a/rewrite-json/src/main/java/org/openrewrite/json/JsonVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/JsonVisitor.java index ad1980f5b8b..2389969407e 100755 --- a/rewrite-json/src/main/java/org/openrewrite/json/JsonVisitor.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/JsonVisitor.java @@ -20,6 +20,7 @@ import org.openrewrite.SourceFile; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; +import org.openrewrite.json.format.AutoFormatVisitor; import org.openrewrite.json.tree.Json; import org.openrewrite.json.tree.JsonRightPadded; import org.openrewrite.json.tree.JsonValue; @@ -37,6 +38,35 @@ public String getLanguage() { return "json"; } + public Y2 maybeAutoFormat(Y2 before, Y2 after, P p) { + return maybeAutoFormat(before, after, p, getCursor()); + } + + public Y2 maybeAutoFormat(Y2 before, Y2 after, P p, Cursor cursor) { + return maybeAutoFormat(before, after, null, p, cursor); + } + + @SuppressWarnings({"unchecked", "ConstantConditions"}) + public Y2 maybeAutoFormat(Y2 before, Y2 after, @Nullable Json stopAfter, P p, Cursor cursor) { + if (before != after) { + return (Y2) new AutoFormatVisitor<>(stopAfter).visit(after, p, cursor); + } + return after; + } + + public Y2 autoFormat(Y2 y, P p) { + return autoFormat(y, p, getCursor()); + } + + public Y2 autoFormat(Y2 y, P p, Cursor cursor) { + return autoFormat(y, null, p, cursor); + } + + @SuppressWarnings({"ConstantConditions", "unchecked"}) + public Y2 autoFormat(Y2 y, @Nullable Json stopAfter, P p, Cursor cursor) { + return (Y2) new AutoFormatVisitor<>(stopAfter).visit(y, p, cursor); + } + public Json visitArray(Json.Array array, P p) { Json.Array a = array; a = a.withPrefix(visitSpace(a.getPrefix(), p)); diff --git a/rewrite-json/src/main/java/org/openrewrite/json/format/AutoFormatVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/format/AutoFormatVisitor.java new file mode 100644 index 00000000000..f161c95c20e --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/format/AutoFormatVisitor.java @@ -0,0 +1,93 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.json.format; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.Tree; +import org.openrewrite.json.JsonIsoVisitor; +import org.openrewrite.json.style.Autodetect; +import org.openrewrite.json.style.TabsAndIndentsStyle; +import org.openrewrite.json.tree.Json; +import org.openrewrite.style.GeneralFormatStyle; +import org.openrewrite.style.NamedStyles; + +import java.util.Optional; + +import static java.util.Collections.singletonList; + +public class AutoFormatVisitor

extends JsonIsoVisitor

{ + @Nullable + private final Tree stopAfter; + + public AutoFormatVisitor(@Nullable Tree stopAfter) { + this.stopAfter = stopAfter; + } + + @Override + public Json preVisit(Json tree, P p) { + stopAfterPreVisit(); + Json.Document doc = getCursor().firstEnclosingOrThrow(Json.Document.class); + Cursor cursor = getCursor().getParentOrThrow(); + Autodetect autodetectedStyle = Autodetect.detector().sample(doc).build(); + Json js = tree; + + TabsAndIndentsStyle taiStyle = Optional.ofNullable(doc.getStyle(TabsAndIndentsStyle.class)) + .orElseGet(() -> NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(autodetectedStyle))); + assert(taiStyle != null); + js = new TabsAndIndentsVisitor<>(taiStyle, stopAfter).visitNonNull(js, p, cursor.fork()); + + GeneralFormatStyle gfStyle = Optional.ofNullable(doc.getStyle(GeneralFormatStyle.class)) + .orElseGet(() -> NamedStyles.merge(GeneralFormatStyle.class, singletonList(autodetectedStyle))); + assert(gfStyle != null); + js = new NormalizeLineBreaksVisitor<>(gfStyle, stopAfter).visitNonNull(js, p, cursor.fork()); + + return js; + } + + @Override + public Json.Document visitDocument(Json.Document js, P p) { + Autodetect autodetectedStyle = Autodetect.detector().sample(js).build(); + + TabsAndIndentsStyle taiStyle = Optional.ofNullable(js.getStyle(TabsAndIndentsStyle.class)) + .orElseGet(() -> NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(autodetectedStyle))); + assert(taiStyle != null); + js = (Json.Document) new TabsAndIndentsVisitor<>(taiStyle, stopAfter).visitNonNull(js, p); + + GeneralFormatStyle gfStyle = Optional.ofNullable(js.getStyle(GeneralFormatStyle.class)) + .orElseGet(() -> NamedStyles.merge(GeneralFormatStyle.class, singletonList(autodetectedStyle))); + assert(gfStyle != null); + js = (Json.Document) new NormalizeLineBreaksVisitor<>(gfStyle, stopAfter).visitNonNull(js, p); + + return js; + } + + @Override + public @Nullable Json postVisit(Json tree, P p) { + if (stopAfter != null && stopAfter.isScope(tree)) { + getCursor().putMessageOnFirstEnclosing(Json.Document.class, "stop", true); + } + return super.postVisit(tree, p); + } + + @Override + public @Nullable Json visit(@Nullable Tree tree, P p) { + if (getCursor().getNearestMessage("stop") != null) { + return (Json) tree; + } + return super.visit(tree, p); + } +} diff --git a/rewrite-json/src/main/java/org/openrewrite/json/format/Indents.java b/rewrite-json/src/main/java/org/openrewrite/json/format/Indents.java new file mode 100644 index 00000000000..d636ea9e07d --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/format/Indents.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.json.format; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.json.JsonIsoVisitor; +import org.openrewrite.json.style.Autodetect; +import org.openrewrite.json.style.TabsAndIndentsStyle; +import org.openrewrite.json.tree.Json; +import org.openrewrite.style.NamedStyles; + +import static java.util.Collections.singletonList; + +public class Indents extends Recipe { + @Override + public String getDisplayName() { + return "JSON indent"; + } + + @Override + public String getDescription() { + return "Format tabs and indents in JSON."; + } + + @Override + public TreeVisitor getVisitor() { + return new TabsAndIndentsFromCompilationUnitStyle(); + } + + private static class TabsAndIndentsFromCompilationUnitStyle extends JsonIsoVisitor { + @Override + public Json. Document visitDocument(Json.Document docs, ExecutionContext ctx) { + TabsAndIndentsStyle style = docs.getStyle(TabsAndIndentsStyle.class); + if (style == null) { + style = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(Autodetect.detector().sample(docs).build())); + assert(style != null); + } + doAfterVisit(new TabsAndIndentsVisitor<>(style, null)); + return docs; + } + } +} diff --git a/rewrite-json/src/main/java/org/openrewrite/json/format/NormalizeLineBreaksVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/format/NormalizeLineBreaksVisitor.java new file mode 100644 index 00000000000..34f19d6829f --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/format/NormalizeLineBreaksVisitor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.json.format; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.Tree; +import org.openrewrite.style.GeneralFormatStyle; +import org.openrewrite.json.JsonIsoVisitor; +import org.openrewrite.json.tree.Json; + +import static org.openrewrite.format.LineBreaks.normalizeNewLines; + +public class NormalizeLineBreaksVisitor

extends JsonIsoVisitor

{ + private final GeneralFormatStyle generalFormatStyle; + + @Nullable + private final Tree stopAfter; + + public NormalizeLineBreaksVisitor(GeneralFormatStyle generalFormatStyle, @Nullable Tree stopAfter) { + this.generalFormatStyle = generalFormatStyle; + this.stopAfter = stopAfter; + } + + @Override + public @Nullable Json postVisit(Json tree, P p) { + if (stopAfter != null && stopAfter.isScope(tree)) { + getCursor().putMessageOnFirstEnclosing(Json.Document.class, "stop", true); + } + return super.postVisit(tree, p); + } + + @Override + public @Nullable Json visit(@Nullable Tree tree, P p) { + if (getCursor().getNearestMessage("stop") != null) { + return (Json) tree; + } + + Json y = super.visit(tree, p); + if (y != null) { + String modifiedWs = normalizeNewLines(y.getPrefix().getWhitespace(), generalFormatStyle.isUseCRLFNewLines()); + if (!y.getPrefix().getWhitespace().equals(modifiedWs)) { + y = y.withPrefix(y.getPrefix().withWhitespace(modifiedWs)); + } + } + return y; + } +} diff --git a/rewrite-json/src/main/java/org/openrewrite/json/format/TabsAndIndentsVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/format/TabsAndIndentsVisitor.java new file mode 100755 index 00000000000..bd17823efaa --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/format/TabsAndIndentsVisitor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.json.format; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.Tree; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.json.JsonIsoVisitor; +import org.openrewrite.json.style.TabsAndIndentsStyle; +import org.openrewrite.json.tree.Json; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TabsAndIndentsVisitor

extends JsonIsoVisitor

{ + + private final TabsAndIndentsStyle style; + + @Nullable + private final Tree stopAfter; + + public TabsAndIndentsVisitor(TabsAndIndentsStyle style, @Nullable Tree stopAfter) { + this.style = style; + this.stopAfter = stopAfter; + } + + @Override + public Json preVisit(Json tree, P p) { + Json json = super.preVisit(tree, p); + if (json != null) { + final String ws = json.getPrefix().getWhitespace(); + if (ws.contains("\n")) { + int indentMultiple = (int) getCursor().getPathAsStream().filter(Json.JsonObject.class::isInstance).count(); + String shiftedPrefix = createIndent(ws, indentMultiple); + if (!shiftedPrefix.equals(ws)) { + return json.withPrefix(json.getPrefix().withWhitespace(shiftedPrefix)); + } + } + } + return json; + } + + @Override + public @Nullable Json postVisit(Json tree, P p) { + if (stopAfter != null && stopAfter.isScope(tree)) { + getCursor().putMessageOnFirstEnclosing(Json.Document.class, "stop", true); + } + return super.postVisit(tree, p); + } + + @Override + public @Nullable Json visit(@Nullable Tree tree, P p) { + if (getCursor().getNearestMessage("stop") != null) { + return (Json) tree; + } + return super.visit(tree, p); + } + + private String createIndent(String ws, int indentMultiple) { + StringBuilder shiftedPrefixBuilder = new StringBuilder(ws.substring(0, ws.lastIndexOf('\n') + 1)); + for (int i = 0; i < indentMultiple; i++) { + if (style.getUseTabCharacter()) { + shiftedPrefixBuilder.append("\t"); + } else { + for (int j = 0; j < style.getIndentSize(); j++) { + shiftedPrefixBuilder.append(" "); + } + } + } + + return shiftedPrefixBuilder.toString(); + } +} diff --git a/rewrite-json/src/main/java/org/openrewrite/json/format/package-info.java b/rewrite-json/src/main/java/org/openrewrite/json/format/package-info.java new file mode 100644 index 00000000000..cfab9d193b9 --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/format/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.json.format; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-json/src/main/java/org/openrewrite/json/style/Autodetect.java b/rewrite-json/src/main/java/org/openrewrite/json/style/Autodetect.java new file mode 100644 index 00000000000..c07657d42e6 --- /dev/null +++ b/rewrite-json/src/main/java/org/openrewrite/json/style/Autodetect.java @@ -0,0 +1,237 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.json.style; + +import com.fasterxml.jackson.annotation.JsonCreator; +import org.jspecify.annotations.Nullable; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.json.JsonVisitor; +import org.openrewrite.style.GeneralFormatStyle; +import org.openrewrite.style.NamedStyles; +import org.openrewrite.style.Style; +import org.openrewrite.json.tree.Json; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static java.util.Collections.emptySet; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.counting; + +public class Autodetect extends NamedStyles { + @JsonCreator + public Autodetect(UUID id, Collection - - - - - - - """.trimIndent() - ) - } -} - -fun visualize( - gav: String, - ctx: ExecutionContext = InMemoryExecutionContext { t -> throw t }, - scope: Scope = Scope.Compile, - showProperties: Boolean = false, - showManagedDependencies: Boolean = false -) { - visualize(ctx, scope, Paths.get("diagrams"), gav.replace(':', '_'), showProperties, showManagedDependencies) { - parse(gav, ctx) - } -} - -fun parse(gav: String, ctx: ExecutionContext = InMemoryExecutionContext { t -> throw t }): MavenResolutionResult { - val (group, artifact, version) = gav.split(":") - val maven = MavenParser.builder().build().parse( - ctx, """ - - org.openrewrite - dependency-viz - 0.0.1 - - - ${group} - ${artifact} - ${version} - - - - """ - )[0] - - return maven.mavenResolutionResult() -} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java index fbc2644eb39..6bcac007942 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java @@ -28,6 +28,7 @@ import org.openrewrite.xml.tree.Xml; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import static org.openrewrite.maven.trait.Traits.mavenPlugin; @@ -49,7 +50,7 @@ public class AddAnnotationProcessor extends Recipe { @Option(displayName = "Version", description = "The third part of a coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add. " + - "Note that an exact version is expected", + "Note that an exact version is expected", example = "0.2.0") String version; @@ -61,8 +62,8 @@ public String getDisplayName() { @Override public String getDescription() { return "Add an annotation processor to the maven compiler plugin. Will not do anything if it already exists. " + - "Also doesn't add anything when no other annotation processors are defined yet. " + - "(Perhaps `ChangePluginConfiguration` can be used)."; + "Also doesn't add anything when no other annotation processors are defined yet. " + + "(Perhaps `ChangePluginConfiguration` can be used)."; } @Override @@ -73,8 +74,9 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag plugins = (Xml.Tag) super.visitTag(tag, ctx); plugins = (Xml.Tag) mavenPlugin().asVisitor(plugin -> { if (MAVEN_COMPILER_PLUGIN_GROUP_ID.equals(plugin.getGroupId()) && - MAVEN_COMPILER_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) { - return new XmlIsoVisitor() { + MAVEN_COMPILER_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) { + AtomicReference> afterVisitor = new AtomicReference<>(); + Xml.Tag modifiedPlugin = new XmlIsoVisitor() { @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag tg = super.visitTag(tag, ctx); @@ -82,14 +84,22 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { for (int i = 0; i < tg.getChildren().size(); i++) { Xml.Tag child = tg.getChildren().get(i); if (groupId.equals(child.getChildValue("groupId").orElse(null)) && - artifactId.equals(child.getChildValue("artifactId").orElse(null))) { + artifactId.equals(child.getChildValue("artifactId").orElse(null))) { if (!version.equals(child.getChildValue("version").orElse(null))) { String oldVersion = child.getChildValue("version").orElse(""); - VersionComparator comparator = Semver.validate(oldVersion, null).getValue(); - if (comparator.compare(version, oldVersion) > 0) { - List tags = tg.getChildren(); - tags.set(i, child.withChildValue("version", version)); - return tg.withContent(tags); + boolean oldVersionUsesProperty = oldVersion.startsWith("${"); + String lookupVersion = oldVersionUsesProperty ? + getResolutionResult().getPom().getValue(oldVersion.trim()) : + oldVersion; + VersionComparator comparator = Semver.validate(lookupVersion, null).getValue(); + if (comparator.compare(version, lookupVersion) > 0) { + if (oldVersionUsesProperty) { + afterVisitor.set(new ChangePropertyValue(oldVersion, version, null, null).getVisitor()); + } else { + List tags = tg.getChildren(); + tags.set(i, child.withChildValue("version", version)); + return tg.withContent(tags); + } } } return tg; @@ -102,6 +112,10 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { return tg; } }.visitTag(plugin.getTree(), ctx); + if (afterVisitor.get() != null) { + doAfterVisit(afterVisitor.get()); + } + return modifiedPlugin; } return plugin.getTree(); }).visitNonNull(plugins, 0); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index 0dc1f33bd74..f9e4b4e8d9f 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -78,7 +78,7 @@ public class AddDependency extends ScanningRecipe { description = "A scope to use when it is not what can be inferred from usage. Most of the time this will be left empty, but " + "is used when adding a runtime, provided, or import dependency.", example = "runtime", - valid = {"import", "runtime", "provided"}, + valid = {"import", "runtime", "provided", "test"}, required = false) @Nullable String scope; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index 96e6b4a5eaa..4bb4ccce955 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -175,7 +175,7 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { maybeUpdateModel(); return t; } - if (isOldDependencyTag) { + if (isOldDependencyTag || isPluginDependencyTag(oldGroupId, oldArtifactId)) { String groupId = newGroupId; if (groupId != null) { t = changeChildTagValue(t, "groupId", groupId, ctx); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactId.java index ba633f1dd9e..3ca0e66448e 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactId.java @@ -58,17 +58,12 @@ public class ChangePluginGroupIdAndArtifactId extends Recipe { @Nullable String newArtifactId; - /** - * Mistakenly introduced, we restored newArtifactId but let's not break recipes abruptly. - */ - @Option(displayName = "New artifact ID", - description = "The new artifact ID to use. Defaults to the existing artifact ID. This property is deprecated, use newArtifactId instead.", - example = "my-new-maven-plugin", - required = false) + @Option(displayName = "New version", + description = "An exact version number or node-style semver selector used to select the version number.", + example = "29.X", + required = false) @Nullable - @Deprecated - @SuppressWarnings("DeprecatedIsStillUsed") - String newArtifact; + String newVersion; @Override public String getDisplayName() { @@ -82,7 +77,7 @@ public String getInstanceNameSuffix() { @Override public String getDescription() { - return "Change the groupId and/or the artifactId of a specified Maven plugin."; + return "Change the groupId and/or the artifactId of a specified Maven plugin. Optionally update the plugin version."; } @Override @@ -98,8 +93,9 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { } if (newArtifactId != null) { t = changeChildTagValue(t, "artifactId", newArtifactId, ctx); - } else if (newArtifact != null) { - t = changeChildTagValue(t, "artifactId", newArtifact, ctx); + } + if (newVersion != null) { + t = changeChildTagValue(t, "version", newVersion, ctx); } if (t != tag) { maybeUpdateModel(); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/EnableDevelocityBuildCache.java b/rewrite-maven/src/main/java/org/openrewrite/maven/EnableDevelocityBuildCache.java new file mode 100644 index 00000000000..ef3f58f0ca6 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/EnableDevelocityBuildCache.java @@ -0,0 +1,107 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.xml.XmlVisitor; +import org.openrewrite.xml.tree.Xml; + +@Value +@EqualsAndHashCode(callSuper = false) +public class EnableDevelocityBuildCache extends Recipe { + + @Override + public String getDisplayName() { + return "Enable Develocity build cache"; + } + + @Override + public String getDescription() { + return "Add Develocity build cache configuration to any `.mvn/` Develocity configuration file that lack existing configuration."; + } + + @Option(displayName = "Enable local build cache", + description = "Value for `//develocity/buildCache/local/enabled`.", + example = "true", + required = false) + @Nullable + String localEnabled; + + @Option(displayName = "Enable remote build cache", + description = "Value for `//develocity/buildCache/remote/enabled`.", + example = "true", + required = false) + @Nullable + String remoteEnabled; + + @Option(displayName = "Enable remote build cache store", + description = "Value for `//develocity/buildCache/remote/storeEnabled`.", + example = "#{isTrue(env['CI'])}", + required = false) + @Nullable + String remoteStoreEnabled; + + @Override + public Validated validate(ExecutionContext ctx) { + return super.validate(ctx) + .and(Validated.notBlank("localEnabled", localEnabled) + .or(Validated.notBlank("remoteEnabled", remoteEnabled)) + .or(Validated.notBlank("remoteStoreEnabled", remoteStoreEnabled))); + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new FindSourceFiles(".mvn/*.xml"), new XmlVisitor() { + @Override + public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { + Xml.Tag rootTag = document.getRoot(); + + if ("develocity".equals(rootTag.getName()) && !rootTag.getChild("buildCache").isPresent()) { + Xml.Tag tag = Xml.Tag.build(buildCacheConfig()); + rootTag = maybeAutoFormat(rootTag, rootTag.withContent(ListUtils.concat(rootTag.getChildren(), tag)), ctx); + return document.withRoot(rootTag); + } + return document; + } + + private String buildCacheConfig() { + StringBuilder sb = new StringBuilder(""); + if (!StringUtils.isBlank(localEnabled)) { + sb.append(""); + sb.append("").append(localEnabled).append(""); + sb.append(""); + } + if (!StringUtils.isBlank(remoteEnabled) || !StringUtils.isBlank(remoteStoreEnabled)) { + sb.append(""); + if (!StringUtils.isBlank(remoteEnabled)) { + sb.append("").append(remoteEnabled).append(""); + } + if (!StringUtils.isBlank(remoteStoreEnabled)) { + sb.append("").append(remoteStoreEnabled).append(""); + } + sb.append(""); + } + sb.append(""); + return sb.toString(); + } + }); + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java index 780127619be..af1691941b7 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java @@ -165,13 +165,15 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { return t; } String newVersion = acc.get(new GroupArtifact( - t.getChildValue("groupId").orElse(null), t.getChildValue("artifactId").orElse(null))); - if (newVersion == null || newVersion.equals(t.getChildValue("version").orElse(null))) { + t.getChildValue("groupId").orElse(null), + t.getChildValue("artifactId").orElse(null))); + String oldVersion = t.getChildValue("version").orElse(null); + if (newVersion == null || newVersion.equals(oldVersion)) { return t; } t = t.withMarkers(t.getMarkers().add(new AlreadyIncremented(randomId()))); - return (Xml.Tag) new ChangeTagValue("version", null, newVersion).getVisitor() - .visitNonNull(t, ctx); + return (Xml.Tag) new ChangeTagValue("version", oldVersion, newVersion, null) + .getVisitor().visitNonNull(t, ctx); } }; } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSecuritySettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSecuritySettings.java new file mode 100644 index 00000000000..f5006c9c612 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSecuritySettings.java @@ -0,0 +1,214 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.maven; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.internal.PropertyPlaceholderHelper; +import org.openrewrite.maven.internal.MavenXmlMapper; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Optional; +import java.util.function.UnaryOperator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Collections.emptyList; + +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +@ToString(onlyExplicitlyIncluded = true) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Data +@AllArgsConstructor +@JacksonXmlRootElement(localName = "settingsSecurity") +public class MavenSecuritySettings { + + @Nullable + String master; + + @Nullable + String relocation; + + private static @Nullable MavenSecuritySettings parse(Parser.Input source, ExecutionContext ctx) { + try { + return new Interpolator().interpolate( + MavenXmlMapper.readMapper().readValue(source.getSource(ctx), MavenSecuritySettings.class)); + } catch (IOException e) { + ctx.getOnError().accept(new IOException("Failed to parse " + source.getPath(), e)); + return null; + } + } + + private static @Nullable MavenSecuritySettings parse(Path settingsPath, ExecutionContext ctx) { + return parse(new Parser.Input(settingsPath, () -> { + try { + return Files.newInputStream(settingsPath); + } catch (IOException e) { + ctx.getOnError().accept(new IOException("Failed to read settings-security.xml at " + settingsPath, e)); + return null; + } + }), ctx); + } + + public static @Nullable MavenSecuritySettings readMavenSecuritySettingsFromDisk(ExecutionContext ctx) { + Optional userSettings = Optional.of(userSecuritySettingsPath()) + .filter(MavenSecuritySettings::exists) + .map(path -> parse(path, ctx)); + MavenSecuritySettings installSettings = findMavenHomeSettings().map(path -> parse(path, ctx)).orElse(null); + MavenSecuritySettings mergedSettings = userSettings + .map(mavenSecuritySettings -> mavenSecuritySettings.merge(installSettings)) + .orElse(installSettings); + if (mergedSettings != null && mergedSettings.relocation != null) { + return mergedSettings.merge(parse(Paths.get(mergedSettings.relocation), ctx)); + } + return mergedSettings; + } + + private static Path userSecuritySettingsPath() { + return Paths.get(System.getProperty("user.home")).resolve(".m2/settings-security.xml"); + } + + private static Optional findMavenHomeSettings() { + for (String envVariable : Arrays.asList("MVN_HOME", "M2_HOME", "MAVEN_HOME")) { + for (String s : Optional.ofNullable(System.getenv(envVariable)).map(Arrays::asList).orElse(emptyList())) { + Path resolve = Paths.get(s).resolve("conf/settings-security.xml"); + if (exists(resolve)) { + return Optional.of(resolve); + } + } + } + return Optional.empty(); + } + + private static boolean exists(Path path) { + try { + return path.toFile().exists(); + } catch (SecurityException e) { + return false; + } + } + + private MavenSecuritySettings merge(@Nullable MavenSecuritySettings installSettings) { + return installSettings == null ? this : new MavenSecuritySettings( + master == null ? installSettings.master : master, + relocation == null ? installSettings.relocation : relocation + ); + } + + /** + * Resolve all properties EXCEPT in the profiles section, which can be affected by + * the POM using the settings. + */ + private static class Interpolator { + private static final PropertyPlaceholderHelper propertyPlaceholders = new PropertyPlaceholderHelper( + "${", "}", null); + + private static final UnaryOperator propertyResolver = key -> { + String property = System.getProperty(key); + if (property != null) { + return property; + } + if (key.startsWith("env.")) { + return System.getenv().get(key.substring(4)); + } + return System.getenv().get(key); + }; + + public MavenSecuritySettings interpolate(MavenSecuritySettings mavenSecuritySettings) { + return new MavenSecuritySettings( + interpolate(mavenSecuritySettings.master), + interpolate(mavenSecuritySettings.relocation) + ); + } + + private @Nullable String interpolate(@Nullable String s) { + return s == null ? null : propertyPlaceholders.replacePlaceholders(s, propertyResolver); + } + } + + @Nullable + String decrypt(@Nullable String fieldValue, @Nullable String password) { + if (fieldValue == null || fieldValue.isEmpty() || password == null) { + return null; + } + + try { + byte[] encryptedText = extractPassword(fieldValue); + + byte[] salt = new byte[8]; + System.arraycopy(encryptedText, 0, salt, 0, 8); + + int padLength = encryptedText[8]; + byte[] encryptedBytes = new byte[encryptedText.length - 9 - padLength]; + System.arraycopy(encryptedText, 9, encryptedBytes, 0, encryptedBytes.length); + + byte[] keyAndIV = new byte[32]; + byte[] pwdBytes = extractPassword(password); + int offset = 0; + while (offset < 32) { + java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256"); + digest.update(pwdBytes); + digest.update(salt); + byte[] hash = digest.digest(); + System.arraycopy(hash, 0, keyAndIV, offset, Math.min(hash.length, 32 - offset)); + offset += hash.length; + } + + Key key = new SecretKeySpec(keyAndIV, 0, 16, "AES"); + IvParameterSpec iv = new IvParameterSpec(keyAndIV, 16, 16); + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + byte[] clearBytes = cipher.doFinal(encryptedBytes); + + int paddingLength = clearBytes[clearBytes.length - 1]; + byte[] decryptedBytes = new byte[clearBytes.length - paddingLength]; + System.arraycopy(clearBytes, 0, decryptedBytes, 0, decryptedBytes.length); + return new String(decryptedBytes, StandardCharsets.UTF_8); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | + InvalidKeyException | InvalidAlgorithmParameterException | IllegalArgumentException e) { + return null; + } + } + + private byte[] extractPassword(String pwd) throws IllegalArgumentException { + Pattern pattern = Pattern.compile(".*?[^\\\\]?\\{(.*?)}.*"); + Matcher matcher = pattern.matcher(pwd); + if (matcher.find()) { + return Base64.getDecoder().decode(matcher.group(1)); + } + return pwd.getBytes(StandardCharsets.UTF_8); + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java index ae23c9ff06b..2c56bd6ddee 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java @@ -34,12 +34,25 @@ import org.openrewrite.maven.tree.MavenRepository; import org.openrewrite.maven.tree.ProfileActivation; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.function.UnaryOperator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static java.util.Collections.emptyList; import static org.openrewrite.maven.tree.MavenRepository.MAVEN_LOCAL_DEFAULT; @@ -109,10 +122,40 @@ public MavenSettings(@Nullable String localRepository, @Nullable Profiles profil .filter(MavenSettings::exists) .map(path -> parse(path, ctx)); final MavenSettings installSettings = findMavenHomeSettings().map(path -> parse(path, ctx)).orElse(null); - return userSettings.map(mavenSettings -> mavenSettings.merge(installSettings)) + MavenSettings settings = userSettings.map(mavenSettings -> mavenSettings.merge(installSettings)) .orElse(installSettings); + + if (settings != null) { + settings.maybeDecryptPasswords(ctx); + } + + return settings; } + void maybeDecryptPasswords(ExecutionContext ctx) { + MavenSecuritySettings security = MavenSecuritySettings.readMavenSecuritySettingsFromDisk(ctx); + if (security == null) { + return; + } + + String decryptedMasterPassword = security.decrypt(security.getMaster(), "settings.security"); + if (decryptedMasterPassword != null) { + if (mavenLocal != null) { + String password = security.decrypt(mavenLocal.getPassword(), decryptedMasterPassword); + if (password != null) { + mavenLocal = mavenLocal.withPassword(password); + } + } + if (servers != null) { + servers.servers = ListUtils.map(servers.servers, server -> { + String password = security.decrypt(server.getPassword(), decryptedMasterPassword); + return password == null ? server : server.withPassword(password); + }); + } + } + } + + public static boolean readFromDiskEnabled() { final String propertyValue = System.getProperty("org.openrewrite.test.readMavenSettingsFromDisk"); return propertyValue != null && !propertyValue.equalsIgnoreCase("false"); @@ -158,7 +201,7 @@ public List getActiveRepositories(Iterable a if (profiles != null) { for (Profile profile : profiles.getProfiles()) { if (profile.isActive(activeProfiles) || (this.activeProfiles != null && - profile.isActive(this.activeProfiles.getActiveProfiles()))) { + profile.isActive(this.activeProfiles.getActiveProfiles()))) { if (profile.repositories != null) { for (RawRepositories.Repository repository : profile.repositories.getRepositories()) { activeRepositories.put(repository.getId(), repository); @@ -409,7 +452,8 @@ public static class Server { @JsonIgnoreProperties(value = "httpHeaders") public static class ServerConfiguration { @JacksonXmlProperty(localName = "property") - @JacksonXmlElementWrapper(localName = "httpHeaders", useWrapping = true) // wrapping is disabled by default on MavenXmlMapper + @JacksonXmlElementWrapper(localName = "httpHeaders", useWrapping = true) + // wrapping is disabled by default on MavenXmlMapper @Nullable List httpHeaders; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java index 0d015f4417d..d60df379b3c 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java @@ -70,7 +70,7 @@ public TreeVisitor getVisitor() { @Override public Validated validate() { return super.validate().and(Validated.test("scope", "Scope must be one of compile, runtime, test, or provided", - scope, s -> !Scope.Invalid.equals(Scope.fromName(s)))); + scope, s -> Scope.Invalid != Scope.fromName(s))); } private class RemoveDependencyVisitor extends MavenIsoVisitor { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveManagedDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveManagedDependency.java index f65fbf99269..2f1de67fc90 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveManagedDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveManagedDependency.java @@ -69,7 +69,7 @@ public TreeVisitor getVisitor() { @Override public Validated validate() { return super.validate().and(Validated.test("scope", "Scope must be one of compile, runtime, test, or provided", - scope, s -> !Scope.Invalid.equals(Scope.fromName(s)))); + scope, s -> Scope.Invalid != Scope.fromName(s))); } private class RemoveManagedDependencyVisitor extends MavenIsoVisitor { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveRedundantDependencyVersions.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveRedundantDependencyVersions.java index d4e94d99975..dbe5719624e 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveRedundantDependencyVersions.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveRedundantDependencyVersions.java @@ -167,7 +167,7 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { if (d != document) { d = (Xml.Document) new RemoveEmptyDependenciesTags().visitNonNull(d, ctx); d = (Xml.Document) new RemoveEmptyPluginsTags().visitNonNull(d, ctx); - if (!comparator.equals(Comparator.EQ)) { + if (comparator != Comparator.EQ) { maybeUpdateModel(); } } @@ -323,7 +323,7 @@ private boolean matchesComparator(@Nullable String managedVersion, String reques if (managedVersion == null) { return false; } - if (comparator.equals(Comparator.ANY)) { + if (comparator == Comparator.ANY) { return true; } if (!isExact(managedVersion)) { @@ -333,11 +333,11 @@ private boolean matchesComparator(@Nullable String managedVersion, String reques .compare(null, managedVersion, Objects.requireNonNull(getResolutionResult().getPom().getValue(requestedVersion))); if (comparison < 0) { - return comparator.equals(Comparator.LT) || comparator.equals(Comparator.LTE); + return comparator == Comparator.LT || comparator == Comparator.LTE; } else if (comparison > 0) { - return comparator.equals(Comparator.GT) || comparator.equals(Comparator.GTE); + return comparator == Comparator.GT || comparator == Comparator.GTE; } else { - return comparator.equals(Comparator.EQ) || comparator.equals(Comparator.LTE) || comparator.equals(Comparator.GTE); + return comparator == Comparator.EQ || comparator == Comparator.LTE || comparator == Comparator.GTE; } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveUnusedProperties.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveUnusedProperties.java index 59fc3fc889a..913a3f4bf39 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveUnusedProperties.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveUnusedProperties.java @@ -19,11 +19,14 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import org.openrewrite.binary.Binary; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.quark.Quark; +import org.openrewrite.remote.Remote; import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextParser; import org.openrewrite.text.PlainTextVisitor; @@ -97,6 +100,9 @@ public TreeVisitor getScanner(RemoveUnusedProperties.Accumu return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof Quark || tree instanceof Remote || tree instanceof Binary) { + return tree; + } if (tree instanceof SourceFile) { SourceFile sf = (SourceFile) tree; if (findPomUsagesVisitor.isAcceptable(sf, ctx)) { // ie: is a pom @@ -232,7 +238,7 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { if (resourceMatcher.matches(getCursor())) { String directory = tag.getChildValue("directory").orElse(null); if (tag.getChildValue("filtering").map(Boolean::valueOf).orElse(false) && - directory != null) { + directory != null) { Path path = getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath(); try { acc.filteredResourcePathsToDeclaringPoms.put(path.getParent().resolve(directory), getResolutionResult()); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java new file mode 100644 index 00000000000..405ffbb4645 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java @@ -0,0 +1,142 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UpdateMavenProjectPropertyJavaVersion extends Recipe { + + private static final List JAVA_VERSION_PROPERTIES = Arrays.asList( + "java.version", + "jdk.version", + "javaVersion", + "jdkVersion", + "maven.compiler.source", + "maven.compiler.target", + "maven.compiler.release", + "release.version"); + + private static final List JAVA_VERSION_XPATH_MATCHERS = + JAVA_VERSION_PROPERTIES.stream() + .map(property -> "/project/properties/" + property) + .map(XPathMatcher::new).collect(Collectors.toList()); + + private static final XPathMatcher PLUGINS_MATCHER = new XPathMatcher("/project/build//plugins"); + + @Option(displayName = "Java version", + description = "The Java version to upgrade to.", + example = "11") + Integer version; + + @Override + public String getDisplayName() { + return "Update Maven Java project properties"; + } + + @Override + public String getDescription() { + //language=markdown + return "The Java version is determined by several project properties, including:\n\n" + + " * `java.version`\n" + + " * `jdk.version`\n" + + " * `javaVersion`\n" + + " * `jdkVersion`\n" + + " * `maven.compiler.source`\n" + + " * `maven.compiler.target`\n" + + " * `maven.compiler.release`\n" + + " * `release.version`\n\n" + + "If none of these properties are in use and the maven compiler plugin is not otherwise configured, adds the `maven.compiler.release` property."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenIsoVisitor() { + boolean compilerPluginConfiguredExplicitly; + + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + // Update properties already defined in the current pom + Xml.Document d = super.visitDocument(document, ctx); + + // Return early if the parent appears to be within the current repository, as properties defined there will be updated + if (getResolutionResult().parentPomIsProjectPom()) { + return d; + } + + // Otherwise override remote parent's properties locally + Map currentProperties = getResolutionResult().getPom().getProperties(); + boolean foundProperty = false; + for (String property : JAVA_VERSION_PROPERTIES) { + String propertyValue = currentProperties.get(property); + if (propertyValue != null) { + foundProperty = true; + try { + if (Float.parseFloat(propertyValue) < version) { + d = (Xml.Document) new AddProperty(property, String.valueOf(version), null, false) + .getVisitor() + .visitNonNull(d, ctx); + maybeUpdateModel(); + } + } catch (NumberFormatException ex) { + // either an expression or something else, don't touch + } + } + } + + // When none of the relevant properties are explicitly configured Maven defaults to Java 8 + // The release option was added in 9 + // If no properties have yet been updated then set release explicitly + if (!foundProperty && version >= 9 && !compilerPluginConfiguredExplicitly) { + d = (Xml.Document) new AddProperty("maven.compiler.release", String.valueOf(version), null, false) + .getVisitor() + .visitNonNull(d, ctx); + maybeUpdateModel(); + } + + return d; + } + + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + if (isPluginTag("org.apache.maven.plugins", "maven-compiler-plugin")) { + t.getChild("configuration").ifPresent(compilerPluginConfig -> { + if (compilerPluginConfig.getChildValue("source").isPresent() || + compilerPluginConfig.getChildValue("target").isPresent() || + compilerPluginConfig.getChildValue("release").isPresent()) { + compilerPluginConfiguredExplicitly = true; + } + }); + } + return t; + } + }; + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java new file mode 100644 index 00000000000..c126accf448 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Optional; + +import static org.openrewrite.xml.AddOrUpdateChild.addOrUpdateChild; +import static org.openrewrite.xml.FilterTagChildrenVisitor.filterTagChildren; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseMavenCompilerPluginReleaseConfiguration extends Recipe { + private static final XPathMatcher PLUGINS_MATCHER = new XPathMatcher("/project/build//plugins"); + + @Option( + displayName = "Release version", + description = "The new value for the release configuration. This recipe prefers ${java.version} if defined.", + example = "11" + ) + Integer releaseVersion; + + @Override + public String getDisplayName() { + return "Use Maven compiler plugin release configuration"; + } + + @Override + public String getDescription() { + return "Replaces any explicit `source` or `target` configuration (if present) on the `maven-compiler-plugin` with " + + "`release`, and updates the `release` value if needed. Will not downgrade the Java version if the current version is higher."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenIsoVisitor() { + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + if (!PLUGINS_MATCHER.matches(getCursor())) { + return t; + } + Optional maybeCompilerPlugin = t.getChildren().stream() + .filter(plugin -> + "plugin".equals(plugin.getName()) && + "org.apache.maven.plugins".equals(plugin.getChildValue("groupId").orElse("org.apache.maven.plugins")) && + "maven-compiler-plugin".equals(plugin.getChildValue("artifactId").orElse(null))) + .findAny(); + Optional maybeCompilerPluginConfig = maybeCompilerPlugin + .flatMap(it -> it.getChild("configuration")); + if (!maybeCompilerPluginConfig.isPresent()) { + return t; + } + Xml.Tag compilerPluginConfig = maybeCompilerPluginConfig.get(); + Optional source = compilerPluginConfig.getChildValue("source"); + Optional target = compilerPluginConfig.getChildValue("target"); + Optional release = compilerPluginConfig.getChildValue("release"); + if (!source.isPresent() && + !target.isPresent() && + !release.isPresent() || + currentNewerThanProposed(release)) { + return t; + } + Xml.Tag updated = filterTagChildren(t, compilerPluginConfig, + child -> !("source".equals(child.getName()) || "target".equals(child.getName()))); + String releaseVersionValue = hasJavaVersionProperty(getCursor().firstEnclosingOrThrow(Xml.Document.class)) ? + "${java.version}" : releaseVersion.toString(); + updated = addOrUpdateChild(updated, compilerPluginConfig, + Xml.Tag.build("" + releaseVersionValue + ""), getCursor().getParentOrThrow()); + return updated; + } + + }; + } + + private boolean currentNewerThanProposed(@SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional maybeRelease) { + if (!maybeRelease.isPresent()) { + return false; + } + try { + float currentVersion = Float.parseFloat(maybeRelease.get()); + float proposedVersion = Float.parseFloat(releaseVersion.toString()); + return proposedVersion < currentVersion; + } catch (NumberFormatException e) { + return false; + } + } + + private boolean hasJavaVersionProperty(Xml.Document xml) { + return xml.getMarkers().findFirst(MavenResolutionResult.class) + .map(r -> r.getPom().getProperties().get("java.version") != null) + .orElse(false); + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java index 25e50422922..452c01b7eb2 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java @@ -44,7 +44,7 @@ public TreeVisitor getVisitor() { @Override public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx); - if (isPluginTag() && !t.getChild("groupId").isPresent()) { + if (isPluginTag() && !t.getChild("groupId").isPresent() && t.getChild("artifactId").isPresent()) { Xml.Tag groupIdTag = Xml.Tag.build("org.apache.maven.plugins"); t = (Xml.Tag) new AddToTagVisitor<>(t, groupIdTag, new MavenTagInsertionComparator(t.getChildren())) .visitNonNull(t, ctx, getCursor().getParentOrThrow()); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java index 6669c1321af..d439dcb0154 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java @@ -71,7 +71,7 @@ public int compare(Content o1, Content o2) { private static final Comparator dependencyComparator = (d1, d2) -> { Scope scope1 = Scope.fromName(d1.getChildValue("scope").orElse(null)); Scope scope2 = Scope.fromName(d2.getChildValue("scope").orElse(null)); - if (!scope1.equals(scope2)) { + if (scope1 != scope2) { return scope1.compareTo(scope2); } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 364078042d7..175cdb7a9a0 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -219,7 +219,7 @@ private List getAncestryWithinProject(Pom projectPom, Map projec .normalize(); Pom parentPom = projectPoms.get(parentPath); return parentPom != null && parentPom.getGav().getGroupId().equals(parent.getGav().getGroupId()) && - parentPom.getGav().getArtifactId().equals(parent.getGav().getArtifactId()) ? parentPom : null; + parentPom.getGav().getArtifactId().equals(parent.getGav().getArtifactId()) ? parentPom : null; } public MavenMetadata downloadMetadata(GroupArtifact groupArtifact, @Nullable ResolvedPom containingPom, List repositories) throws MavenDownloadingException { @@ -259,9 +259,9 @@ public MavenMetadata downloadMetadata(GroupArtifactVersion gav, @Nullable Resolv try { String scheme = URI.create(repo.getUri()).getScheme(); String baseUri = repo.getUri() + (repo.getUri().endsWith("/") ? "" : "/") + - requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + - gav.getArtifactId() + '/' + - (gav.getVersion() == null ? "" : gav.getVersion() + '/'); + requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + + gav.getArtifactId() + '/' + + (gav.getVersion() == null ? "" : gav.getVersion() + '/'); if ("file".equals(scheme)) { // A maven repository can be expressed as a URI with a file scheme @@ -356,8 +356,8 @@ public MavenMetadata downloadMetadata(GroupArtifactVersion gav, @Nullable Resolv String scheme = URI.create(repo.getUri()).getScheme(); String uri = repo.getUri() + (repo.getUri().endsWith("/") ? "" : "/") + - requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + - gav.getArtifactId() + '/'; + requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + + gav.getArtifactId() + '/'; try { MavenMetadata.Versioning versioning; @@ -496,7 +496,7 @@ public Pom download(GroupArtifactVersion gav, // The requested gav might itself have an unresolved placeholder in the version, so also check raw values for (Pom projectPom : projectPoms.values()) { if (!projectPom.getGroupId().equals(gav.getGroupId()) || - !projectPom.getArtifactId().equals(gav.getArtifactId())) { + !projectPom.getArtifactId().equals(gav.getArtifactId())) { continue; } @@ -512,7 +512,7 @@ public Pom download(GroupArtifactVersion gav, } if (containingPom != null && containingPom.getRequested().getSourcePath() != null && - !StringUtils.isBlank(relativePath) && !relativePath.contains(":")) { + !StringUtils.isBlank(relativePath) && !relativePath.contains(":")) { Path folderContainingPom = containingPom.getRequested().getSourcePath().getParent(); if (folderContainingPom != null) { Pom maybeLocalPom = projectPoms.get(folderContainingPom.resolve(Paths.get(relativePath, "pom.xml")) @@ -521,9 +521,9 @@ public Pom download(GroupArtifactVersion gav, // So double check that the GAV coordinates match so that we don't get a relative path from a remote // pom like ".." or "../.." which coincidentally _happens_ to have led to an unrelated pom on the local filesystem if (maybeLocalPom != null && - gav.getGroupId().equals(maybeLocalPom.getGroupId()) && - gav.getArtifactId().equals(maybeLocalPom.getArtifactId()) && - gav.getVersion().equals(maybeLocalPom.getVersion())) { + gav.getGroupId().equals(maybeLocalPom.getGroupId()) && + gav.getArtifactId().equals(maybeLocalPom.getArtifactId()) && + gav.getVersion().equals(maybeLocalPom.getVersion())) { return maybeLocalPom; } } @@ -552,60 +552,84 @@ public Pom download(GroupArtifactVersion gav, if (result == null) { URI uri = URI.create(repo.getUri() + (repo.getUri().endsWith("/") ? "" : "/") + - requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + - gav.getArtifactId() + '/' + - gav.getVersion() + '/' + - gav.getArtifactId() + '-' + versionMaybeDatedSnapshot + ".pom"); + requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + + gav.getArtifactId() + '/' + + gav.getVersion() + '/' + + gav.getArtifactId() + '-' + versionMaybeDatedSnapshot + ".pom"); uris.add(uri.toString()); if ("file".equals(uri.getScheme())) { Path inputPath = Paths.get(gav.getGroupId(), gav.getArtifactId(), gav.getVersion()); try { - File f = new File(uri); + File pomFile = new File(uri); + File jarFile = pomFile.toPath().resolveSibling(gav.getArtifactId() + '-' + versionMaybeDatedSnapshot + ".jar").toFile(); - //NOTE: The pom may exist without a .jar artifact if the pom packaging is "pom" - if (!f.exists()) { + //NOTE: + // - The pom may exist without a .jar artifact if the pom packaging is "pom" + // - The jar may exist without a pom, if manually installed + if (!pomFile.exists() && !jarFile.exists()) { continue; } - try (FileInputStream fis = new FileInputStream(f)) { - RawPom rawPom = RawPom.parse(fis, Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot); - Pom pom = rawPom.toPom(inputPath, repo).withGav(resolvedGav); - - if (pom.getPackaging() == null || pom.hasJarPackaging()) { - File jar = f.toPath().resolveSibling(gav.getArtifactId() + '-' + versionMaybeDatedSnapshot + ".jar").toFile(); - if (!jar.exists() || jar.length() == 0) { - // The jar has not been downloaded, making this dependency unusable. - continue; - } + RawPom rawPom; + if (pomFile.exists()) { + try (FileInputStream fis = new FileInputStream(pomFile)) { + rawPom = RawPom.parse(fis, Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot); } + } else { + // Record the absense of the pom file + ctx.getResolutionListener().downloadError(gav, uris, (containingPom == null) ? null : containingPom.getRequested()); + // infer rawPom from jar + rawPom = rawPomFromGav(gav); + } - if (repo.getUri().equals(MavenRepository.MAVEN_LOCAL_DEFAULT.getUri())) { - // so that the repository path is the same regardless of username - pom = pom.withRepository(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL); - } + Pom pom = rawPom.toPom(inputPath, repo).withGav(resolvedGav); - if (!Objects.equals(versionMaybeDatedSnapshot, pom.getVersion())) { - pom = pom.withGav(pom.getGav().withDatedSnapshotVersion(versionMaybeDatedSnapshot)); + if (pom.getPackaging() == null || pom.hasJarPackaging()) { + if (!jarFile.exists() || jarFile.length() == 0) { + // The jar has not been downloaded, making this dependency unusable. + continue; } - mavenCache.putPom(resolvedGav, pom); - ctx.getResolutionListener().downloadSuccess(resolvedGav, containingPom); - sample.stop(timer.tags("outcome", "from maven local").register(Metrics.globalRegistry)); - return pom; } + + if (repo.getUri().equals(MavenRepository.MAVEN_LOCAL_DEFAULT.getUri())) { + // so that the repository path is the same regardless of username + pom = pom.withRepository(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL); + } + + if (!Objects.equals(versionMaybeDatedSnapshot, pom.getVersion())) { + pom = pom.withGav(pom.getGav().withDatedSnapshotVersion(versionMaybeDatedSnapshot)); + } + mavenCache.putPom(resolvedGav, pom); + ctx.getResolutionListener().downloadSuccess(resolvedGav, containingPom); + sample.stop(timer.tags("outcome", "from maven local").register(Metrics.globalRegistry)); + return pom; } catch (IOException e) { // unable to read the pom from a file-based repository. repositoryResponses.put(repo, e.getMessage()); } } else { try { - byte[] responseBody = requestAsAuthenticatedOrAnonymous(repo, uri.toString()); + RawPom rawPom; + try { + byte[] responseBody = requestAsAuthenticatedOrAnonymous(repo, uri.toString()); + rawPom = RawPom.parse( + new ByteArrayInputStream(responseBody), + Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot + ); + } catch (HttpSenderResponseException e) { + repositoryResponses.put(repo, e.getMessage()); + // When `pom` is not found, try to see if `jar` exists for the same GAV + if (!e.isClientSideException() || !jarExistsForPomUri(repo, uri.toString())) { + throw e; + } + // Record the absense of the pom file + ctx.getResolutionListener().downloadError(gav, uris, (containingPom == null) ? null : containingPom.getRequested()); + // Continue with a recreated pom + rawPom = rawPomFromGav(gav); + } Path inputPath = Paths.get(gav.getGroupId(), gav.getArtifactId(), gav.getVersion()); - RawPom rawPom = RawPom.parse( - new ByteArrayInputStream(responseBody), - Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot - ); Pom pom = rawPom.toPom(inputPath, repo).withGav(resolvedGav); if (!Objects.equals(versionMaybeDatedSnapshot, pom.getVersion())) { pom = pom.withGav(pom.getGav().withDatedSnapshotVersion(versionMaybeDatedSnapshot)); @@ -637,6 +661,12 @@ public Pom download(GroupArtifactVersion gav, .setRepositoryResponses(repositoryResponses); } + private RawPom rawPomFromGav(GroupArtifactVersion gav) { + return new RawPom(null, null, gav.getGroupId(), gav.getArtifactId(), gav.getVersion(), null, + null, null, null, "jar", null, null, null, + null, null, null, null, null, null); + } + /** * Gets the base version from snapshot timestamp version. */ @@ -658,9 +688,9 @@ private GroupArtifactVersion handleSnapshotTimestampVersion(GroupArtifactVersion if (gav.getVersion() != null && gav.getVersion().endsWith("-SNAPSHOT")) { for (ResolvedGroupArtifactVersion pinnedSnapshotVersion : new MavenExecutionContextView(ctx).getPinnedSnapshotVersions()) { if (pinnedSnapshotVersion.getDatedSnapshotVersion() != null && - pinnedSnapshotVersion.getGroupId().equals(gav.getGroupId()) && - pinnedSnapshotVersion.getArtifactId().equals(gav.getArtifactId()) && - pinnedSnapshotVersion.getVersion().equals(gav.getVersion())) { + pinnedSnapshotVersion.getGroupId().equals(gav.getGroupId()) && + pinnedSnapshotVersion.getArtifactId().equals(gav.getArtifactId()) && + pinnedSnapshotVersion.getVersion().equals(gav.getVersion())) { return pinnedSnapshotVersion.getDatedSnapshotVersion(); } } @@ -672,6 +702,23 @@ private GroupArtifactVersion handleSnapshotTimestampVersion(GroupArtifactVersion //This can happen if the artifact only exists in the local maven cache. In this case, just return the original return gav.getVersion(); } + + // Find the newest with a matching classifier - the latest snapshot may not have artifacts for all classifiers. + List snapshotVersions = mavenMetadata.getVersioning().getSnapshotVersions(); + if (snapshotVersions != null) { + // Try to get requested classifier (this is unfortunately not present in 'gav' structure) + String classifier = getClassifierFromContainingPom(gav, containingPom); + MavenMetadata.SnapshotVersion snapshotVersion = snapshotVersions.stream() + .sorted(Comparator.comparing(MavenMetadata.SnapshotVersion::getUpdated).reversed()) + .filter(s -> Objects.equals(s.getClassifier(), classifier)) + .findFirst() + .orElse(null); + if (snapshotVersion != null) { + return snapshotVersion.getValue(); + } + } + + // Replace SNAPSHOT suffix with timestamp and build number MavenMetadata.Snapshot snapshot = mavenMetadata.getVersioning().getSnapshot(); if (snapshot != null && snapshot.getTimestamp() != null) { return gav.getVersion().replaceFirst("SNAPSHOT$", snapshot.getTimestamp() + "-" + snapshot.getBuildNumber()); @@ -850,6 +897,34 @@ private ReachabilityResult reachable(HttpSender.Request.Builder request) { } } + private boolean jarExistsForPomUri(MavenRepository repo, String pomUrl) { + String jarUrl = pomUrl.replaceAll("\\.pom$", ".jar"); + try { + try { + return Failsafe.with(retryPolicy).get(() -> { + HttpSender.Request authenticated = applyAuthenticationAndTimeoutToRequest(repo, httpSender.get(jarUrl)).build(); + try (HttpSender.Response response = httpSender.send(authenticated)) { + return response.isSuccessful(); + } + }); + } catch (FailsafeException failsafeException) { + Throwable cause = failsafeException.getCause(); + if (cause instanceof HttpSenderResponseException && hasCredentials(repo) && + ((HttpSenderResponseException) cause).isClientSideException()) { + return Failsafe.with(retryPolicy).get(() -> { + HttpSender.Request unauthenticated = httpSender.get(jarUrl).build(); + try (HttpSender.Response response = httpSender.send(unauthenticated)) { + return response.isSuccessful(); + } + }); + } + } + } catch (Throwable e) { + // Not interested in exceptions downloading the jar; we'll throw the original exception for the pom + } + return false; + } + /** * Replicates Apache Maven's behavior to attempt anonymous download if repository credentials prove invalid @@ -996,4 +1071,27 @@ private static boolean hasPomFile(Path dir, Path versionPath, GroupArtifactVersi String artifactPomFile = gav.getArtifactId() + "-" + versionPath.getFileName() + ".pom"; return Files.exists(dir.resolve(versionPath.resolve(artifactPomFile))); } + + /** + * Retrieves the classifier of a dependency from a provided resolved POM, if it exists. + * + * @param gav The group, artifact, and version information of the dependency to locate. + * @param containingPom The resolved POM that potentially contains the dependency information. + * If null, the method will return null. + * @return The classifier of the dependency within the provided POM, or null if no matching + * dependency or classifier is found. + */ + private static @Nullable String getClassifierFromContainingPom(GroupArtifactVersion gav, @Nullable ResolvedPom containingPom) { + if (containingPom != null) { + for (Dependency dep : containingPom.getRequestedDependencies()) { + if (Objects.equals(dep.getGroupId(), gav.getGroupId())) { + if (Objects.equals(dep.getArtifactId(), gav.getArtifactId())) { + return dep.getClassifier(); + } + } + } + } + return null; + } + } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/VersionRequirement.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/VersionRequirement.java index f488d30f79b..ee67ff652f4 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/VersionRequirement.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/VersionRequirement.java @@ -30,8 +30,6 @@ import org.openrewrite.maven.tree.MavenMetadata; import org.openrewrite.maven.tree.MavenRepository; import org.openrewrite.maven.tree.Version; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Iterator; import java.util.List; @@ -41,7 +39,6 @@ @RequiredArgsConstructor public class VersionRequirement { - private static final Logger logger = LoggerFactory.getLogger(VersionRequirement.class); @Nullable private final VersionRequirement nearer; @@ -100,7 +97,7 @@ static VersionSpec build(String requested, boolean direct) { CharStreams.fromString(requested)))); parser.removeErrorListeners(); - parser.addErrorListener(new LoggingErrorListener()); + parser.addErrorListener(new PrintingErrorListener()); return new VersionRangeParserBaseVisitor() { @Override @@ -287,11 +284,11 @@ public String toString() { }); } - private static class LoggingErrorListener extends BaseErrorListener { + private static class PrintingErrorListener extends BaseErrorListener { @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { - logger.warn("Syntax error at line {}:{} {}", line, charPositionInLine, msg); + System.out.printf("Syntax error at line %d:%d %s%n", line, charPositionInLine, msg); } } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GraphvizResolutionEventListener.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GraphvizResolutionEventListener.java deleted file mode 100644 index cf94887334e..00000000000 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GraphvizResolutionEventListener.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * Licensed 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.openrewrite.maven.tree; - -import guru.nidi.graphviz.attribute.Color; -import guru.nidi.graphviz.attribute.Label; -import guru.nidi.graphviz.attribute.Shape; -import guru.nidi.graphviz.attribute.Style; -import guru.nidi.graphviz.engine.Graphviz; -import guru.nidi.graphviz.model.Link; -import guru.nidi.graphviz.model.MutableGraph; -import guru.nidi.graphviz.model.MutableNode; -import lombok.RequiredArgsConstructor; -import org.jspecify.annotations.Nullable; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.stream.Collectors; - -import static guru.nidi.graphviz.model.Factory.mutGraph; -import static guru.nidi.graphviz.model.Factory.mutNode; -import static guru.nidi.graphviz.model.Link.to; -import static java.util.Collections.emptySet; - -@RequiredArgsConstructor -public class GraphvizResolutionEventListener implements ResolutionEventListener { - public static final String GRAPH_NAME = "resolution"; - - private final Map propToNode = new HashMap<>(); - private final Map pomToNode = new HashMap<>(); - private final Map> alreadySeen = new HashMap<>(); - - private MutableGraph g = mutGraph(GRAPH_NAME).setDirected(true); - - private final Scope scope; - private final boolean showProperties; - private final boolean showManagedDependencies; - - private int parentLinks; - private int dependencyLinks; - private int managedDependencies; - - @Override - public void clear() { - g = mutGraph(GRAPH_NAME).setDirected(true); - propToNode.clear(); - pomToNode.clear(); - alreadySeen.clear(); - parentLinks = 0; - dependencyLinks = 0; - managedDependencies = 0; - } - - @Override - public void parent(Pom parent, Pom containing) { - if (alreadySeen.getOrDefault(simplifyGav(containing.getGav()), emptySet()).contains(simplifyGav(parent.getGav()))) { - return; - } - alreadySeen.computeIfAbsent(simplifyGav(containing.getGav()), g -> new HashSet<>()).add(simplifyGav(parent.getGav())); - - Link link = to(gavNode(parent.getGav()).add(Style.FILLED, Color.rgb("deefee"))).with(Style.DASHED); - if (parentLinks++ == 0) { - link = link.with(Label.of("parent")); - } - gavNode(containing.getGav()).addLink(link); - } - - @Override - public void dependency(Scope scope, ResolvedDependency resolvedDependency, ResolvedPom containing) { - if (scope != this.scope) { - return; - } - - Link link = to(gavNode(resolvedDependency.getGav()).add(Style.FILLED, Color.rgb("e6eaff"))); - if (dependencyLinks++ == 0) { - link = link.with(Label.of("dependency")); - } - gavNode(containing.getGav()).addLink(link); - } - - @Override - public void property(String key, String value, Pom containing) { - if (!showProperties) { - return; - } - gavNode(containing.getGav()).addLink(propNode(key, value)); - } - - @Override - public void dependencyManagement(ManagedDependency dependencyManagement, Pom containing) { - if (!showManagedDependencies) { - return; - } - GroupArtifactVersion gav = new GroupArtifactVersion(dependencyManagement.getGroupId(), dependencyManagement.getArtifactId(), dependencyManagement.getVersion()); - - if (alreadySeen.getOrDefault(simplifyGav(containing.getGav()), emptySet()).contains(gav)) { - return; - } - alreadySeen.computeIfAbsent(simplifyGav(containing.getGav()), g -> new HashSet<>()).add(gav); - - Link link = to(dmNode(gav).add(Style.FILLED, Color.rgb("d6d6de"))); - if (managedDependencies++ == 0) { - link = link.with(Label.of("dependencyManagement")); - } - gavNode(containing.getGav()).addLink(link); - } - - @Override - public void downloadError(GroupArtifactVersion gav, List attemptedUris, @Nullable Pom containing) { - Link link = to(gavNode(gav).add(Style.FILLED, Color.rgb("ff1947"))) - .with(Label.of("error")); - if(containing != null) { - gavNode(containing.getGav()) - .addLink(link); - } - } - - @Override - public void bomImport(ResolvedGroupArtifactVersion gav, Pom containing) { - if (alreadySeen.getOrDefault(simplifyGav(containing.getGav()), emptySet()).contains(simplifyGav(gav))) { - return; - } - alreadySeen.computeIfAbsent(simplifyGav(containing.getGav()), g -> new HashSet<>()).add(simplifyGav(gav)); - - Link link = to(gavNode(gav).add(Style.FILLED, Color.rgb("e6eaff"))); - if (dependencyLinks++ == 0) { - link = link.with(Label.of("dependency")); - } - gavNode(containing.getGav()).addLink(link); - } - - @SuppressWarnings("unused") - public Graphviz graphviz() { - return Graphviz.fromGraph(g); - } - - private GroupArtifactVersion simplifyGav(ResolvedGroupArtifactVersion gav) { - return new GroupArtifactVersion(gav.getGroupId(), gav.getArtifactId(), - gav.getDatedSnapshotVersion() == null ? gav.getVersion() : gav.getDatedSnapshotVersion()); - } - - private MutableNode gavNode(ResolvedGroupArtifactVersion gav) { - return gavNode(simplifyGav(gav)); - } - - private MutableNode gavNode(GroupArtifactVersion gav) { - return pomToNode.computeIfAbsent(gav, ignored -> { - MutableNode node = mutNode(Label.lines(gav.getGroupId(), gav.getArtifactId(), gav.getVersion())) - .add(Shape.RECTANGLE, Style.FILLED, Color.rgb("f9a91b")); - String url = gavUrl(gav); - if (url != null) { - node = node.add("URL", url); - } - g.add(node); - return node; - }); - } - - private MutableNode dmNode(GroupArtifactVersion gav) { - return pomToNode.computeIfAbsent(gav, ignored -> { - MutableNode node = mutNode(Label.lines(gav.getGroupId(), gav.getArtifactId(), gav.getVersion())).add(Shape.RECTANGLE); - String url = gavUrl(gav); - if (url != null) { - node = node.add("URL", url); - } - g.add(node); - return node; - }); - } - - private MutableNode propNode(String key, String value) { - return propToNode.computeIfAbsent(key + "=" + value, ignored -> { - MutableNode node = mutNode(Label.lines(key, value)); - g.add(node); - return node; - }); - } - - private @Nullable String gavUrl(GroupArtifactVersion gav) { - if (gav.getGroupId() == null || gav.getArtifactId() == null || gav.getVersion() == null) { - return null; - } - try { - return "https://repo1.maven.org/maven2/" + - Arrays.stream(gav.getGroupId().split("\\.")) - .map(g -> { - try { - return URLEncoder.encode(g, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - }) - .collect(Collectors.joining("/")) + '/' + - URLEncoder.encode(gav.getArtifactId(), StandardCharsets.UTF_8.name()) + '/' + - URLEncoder.encode(gav.getVersion(), StandardCharsets.UTF_8.name()) + '/' + - URLEncoder.encode(gav.getArtifactId() + '-' + gav.getVersion() + ".pom", StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } -} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenMetadata.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenMetadata.java index 2ece965a7f5..8d94d2dd036 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenMetadata.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenMetadata.java @@ -101,5 +101,7 @@ public static class SnapshotVersion { String extension; String value; String updated; + @Nullable + String classifier; } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java index d23f22dbe2c..7b9b00721d3 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java @@ -281,7 +281,9 @@ public String getPackaging() { if (property == null) { return null; } - String propVal = properties.get(property); + // Maven allows system properties to override project properties + // This facilitates the usage of "-D" arguments on the command line to customize builds + String propVal = System.getProperty(property, properties.get(property)); if (propVal != null) { return propVal; } @@ -318,11 +320,13 @@ public String getPackaging() { public @Nullable String getManagedVersion(@Nullable String groupId, String artifactId, @Nullable String type, @Nullable String classifier) { for (ResolvedManagedDependency dm : dependencyManagement) { + if (dm.getVersion() == null) { + continue; // Unclear why this happens; just ignore those entries, because a valid version is requested + } if (dm.matches(groupId, artifactId, type, classifier)) { return getValue(dm.getVersion()); } } - return null; } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java index b164cb28f98..851e394da5d 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java @@ -178,4 +178,111 @@ void shouldUpdateProcessorVersionAlreadyPresent() { ) ); } + + @Test + void addAnnotationWithOlderVersionAsMavenProperty() { + rewriteRun( + spec -> spec.recipe(new AddAnnotationProcessor( + "org.projectlombok", + "lombok-mapstruct-binding", + "0.2.0" + )), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + 0.1.0 + + + + + maven-compiler-plugin + + + + org.projectlombok + lombok-mapstruct-binding + ${version.lombok.mapstruct.binding} + + + + + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + 0.2.0 + + + + + maven-compiler-plugin + + + + org.projectlombok + lombok-mapstruct-binding + ${version.lombok.mapstruct.binding} + + + + + + + + """ + ) + ); + } + + @Test + void addAnnotationWithSameVersionAsMavenProperty() { + rewriteRun( + spec -> spec.recipe(new AddAnnotationProcessor( + "org.projectlombok", + "lombok-mapstruct-binding", + "0.2.0" + )), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + 0.2.0 + + + + + maven-compiler-plugin + + + + org.projectlombok + lombok-mapstruct-binding + ${version.lombok.mapstruct.binding} + + + + + + + + """ + ) + ); + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java index 70bfef61989..a0fb4005f9c 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java @@ -1557,4 +1557,71 @@ void changeProfileDependencyGroupIdAndArtifactId() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/4779") + void changePluginDependencyGroupIdAndArtifactId() { + rewriteRun( + spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId( + "javax.activation", + "javax.activation-api", + "jakarta.activation", + "jakarta.activation-api", + null, + null + )), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + com.mycompany.myplugin + my-plugin + 1.0.0 + + + javax.activation + javax.activation-api + 1.2.0 + + + + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + com.mycompany.myplugin + my-plugin + 1.0.0 + + + jakarta.activation + jakarta.activation-api + 1.2.0 + + + + + + + """ + ) + ); + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactIdTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactIdTest.java index 55147edecf3..1de416271d6 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactIdTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePluginGroupIdAndArtifactIdTest.java @@ -103,14 +103,14 @@ void changePluginGroupIdAndArtifactId() { @DocumentExample @Test - void changePluginGroupIdAndArtifactIdDeprecatedNewArtifact() { + void changePluginGroupIdAndArtifactIdWithVersion() { rewriteRun( spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( "io.quarkus", "quarkus-bootstrap-maven-plugin", null, - null, - "quarkus-extension-maven-plugin" + "quarkus-extension-maven-plugin", + "4.0.0" )), pomXml( """ @@ -128,6 +128,55 @@ void changePluginGroupIdAndArtifactIdDeprecatedNewArtifact() { + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + io.quarkus + quarkus-extension-maven-plugin + 4.0.0 + + + + + """ + ) + ); + } + + @Test + void changePluginGroupIdAndArtifactIdNoChange() { + rewriteRun( + spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( + "io.quarkus", + "quarkus-bootstrap-maven-plugin", + null, + "quarkus-extension-maven-plugin", + null + )), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + io.quarkus + quarkus-extension-maven-plugin + 3.0.0.Beta1 + + + profile @@ -135,7 +184,7 @@ void changePluginGroupIdAndArtifactIdDeprecatedNewArtifact() { io.quarkus - quarkus-bootstrap-maven-plugin + quarkus-extension-maven-plugin 3.0.0.Beta1 @@ -143,7 +192,22 @@ void changePluginGroupIdAndArtifactIdDeprecatedNewArtifact() { - """, + """ + ) + ); + } + + @Test + void changePluginGroupIdAndArtifactIdNoChangeWithVersion() { + rewriteRun( + spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( + "io.quarkus", + "quarkus-bootstrap-maven-plugin", + null, + "quarkus-extension-maven-plugin", + "4.0.0" + )), + pomXml( """ 4.0.0 @@ -180,7 +244,7 @@ void changePluginGroupIdAndArtifactIdDeprecatedNewArtifact() { } @Test - void changePluginGroupIdAndArtifactIdNoChange() { + void changeManagedPluginGroupIdAndArtifactId() { rewriteRun( spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( "io.quarkus", @@ -197,6 +261,63 @@ void changePluginGroupIdAndArtifactIdNoChange() { my-app 1 + + + + io.quarkus + quarkus-bootstrap-maven-plugin + 3.0.0.Beta1 + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + 3.0.0.Beta1 + + + + + + profile + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + 3.0.0.Beta1 + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + io.quarkus + quarkus-extension-maven-plugin + 3.0.0.Beta1 + + + io.quarkus @@ -209,11 +330,19 @@ void changePluginGroupIdAndArtifactIdNoChange() { profile + + + + io.quarkus + quarkus-extension-maven-plugin + 3.0.0.Beta1 + + + io.quarkus quarkus-extension-maven-plugin - 3.0.0.Beta1 @@ -226,14 +355,14 @@ void changePluginGroupIdAndArtifactIdNoChange() { } @Test - void changeManagedPluginGroupIdAndArtifactId() { + void changeManagedPluginGroupIdAndArtifactIdWithVersion() { rewriteRun( spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( "io.quarkus", "quarkus-bootstrap-maven-plugin", null, "quarkus-extension-maven-plugin", - null + "4.0.0" )), pomXml( """ @@ -284,6 +413,69 @@ void changeManagedPluginGroupIdAndArtifactId() { """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + io.quarkus + quarkus-extension-maven-plugin + 4.0.0 + + + + + + io.quarkus + quarkus-extension-maven-plugin + 4.0.0 + + + + + + profile + + + + + io.quarkus + quarkus-extension-maven-plugin + 4.0.0 + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + + + + + """ + ) + ); + } + + @Test + void changeManagedPluginGroupIdAndArtifactIdNoChange() { + rewriteRun( + spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( + "io.quarkus", + "quarkus-bootstrap-maven-plugin", + null, + "quarkus-extension-maven-plugin", + null + )), + pomXml( """ 4.0.0 @@ -337,14 +529,14 @@ void changeManagedPluginGroupIdAndArtifactId() { } @Test - void changeManagedPluginGroupIdAndArtifactIdNoChange() { + void changeManagedPluginGroupIdAndArtifactIdNoChangeWithVersion() { rewriteRun( spec -> spec.recipe(new ChangePluginGroupIdAndArtifactId( "io.quarkus", "quarkus-bootstrap-maven-plugin", null, "quarkus-extension-maven-plugin", - null + "4.0.0" )), pomXml( """ diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/EnableDevelocityBuildCacheTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/EnableDevelocityBuildCacheTest.java new file mode 100644 index 00000000000..dfdda0ae48e --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/EnableDevelocityBuildCacheTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Validated; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.openrewrite.xml.Assertions.xml; + +class EnableDevelocityBuildCacheTest implements RewriteTest { + + @DocumentExample + @Test + void addBuildCacheAllConfigWithOptions() { + rewriteRun( + spec -> spec.recipe(new EnableDevelocityBuildCache("true", "true", "#{isTrue(env['CI'])}")), + xml( + """ + + + """, + """ + + + + true + + + true + #{isTrue(env['CI'])} + + + + """, + spec -> spec.path(".mvn/develocity.xml") + ) + ); + } + + @Test + void requireAtLeastOneOption() { + Validated validate = new EnableDevelocityBuildCache(null, null, null).validate(new InMemoryExecutionContext()); + assertThat(validate.isValid()).isFalse(); + } + + @Test + void addBuildCacheConfigWithLocalOnly() { + rewriteRun( + spec -> spec.recipe(new EnableDevelocityBuildCache("true", null, null)), + xml( + """ + + + """, + """ + + + + true + + + + """, + spec -> spec.path(".mvn/develocity.xml") + ) + ); + } + + @Test + void addBuildCacheAllConfigWithRemoteOnly() { + rewriteRun( + spec -> spec.recipe(new EnableDevelocityBuildCache(null, "true", "#{isTrue(env['CI'])}")), + xml( + """ + + + """, + """ + + + + true + #{isTrue(env['CI'])} + + + + """, + spec -> spec.path(".mvn/develocity.xml") + ) + ); + } + + @Test + void shouldNotModifyExistingConfiguration() { + rewriteRun( + spec -> spec.recipe(new EnableDevelocityBuildCache("true", "true", "true")), + xml( + """ + + + + false + + + false + false + + + + """, + spec -> spec.path(".mvn/develocity.xml") + ) + ); + } + + @Test + void shouldNotModifyOtherLocations() { + rewriteRun( + spec -> spec.recipe(new EnableDevelocityBuildCache("true", "true", "true")), + xml( + """ + + + """, + spec -> spec.path("src/test/resources/develocity.xml") + ) + ); + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java index 9e68f1f8cea..4198418ee22 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java @@ -35,18 +35,54 @@ public void defaults(RecipeSpec spec) { void changeProjectVersion() { rewriteRun( pomXml( - """ + """ + + org.openrewrite + rewrite-maven + 8.40.1-SNAPSHOT + + """, + """ org.openrewrite rewrite-maven - 8.40.1-SNAPSHOT + 8.41.0-SNAPSHOT - """, + """ + ) + ); + } + + @Test + void changeProjectVersionShouldNotUpdateDependencyVersion() { + rewriteRun( + pomXml( + """ + + org.openrewrite + rewrite-maven + 8.40.1-SNAPSHOT + + + com.google.guava + guava + 29.0-jre + + + + """, """ org.openrewrite rewrite-maven 8.41.0-SNAPSHOT + + + com.google.guava + guava + 29.0-jre + + """ ) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java index b3cbe440c88..3b3367c23ff 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java @@ -31,7 +31,7 @@ import org.openrewrite.Issue; import org.openrewrite.ParseExceptionResult; import org.openrewrite.Parser; -import org.openrewrite.ipc.http.OkHttpSender; +import org.openrewrite.maven.http.OkHttpSender; import org.openrewrite.maven.internal.MavenParsingException; import org.openrewrite.maven.tree.*; import org.openrewrite.test.RewriteTest; @@ -3358,4 +3358,33 @@ void exclusionsAffectTransitiveDependencies() { ) ); } + + @Test + void systemPropertyTakesPrecedence() { + System.setProperty("hatversion", "2.3.0"); + rewriteRun( + pomXml( + """ + + com.mycompany.app + parent + 1.0-SNAPSHOT + pom + parent + http://www.example.com + + SYSTEM_PROPERTY_SHOULD_OVERRIDE_THIS + + + + org.springframework.hateoas + spring-hateoas + ${hatversion} + + + + """ + ) + ); + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSecuritySettingsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSecuritySettingsTest.java new file mode 100644 index 00000000000..1166ba41b81 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSecuritySettingsTest.java @@ -0,0 +1,297 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.maven; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.InMemoryExecutionContext; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +class MavenSecuritySettingsTest { + + private static final String MASTER_PASS_ENCRYPTED = "FyoLIiN2Fx8HpT8O0aBsTn2/s3pYmtLRRCpoWPzhN4A="; // "master" + private static final String USER_PASS_ENCRYPTED = "ERozWEamSJoHRBT+wVx51V2Emr9PazZR9txMntZPlJc="; // "testpass" + private static final String USER_PASS_DECRYPTED = "testpass"; + + private String originalUserHome; + + @TempDir + Path tempDir; + + @BeforeEach + void setUp() throws IOException { + originalUserHome = System.getProperty("user.home"); + System.setProperty("user.home", tempDir.toString()); + Files.createDirectories(tempDir.resolve(".m2")); + } + + @AfterEach + void tearDown() { + System.setProperty("user.home", originalUserHome); + } + + @Test + void decryptCredentials() throws IOException { + // Create settings-security.xml with master password + Files.writeString(tempDir.resolve(".m2/settings-security.xml"), + //language=xml + """ + + + {%s} + + """.formatted(MASTER_PASS_ENCRYPTED)); + + // Create settings.xml with encrypted password + Files.writeString(tempDir.resolve(".m2/settings.xml"), + //language=xml + """ + + + + + test-server + admin + {%s} + + + + """.formatted(USER_PASS_ENCRYPTED)); + + // Use the public API to read settings + var ctx = new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + MavenSettings settings = MavenSettings.readMavenSettingsFromDisk(ctx); + assert settings != null && settings.getServers() != null; + assertThat(settings.getServers().getServers()) + .hasSize(1) + .first() + .satisfies(server -> { + assertThat(server.getId()).isEqualTo("test-server"); + assertThat(server.getUsername()).isEqualTo("admin"); + assertThat(server.getPassword()).isEqualTo(USER_PASS_DECRYPTED); + }); + } + + @Test + void relocatedCredentials() throws IOException { + // Create settings-security.xml with relocation + Path relocated = tempDir.resolve(".m2/relocation-settings-security.xml"); + Files.writeString(tempDir.resolve(".m2/settings-security.xml"), + //language=xml + """ + + + %s + + """.formatted(relocated)); + // Create relocation-settings-security.xml with master password + Files.writeString(relocated, + //language=xml + """ + + + {%s} + + """.formatted(MASTER_PASS_ENCRYPTED)); + + // Create settings.xml with encrypted password + Files.writeString(tempDir.resolve(".m2/settings.xml"), + //language=xml + """ + + + + + test-server + admin + {%s} + + + + """.formatted(USER_PASS_ENCRYPTED)); + + // Use the public API to read settings + var ctx = new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + MavenSettings settings = MavenSettings.readMavenSettingsFromDisk(ctx); + assert settings != null && settings.getServers() != null; + assertThat(settings.getServers().getServers()) + .hasSize(1) + .first() + .satisfies(server -> { + assertThat(server.getId()).isEqualTo("test-server"); + assertThat(server.getUsername()).isEqualTo("admin"); + assertThat(server.getPassword()).isEqualTo(USER_PASS_DECRYPTED); + }); + } + + @Test + void handleInvalidEncryptedPassword() throws IOException { + // Create settings-security.xml with master password + Files.writeString(tempDir.resolve(".m2/settings-security.xml"), + //language=xml + """ + + + {jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=} + + """); + + // Create settings.xml with invalid encrypted password + Files.writeString(tempDir.resolve(".m2/settings.xml"), + //language=xml + """ + + + + + test-server + admin + {invalid_format} + + + + """); + + var ctx = new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + MavenSettings settings = MavenSettings.readMavenSettingsFromDisk(ctx); + assert settings != null && settings.getServers() != null; + assertThat(settings.getServers().getServers()).hasSize(1) + .first() + .satisfies(server -> assertThat(server.getPassword()).isEqualTo("{invalid_format}")); + } + + @Test + void noSecuritySettingsNoDecryption() throws IOException { + // Only create settings.xml without settings-security.xml + Files.writeString(tempDir.resolve(".m2/settings.xml"), + //language=xml + """ + + + + + test-server + admin + {encrypted_password} + + + + """); + + var ctx = new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + MavenSettings settings = MavenSettings.readMavenSettingsFromDisk(ctx); + assert settings != null && settings.getServers() != null; + assertThat(settings.getServers().getServers()) + .hasSize(1) + .first() + .satisfies(server -> assertThat(server.getPassword()).isEqualTo("{encrypted_password}")); + } + + @Test + void decryptPasswordWithComments() throws IOException { + // Create settings-security.xml with master password + Files.writeString(tempDir.resolve(".m2/settings-security.xml"), + //language=xml + """ + + + {%s} + + """.formatted(MASTER_PASS_ENCRYPTED)); + + // Create settings.xml with password containing comments + Files.writeString(tempDir.resolve(".m2/settings.xml"), + //language=xml + """ + + + + + test-server + admin + Oleg reset this password on 2009-03-11, expires on 2009-04-11 {%s} + + + + """.formatted(USER_PASS_ENCRYPTED)); + + var ctx = new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + MavenSettings settings = MavenSettings.readMavenSettingsFromDisk(ctx); + assert settings != null && settings.getServers() != null; + assertThat(settings.getServers().getServers()) + .hasSize(1) + .first() + .satisfies(server -> assertThat(server.getPassword()).isEqualTo(USER_PASS_DECRYPTED)); + } + + @Test + void invalidMasterPasswordButValidPasswordFormat() throws IOException { + // Create settings-security.xml with invalid master password + Files.writeString(tempDir.resolve(".m2/settings-security.xml"), + //language=xml + """ + + + {invalid_master_password} + + """); + + // Create settings.xml with valid encrypted password + Files.writeString(tempDir.resolve(".m2/settings.xml"), + //language=xml + """ + + + + + test-server + admin + {%s} + + + + """.formatted(USER_PASS_ENCRYPTED)); + + var ctx = new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }); + MavenSettings settings = MavenSettings.readMavenSettingsFromDisk(ctx); + assert settings != null && settings.getServers() != null; + assertThat(settings.getServers().getServers()) + .hasSize(1) + .first() + .satisfies(server -> + // Password should remain in encrypted form since master password is invalid + assertThat(server.getPassword()).isEqualTo("{%s}".formatted(USER_PASS_ENCRYPTED))); + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java new file mode 100644 index 00000000000..6fc6a2fca0b --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java @@ -0,0 +1,316 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +@Deprecated(forRemoval = true) +class UpdateMavenProjectPropertyJavaVersionTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UpdateMavenProjectPropertyJavaVersion(17)); + } + + @DocumentExample + @Test + void basic() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + foo + 1.0.0 + 4.0 + + 11 + 11 + 11 + 11 + 11 + 11 + 11 + 11 + + + """, + """ + + com.example + foo + 1.0.0 + 4.0 + + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + + + """ + ) + ); + } + + @Test + void basicWithVariables() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + foo + 1.0.0 + 4.0 + + ${release.version} + 11 + ${release.version} + ${jdk.version} + ${maven.compiler.release} + ${maven.compiler.release} + 11 + 11 + + + """, + """ + + com.example + foo + 1.0.0 + 4.0 + + ${release.version} + 17 + ${release.version} + ${jdk.version} + ${maven.compiler.release} + ${maven.compiler.release} + 17 + 17 + + + """) + ); + } + + @Test + void updateLocalParent() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + example-parent + 1.0.0 + 4.0 + + 11 + 11 + 11 + 11 + 11 + 11 + 11 + 11 + + + """, + """ + + com.example + example-parent + 1.0.0 + 4.0 + + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + + + """), + mavenProject("example-child", + //language=xml + pomXml( + """ + + + com.example + example-parent + 1.0.0 + + com.example + example-child + 1.0.0 + 4.0 + + """ + ) + ) + ); + } + + @Test + void doNothingForExplicitPluginConfiguration() { + // Use UseMavenCompilerPluginReleaseConfiguration for this case + rewriteRun( + //language=xml + pomXml( + """ + + com.example + example-child + 1.0.0 + 4.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + 11 + 11 + + + + + + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/514") + void addReleaseIfNoOtherChangeIsMade() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + example-child + 1.0.0 + 4.0 + + """, + """ + + com.example + example-child + 1.0.0 + 4.0 + + 17 + + + """ + ) + ); + } + + @Test + void springBoot3ParentToJava17() { + // Spring Boot Starter Parent already enforces Java 17 + rewriteRun( + pomXml( + //language=xml + """ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.mycompany.app + my-app + 1 + + """ + ) + ); + } + + @Test + void springBoot3ParentToJava21() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenProjectPropertyJavaVersion(21)), + pomXml( + //language=xml + """ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.mycompany.app + my-app + 1 + + """, + //language=xml + """ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.mycompany.app + my-app + 1 + + 21 + + + """ + ) + ); + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java new file mode 100644 index 00000000000..ce23a97daa5 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java @@ -0,0 +1,561 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +class UseMavenCompilerPluginReleaseConfigurationTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UseMavenCompilerPluginReleaseConfiguration(11)); + } + + @DocumentExample + @Test + void replacesSourceAndTargetConfig() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 1.8 + 1.8 + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + + + + + + + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/514") + @Test + void replaceSourceAndTargetConfigIfDefault() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + true + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + true + 11 + + + + + + + """ + ) + ); + } + + @Test + void reusesJavaVersionVariableIfAvailable() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + + + + """ + ) + ); + } + + @Test + void upgradesExistingReleaseConfig() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + + + + + + + """ + ) + ); + } + + @Test + void prefersJavaVersionIfAvailable() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + + + + """ + ) + ); + } + + @Test + void notMisledByUnrelatedProperty() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + ${foobar} + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + ${foobar} + + + + + + + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/169") + @Test + void noVersionDowngrade() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 17 + + + + + + + """) + ); + } + + @Test + void reusesJavaVersionVariableIfDefinedInParentPom() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + parent + 1.0.0 + + + 11 + + + pom + + """), + mavenProject( + "sample", + //language=xml + pomXml(""" + + + 4.0.0 + + + org.sample + parent + 1.0.0 + + + sample + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + + """, + """ + + + 4.0.0 + + + org.sample + parent + 1.0.0 + + + sample + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + + + """ + ) + ) + ); + } + + @Test + void pluginManagement() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + parent + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + 8 + + + + + + + """, + """ + + + 4.0.0 + org.sample + parent + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + 11 + + + + + + + """ + ) + ); + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/ipc/http/OkHttpSender.java b/rewrite-maven/src/test/java/org/openrewrite/maven/http/OkHttpSender.java similarity index 97% rename from rewrite-core/src/main/java/org/openrewrite/ipc/http/OkHttpSender.java rename to rewrite-maven/src/test/java/org/openrewrite/maven/http/OkHttpSender.java index eb42f70c913..e92a9359c1d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/ipc/http/OkHttpSender.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/http/OkHttpSender.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.ipc.http; +package org.openrewrite.maven.http; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okhttp3.ResponseBody; +import org.openrewrite.ipc.http.HttpSender; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java index f16810183a6..b641f835b64 100755 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java @@ -33,11 +33,11 @@ import org.openrewrite.*; import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.ipc.http.HttpUrlConnectionSender; -import org.openrewrite.ipc.http.OkHttpSender; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenParser; import org.openrewrite.maven.MavenSettings; +import org.openrewrite.maven.http.OkHttpSender; import org.openrewrite.maven.tree.*; import javax.net.ssl.SSLSocketFactory; @@ -314,7 +314,7 @@ void useHttpWhenHttpsFails() throws IOException { @Test @Disabled - void dontFetchSnapshotsFromReleaseRepos() { + void dontFetchSnapshotsFromReleaseRepos() throws Exception { try (MockWebServer snapshotRepo = new MockWebServer(); MockWebServer releaseRepo = new MockWebServer()) { snapshotRepo.setDispatcher(new Dispatcher() { @@ -322,39 +322,39 @@ void dontFetchSnapshotsFromReleaseRepos() { public MockResponse dispatch(RecordedRequest request) { MockResponse response = new MockResponse().setResponseCode(200); if (request.getPath() != null && request.getPath().contains("maven-metadata")) { + //language=xml response.setBody( - //language=xml """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - - 20220201.001946 - 85 - - 20220201001950 - - - pom - 2.10.0-20220201.001946-85 - 20220201001946 - - - - + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + + 20220201.001946 + 85 + + 20220201001950 + + + pom + 2.10.0-20220201.001946-85 + 20220201001946 + + + + """ ); } else if (request.getPath() != null && request.getPath().endsWith(".pom")) { + //language=xml response.setBody( - //language=xml """ - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + """ ); } @@ -380,45 +380,173 @@ public MockResponse dispatch(RecordedRequest request) { MavenParser.builder().build().parse(ctx, //language=xml """ - - 4.0.0 + + 4.0.0 - org.openrewrite.test - foo - 0.1.0-SNAPSHOT + org.openrewrite.test + foo + 0.1.0-SNAPSHOT - - - snapshot - - true - - http://%s:%d - - - release - - false - - http://%s:%d - - + + + snapshot + + true + + http://%s:%d + + + release + + false + + http://%s:%d + + - - - org.springframework.cloud - spring-cloud-dataflow-build - 2.10.0-SNAPSHOT - - - + + + org.springframework.cloud + spring-cloud-dataflow-build + 2.10.0-SNAPSHOT + + + """.formatted(snapshotRepo.getHostName(), snapshotRepo.getPort(), releaseRepo.getHostName(), releaseRepo.getPort()) ); assertThat(snapshotRepo.getRequestCount()).isGreaterThan(1); assertThat(metadataPaths.get()).isEqualTo(0); - } catch (IOException e) { - throw new RuntimeException(e); + } + } + + @Issue("https://github.com/openrewrite/rewrite-maven-plugin/issues/862") + @Test + void fetchSnapshotWithCorrectClassifier() throws Exception { + try (MockWebServer snapshotServer = new MockWebServer()) { + snapshotServer.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + MockResponse response = new MockResponse().setResponseCode(200); + if (request.getPath() != null && request.getPath().contains("maven-metadata")) { + //language=xml + response.setBody( + """ + + com.some + an-artifact + 10.5.0-SNAPSHOT + + + 20250113.114247 + 36 + + 20250113114247 + + + javadoc + jar + 10.5.0-20250113.114247-36 + 20250113114247 + + + tests + jar + 10.5.0-20250113.114244-35 + 20250113114244 + + + sources + jar + 10.5.0-20250113.114242-34 + 20250113114242 + + + jar + 10.5.0-20250113.114227-33 + 20250113114227 + + + pom + 10.5.0-20250113.114227-33 + 20250113114227 + + + + + """ + ); + } else if (request.getPath() != null && request.getPath().endsWith(".pom")) { + //language=xml + response.setBody( + """ + + com.some + an-artifact + 10.5.0-SNAPSHOT + + """ + ); + } + return response; + } + }); + + snapshotServer.start(); + + //language=xml + MavenParser.builder().build().parse(ctx, + """ + + 4.0.0 + org.openrewrite.test + foo + 0.1.0-SNAPSHOT + + + snapshot + + true + + http://%s:%d + + + + + com.some + an-artifact + 10.5.0-SNAPSHOT + + + + """.formatted(snapshotServer.getHostName(), snapshotServer.getPort()) + ); + + MavenRepository snapshotRepo = MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven/".formatted(snapshotServer.getHostName(), snapshotServer.getPort())) + .build(); + + var gav = new GroupArtifactVersion("com.some", "an-artifact", "10.5.0-SNAPSHOT"); + var mavenPomDownloader = new MavenPomDownloader(emptyMap(), ctx); + + var pomPath = Paths.get("pom.xml"); + var pom = Pom.builder() + .sourcePath(pomPath) + .repository(snapshotRepo) + .properties(singletonMap("REPO_URL", snapshotRepo.getUri())) + .gav(new ResolvedGroupArtifactVersion( + "${REPO_URL}", gav.getGroupId(), gav.getArtifactId(), gav.getVersion(), null)) + .build(); + var resolvedPom = ResolvedPom.builder() + .requested(pom) + .properties(singletonMap("REPO_URL", snapshotRepo.getUri())) + .repositories(singletonList(snapshotRepo)) + .build(); + + // Ensure that classifier 'javadoc', 'tests' and 'sources' are not used + var downloadedPom = mavenPomDownloader.download(gav, null, resolvedPom, List.of(snapshotRepo)); + assertThat(downloadedPom).returns("10.5.0-20250113.114227-33", Pom::getDatedSnapshotVersion); } } @@ -573,6 +701,42 @@ void skipsLocalInvalidArtifactsEmptyJar(@TempDir Path localRepository) throws IO .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); } + @Test + void dontAllowPomDowloadFailureWithoutJar(@TempDir Path localRepository) throws IOException, MavenDownloadingException { + MavenRepository mavenLocal = MavenRepository.builder() + .id("local") + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); + + // Do not return invalid dependency + assertThrows(MavenDownloadingException.class, () -> new MavenPomDownloader(emptyMap(), ctx) + .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); + } + + @Test + void allowPomDowloadFailureWithJar(@TempDir Path localRepository) throws IOException, MavenDownloadingException { + MavenRepository mavenLocal = MavenRepository.builder() + .id("local") + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); + + // Create a valid jar + Path localJar = localRepository.resolve("com/some/some-artifact/1/some-artifact-1.jar"); + assertThat(localJar.getParent().toFile().mkdirs()).isTrue(); + Files.writeString(localJar, "some content not to be empty"); + + // Do not throw exception since we have a jar + var result = new MavenPomDownloader(emptyMap(), ctx) + .download(new GroupArtifactVersion("com.some", "some-artifact", "1"), null, null, List.of(mavenLocal)); + assertThat(result.getGav().getGroupId()).isEqualTo("com.some"); + assertThat(result.getGav().getArtifactId()).isEqualTo("some-artifact"); + assertThat(result.getGav().getVersion()).isEqualTo("1"); + } + @Test void doNotRenameRepoForCustomMavenLocal(@TempDir Path tempDir) throws MavenDownloadingException, IOException { GroupArtifactVersion gav = createArtifact(tempDir); @@ -1023,5 +1187,62 @@ public MockResponse dispatch(RecordedRequest recordedRequest) { throw new RuntimeException(e); } } + + @Test + @DisplayName("Throw exception if there is no pom and no jar for the artifact") + @Issue("https://github.com/openrewrite/rewrite/issues/4687") + void pomNotFoundWithNoJarShouldThrow() throws Exception { + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + assert recordedRequest.getPath() != null; + return new MockResponse().setResponseCode(404).setBody(""); + } + }); + mockRepo.start(); + var repositories = List.of(MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .username("user") + .password("pass") + .build()); + + var downloader = new MavenPomDownloader(emptyMap(), ctx); + var gav = new GroupArtifactVersion("fred", "fred", "1"); + assertThrows(MavenDownloadingException.class, () -> downloader.download(gav, null, null, repositories)); + } + } + + @Test + @DisplayName("Don't throw exception if there is no pom and but there is a jar for the artifact") + @Issue("https://github.com/openrewrite/rewrite/issues/4687") + void pomNotFoundWithJarFoundShouldntThrow() throws Exception { + try (MockWebServer mockRepo = getMockServer()) { + mockRepo.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + assert recordedRequest.getPath() != null; + if (recordedRequest.getPath().endsWith("fred/fred/1/fred-1.pom")) + return new MockResponse().setResponseCode(404).setBody(""); + return new MockResponse().setResponseCode(200).setBody("some bytes so the jar isn't empty"); + } + }); + mockRepo.start(); + var repositories = List.of(MavenRepository.builder() + .id("id") + .uri("http://%s:%d/maven".formatted(mockRepo.getHostName(), mockRepo.getPort())) + .username("user") + .password("pass") + .build()); + + var gav = new GroupArtifactVersion("fred", "fred", "1"); + var downloader = new MavenPomDownloader(emptyMap(), ctx); + Pom downloaded = downloader.download(gav, null, null, repositories); + assertThat(downloaded.getGav().getGroupId()).isEqualTo("fred"); + assertThat(downloaded.getGav().getArtifactId()).isEqualTo("fred"); + assertThat(downloaded.getGav().getVersion()).isEqualTo("1"); + } + } } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java index 0d2714390ab..ea52800af59 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java @@ -16,9 +16,20 @@ package org.openrewrite.maven.tree; import com.fasterxml.jackson.databind.ObjectMapper; +import org.intellij.lang.annotations.Language; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; +import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.test.RewriteTest; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -278,4 +289,213 @@ void resolveExecutionsFromDifferentParents() { ) ); } + + @Nested + @Issue("https://github.com/openrewrite/rewrite/issues/4687") + class TolerateMissingPom { + + @Language("xml") + private static final String POM_WITH_DEPENDENCY = """ + + 4.0.0 + foo + bar + 1.0-SNAPSHOT + + + com.some + some-artifact + 1 + + + + """; + + @TempDir + Path localRepository; + @TempDir + Path localRepository2; + + @Test + void singleRepositoryContainingJar() throws IOException { + MavenRepository mavenLocal = createMavenRepository(localRepository, "local"); + createJarFile(localRepository); + + List> downloadErrorArgs = new ArrayList<>(); + MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext(Throwable::printStackTrace)); + ctx.setRepositories(List.of(mavenLocal)); + ctx.setResolutionListener(new ResolutionEventListener() { + @Override + public void downloadError(GroupArtifactVersion gav, List attemptedUris, @Nullable Pom containing) { + List list = new ArrayList<>(); + list.add(gav); + list.add(attemptedUris); + list.add(containing); + downloadErrorArgs.add(list); + } + }); + rewriteRun( + spec -> spec.executionContext(ctx), + pomXml(POM_WITH_DEPENDENCY) + ); + assertThat(downloadErrorArgs).hasSize(1); + } + + @Test + void twoRepositoriesSecondContainingJar() throws IOException { + MavenRepository mavenLocal = createMavenRepository(localRepository, "local"); + MavenRepository mavenLocal2 = createMavenRepository(localRepository2, "local2"); + createJarFile(localRepository2); + + List> downloadErrorArgs = new ArrayList<>(); + MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext(Throwable::printStackTrace)); + ctx.setRepositories(List.of(mavenLocal, mavenLocal2)); + ctx.setResolutionListener(new ResolutionEventListener() { + @Override + public void downloadError(GroupArtifactVersion gav, List attemptedUris, @Nullable Pom containing) { + List list = new ArrayList<>(); + list.add(gav); + list.add(attemptedUris); + list.add(containing); + downloadErrorArgs.add(list); + } + }); + rewriteRun( + spec -> spec.executionContext(ctx), + pomXml(POM_WITH_DEPENDENCY) + ); + assertThat(downloadErrorArgs).hasSize(1); + } + + } + + @Nested + class DependencyManagement { + + @Issue("https://github.com/openrewrite/rewrite-maven-plugin/issues/862") + @Test + void resolveVersionFromParentDependencyManagement(@TempDir Path localRepository) throws IOException { + MavenRepository mavenLocal = createMavenRepository(localRepository, "local"); + createJarFile(localRepository); + createJarFile(localRepository, "org.openrewrite.test", "lib", "1.0"); + + List> downloadErrorArgs = new ArrayList<>(); + MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext(Throwable::printStackTrace)); + ctx.setRepositories(List.of(mavenLocal)); + ctx.setResolutionListener(new ResolutionEventListener() { + @Override + public void downloadError(GroupArtifactVersion gav, List attemptedUris, @Nullable Pom containing) { + List list = new ArrayList<>(); + list.add(gav); + list.add(attemptedUris); + list.add(containing); + downloadErrorArgs.add(list); + } + }); + + String father = """ + + 4.0.0 + org.example + father + 1.0.0-SNAPSHOT + pom + + childA + childB + + + + + com.some + some-artifact + 1 + + + + + """; + String childA = """ + + 4.0.0 + + org.example + father + 1.0.0-SNAPSHOT + + childA + + + + org.openrewrite.test + lib + 1.0 + pom + import + + + + + + com.some + some-artifact + + + + """; + String childB = """ + + 4.0.0 + + org.example + father + 1.0.0-SNAPSHOT + + childB + + + + com.some + some-artifact + compile + + + + + """; + + rewriteRun( + spec -> spec.executionContext(ctx), + pomXml(father, spec -> spec.path("pom.xml")), + pomXml(childA, spec -> spec.path("childA/pom.xml")), + pomXml(childB, spec -> spec.path("childB/pom.xml").afterRecipe(doc -> { + ResolvedPom pom = doc.getMarkers().findFirst(MavenResolutionResult.class).get().getPom(); + String version = pom.getManagedVersion("com.some", "some-artifact", null, null); + // Assert that version is not null! + assertThat(version).isEqualTo("1"); + }) + ) + ); + } + } + + private static void createJarFile(Path localRepository1) throws IOException { + createJarFile(localRepository1, "com/some", "some-artifact", "1"); + } + + private static void createJarFile(Path localRepository, String groupId, String artifactId, String version) throws IOException { + Path localJar = localRepository.resolve("%s/%s/%s/%s-%s.jar".formatted( + groupId.replace('.', '/'), artifactId, version, artifactId, version)); + assertThat(localJar.getParent().toFile().mkdirs()).isTrue(); + Files.writeString(localJar, "some content not to be empty"); + } + + private static MavenRepository createMavenRepository(Path localRepository, String name) { + return MavenRepository.builder() + .id(name) + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); + } } diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java b/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java index 038aafccfd6..f2ad9404f93 100644 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java @@ -25,8 +25,6 @@ import org.openrewrite.trait.Reference; import org.openrewrite.trait.SimpleTraitMatcher; -import java.util.HashSet; -import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -53,9 +51,6 @@ public boolean supportsRename() { return true; } - /** - * {@inheritDoc} - */ @Override public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { Tree tree = cursor.getValue(); @@ -67,42 +62,37 @@ public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { return tree; } - public static class Matcher extends SimpleTraitMatcher { - private static final Predicate javaFullyQualifiedTypeMatcher = Pattern.compile( - "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*").asPredicate(); + public static class Provider extends AbstractProvider { + private static final Predicate applicationPropertiesMatcher = Pattern.compile("^application(-\\w+)?\\.properties$").asPredicate(); + private static final SimpleTraitMatcher matcher = new SimpleTraitMatcher() { + private final Predicate javaFullyQualifiedTypeMatcher = Pattern.compile( + "^\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" + + "\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" + + "(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*$").asPredicate(); - @Override - protected @Nullable PropertiesReference test(Cursor cursor) { - Object value = cursor.getValue(); - if (value instanceof Properties.Entry && - javaFullyQualifiedTypeMatcher.test(((Properties.Entry) value).getValue().getText())) { - return new PropertiesReference(cursor, determineKind(((Properties.Entry) value).getValue().getText())); + @Override + protected @Nullable PropertiesReference test(Cursor cursor) { + Object value = cursor.getValue(); + if (value instanceof Properties.Entry && + javaFullyQualifiedTypeMatcher.test(((Properties.Entry) value).getValue().getText())) { + return new PropertiesReference(cursor, determineKind(((Properties.Entry) value).getValue().getText())); + } + return null; } - return null; - } - - private Kind determineKind(String value) { - return Character.isUpperCase(value.charAt(value.lastIndexOf('.') + 1)) ? Kind.TYPE : Kind.PACKAGE; - } - } - @SuppressWarnings("unused") - public static class Provider implements Reference.Provider { - private static final Predicate applicationPropertiesMatcher = Pattern.compile("^application(-\\w+)?\\.properties$").asPredicate(); + private Kind determineKind(String value) { + return Character.isUpperCase(value.charAt(value.lastIndexOf('.') + 1)) ? Kind.TYPE : Kind.PACKAGE; + } + }; @Override - public Set getReferences(SourceFile sourceFile) { - Set references = new HashSet<>(); - new Matcher().asVisitor(reference -> { - references.add(reference); - return reference.getTree(); - }).visit(sourceFile, 0); - return references; + public boolean isAcceptable(SourceFile sourceFile) { + return sourceFile instanceof Properties.File && applicationPropertiesMatcher.test(sourceFile.getSourcePath().getFileName().toString()); } @Override - public boolean isAcceptable(SourceFile sourceFile) { - return sourceFile instanceof Properties.File && applicationPropertiesMatcher.test(sourceFile.getSourcePath().getFileName().toString()); + public SimpleTraitMatcher getMatcher() { + return matcher; } } } diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/tree/Properties.java b/rewrite-properties/src/main/java/org/openrewrite/properties/tree/Properties.java index 098ea01dd35..d24b3706fa7 100755 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/tree/Properties.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/tree/Properties.java @@ -23,12 +23,12 @@ import org.openrewrite.properties.PropertiesVisitor; import org.openrewrite.properties.internal.PropertiesPrinter; -import java.beans.Transient; import java.lang.ref.SoftReference; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.regex.Pattern; @@ -117,21 +117,10 @@ public

TreeVisitor> printer(Cursor cursor) { @NonFinal transient SoftReference references; - @Transient @Override public References getReferences() { - References cache; - if (this.references == null) { - cache = References.build(this); - this.references = new SoftReference<>(cache); - } else { - cache = this.references.get(); - if (cache == null || cache.getSourceFile() != this) { - cache = References.build(this); - this.references = new SoftReference<>(cache); - } - } - return cache; + this.references = build(this.references); + return Objects.requireNonNull(this.references.get()); } } diff --git a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java index 0bdb42ad260..acae7b05ab3 100644 --- a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java +++ b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java @@ -23,7 +23,6 @@ import org.openrewrite.Parser; import org.openrewrite.SourceFile; import org.openrewrite.internal.EncodingDetectingInputStream; -import org.openrewrite.marker.Markers; import org.openrewrite.protobuf.internal.ProtoParserVisitor; import org.openrewrite.protobuf.internal.grammar.Protobuf2Lexer; import org.openrewrite.protobuf.internal.grammar.Protobuf2Parser; @@ -36,8 +35,6 @@ import java.nio.file.Path; import java.util.stream.Stream; -import static org.openrewrite.Tree.randomId; - public class ProtoParser implements Parser { @Override @@ -57,17 +54,13 @@ public Stream parseInputs(Iterable sourceFiles, @Nullable Pat if (sourceStr.contains("proto3")) { // Pending Proto3 support, the best we can do is plain text & not skip files - return new PlainText( - randomId(), - path, - Markers.EMPTY, - is.getCharset().name(), - is.isCharsetBomMarked(), - input.getFileAttributes(), - null, - sourceStr, - null - ); + return PlainText.builder() + .sourcePath(path) + .charsetName(is.getCharset().name()) + .charsetBomMarked(is.isCharsetBomMarked()) + .fileAttributes(input.getFileAttributes()) + .text(sourceStr) + .build(); } Proto.Document document = new ProtoParserVisitor( diff --git a/rewrite-test/build.gradle.kts b/rewrite-test/build.gradle.kts index f8dbafd3a40..7c90aa85483 100644 --- a/rewrite-test/build.gradle.kts +++ b/rewrite-test/build.gradle.kts @@ -8,10 +8,12 @@ dependencies { compileOnly("io.micrometer:micrometer-core:latest.release") api("org.junit.jupiter:junit-jupiter-api") api("org.junit.jupiter:junit-jupiter-params") + api("org.junit.platform:junit-platform-launcher") implementation("org.assertj:assertj-core:latest.release") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation("org.slf4j:slf4j-api:1.7.36") + implementation("org.slf4j:slf4j-nop:1.7.36") testImplementation(project(":rewrite-groovy")) } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index cad40ee819e..073da655b1e 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -23,10 +23,7 @@ import org.openrewrite.config.CompositeRecipe; import org.openrewrite.config.Environment; import org.openrewrite.config.OptionDescriptor; -import org.openrewrite.internal.InMemoryDiffEntry; -import org.openrewrite.internal.RecipeIntrospectionUtils; -import org.openrewrite.internal.StringUtils; -import org.openrewrite.internal.WhitespaceValidationService; +import org.openrewrite.internal.*; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; import org.openrewrite.quark.Quark; @@ -370,9 +367,9 @@ default void rewriteRun(Consumer spec, SourceSpec... sourceSpecs) lss = new LargeSourceSetCheckingExpectedCycles(expectedCyclesThatMakeChanges, runnableSourceFiles); } - CursorValidatingExecutionContextView.view(recipeCtx) - .setValidateCursorAcyclic(TypeValidation.before(testMethodSpec, testClassSpec) - .cursorAcyclic()); + recipeCtx = CursorValidatingExecutionContextView.view(recipeCtx) + .setValidateCursorAcyclic(TypeValidation.before(testMethodSpec, testClassSpec).cursorAcyclic()) + .setValidateImmutableExecutionContext(TypeValidation.before(testMethodSpec, testClassSpec).immutableExecutionContext()); RecipeRun recipeRun = recipe.run( lss, recipeCtx, @@ -633,7 +630,12 @@ default void rewriteRun(SourceSpec... sources) { } default ExecutionContext defaultExecutionContext(SourceSpec[] sourceSpecs) { - InMemoryExecutionContext ctx = new InMemoryExecutionContext(t -> fail("Failed to parse sources or run recipe", t)); + InMemoryExecutionContext ctx = new InMemoryExecutionContext(t -> { + if (t instanceof RecipeRunException){ + fail("Failed to run recipe at " + ((RecipeRunException) t).getCursor(), t); + } + fail("Failed to parse sources or run recipe", t); + }); ParsingExecutionContextView.view(ctx).setCharset(StandardCharsets.UTF_8); return ctx; } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java index 76abc5dfd6e..81bbc478a61 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java @@ -21,6 +21,8 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import java.util.function.Function; + /** * Controls the test framework's validation of invariants which are expected to hold true in an LST both before and * after the recipe run. Originally this applied only to validating the well-formedness of type metadata in Java LSTs @@ -88,6 +90,33 @@ public class TypeValidation { @Builder.Default private boolean cursorAcyclic = true; + /** + * Given finer control to client when they need to allow missing type metadata for a specific node. + */ + @Builder.Default + private Function allowMissingType = o -> false; + + + /** + * Controls whether the LST is validated not to contain any `J.Erroneous` elements. + */ + @Builder.Default + private boolean erroneous = true; + + /** + * Controls whether the LST is validated not to contain any `J.Unknown` elements. + */ + @Builder.Default + private boolean unknown = true; + + /** + * Adding messages to execution context is a side effect which makes the recipe run itself stateful. + * Potentially allows recipes to interfere with each other in surprising and hard to debug ways. + * Problematic for all the same reasons mutable global variables or singletons are. + */ + @Builder.Default + private boolean immutableExecutionContext = true; + /** * Enable all invariant validation checks. */ @@ -99,7 +128,7 @@ public static TypeValidation all() { * Skip all invariant validation checks. */ public static TypeValidation none() { - return new TypeValidation(false, false, false, false, false, false, false, false); + return new TypeValidation(false, false, false, false, false, false, false, false, o -> false, false, false, false); } static TypeValidation before(RecipeSpec testMethodSpec, RecipeSpec testClassSpec) { diff --git a/rewrite-test/src/test/java/org/openrewrite/FindSourceFilesTest.java b/rewrite-test/src/test/java/org/openrewrite/FindSourceFilesTest.java index ecb8a32977b..b837b420a7a 100644 --- a/rewrite-test/src/test/java/org/openrewrite/FindSourceFilesTest.java +++ b/rewrite-test/src/test/java/org/openrewrite/FindSourceFilesTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; @@ -154,7 +153,6 @@ void findDotfiles() { @Test @Issue("https://github.com/openrewrite/rewrite/pull/3758") - @Disabled("{} syntax not supported yet") void eitherOr() { rewriteRun( spec -> spec.recipe(new FindSourceFiles("**/*.{md,txt}")), @@ -171,6 +169,24 @@ void eitherOr() { ); } + @Test + void multiplePathsSemicolonDelimitedPaths() { + rewriteRun( + spec -> spec.recipe(new FindSourceFiles("a.txt ; b.txt")), + text( + "this one", + "~~>this one", + spec -> spec.path("a.txt") + ), + text( + "also this one", + "~~>also this one", + spec -> spec.path("b.txt") + ), + text("not this one", spec -> spec.path("c.txt")) + ); + } + @Test void negation() { rewriteRun( @@ -179,4 +195,13 @@ void negation() { text("this", "~~>this", spec -> spec.path("this.txt")) ); } + + @Test + void folderNegation() { + rewriteRun( + spec -> spec.recipe(new FindSourceFiles("!(not-this/**)")), + text("not-this", spec -> spec.path("not-this/not-this.txt")), + text("this", "~~>this", spec -> spec.path("this/this.txt")) + ); + } } diff --git a/rewrite-test/src/test/java/org/openrewrite/test/internal/RewriteTestTest.java b/rewrite-test/src/test/java/org/openrewrite/test/internal/RewriteTestTest.java index 343fe976074..aacab205113 100644 --- a/rewrite-test/src/test/java/org/openrewrite/test/internal/RewriteTestTest.java +++ b/rewrite-test/src/test/java/org/openrewrite/test/internal/RewriteTestTest.java @@ -112,6 +112,42 @@ void rejectRecipeValidationFailure() { """, "org.openrewrite.RefersToNonExistentRecipe") )); } + + @Test + void rejectExecutionContextMutation() { + assertThrows(AssertionError.class, () -> + rewriteRun( + spec -> spec.recipe(new MutateExecutionContext()), + text("irrelevant") + )); + } +} + +@Value +@EqualsAndHashCode(callSuper = false) +@NullMarked +class MutateExecutionContext extends Recipe { + + @Override + public String getDisplayName() { + return "Mutate execution context"; + } + + @Override + public String getDescription() { + return "Mutates the execution context to trigger a validation failure."; + } + + @Override + public TreeVisitor getVisitor() { + return new TreeVisitor<>() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + ctx.putMessage("mutated", true); + return tree; + } + }; + } } @Value diff --git a/rewrite-toml/build.gradle.kts b/rewrite-toml/build.gradle.kts new file mode 100644 index 00000000000..b7e2005ef2e --- /dev/null +++ b/rewrite-toml/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("org.openrewrite.build.language-library") +} + +val antlrGeneration by configurations.creating { + extendsFrom(configurations.implementation.get()) +} + +tasks.register("generateAntlrSources") { + mainClass.set("org.antlr.v4.Tool") + + args = listOf( + "-o", "src/main/java/org/openrewrite/toml/internal/grammar", + "-package", "org.openrewrite.toml.internal.grammar", + "-visitor" + ) + fileTree("src/main/antlr").matching { include("**/*.g4") }.map { it.path } + + classpath = antlrGeneration +} + +dependencies { + implementation(project(":rewrite-core")) + implementation("org.antlr:antlr4-runtime:4.11.1") + implementation("io.micrometer:micrometer-core:1.9.+") + + antlrGeneration("org.antlr:antlr4:4.11.1") + + compileOnly(project(":rewrite-test")) + + testImplementation(project(":rewrite-test")) +} diff --git a/rewrite-toml/src/main/antlr/TomlLexer.g4 b/rewrite-toml/src/main/antlr/TomlLexer.g4 new file mode 100644 index 00000000000..2c698f64e75 --- /dev/null +++ b/rewrite-toml/src/main/antlr/TomlLexer.g4 @@ -0,0 +1,149 @@ +/* +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. +*/ + +// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine +// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true + +lexer grammar TomlLexer; + +WS : [ \t]+ -> skip; +NL : ('\r'? '\n')+; +COMMENT : '#' (~[\r\n])*; +L_BRACKET : '['; +DOUBLE_L_BRACKET : '[['; +R_BRACKET : ']'; +DOUBLE_R_BRACKET : ']]'; +EQUALS : '=' -> pushMode(SIMPLE_VALUE_MODE); +DOT : '.'; +COMMA : ',' -> skip; + +fragment DIGIT : [0-9]; +fragment ALPHA : [A-Za-z]; + +// strings +fragment ESC : '\\' (["\\/bfnrt] | UNICODE | EX_UNICODE); +fragment UNICODE : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT; +fragment EX_UNICODE: + 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT +; +BASIC_STRING : '"' (ESC | ~["\\\n])*? '"'; +LITERAL_STRING : '\'' (~['\n])*? '\''; + +// keys +UNQUOTED_KEY: (ALPHA | DIGIT | '-' | '_')+; + +mode SIMPLE_VALUE_MODE; + +VALUE_WS: WS -> skip; + +L_BRACE : '{' -> mode(INLINE_TABLE_MODE); +ARRAY_START : L_BRACKET -> type(L_BRACKET), mode(ARRAY_MODE); + +// booleans +BOOLEAN: ('true' | 'false') -> popMode; + +// strings +fragment ML_ESC : '\\' '\r'? '\n' | ESC; +VALUE_BASIC_STRING : BASIC_STRING -> type(BASIC_STRING), popMode; +ML_BASIC_STRING : '"""' (ML_ESC | ~["\\])*? '"""' -> popMode; +VALUE_LITERAL_STRING : LITERAL_STRING -> type(LITERAL_STRING), popMode; +ML_LITERAL_STRING : '\'\'\'' (.)*? '\'\'\'' -> popMode; + +// floating point numbers +fragment EXP : ('e' | 'E') [+-]? ZERO_PREFIXABLE_INT; +fragment ZERO_PREFIXABLE_INT : DIGIT (DIGIT | '_' DIGIT)*; +fragment FRAC : '.' ZERO_PREFIXABLE_INT; +FLOAT : DEC_INT ( EXP | FRAC EXP?) -> popMode; +INF : [+-]? 'inf' -> popMode; +NAN : [+-]? 'nan' -> popMode; + +// integers +fragment HEX_DIGIT : [A-Fa-f] | DIGIT; +fragment DIGIT_1_9 : [1-9]; +fragment DIGIT_0_7 : [0-7]; +fragment DIGIT_0_1 : [0-1]; +DEC_INT : [+-]? (DIGIT | (DIGIT_1_9 (DIGIT | '_' DIGIT)+)) -> popMode; +HEX_INT : '0x' HEX_DIGIT (HEX_DIGIT | '_' HEX_DIGIT)* -> popMode; +OCT_INT : '0o' DIGIT_0_7 (DIGIT_0_7 | '_' DIGIT_0_7)* -> popMode; +BIN_INT : '0b' DIGIT_0_1 (DIGIT_0_1 | '_' DIGIT_0_1)* -> popMode; + +// dates +fragment YEAR : DIGIT DIGIT DIGIT DIGIT; +fragment MONTH : DIGIT DIGIT; +fragment DAY : DIGIT DIGIT; +fragment DELIM : 'T' | 't' | ' '; +fragment HOUR : DIGIT DIGIT; +fragment MINUTE : DIGIT DIGIT; +fragment SECOND : DIGIT DIGIT; +fragment SECFRAC : '.' DIGIT+; +fragment NUMOFFSET : ('+' | '-') HOUR ':' MINUTE; +fragment OFFSET : 'Z' | NUMOFFSET; +fragment PARTIAL_TIME : HOUR ':' MINUTE ':' SECOND SECFRAC?; +fragment FULL_DATE : YEAR '-' MONTH '-' DAY; +fragment FULL_TIME : PARTIAL_TIME OFFSET; +OFFSET_DATE_TIME : FULL_DATE DELIM FULL_TIME -> popMode; +LOCAL_DATE_TIME : FULL_DATE DELIM PARTIAL_TIME -> popMode; +LOCAL_DATE : FULL_DATE -> popMode; +LOCAL_TIME : PARTIAL_TIME -> popMode; + +mode INLINE_TABLE_MODE; + +INLINE_TABLE_WS : WS -> skip; +INLINE_TABLE_KEY_DOT : DOT -> type(DOT); +INLINE_TABLE_COMMA : COMMA -> type(COMMA); +R_BRACE : '}' -> popMode; + +INLINE_TABLE_KEY_BASIC_STRING : BASIC_STRING -> type(BASIC_STRING); +INLINE_TABLE_KEY_LITERAL_STRING : LITERAL_STRING -> type(LITERAL_STRING); +INLINE_TABLE_KEY_UNQUOTED : UNQUOTED_KEY -> type(UNQUOTED_KEY); + +INLINE_TABLE_EQUALS: EQUALS -> type(EQUALS), pushMode(SIMPLE_VALUE_MODE); + +mode ARRAY_MODE; + +ARRAY_WS : WS -> skip; +ARRAY_NL : NL -> type(NL); +ARRAY_COMMENT : COMMENT -> type(COMMENT); +ARRAY_COMMA : COMMA -> type(COMMA); + +ARRAY_INLINE_TABLE_START : L_BRACE -> type(L_BRACE), pushMode(INLINE_TABLE_MODE); +NESTED_ARRAY_START : L_BRACKET -> type(L_BRACKET), pushMode(ARRAY_MODE); +ARRAY_END : R_BRACKET -> type(R_BRACKET), popMode; + +ARRAY_BOOLEAN: BOOLEAN -> type(BOOLEAN); + +ARRAY_BASIC_STRING : BASIC_STRING -> type(BASIC_STRING); +ARRAY_ML_BASIC_STRING : ML_BASIC_STRING -> type(ML_BASIC_STRING); +ARRAY_LITERAL_STRING : LITERAL_STRING -> type(LITERAL_STRING); +ARRAY_ML_LITERAL_STRING : ML_LITERAL_STRING -> type(ML_LITERAL_STRING); + +ARRAY_FLOAT : FLOAT -> type(FLOAT); +ARRAY_INF : INF -> type(INF); +ARRAY_NAN : NAN -> type(NAN); + +ARRAY_DEC_INT : DEC_INT -> type(DEC_INT); +ARRAY_HEX_INT : HEX_INT -> type(HEX_INT); +ARRAY_OCT_INT : OCT_INT -> type(OCT_INT); +ARRAY_BIN_INT : BIN_INT -> type(BIN_INT); + +ARRAY_OFFSET_DATE_TIME : OFFSET_DATE_TIME -> type(OFFSET_DATE_TIME); +ARRAY_LOCAL_DATE_TIME : LOCAL_DATE_TIME -> type(LOCAL_DATE_TIME); +ARRAY_LOCAL_DATE : LOCAL_DATE -> type(LOCAL_DATE); +ARRAY_LOCAL_TIME : LOCAL_TIME -> type(LOCAL_TIME); diff --git a/rewrite-toml/src/main/antlr/TomlParser.g4 b/rewrite-toml/src/main/antlr/TomlParser.g4 new file mode 100644 index 00000000000..c177f35ce54 --- /dev/null +++ b/rewrite-toml/src/main/antlr/TomlParser.g4 @@ -0,0 +1,136 @@ +/* +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. +*/ + +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +parser grammar TomlParser; + +options { + tokenVocab = TomlLexer; +} + +document + : expression? (NL expression?)* EOF + ; + +expression + : keyValue comment? + | table comment? + | comment + ; + +comment + : COMMENT + ; + +keyValue + : key EQUALS value + ; + +key + : simpleKey + | dottedKey + ; + +simpleKey + : quotedKey + | unquotedKey + ; + +unquotedKey + : UNQUOTED_KEY + ; + +quotedKey + : BASIC_STRING + | LITERAL_STRING + ; + +dottedKey + : simpleKey (DOT simpleKey)+ + ; + +value + : string + | integer + | floatingPoint + | bool + | dateTime + | array + | inlineTable + ; + +string + : BASIC_STRING + | ML_BASIC_STRING + | LITERAL_STRING + | ML_LITERAL_STRING + ; + +integer + : DEC_INT + | HEX_INT + | OCT_INT + | BIN_INT + ; + +floatingPoint + : FLOAT + | INF + | NAN + ; + +bool + : BOOLEAN + ; + +dateTime + : OFFSET_DATE_TIME + | LOCAL_DATE_TIME + | LOCAL_DATE + | LOCAL_TIME + ; + +commentOrNl + : COMMENT? NL + ; + +array + : L_BRACKET commentOrNl* R_BRACKET + | L_BRACKET commentOrNl* value (COMMA commentOrNl* value COMMA?)* commentOrNl* R_BRACKET + ; + +table + : standardTable + | arrayTable + ; + +standardTable + : L_BRACKET key R_BRACKET (commentOrNl* keyValue)* + ; + +inlineTable + : L_BRACE commentOrNl* R_BRACE + | L_BRACE commentOrNl* keyValue (COMMA commentOrNl* keyValue COMMA?)* commentOrNl* R_BRACE + ; + +arrayTable + : DOUBLE_L_BRACKET key DOUBLE_R_BRACKET (commentOrNl* keyValue)* + ; diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/Assertions.java b/rewrite-toml/src/main/java/org/openrewrite/toml/Assertions.java new file mode 100644 index 00000000000..85de7b6d79c --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/Assertions.java @@ -0,0 +1,52 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import org.intellij.lang.annotations.Language; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.SourceSpecs; +import org.openrewrite.toml.tree.Toml; + +import java.util.function.Consumer; + +public class Assertions { + private Assertions() { + } + + public static SourceSpecs toml(@Language("toml") @Nullable String before) { + return Assertions.toml(before, s -> { + }); + } + + public static SourceSpecs toml(@Language("toml") @Nullable String before, Consumer> spec) { + SourceSpec toml = new SourceSpec<>(Toml.Document.class, null, TomlParser.builder(), before, null); + spec.accept(toml); + return toml; + } + + public static SourceSpecs toml(@Language("toml") @Nullable String before, @Language("toml") @Nullable String after) { + return toml(before, after, s -> { + }); + } + + public static SourceSpecs toml(@Language("toml") @Nullable String before, @Language("toml") @Nullable String after, + Consumer> spec) { + SourceSpec toml = new SourceSpec<>(Toml.Document.class, null, TomlParser.builder(), before, s -> after); + spec.accept(toml); + return toml; + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/TomlIsoVisitor.java b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlIsoVisitor.java new file mode 100644 index 00000000000..850c76832a1 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlIsoVisitor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import org.openrewrite.toml.tree.Toml; + +public class TomlIsoVisitor

extends TomlVisitor

{ + + @Override + public Toml.Array visitArray(Toml.Array array, P p) { + return (Toml.Array) super.visitArray(array, p); + } + + @Override + public Toml.Document visitDocument(Toml.Document document, P p) { + return (Toml.Document) super.visitDocument(document, p); + } + + @Override + public Toml.Empty visitEmpty(Toml.Empty empty, P p) { + return (Toml.Empty) super.visitEmpty(empty, p); + } + + @Override + public Toml.Identifier visitIdentifier(Toml.Identifier identifier, P p) { + return (Toml.Identifier) super.visitIdentifier(identifier, p); + } + + @Override + public Toml.KeyValue visitKeyValue(Toml.KeyValue keyValue, P p) { + return (Toml.KeyValue) super.visitKeyValue(keyValue, p); + } + + @Override + public Toml.Literal visitLiteral(Toml.Literal literal, P p) { + return (Toml.Literal) super.visitLiteral(literal, p); + } + + @Override + public Toml.Table visitTable(Toml.Table table, P p) { + return (Toml.Table) super.visitTable(table, p); + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/TomlParser.java b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlParser.java new file mode 100644 index 00000000000..37b2ac6f826 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlParser.java @@ -0,0 +1,117 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import org.antlr.v4.runtime.*; +import org.intellij.lang.annotations.Language; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.toml.internal.TomlParserVisitor; +import org.openrewrite.toml.internal.grammar.TomlLexer; +import org.openrewrite.toml.tree.Toml; +import org.openrewrite.tree.ParseError; +import org.openrewrite.tree.ParsingEventListener; +import org.openrewrite.tree.ParsingExecutionContextView; + +import java.io.InputStream; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class TomlParser implements Parser { + @Override + public Stream parseInputs(Iterable sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { + ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); + return acceptedInputs(sourceFiles).map(input -> { + parsingListener.startedParsing(input); + try (InputStream sourceStream = input.getSource(ctx)) { + TomlLexer lexer = new TomlLexer(CharStreams.fromStream(sourceStream)); + lexer.removeErrorListeners(); + lexer.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); + + org.openrewrite.toml.internal.grammar.TomlParser parser = new org.openrewrite.toml.internal.grammar.TomlParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); + + Toml.Document document = new TomlParserVisitor( + input.getRelativePath(relativeTo), + input.getFileAttributes(), + input.getSource(ctx) + ).visitDocument(parser.document()); + parsingListener.parsed(input, document); + return requirePrintEqualsInput(document, input, relativeTo, ctx); + } catch (Throwable t) { + ctx.getOnError().accept(t); + return ParseError.build(this, input, relativeTo, ctx, t); + } + }); + } + + @Override + public Stream parse(@Language("toml") String... sources) { + return parse(new InMemoryExecutionContext(), sources); + } + + @Override + public boolean accept(Path path) { + return path.toString().endsWith(".toml"); + } + + @Override + public Path sourcePathFromSourceText(Path prefix, String sourceCode) { + return prefix.resolve("file.toml"); + } + + private static class ForwardingErrorListener extends BaseErrorListener { + private final Path sourcePath; + private final ExecutionContext ctx; + + private ForwardingErrorListener(Path sourcePath, ExecutionContext ctx) { + this.sourcePath = sourcePath; + this.ctx = ctx; + } + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, + int line, int charPositionInLine, String msg, RecognitionException e) { + ctx.getOnError().accept(new TomlParsingException(sourcePath, + String.format("Syntax error in %s at line %d:%d %s.", sourcePath, line, charPositionInLine, msg), e)); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends Parser.Builder { + + public Builder() { + super(Toml.Document.class); + } + + @Override + public TomlParser build() { + return new TomlParser(); + } + + @Override + public String getDslName() { + return "toml"; + } + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/TomlParsingException.java b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlParsingException.java new file mode 100644 index 00000000000..2de5c34fafe --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlParsingException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import java.nio.file.Path; + +public class TomlParsingException extends Exception { + private final Path sourcePath; + + public TomlParsingException(Path sourcePath, String message, Throwable t) { + super(message, t); + this.sourcePath = sourcePath; + } + + public Path getSourcePath() { + return sourcePath; + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/TomlVisitor.java b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlVisitor.java new file mode 100644 index 00000000000..940d46db59c --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/TomlVisitor.java @@ -0,0 +1,119 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import org.openrewrite.Cursor; +import org.openrewrite.SourceFile; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.toml.tree.*; + +public class TomlVisitor

extends TreeVisitor { + + @Override + public boolean isAcceptable(SourceFile sourceFile, P p) { + return sourceFile instanceof Toml.Document; + } + + @Override + public String getLanguage() { + return "toml"; + } + + public Toml visitArray(Toml.Array array, P p) { + Toml.Array a = array; + a = a.withPrefix(visitSpace(a.getPrefix(), p)); + a = a.withMarkers(visitMarkers(a.getMarkers(), p)); + a = a.withValues(ListUtils.map(a.getValues(), v -> visit(v, p))); + return a; + } + + public Toml visitDocument(Toml.Document document, P p) { + Toml.Document d = document; + d = d.withPrefix(visitSpace(d.getPrefix(), p)); + d = d.withMarkers(visitMarkers(d.getMarkers(), p)); + d = d.withValues(ListUtils.map(d.getValues(), v -> (TomlValue) visit(v, p))); + d = d.withEof(visitSpace(d.getEof(), p)); + return d; + } + + public Toml visitEmpty(Toml.Empty empty, P p) { + Toml.Empty e = empty; + e = e.withPrefix(visitSpace(e.getPrefix(), p)); + e = e.withMarkers(visitMarkers(e.getMarkers(), p)); + return e; + } + + public Toml visitIdentifier(Toml.Identifier identifier, P p) { + Toml.Identifier i = identifier; + i = i.withPrefix(visitSpace(i.getPrefix(), p)); + i = i.withMarkers(visitMarkers(i.getMarkers(), p)); + return i; + } + + public Toml visitKeyValue(Toml.KeyValue keyValue, P p) { + Toml.KeyValue kv = keyValue; + kv = kv.withPrefix(visitSpace(kv.getPrefix(), p)); + kv = kv.withMarkers(visitMarkers(kv.getMarkers(), p)); + kv = kv.getPadding().withKey(visitRightPadded(kv.getPadding().getKey(), p)); + kv = kv.withValue(visit(kv.getValue(), p)); + return kv; + } + + public Toml visitLiteral(Toml.Literal literal, P p) { + Toml.Literal l = literal; + l = l.withPrefix(visitSpace(l.getPrefix(), p)); + l = l.withMarkers(visitMarkers(l.getMarkers(), p)); + return l; + } + + public Space visitSpace(Space space, P p) { + return space; + } + + public Toml visitTable(Toml.Table table, P p) { + Toml.Table t = table; + t = t.withPrefix(visitSpace(t.getPrefix(), p)); + t = t.withMarkers(visitMarkers(t.getMarkers(), p)); + t = t.withValues(ListUtils.map(t.getValues(), v -> visit(v, p))); + return t; + } + + public TomlRightPadded visitRightPadded(@Nullable TomlRightPadded right, P p) { + if (right == null) { + //noinspection ConstantConditions + return null; + } + + setCursor(new Cursor(getCursor(), right)); + + T t = right.getElement(); + if (t instanceof Toml) { + //noinspection unchecked + t = visitAndCast((Toml) right.getElement(), p); + } + + setCursor(getCursor().getParent()); + if (t == null) { + //noinspection ConstantConditions + return null; + } + + Space after = visitSpace(right.getAfter(), p); + return (after == right.getAfter() && t == right.getElement()) ? right : new TomlRightPadded<>(t, after, right.getMarkers()); + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/TomlParserVisitor.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/TomlParserVisitor.java new file mode 100644 index 00000000000..61c8a4a7551 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/TomlParserVisitor.java @@ -0,0 +1,517 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.internal; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.RuleNode; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.jspecify.annotations.Nullable; +import org.openrewrite.FileAttributes; +import org.openrewrite.internal.EncodingDetectingInputStream; +import org.openrewrite.marker.Markers; +import org.openrewrite.toml.internal.grammar.TomlParser; +import org.openrewrite.toml.internal.grammar.TomlParserBaseVisitor; +import org.openrewrite.toml.marker.ArrayTable; +import org.openrewrite.toml.marker.InlineTable; +import org.openrewrite.toml.tree.*; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; + +import static org.openrewrite.Tree.randomId; + +public class TomlParserVisitor extends TomlParserBaseVisitor { + private final Path path; + private final String source; + private final Charset charset; + private final boolean charsetBomMarked; + + @Nullable + private final FileAttributes fileAttributes; + + private int cursor = 0; + private int codePointCursor = 0; + + public TomlParserVisitor(Path path, @Nullable FileAttributes fileAttributes, EncodingDetectingInputStream source) { + this.path = path; + this.fileAttributes = fileAttributes; + this.source = source.readFully(); + this.charset = source.getCharset(); + this.charsetBomMarked = source.isCharsetBomMarked(); + } + + @Override + public Toml visitChildren(RuleNode node) { + Toml result = defaultResult(); + int n = node.getChildCount(); + for (int i = 0; i < n; i++) { + if (!shouldVisitNextChild(node, result)) { + break; + } + + ParseTree c = node.getChild(i); + if (c instanceof TomlParser.CommentContext) { + continue; + } + + Toml childResult = c.accept(this); + result = aggregateResult(result, childResult); + } + + return result; + } + + @Override + public Toml.Document visitDocument(TomlParser.DocumentContext ctx) { + if (!ctx.children.isEmpty() && ctx.children.get(0) instanceof TerminalNode && ((TerminalNode) ctx.children.get(0)).getSymbol().getType() == TomlParser.EOF) { + new Toml.Document( + randomId(), + path, + Space.EMPTY, + Markers.EMPTY, + charset.name(), + charsetBomMarked, + null, + fileAttributes, + Collections.emptyList(), + Space.EMPTY + ); + } + + List elements = new ArrayList<>(); + // The last element is a "TerminalNode" which we are uninterested in + for (int i = 0; i < ctx.children.size() - 1; i++) { + TomlValue element = (TomlValue) visit(ctx.children.get(i)); + if (element != null) { + elements.add(element); + } + } + + return new Toml.Document( + randomId(), + path, + Space.EMPTY, + Markers.EMPTY, + charset.name(), + charsetBomMarked, + null, + fileAttributes, + elements, + Space.format(source, cursor, source.length()) + ); + } + + @Override + public Toml.Identifier visitKey(TomlParser.KeyContext ctx) { + return (Toml.Identifier) super.visitKey(ctx); + } + + @Override + public Toml.Identifier visitSimpleKey(TomlParser.SimpleKeyContext ctx) { + return convert(ctx, (c, prefix) -> new Toml.Identifier( + randomId(), + prefix, + Markers.EMPTY, + c.getText(), + c.getText() + )); + } + + @Override + public Toml.Identifier visitDottedKey(TomlParser.DottedKeyContext ctx) { + Space prefix = prefix(ctx); + StringBuilder text = new StringBuilder(); + StringBuilder key = new StringBuilder(); + for (ParseTree child : ctx.children) { + Space space = sourceBefore(child.getText()); + text.append(space.getWhitespace()).append(child.getText()); + key.append(child.getText()); + } + return new Toml.Identifier( + randomId(), + prefix, + Markers.EMPTY, + text.toString(), + key.toString() + ); + } + + @Override + public Toml.KeyValue visitKeyValue(TomlParser.KeyValueContext ctx) { + return convert(ctx, (c, prefix) -> new Toml.KeyValue( + randomId(), + prefix, + Markers.EMPTY, + TomlRightPadded.build((TomlKey) visitKey(c.key())).withAfter(sourceBefore("=")), + visitValue(c.value()) + )); + } + + @Override + public Toml visitString(TomlParser.StringContext ctx) { + return convert(ctx, (c, prefix) -> { + String string = c.getText(); + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.String, + string, + string.substring(1, string.length() - 1) + ); + }); + } + + @Override + public Toml visitInteger(TomlParser.IntegerContext ctx) { + return convert(ctx, (c, prefix) -> { + String rawNumber = c.getText(); + String number = rawNumber.replace("_", ""); + Long numberValue = rawNumber.startsWith("0x") ? Long.parseLong(number.substring(2), 16) : + rawNumber.startsWith("0o") ? Long.parseLong(number.substring(2), 8) : + rawNumber.startsWith("0b") ? Long.parseLong(number.substring(2), 2) : + Long.parseLong(number); + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.Integer, + rawNumber, + numberValue + ); + }); + } + + @Override + public Toml visitFloatingPoint(TomlParser.FloatingPointContext ctx) { + return convert(ctx, (c, prefix) -> { + String rawNumber = c.getText(); + if (c.NAN() != null) { + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.Float, + rawNumber, + Double.NaN + ); + } else if (c.INF() != null) { + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.Float, + rawNumber, + source.charAt(cursor) == '-' ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY + ); + } + + String number = rawNumber.replace("_", ""); + Double numberValue = Double.parseDouble(number); + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.Float, + rawNumber, + numberValue + ); + }); + } + + @Override + public Toml visitBool(TomlParser.BoolContext ctx) { + return convert(ctx, (c, prefix) -> { + String bool = c.getText(); + Boolean boolValue = Boolean.parseBoolean(bool); + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.Boolean, + bool, + boolValue + ); + }); + } + + private static final DateTimeFormatter RFC3339_OFFSET_DATE_TIME; + + static { + RFC3339_OFFSET_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(DateTimeFormatter.ISO_LOCAL_TIME) + .parseLenient() + .appendOffsetId() + .parseStrict() + .toFormatter(); + } + + @Override + public Toml visitDateTime(TomlParser.DateTimeContext ctx) { + return convert(ctx, (c, prefix) -> { + String dateTime = c.getText(); + if (c.OFFSET_DATE_TIME() != null) { + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.OffsetDateTime, + dateTime, + dateTime.contains("T") ? OffsetDateTime.parse(dateTime) : OffsetDateTime.parse(dateTime, RFC3339_OFFSET_DATE_TIME) + ); + } else if (c.LOCAL_DATE_TIME() != null) { + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.LocalDateTime, + dateTime, + LocalDateTime.parse(dateTime) + ); + } else if (c.LOCAL_DATE() != null) { + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.LocalDate, + dateTime, + LocalDate.parse(dateTime) + ); + } + + return new Toml.Literal( + randomId(), + prefix, + Markers.EMPTY, + TomlType.Primitive.LocalTime, + dateTime, + LocalTime.parse(dateTime) + ); + }); + } + + @Override + public Toml visitArray(TomlParser.ArrayContext ctx) { + return convert(ctx, (c, prefix) -> { + sourceBefore("["); + List values = c.value(); + List> elements = new ArrayList<>(); + for (int i = 0; i < values.size(); i++) { + Toml element = visit(values.get(i)); + if (i == values.size() - 1) { + if (positionOfNext(",", ']') >= 0) { + elements.add(TomlRightPadded.build(element).withAfter(sourceBefore(","))); + elements.add(TomlRightPadded.build((Toml) new Toml.Empty(randomId(), Space.EMPTY, Markers.EMPTY)).withAfter(sourceBefore("]"))); + } else { + elements.add(TomlRightPadded.build(element).withAfter(sourceBefore("]"))); + } + } else { + elements.add(TomlRightPadded.build(element).withAfter(sourceBefore(","))); + } + } + + return new Toml.Array( + randomId(), + prefix, + Markers.EMPTY, + elements + ); + }); + } + + @Override + public Toml visitInlineTable(TomlParser.InlineTableContext ctx) { + return convert(ctx, (c, prefix) -> { + sourceBefore("{"); + List values = c.keyValue(); + List> elements = new ArrayList<>(); + for (int i = 0; i < values.size(); i++) { + Toml element = visit(values.get(i)); + if (i == values.size() - 1) { + if (positionOfNext(",", '}') >= 0) { + elements.add(TomlRightPadded.build(element).withAfter(sourceBefore(","))); + elements.add(TomlRightPadded.build((Toml) new Toml.Empty(randomId(), Space.EMPTY, Markers.EMPTY)).withAfter(sourceBefore("}"))); + } else { + elements.add(TomlRightPadded.build(element).withAfter(sourceBefore("}"))); + } + } else { + elements.add(TomlRightPadded.build(element).withAfter(sourceBefore(","))); + } + } + + return new Toml.Table( + randomId(), + prefix, + Markers.build(Collections.singletonList(new InlineTable(randomId()))), + null, + elements + ); + }); + } + + @Override + public Toml visitStandardTable(TomlParser.StandardTableContext ctx) { + return convert(ctx, (c, prefix) -> { + sourceBefore("["); + Toml.Identifier tableName = visitKey(c.key()); + TomlRightPadded nameRightPadded = TomlRightPadded.build(tableName).withAfter(sourceBefore("]")); + + List values = c.keyValue(); + List> elements = new ArrayList<>(); + for (int i = 0; i < values.size(); i++) { + elements.add(TomlRightPadded.build(visit(values.get(i)))); + } + + return new Toml.Table( + randomId(), + prefix, + Markers.EMPTY, + nameRightPadded, + elements + ); + }); + } + + @Override + public Toml visitArrayTable(TomlParser.ArrayTableContext ctx) { + return convert(ctx, (c, prefix) -> { + sourceBefore("[["); + Toml.Identifier tableName = visitKey(c.key()); + TomlRightPadded nameRightPadded = TomlRightPadded.build(tableName).withAfter(sourceBefore("]]")); + + List values = c.keyValue(); + List> elements = new ArrayList<>(); + for (int i = 0; i < values.size(); i++) { + elements.add(TomlRightPadded.build(visit(values.get(i)))); + } + + return new Toml.Table( + randomId(), + prefix, + Markers.build(Collections.singletonList(new ArrayTable(randomId()))), + nameRightPadded, + elements + ); + }); + } + + private Space prefix(ParserRuleContext ctx) { + return prefix(ctx.getStart()); + } + + private Space prefix(@Nullable TerminalNode terminalNode) { + return terminalNode == null ? Space.EMPTY : prefix(terminalNode.getSymbol()); + } + + private Space prefix(Token token) { + int start = token.getStartIndex(); + if (start < codePointCursor) { + return Space.EMPTY; + } + return Space.format(source, cursor, advanceCursor(start)); + } + + public int advanceCursor(int newCodePointIndex) { + for (; codePointCursor < newCodePointIndex; codePointCursor++) { + cursor = source.offsetByCodePoints(cursor, 1); + } + return cursor; + } + + private @Nullable T convert(C ctx, BiFunction conversion) { + if (ctx == null) { + return null; + } + + T t = conversion.apply(ctx, prefix(ctx)); + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return t; + } + + private T convert(TerminalNode node, BiFunction conversion) { + T t = conversion.apply(node, prefix(node)); + advanceCursor(node.getSymbol().getStopIndex() + 1); + return t; + } + + private void skip(TerminalNode node) { + advanceCursor(node.getSymbol().getStopIndex() + 1); + } + + /** + * @return Source from cursor to next occurrence of untilDelim, + * and if not found in the remaining source, the empty String. If stop is reached before + * untilDelim return the empty String. + */ + private Space sourceBefore(String untilDelim) { + int delimIndex = positionOfNext(untilDelim, null); + if (delimIndex < 0) { + return Space.EMPTY; // unable to find this delimiter + } + + Space space = Space.format(source, cursor, delimIndex); + advanceCursor(codePointCursor + Character.codePointCount(source, cursor, delimIndex) + untilDelim.length()); + return space; + } + + private int positionOfNext(String untilDelim, @Nullable Character stop) { + boolean inSingleLineComment = false; + + int delimIndex = cursor; + for (; delimIndex < source.length() - untilDelim.length() + 1; delimIndex++) { + if (inSingleLineComment) { + if (source.charAt(delimIndex) == '\n') { + inSingleLineComment = false; + } + } else { + if (source.charAt(delimIndex) == '#') { + inSingleLineComment = true; + delimIndex++; + } + + if (!inSingleLineComment) { + if (stop != null && source.charAt(delimIndex) == stop) { + return -1; // reached stop word before finding the delimiter + } + + if (source.startsWith(untilDelim, delimIndex)) { + break; // found it! + } + } + } + } + + return delimIndex > source.length() - untilDelim.length() ? -1 : delimIndex; + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/TomlPrinter.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/TomlPrinter.java new file mode 100644 index 00000000000..b0bdeceed5f --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/TomlPrinter.java @@ -0,0 +1,157 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.internal; + +import org.openrewrite.Cursor; +import org.openrewrite.PrintOutputCapture; +import org.openrewrite.marker.Marker; +import org.openrewrite.marker.Markers; +import org.openrewrite.toml.TomlVisitor; +import org.openrewrite.toml.marker.ArrayTable; +import org.openrewrite.toml.marker.InlineTable; +import org.openrewrite.toml.tree.Comment; +import org.openrewrite.toml.tree.Space; +import org.openrewrite.toml.tree.Toml; +import org.openrewrite.toml.tree.TomlRightPadded; + +import java.util.List; +import java.util.function.UnaryOperator; + +public class TomlPrinter

extends TomlVisitor> { + + @Override + public Toml visitArray(Toml.Array array, PrintOutputCapture

p) { + beforeSyntax(array, p); + p.append("["); + visitRightPadded(array.getPadding().getValues(), ",", p); + p.append("]"); + afterSyntax(array, p); + return array; + } + + @Override + public Toml visitDocument(Toml.Document document, PrintOutputCapture

p) { + beforeSyntax(document, p); + visit(document.getValues(), p); + visitSpace(document.getEof(), p); + afterSyntax(document, p); + return document; + } + + @Override + public Toml visitEmpty(Toml.Empty empty, PrintOutputCapture

p) { + beforeSyntax(empty, p); + afterSyntax(empty, p); + return empty; + } + + @Override + public Toml visitIdentifier(Toml.Identifier identifier, PrintOutputCapture

p) { + beforeSyntax(identifier, p); + p.append(identifier.getSource()); + afterSyntax(identifier, p); + return identifier; + } + + @Override + public Toml visitKeyValue(Toml.KeyValue keyValue, PrintOutputCapture

p) { + beforeSyntax(keyValue, p); + visitRightPadded(keyValue.getPadding().getKey(), p); + p.append("="); + visit(keyValue.getValue(), p); + afterSyntax(keyValue, p); + return keyValue; + } + + @Override + public Toml visitLiteral(Toml.Literal literal, PrintOutputCapture

p) { + beforeSyntax(literal, p); + p.append(literal.getSource()); + afterSyntax(literal, p); + return literal; + } + + @Override + public Space visitSpace(Space space, PrintOutputCapture

p) { + p.append(space.getWhitespace()); + for (Comment comment : space.getComments()) { + visitMarkers(comment.getMarkers(), p); + p.append("#").append(comment.getText()).append(comment.getSuffix()); + } + return space; + } + + @Override + public Toml visitTable(Toml.Table table, PrintOutputCapture

p) { + beforeSyntax(table, p); + if (table.getMarkers().findFirst(InlineTable.class).isPresent()) { + p.append("{"); + visitRightPadded(table.getPadding().getValues(), ",", p); + p.append("}"); + } else if (table.getMarkers().findFirst(ArrayTable.class).isPresent()) { + p.append("[["); + visitRightPadded(table.getPadding().getName(), p); + p.append("]]"); + visitRightPadded(table.getPadding().getValues(), "", p); + } else { + p.append("["); + visitRightPadded(table.getPadding().getName(), p); + p.append("]"); + visitRightPadded(table.getPadding().getValues(), "", p); + } + afterSyntax(table, p); + return table; + } + + protected void visitRightPadded(List> nodes, String suffixBetween, PrintOutputCapture

p) { + for (int i = 0; i < nodes.size(); i++) { + TomlRightPadded node = nodes.get(i); + visit(node.getElement(), p); + visitSpace(node.getAfter(), p); + if (i < nodes.size() - 1) { + p.append(suffixBetween); + } + } + } + + private static final UnaryOperator TOML_MARKER_WRAPPER = + out -> "~~" + out + (out.isEmpty() ? "" : "~~") + ">"; + + protected void beforeSyntax(Toml t, PrintOutputCapture

p) { + beforeSyntax(t.getPrefix(), t.getMarkers(), p); + } + + protected void beforeSyntax(Space prefix, Markers markers, PrintOutputCapture

p) { + for (Marker marker : markers.getMarkers()) { + p.append(p.getMarkerPrinter().beforePrefix(marker, new Cursor(getCursor(), marker), TOML_MARKER_WRAPPER)); + } + visitSpace(prefix, p); + visitMarkers(markers, p); + for (Marker marker : markers.getMarkers()) { + p.append(p.getMarkerPrinter().beforeSyntax(marker, new Cursor(getCursor(), marker), TOML_MARKER_WRAPPER)); + } + } + + protected void afterSyntax(Toml t, PrintOutputCapture

p) { + afterSyntax(t.getMarkers(), p); + } + + protected void afterSyntax(Markers markers, PrintOutputCapture

p) { + for (Marker marker : markers.getMarkers()) { + p.append(p.getMarkerPrinter().afterSyntax(marker, new Cursor(getCursor(), marker), TOML_MARKER_WRAPPER)); + } + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.interp b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.interp new file mode 100644 index 00000000000..45ff08e0e5f --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.interp @@ -0,0 +1,173 @@ +token literal names: +null +null +null +null +'[' +'[[' +']' +']]' +'=' +'.' +',' +null +null +null +null +'{' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +'}' +null + +token symbolic names: +null +WS +NL +COMMENT +L_BRACKET +DOUBLE_L_BRACKET +R_BRACKET +DOUBLE_R_BRACKET +EQUALS +DOT +COMMA +BASIC_STRING +LITERAL_STRING +UNQUOTED_KEY +VALUE_WS +L_BRACE +BOOLEAN +ML_BASIC_STRING +ML_LITERAL_STRING +FLOAT +INF +NAN +DEC_INT +HEX_INT +OCT_INT +BIN_INT +OFFSET_DATE_TIME +LOCAL_DATE_TIME +LOCAL_DATE +LOCAL_TIME +INLINE_TABLE_WS +R_BRACE +ARRAY_WS + +rule names: +WS +NL +COMMENT +L_BRACKET +DOUBLE_L_BRACKET +R_BRACKET +DOUBLE_R_BRACKET +EQUALS +DOT +COMMA +DIGIT +ALPHA +ESC +UNICODE +EX_UNICODE +BASIC_STRING +LITERAL_STRING +UNQUOTED_KEY +VALUE_WS +L_BRACE +ARRAY_START +BOOLEAN +ML_ESC +VALUE_BASIC_STRING +ML_BASIC_STRING +VALUE_LITERAL_STRING +ML_LITERAL_STRING +EXP +ZERO_PREFIXABLE_INT +FRAC +FLOAT +INF +NAN +HEX_DIGIT +DIGIT_1_9 +DIGIT_0_7 +DIGIT_0_1 +DEC_INT +HEX_INT +OCT_INT +BIN_INT +YEAR +MONTH +DAY +DELIM +HOUR +MINUTE +SECOND +SECFRAC +NUMOFFSET +OFFSET +PARTIAL_TIME +FULL_DATE +FULL_TIME +OFFSET_DATE_TIME +LOCAL_DATE_TIME +LOCAL_DATE +LOCAL_TIME +INLINE_TABLE_WS +INLINE_TABLE_KEY_DOT +INLINE_TABLE_COMMA +R_BRACE +INLINE_TABLE_KEY_BASIC_STRING +INLINE_TABLE_KEY_LITERAL_STRING +INLINE_TABLE_KEY_UNQUOTED +INLINE_TABLE_EQUALS +ARRAY_WS +ARRAY_NL +ARRAY_COMMENT +ARRAY_COMMA +ARRAY_INLINE_TABLE_START +NESTED_ARRAY_START +ARRAY_END +ARRAY_BOOLEAN +ARRAY_BASIC_STRING +ARRAY_ML_BASIC_STRING +ARRAY_LITERAL_STRING +ARRAY_ML_LITERAL_STRING +ARRAY_FLOAT +ARRAY_INF +ARRAY_NAN +ARRAY_DEC_INT +ARRAY_HEX_INT +ARRAY_OCT_INT +ARRAY_BIN_INT +ARRAY_OFFSET_DATE_TIME +ARRAY_LOCAL_DATE_TIME +ARRAY_LOCAL_DATE +ARRAY_LOCAL_TIME + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE +SIMPLE_VALUE_MODE +INLINE_TABLE_MODE +ARRAY_MODE + +atn: +[4, 0, 32, 669, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 1, 0, 4, 0, 184, 8, 0, 11, 0, 12, 0, 185, 1, 0, 1, 0, 1, 1, 3, 1, 191, 8, 1, 1, 1, 4, 1, 194, 8, 1, 11, 1, 12, 1, 195, 1, 2, 1, 2, 5, 2, 200, 8, 2, 10, 2, 12, 2, 203, 9, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 233, 8, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 254, 8, 15, 10, 15, 12, 15, 257, 9, 15, 1, 15, 1, 15, 1, 16, 1, 16, 5, 16, 263, 8, 16, 10, 16, 12, 16, 266, 9, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 4, 17, 273, 8, 17, 11, 17, 12, 17, 274, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 299, 8, 21, 1, 21, 1, 21, 1, 22, 1, 22, 3, 22, 305, 8, 22, 1, 22, 1, 22, 3, 22, 309, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 322, 8, 24, 10, 24, 12, 24, 325, 9, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 5, 26, 343, 8, 26, 10, 26, 12, 26, 346, 9, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 3, 27, 356, 8, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 5, 28, 364, 8, 28, 10, 28, 12, 28, 367, 9, 28, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 3, 30, 376, 8, 30, 3, 30, 378, 8, 30, 1, 30, 1, 30, 1, 31, 3, 31, 383, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 3, 32, 392, 8, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 3, 33, 402, 8, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36, 1, 37, 3, 37, 411, 8, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 4, 37, 418, 8, 37, 11, 37, 12, 37, 419, 3, 37, 422, 8, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 433, 8, 38, 10, 38, 12, 38, 436, 9, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 447, 8, 39, 10, 39, 12, 39, 450, 9, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 5, 40, 461, 8, 40, 10, 40, 12, 40, 464, 9, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 4, 48, 492, 8, 48, 11, 48, 12, 48, 493, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 3, 50, 503, 8, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 511, 8, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 4, 255, 264, 323, 344, 0, 89, 4, 1, 6, 2, 8, 3, 10, 4, 12, 5, 14, 6, 16, 7, 18, 8, 20, 9, 22, 10, 24, 0, 26, 0, 28, 0, 30, 0, 32, 0, 34, 11, 36, 12, 38, 13, 40, 14, 42, 15, 44, 0, 46, 16, 48, 0, 50, 0, 52, 17, 54, 0, 56, 18, 58, 0, 60, 0, 62, 0, 64, 19, 66, 20, 68, 21, 70, 0, 72, 0, 74, 0, 76, 0, 78, 22, 80, 23, 82, 24, 84, 25, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 0, 98, 0, 100, 0, 102, 0, 104, 0, 106, 0, 108, 0, 110, 0, 112, 26, 114, 27, 116, 28, 118, 29, 120, 30, 122, 0, 124, 0, 126, 31, 128, 0, 130, 0, 132, 0, 134, 0, 136, 32, 138, 0, 140, 0, 142, 0, 144, 0, 146, 0, 148, 0, 150, 0, 152, 0, 154, 0, 156, 0, 158, 0, 160, 0, 162, 0, 164, 0, 166, 0, 168, 0, 170, 0, 172, 0, 174, 0, 176, 0, 178, 0, 180, 0, 4, 0, 1, 2, 3, 16, 2, 0, 9, 9, 32, 32, 2, 0, 10, 10, 13, 13, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116, 3, 0, 10, 10, 34, 34, 92, 92, 2, 0, 10, 10, 39, 39, 2, 0, 45, 45, 95, 95, 2, 0, 34, 34, 92, 92, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 2, 0, 65, 70, 97, 102, 1, 0, 49, 57, 1, 0, 48, 55, 1, 0, 48, 49, 3, 0, 32, 32, 84, 84, 116, 116, 678, 0, 4, 1, 0, 0, 0, 0, 6, 1, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 10, 1, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 14, 1, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 1, 40, 1, 0, 0, 0, 1, 42, 1, 0, 0, 0, 1, 44, 1, 0, 0, 0, 1, 46, 1, 0, 0, 0, 1, 50, 1, 0, 0, 0, 1, 52, 1, 0, 0, 0, 1, 54, 1, 0, 0, 0, 1, 56, 1, 0, 0, 0, 1, 64, 1, 0, 0, 0, 1, 66, 1, 0, 0, 0, 1, 68, 1, 0, 0, 0, 1, 78, 1, 0, 0, 0, 1, 80, 1, 0, 0, 0, 1, 82, 1, 0, 0, 0, 1, 84, 1, 0, 0, 0, 1, 112, 1, 0, 0, 0, 1, 114, 1, 0, 0, 0, 1, 116, 1, 0, 0, 0, 1, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 2, 124, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 3, 136, 1, 0, 0, 0, 3, 138, 1, 0, 0, 0, 3, 140, 1, 0, 0, 0, 3, 142, 1, 0, 0, 0, 3, 144, 1, 0, 0, 0, 3, 146, 1, 0, 0, 0, 3, 148, 1, 0, 0, 0, 3, 150, 1, 0, 0, 0, 3, 152, 1, 0, 0, 0, 3, 154, 1, 0, 0, 0, 3, 156, 1, 0, 0, 0, 3, 158, 1, 0, 0, 0, 3, 160, 1, 0, 0, 0, 3, 162, 1, 0, 0, 0, 3, 164, 1, 0, 0, 0, 3, 166, 1, 0, 0, 0, 3, 168, 1, 0, 0, 0, 3, 170, 1, 0, 0, 0, 3, 172, 1, 0, 0, 0, 3, 174, 1, 0, 0, 0, 3, 176, 1, 0, 0, 0, 3, 178, 1, 0, 0, 0, 3, 180, 1, 0, 0, 0, 4, 183, 1, 0, 0, 0, 6, 193, 1, 0, 0, 0, 8, 197, 1, 0, 0, 0, 10, 204, 1, 0, 0, 0, 12, 206, 1, 0, 0, 0, 14, 209, 1, 0, 0, 0, 16, 211, 1, 0, 0, 0, 18, 214, 1, 0, 0, 0, 20, 218, 1, 0, 0, 0, 22, 220, 1, 0, 0, 0, 24, 224, 1, 0, 0, 0, 26, 226, 1, 0, 0, 0, 28, 228, 1, 0, 0, 0, 30, 234, 1, 0, 0, 0, 32, 240, 1, 0, 0, 0, 34, 250, 1, 0, 0, 0, 36, 260, 1, 0, 0, 0, 38, 272, 1, 0, 0, 0, 40, 276, 1, 0, 0, 0, 42, 280, 1, 0, 0, 0, 44, 284, 1, 0, 0, 0, 46, 298, 1, 0, 0, 0, 48, 308, 1, 0, 0, 0, 50, 310, 1, 0, 0, 0, 52, 315, 1, 0, 0, 0, 54, 332, 1, 0, 0, 0, 56, 337, 1, 0, 0, 0, 58, 353, 1, 0, 0, 0, 60, 359, 1, 0, 0, 0, 62, 368, 1, 0, 0, 0, 64, 371, 1, 0, 0, 0, 66, 382, 1, 0, 0, 0, 68, 391, 1, 0, 0, 0, 70, 401, 1, 0, 0, 0, 72, 403, 1, 0, 0, 0, 74, 405, 1, 0, 0, 0, 76, 407, 1, 0, 0, 0, 78, 410, 1, 0, 0, 0, 80, 425, 1, 0, 0, 0, 82, 439, 1, 0, 0, 0, 84, 453, 1, 0, 0, 0, 86, 467, 1, 0, 0, 0, 88, 472, 1, 0, 0, 0, 90, 475, 1, 0, 0, 0, 92, 478, 1, 0, 0, 0, 94, 480, 1, 0, 0, 0, 96, 483, 1, 0, 0, 0, 98, 486, 1, 0, 0, 0, 100, 489, 1, 0, 0, 0, 102, 495, 1, 0, 0, 0, 104, 502, 1, 0, 0, 0, 106, 504, 1, 0, 0, 0, 108, 512, 1, 0, 0, 0, 110, 518, 1, 0, 0, 0, 112, 521, 1, 0, 0, 0, 114, 527, 1, 0, 0, 0, 116, 533, 1, 0, 0, 0, 118, 537, 1, 0, 0, 0, 120, 541, 1, 0, 0, 0, 122, 545, 1, 0, 0, 0, 124, 549, 1, 0, 0, 0, 126, 553, 1, 0, 0, 0, 128, 557, 1, 0, 0, 0, 130, 561, 1, 0, 0, 0, 132, 565, 1, 0, 0, 0, 134, 569, 1, 0, 0, 0, 136, 574, 1, 0, 0, 0, 138, 578, 1, 0, 0, 0, 140, 582, 1, 0, 0, 0, 142, 586, 1, 0, 0, 0, 144, 590, 1, 0, 0, 0, 146, 595, 1, 0, 0, 0, 148, 600, 1, 0, 0, 0, 150, 605, 1, 0, 0, 0, 152, 609, 1, 0, 0, 0, 154, 613, 1, 0, 0, 0, 156, 617, 1, 0, 0, 0, 158, 621, 1, 0, 0, 0, 160, 625, 1, 0, 0, 0, 162, 629, 1, 0, 0, 0, 164, 633, 1, 0, 0, 0, 166, 637, 1, 0, 0, 0, 168, 641, 1, 0, 0, 0, 170, 645, 1, 0, 0, 0, 172, 649, 1, 0, 0, 0, 174, 653, 1, 0, 0, 0, 176, 657, 1, 0, 0, 0, 178, 661, 1, 0, 0, 0, 180, 665, 1, 0, 0, 0, 182, 184, 7, 0, 0, 0, 183, 182, 1, 0, 0, 0, 184, 185, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 6, 0, 0, 0, 188, 5, 1, 0, 0, 0, 189, 191, 5, 13, 0, 0, 190, 189, 1, 0, 0, 0, 190, 191, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 192, 194, 5, 10, 0, 0, 193, 190, 1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 7, 1, 0, 0, 0, 197, 201, 5, 35, 0, 0, 198, 200, 8, 1, 0, 0, 199, 198, 1, 0, 0, 0, 200, 203, 1, 0, 0, 0, 201, 199, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 9, 1, 0, 0, 0, 203, 201, 1, 0, 0, 0, 204, 205, 5, 91, 0, 0, 205, 11, 1, 0, 0, 0, 206, 207, 5, 91, 0, 0, 207, 208, 5, 91, 0, 0, 208, 13, 1, 0, 0, 0, 209, 210, 5, 93, 0, 0, 210, 15, 1, 0, 0, 0, 211, 212, 5, 93, 0, 0, 212, 213, 5, 93, 0, 0, 213, 17, 1, 0, 0, 0, 214, 215, 5, 61, 0, 0, 215, 216, 1, 0, 0, 0, 216, 217, 6, 7, 1, 0, 217, 19, 1, 0, 0, 0, 218, 219, 5, 46, 0, 0, 219, 21, 1, 0, 0, 0, 220, 221, 5, 44, 0, 0, 221, 222, 1, 0, 0, 0, 222, 223, 6, 9, 0, 0, 223, 23, 1, 0, 0, 0, 224, 225, 7, 2, 0, 0, 225, 25, 1, 0, 0, 0, 226, 227, 7, 3, 0, 0, 227, 27, 1, 0, 0, 0, 228, 232, 5, 92, 0, 0, 229, 233, 7, 4, 0, 0, 230, 233, 3, 30, 13, 0, 231, 233, 3, 32, 14, 0, 232, 229, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 232, 231, 1, 0, 0, 0, 233, 29, 1, 0, 0, 0, 234, 235, 5, 117, 0, 0, 235, 236, 3, 70, 33, 0, 236, 237, 3, 70, 33, 0, 237, 238, 3, 70, 33, 0, 238, 239, 3, 70, 33, 0, 239, 31, 1, 0, 0, 0, 240, 241, 5, 85, 0, 0, 241, 242, 3, 70, 33, 0, 242, 243, 3, 70, 33, 0, 243, 244, 3, 70, 33, 0, 244, 245, 3, 70, 33, 0, 245, 246, 3, 70, 33, 0, 246, 247, 3, 70, 33, 0, 247, 248, 3, 70, 33, 0, 248, 249, 3, 70, 33, 0, 249, 33, 1, 0, 0, 0, 250, 255, 5, 34, 0, 0, 251, 254, 3, 28, 12, 0, 252, 254, 8, 5, 0, 0, 253, 251, 1, 0, 0, 0, 253, 252, 1, 0, 0, 0, 254, 257, 1, 0, 0, 0, 255, 256, 1, 0, 0, 0, 255, 253, 1, 0, 0, 0, 256, 258, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 258, 259, 5, 34, 0, 0, 259, 35, 1, 0, 0, 0, 260, 264, 5, 39, 0, 0, 261, 263, 8, 6, 0, 0, 262, 261, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 265, 267, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 268, 5, 39, 0, 0, 268, 37, 1, 0, 0, 0, 269, 273, 3, 26, 11, 0, 270, 273, 3, 24, 10, 0, 271, 273, 7, 7, 0, 0, 272, 269, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 272, 271, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 272, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 39, 1, 0, 0, 0, 276, 277, 3, 4, 0, 0, 277, 278, 1, 0, 0, 0, 278, 279, 6, 18, 0, 0, 279, 41, 1, 0, 0, 0, 280, 281, 5, 123, 0, 0, 281, 282, 1, 0, 0, 0, 282, 283, 6, 19, 2, 0, 283, 43, 1, 0, 0, 0, 284, 285, 3, 10, 3, 0, 285, 286, 1, 0, 0, 0, 286, 287, 6, 20, 3, 0, 287, 288, 6, 20, 4, 0, 288, 45, 1, 0, 0, 0, 289, 290, 5, 116, 0, 0, 290, 291, 5, 114, 0, 0, 291, 292, 5, 117, 0, 0, 292, 299, 5, 101, 0, 0, 293, 294, 5, 102, 0, 0, 294, 295, 5, 97, 0, 0, 295, 296, 5, 108, 0, 0, 296, 297, 5, 115, 0, 0, 297, 299, 5, 101, 0, 0, 298, 289, 1, 0, 0, 0, 298, 293, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 301, 6, 21, 5, 0, 301, 47, 1, 0, 0, 0, 302, 304, 5, 92, 0, 0, 303, 305, 5, 13, 0, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 309, 5, 10, 0, 0, 307, 309, 3, 28, 12, 0, 308, 302, 1, 0, 0, 0, 308, 307, 1, 0, 0, 0, 309, 49, 1, 0, 0, 0, 310, 311, 3, 34, 15, 0, 311, 312, 1, 0, 0, 0, 312, 313, 6, 23, 6, 0, 313, 314, 6, 23, 5, 0, 314, 51, 1, 0, 0, 0, 315, 316, 5, 34, 0, 0, 316, 317, 5, 34, 0, 0, 317, 318, 5, 34, 0, 0, 318, 323, 1, 0, 0, 0, 319, 322, 3, 48, 22, 0, 320, 322, 8, 8, 0, 0, 321, 319, 1, 0, 0, 0, 321, 320, 1, 0, 0, 0, 322, 325, 1, 0, 0, 0, 323, 324, 1, 0, 0, 0, 323, 321, 1, 0, 0, 0, 324, 326, 1, 0, 0, 0, 325, 323, 1, 0, 0, 0, 326, 327, 5, 34, 0, 0, 327, 328, 5, 34, 0, 0, 328, 329, 5, 34, 0, 0, 329, 330, 1, 0, 0, 0, 330, 331, 6, 24, 5, 0, 331, 53, 1, 0, 0, 0, 332, 333, 3, 36, 16, 0, 333, 334, 1, 0, 0, 0, 334, 335, 6, 25, 7, 0, 335, 336, 6, 25, 5, 0, 336, 55, 1, 0, 0, 0, 337, 338, 5, 39, 0, 0, 338, 339, 5, 39, 0, 0, 339, 340, 5, 39, 0, 0, 340, 344, 1, 0, 0, 0, 341, 343, 9, 0, 0, 0, 342, 341, 1, 0, 0, 0, 343, 346, 1, 0, 0, 0, 344, 345, 1, 0, 0, 0, 344, 342, 1, 0, 0, 0, 345, 347, 1, 0, 0, 0, 346, 344, 1, 0, 0, 0, 347, 348, 5, 39, 0, 0, 348, 349, 5, 39, 0, 0, 349, 350, 5, 39, 0, 0, 350, 351, 1, 0, 0, 0, 351, 352, 6, 26, 5, 0, 352, 57, 1, 0, 0, 0, 353, 355, 7, 9, 0, 0, 354, 356, 7, 10, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 3, 60, 28, 0, 358, 59, 1, 0, 0, 0, 359, 365, 3, 24, 10, 0, 360, 364, 3, 24, 10, 0, 361, 362, 5, 95, 0, 0, 362, 364, 3, 24, 10, 0, 363, 360, 1, 0, 0, 0, 363, 361, 1, 0, 0, 0, 364, 367, 1, 0, 0, 0, 365, 363, 1, 0, 0, 0, 365, 366, 1, 0, 0, 0, 366, 61, 1, 0, 0, 0, 367, 365, 1, 0, 0, 0, 368, 369, 5, 46, 0, 0, 369, 370, 3, 60, 28, 0, 370, 63, 1, 0, 0, 0, 371, 377, 3, 78, 37, 0, 372, 378, 3, 58, 27, 0, 373, 375, 3, 62, 29, 0, 374, 376, 3, 58, 27, 0, 375, 374, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 378, 1, 0, 0, 0, 377, 372, 1, 0, 0, 0, 377, 373, 1, 0, 0, 0, 378, 379, 1, 0, 0, 0, 379, 380, 6, 30, 5, 0, 380, 65, 1, 0, 0, 0, 381, 383, 7, 10, 0, 0, 382, 381, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 384, 1, 0, 0, 0, 384, 385, 5, 105, 0, 0, 385, 386, 5, 110, 0, 0, 386, 387, 5, 102, 0, 0, 387, 388, 1, 0, 0, 0, 388, 389, 6, 31, 5, 0, 389, 67, 1, 0, 0, 0, 390, 392, 7, 10, 0, 0, 391, 390, 1, 0, 0, 0, 391, 392, 1, 0, 0, 0, 392, 393, 1, 0, 0, 0, 393, 394, 5, 110, 0, 0, 394, 395, 5, 97, 0, 0, 395, 396, 5, 110, 0, 0, 396, 397, 1, 0, 0, 0, 397, 398, 6, 32, 5, 0, 398, 69, 1, 0, 0, 0, 399, 402, 7, 11, 0, 0, 400, 402, 3, 24, 10, 0, 401, 399, 1, 0, 0, 0, 401, 400, 1, 0, 0, 0, 402, 71, 1, 0, 0, 0, 403, 404, 7, 12, 0, 0, 404, 73, 1, 0, 0, 0, 405, 406, 7, 13, 0, 0, 406, 75, 1, 0, 0, 0, 407, 408, 7, 14, 0, 0, 408, 77, 1, 0, 0, 0, 409, 411, 7, 10, 0, 0, 410, 409, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 421, 1, 0, 0, 0, 412, 422, 3, 24, 10, 0, 413, 417, 3, 72, 34, 0, 414, 418, 3, 24, 10, 0, 415, 416, 5, 95, 0, 0, 416, 418, 3, 24, 10, 0, 417, 414, 1, 0, 0, 0, 417, 415, 1, 0, 0, 0, 418, 419, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 419, 420, 1, 0, 0, 0, 420, 422, 1, 0, 0, 0, 421, 412, 1, 0, 0, 0, 421, 413, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 6, 37, 5, 0, 424, 79, 1, 0, 0, 0, 425, 426, 5, 48, 0, 0, 426, 427, 5, 120, 0, 0, 427, 428, 1, 0, 0, 0, 428, 434, 3, 70, 33, 0, 429, 433, 3, 70, 33, 0, 430, 431, 5, 95, 0, 0, 431, 433, 3, 70, 33, 0, 432, 429, 1, 0, 0, 0, 432, 430, 1, 0, 0, 0, 433, 436, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 437, 1, 0, 0, 0, 436, 434, 1, 0, 0, 0, 437, 438, 6, 38, 5, 0, 438, 81, 1, 0, 0, 0, 439, 440, 5, 48, 0, 0, 440, 441, 5, 111, 0, 0, 441, 442, 1, 0, 0, 0, 442, 448, 3, 74, 35, 0, 443, 447, 3, 74, 35, 0, 444, 445, 5, 95, 0, 0, 445, 447, 3, 74, 35, 0, 446, 443, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 447, 450, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 448, 449, 1, 0, 0, 0, 449, 451, 1, 0, 0, 0, 450, 448, 1, 0, 0, 0, 451, 452, 6, 39, 5, 0, 452, 83, 1, 0, 0, 0, 453, 454, 5, 48, 0, 0, 454, 455, 5, 98, 0, 0, 455, 456, 1, 0, 0, 0, 456, 462, 3, 76, 36, 0, 457, 461, 3, 76, 36, 0, 458, 459, 5, 95, 0, 0, 459, 461, 3, 76, 36, 0, 460, 457, 1, 0, 0, 0, 460, 458, 1, 0, 0, 0, 461, 464, 1, 0, 0, 0, 462, 460, 1, 0, 0, 0, 462, 463, 1, 0, 0, 0, 463, 465, 1, 0, 0, 0, 464, 462, 1, 0, 0, 0, 465, 466, 6, 40, 5, 0, 466, 85, 1, 0, 0, 0, 467, 468, 3, 24, 10, 0, 468, 469, 3, 24, 10, 0, 469, 470, 3, 24, 10, 0, 470, 471, 3, 24, 10, 0, 471, 87, 1, 0, 0, 0, 472, 473, 3, 24, 10, 0, 473, 474, 3, 24, 10, 0, 474, 89, 1, 0, 0, 0, 475, 476, 3, 24, 10, 0, 476, 477, 3, 24, 10, 0, 477, 91, 1, 0, 0, 0, 478, 479, 7, 15, 0, 0, 479, 93, 1, 0, 0, 0, 480, 481, 3, 24, 10, 0, 481, 482, 3, 24, 10, 0, 482, 95, 1, 0, 0, 0, 483, 484, 3, 24, 10, 0, 484, 485, 3, 24, 10, 0, 485, 97, 1, 0, 0, 0, 486, 487, 3, 24, 10, 0, 487, 488, 3, 24, 10, 0, 488, 99, 1, 0, 0, 0, 489, 491, 5, 46, 0, 0, 490, 492, 3, 24, 10, 0, 491, 490, 1, 0, 0, 0, 492, 493, 1, 0, 0, 0, 493, 491, 1, 0, 0, 0, 493, 494, 1, 0, 0, 0, 494, 101, 1, 0, 0, 0, 495, 496, 7, 10, 0, 0, 496, 497, 3, 94, 45, 0, 497, 498, 5, 58, 0, 0, 498, 499, 3, 96, 46, 0, 499, 103, 1, 0, 0, 0, 500, 503, 5, 90, 0, 0, 501, 503, 3, 102, 49, 0, 502, 500, 1, 0, 0, 0, 502, 501, 1, 0, 0, 0, 503, 105, 1, 0, 0, 0, 504, 505, 3, 94, 45, 0, 505, 506, 5, 58, 0, 0, 506, 507, 3, 96, 46, 0, 507, 508, 5, 58, 0, 0, 508, 510, 3, 98, 47, 0, 509, 511, 3, 100, 48, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 107, 1, 0, 0, 0, 512, 513, 3, 86, 41, 0, 513, 514, 5, 45, 0, 0, 514, 515, 3, 88, 42, 0, 515, 516, 5, 45, 0, 0, 516, 517, 3, 90, 43, 0, 517, 109, 1, 0, 0, 0, 518, 519, 3, 106, 51, 0, 519, 520, 3, 104, 50, 0, 520, 111, 1, 0, 0, 0, 521, 522, 3, 108, 52, 0, 522, 523, 3, 92, 44, 0, 523, 524, 3, 110, 53, 0, 524, 525, 1, 0, 0, 0, 525, 526, 6, 54, 5, 0, 526, 113, 1, 0, 0, 0, 527, 528, 3, 108, 52, 0, 528, 529, 3, 92, 44, 0, 529, 530, 3, 106, 51, 0, 530, 531, 1, 0, 0, 0, 531, 532, 6, 55, 5, 0, 532, 115, 1, 0, 0, 0, 533, 534, 3, 108, 52, 0, 534, 535, 1, 0, 0, 0, 535, 536, 6, 56, 5, 0, 536, 117, 1, 0, 0, 0, 537, 538, 3, 106, 51, 0, 538, 539, 1, 0, 0, 0, 539, 540, 6, 57, 5, 0, 540, 119, 1, 0, 0, 0, 541, 542, 3, 4, 0, 0, 542, 543, 1, 0, 0, 0, 543, 544, 6, 58, 0, 0, 544, 121, 1, 0, 0, 0, 545, 546, 3, 20, 8, 0, 546, 547, 1, 0, 0, 0, 547, 548, 6, 59, 8, 0, 548, 123, 1, 0, 0, 0, 549, 550, 3, 22, 9, 0, 550, 551, 1, 0, 0, 0, 551, 552, 6, 60, 9, 0, 552, 125, 1, 0, 0, 0, 553, 554, 5, 125, 0, 0, 554, 555, 1, 0, 0, 0, 555, 556, 6, 61, 5, 0, 556, 127, 1, 0, 0, 0, 557, 558, 3, 34, 15, 0, 558, 559, 1, 0, 0, 0, 559, 560, 6, 62, 6, 0, 560, 129, 1, 0, 0, 0, 561, 562, 3, 36, 16, 0, 562, 563, 1, 0, 0, 0, 563, 564, 6, 63, 7, 0, 564, 131, 1, 0, 0, 0, 565, 566, 3, 38, 17, 0, 566, 567, 1, 0, 0, 0, 567, 568, 6, 64, 10, 0, 568, 133, 1, 0, 0, 0, 569, 570, 3, 18, 7, 0, 570, 571, 1, 0, 0, 0, 571, 572, 6, 65, 11, 0, 572, 573, 6, 65, 1, 0, 573, 135, 1, 0, 0, 0, 574, 575, 3, 4, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 6, 66, 0, 0, 577, 137, 1, 0, 0, 0, 578, 579, 3, 6, 1, 0, 579, 580, 1, 0, 0, 0, 580, 581, 6, 67, 12, 0, 581, 139, 1, 0, 0, 0, 582, 583, 3, 8, 2, 0, 583, 584, 1, 0, 0, 0, 584, 585, 6, 68, 13, 0, 585, 141, 1, 0, 0, 0, 586, 587, 3, 22, 9, 0, 587, 588, 1, 0, 0, 0, 588, 589, 6, 69, 9, 0, 589, 143, 1, 0, 0, 0, 590, 591, 3, 42, 19, 0, 591, 592, 1, 0, 0, 0, 592, 593, 6, 70, 14, 0, 593, 594, 6, 70, 15, 0, 594, 145, 1, 0, 0, 0, 595, 596, 3, 10, 3, 0, 596, 597, 1, 0, 0, 0, 597, 598, 6, 71, 3, 0, 598, 599, 6, 71, 16, 0, 599, 147, 1, 0, 0, 0, 600, 601, 3, 14, 5, 0, 601, 602, 1, 0, 0, 0, 602, 603, 6, 72, 17, 0, 603, 604, 6, 72, 5, 0, 604, 149, 1, 0, 0, 0, 605, 606, 3, 46, 21, 0, 606, 607, 1, 0, 0, 0, 607, 608, 6, 73, 18, 0, 608, 151, 1, 0, 0, 0, 609, 610, 3, 34, 15, 0, 610, 611, 1, 0, 0, 0, 611, 612, 6, 74, 6, 0, 612, 153, 1, 0, 0, 0, 613, 614, 3, 52, 24, 0, 614, 615, 1, 0, 0, 0, 615, 616, 6, 75, 19, 0, 616, 155, 1, 0, 0, 0, 617, 618, 3, 36, 16, 0, 618, 619, 1, 0, 0, 0, 619, 620, 6, 76, 7, 0, 620, 157, 1, 0, 0, 0, 621, 622, 3, 56, 26, 0, 622, 623, 1, 0, 0, 0, 623, 624, 6, 77, 20, 0, 624, 159, 1, 0, 0, 0, 625, 626, 3, 64, 30, 0, 626, 627, 1, 0, 0, 0, 627, 628, 6, 78, 21, 0, 628, 161, 1, 0, 0, 0, 629, 630, 3, 66, 31, 0, 630, 631, 1, 0, 0, 0, 631, 632, 6, 79, 22, 0, 632, 163, 1, 0, 0, 0, 633, 634, 3, 68, 32, 0, 634, 635, 1, 0, 0, 0, 635, 636, 6, 80, 23, 0, 636, 165, 1, 0, 0, 0, 637, 638, 3, 78, 37, 0, 638, 639, 1, 0, 0, 0, 639, 640, 6, 81, 24, 0, 640, 167, 1, 0, 0, 0, 641, 642, 3, 80, 38, 0, 642, 643, 1, 0, 0, 0, 643, 644, 6, 82, 25, 0, 644, 169, 1, 0, 0, 0, 645, 646, 3, 82, 39, 0, 646, 647, 1, 0, 0, 0, 647, 648, 6, 83, 26, 0, 648, 171, 1, 0, 0, 0, 649, 650, 3, 84, 40, 0, 650, 651, 1, 0, 0, 0, 651, 652, 6, 84, 27, 0, 652, 173, 1, 0, 0, 0, 653, 654, 3, 112, 54, 0, 654, 655, 1, 0, 0, 0, 655, 656, 6, 85, 28, 0, 656, 175, 1, 0, 0, 0, 657, 658, 3, 114, 55, 0, 658, 659, 1, 0, 0, 0, 659, 660, 6, 86, 29, 0, 660, 177, 1, 0, 0, 0, 661, 662, 3, 116, 56, 0, 662, 663, 1, 0, 0, 0, 663, 664, 6, 87, 30, 0, 664, 179, 1, 0, 0, 0, 665, 666, 3, 118, 57, 0, 666, 667, 1, 0, 0, 0, 667, 668, 6, 88, 31, 0, 668, 181, 1, 0, 0, 0, 41, 0, 1, 2, 3, 185, 190, 195, 201, 232, 253, 255, 264, 272, 274, 298, 304, 308, 321, 323, 344, 355, 363, 365, 375, 377, 382, 391, 401, 410, 417, 419, 421, 432, 434, 446, 448, 460, 462, 493, 502, 510, 32, 6, 0, 0, 5, 1, 0, 2, 2, 0, 7, 4, 0, 2, 3, 0, 4, 0, 0, 7, 11, 0, 7, 12, 0, 7, 9, 0, 7, 10, 0, 7, 13, 0, 7, 8, 0, 7, 2, 0, 7, 3, 0, 7, 15, 0, 5, 2, 0, 5, 3, 0, 7, 6, 0, 7, 16, 0, 7, 17, 0, 7, 18, 0, 7, 19, 0, 7, 20, 0, 7, 21, 0, 7, 22, 0, 7, 23, 0, 7, 24, 0, 7, 25, 0, 7, 26, 0, 7, 27, 0, 7, 28, 0, 7, 29, 0] \ No newline at end of file diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.java new file mode 100644 index 00000000000..79166aed4f1 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.java @@ -0,0 +1,582 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +// Generated from java-escape by ANTLR 4.11.1 +package org.openrewrite.toml.internal.grammar; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.dfa.DFA; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) +public class TomlLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.11.1", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + WS=1, NL=2, COMMENT=3, L_BRACKET=4, DOUBLE_L_BRACKET=5, R_BRACKET=6, DOUBLE_R_BRACKET=7, + EQUALS=8, DOT=9, COMMA=10, BASIC_STRING=11, LITERAL_STRING=12, UNQUOTED_KEY=13, + VALUE_WS=14, L_BRACE=15, BOOLEAN=16, ML_BASIC_STRING=17, ML_LITERAL_STRING=18, + FLOAT=19, INF=20, NAN=21, DEC_INT=22, HEX_INT=23, OCT_INT=24, BIN_INT=25, + OFFSET_DATE_TIME=26, LOCAL_DATE_TIME=27, LOCAL_DATE=28, LOCAL_TIME=29, + INLINE_TABLE_WS=30, R_BRACE=31, ARRAY_WS=32; + public static final int + SIMPLE_VALUE_MODE=1, INLINE_TABLE_MODE=2, ARRAY_MODE=3; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE", "SIMPLE_VALUE_MODE", "INLINE_TABLE_MODE", "ARRAY_MODE" + }; + + private static String[] makeRuleNames() { + return new String[] { + "WS", "NL", "COMMENT", "L_BRACKET", "DOUBLE_L_BRACKET", "R_BRACKET", + "DOUBLE_R_BRACKET", "EQUALS", "DOT", "COMMA", "DIGIT", "ALPHA", "ESC", + "UNICODE", "EX_UNICODE", "BASIC_STRING", "LITERAL_STRING", "UNQUOTED_KEY", + "VALUE_WS", "L_BRACE", "ARRAY_START", "BOOLEAN", "ML_ESC", "VALUE_BASIC_STRING", + "ML_BASIC_STRING", "VALUE_LITERAL_STRING", "ML_LITERAL_STRING", "EXP", + "ZERO_PREFIXABLE_INT", "FRAC", "FLOAT", "INF", "NAN", "HEX_DIGIT", "DIGIT_1_9", + "DIGIT_0_7", "DIGIT_0_1", "DEC_INT", "HEX_INT", "OCT_INT", "BIN_INT", + "YEAR", "MONTH", "DAY", "DELIM", "HOUR", "MINUTE", "SECOND", "SECFRAC", + "NUMOFFSET", "OFFSET", "PARTIAL_TIME", "FULL_DATE", "FULL_TIME", "OFFSET_DATE_TIME", + "LOCAL_DATE_TIME", "LOCAL_DATE", "LOCAL_TIME", "INLINE_TABLE_WS", "INLINE_TABLE_KEY_DOT", + "INLINE_TABLE_COMMA", "R_BRACE", "INLINE_TABLE_KEY_BASIC_STRING", "INLINE_TABLE_KEY_LITERAL_STRING", + "INLINE_TABLE_KEY_UNQUOTED", "INLINE_TABLE_EQUALS", "ARRAY_WS", "ARRAY_NL", + "ARRAY_COMMENT", "ARRAY_COMMA", "ARRAY_INLINE_TABLE_START", "NESTED_ARRAY_START", + "ARRAY_END", "ARRAY_BOOLEAN", "ARRAY_BASIC_STRING", "ARRAY_ML_BASIC_STRING", + "ARRAY_LITERAL_STRING", "ARRAY_ML_LITERAL_STRING", "ARRAY_FLOAT", "ARRAY_INF", + "ARRAY_NAN", "ARRAY_DEC_INT", "ARRAY_HEX_INT", "ARRAY_OCT_INT", "ARRAY_BIN_INT", + "ARRAY_OFFSET_DATE_TIME", "ARRAY_LOCAL_DATE_TIME", "ARRAY_LOCAL_DATE", + "ARRAY_LOCAL_TIME" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, null, null, "'['", "'[['", "']'", "']]'", "'='", "'.'", "','", + null, null, null, null, "'{'", null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, "'}'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "WS", "NL", "COMMENT", "L_BRACKET", "DOUBLE_L_BRACKET", "R_BRACKET", + "DOUBLE_R_BRACKET", "EQUALS", "DOT", "COMMA", "BASIC_STRING", "LITERAL_STRING", + "UNQUOTED_KEY", "VALUE_WS", "L_BRACE", "BOOLEAN", "ML_BASIC_STRING", + "ML_LITERAL_STRING", "FLOAT", "INF", "NAN", "DEC_INT", "HEX_INT", "OCT_INT", + "BIN_INT", "OFFSET_DATE_TIME", "LOCAL_DATE_TIME", "LOCAL_DATE", "LOCAL_TIME", + "INLINE_TABLE_WS", "R_BRACE", "ARRAY_WS" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public TomlLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "TomlLexer.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + public static final String _serializedATN = + "\u0004\u0000 \u029d\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ + "\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ + "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ + "\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b\u0002"+ + "\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007\u000f"+ + "\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007\u0012"+ + "\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014\u0002\u0015\u0007\u0015"+ + "\u0002\u0016\u0007\u0016\u0002\u0017\u0007\u0017\u0002\u0018\u0007\u0018"+ + "\u0002\u0019\u0007\u0019\u0002\u001a\u0007\u001a\u0002\u001b\u0007\u001b"+ + "\u0002\u001c\u0007\u001c\u0002\u001d\u0007\u001d\u0002\u001e\u0007\u001e"+ + "\u0002\u001f\u0007\u001f\u0002 \u0007 \u0002!\u0007!\u0002\"\u0007\"\u0002"+ + "#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002"+ + "(\u0007(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002"+ + "-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u0002"+ + "2\u00072\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u0002"+ + "7\u00077\u00028\u00078\u00029\u00079\u0002:\u0007:\u0002;\u0007;\u0002"+ + "<\u0007<\u0002=\u0007=\u0002>\u0007>\u0002?\u0007?\u0002@\u0007@\u0002"+ + "A\u0007A\u0002B\u0007B\u0002C\u0007C\u0002D\u0007D\u0002E\u0007E\u0002"+ + "F\u0007F\u0002G\u0007G\u0002H\u0007H\u0002I\u0007I\u0002J\u0007J\u0002"+ + "K\u0007K\u0002L\u0007L\u0002M\u0007M\u0002N\u0007N\u0002O\u0007O\u0002"+ + "P\u0007P\u0002Q\u0007Q\u0002R\u0007R\u0002S\u0007S\u0002T\u0007T\u0002"+ + "U\u0007U\u0002V\u0007V\u0002W\u0007W\u0002X\u0007X\u0001\u0000\u0004\u0000"+ + "\u00b8\b\u0000\u000b\u0000\f\u0000\u00b9\u0001\u0000\u0001\u0000\u0001"+ + "\u0001\u0003\u0001\u00bf\b\u0001\u0001\u0001\u0004\u0001\u00c2\b\u0001"+ + "\u000b\u0001\f\u0001\u00c3\u0001\u0002\u0001\u0002\u0005\u0002\u00c8\b"+ + "\u0002\n\u0002\f\u0002\u00cb\t\u0002\u0001\u0003\u0001\u0003\u0001\u0004"+ + "\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0006\u0001\u0006"+ + "\u0001\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\b\u0001"+ + "\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\n\u0001\n\u0001\u000b\u0001\u000b"+ + "\u0001\f\u0001\f\u0001\f\u0001\f\u0003\f\u00e9\b\f\u0001\r\u0001\r\u0001"+ + "\r\u0001\r\u0001\r\u0001\r\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e"+ + "\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e"+ + "\u0001\u000f\u0001\u000f\u0001\u000f\u0005\u000f\u00fe\b\u000f\n\u000f"+ + "\f\u000f\u0101\t\u000f\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010"+ + "\u0005\u0010\u0107\b\u0010\n\u0010\f\u0010\u010a\t\u0010\u0001\u0010\u0001"+ + "\u0010\u0001\u0011\u0001\u0011\u0001\u0011\u0004\u0011\u0111\b\u0011\u000b"+ + "\u0011\f\u0011\u0112\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+ + "\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0014\u0001\u0014\u0001"+ + "\u0014\u0001\u0014\u0001\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001"+ + "\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0003"+ + "\u0015\u012b\b\u0015\u0001\u0015\u0001\u0015\u0001\u0016\u0001\u0016\u0003"+ + "\u0016\u0131\b\u0016\u0001\u0016\u0001\u0016\u0003\u0016\u0135\b\u0016"+ + "\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0018"+ + "\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0005\u0018"+ + "\u0142\b\u0018\n\u0018\f\u0018\u0145\t\u0018\u0001\u0018\u0001\u0018\u0001"+ + "\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0001"+ + "\u0019\u0001\u0019\u0001\u0019\u0001\u001a\u0001\u001a\u0001\u001a\u0001"+ + "\u001a\u0001\u001a\u0005\u001a\u0157\b\u001a\n\u001a\f\u001a\u015a\t\u001a"+ + "\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a"+ + "\u0001\u001b\u0001\u001b\u0003\u001b\u0164\b\u001b\u0001\u001b\u0001\u001b"+ + "\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0005\u001c\u016c\b\u001c"+ + "\n\u001c\f\u001c\u016f\t\u001c\u0001\u001d\u0001\u001d\u0001\u001d\u0001"+ + "\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0003\u001e\u0178\b\u001e\u0003"+ + "\u001e\u017a\b\u001e\u0001\u001e\u0001\u001e\u0001\u001f\u0003\u001f\u017f"+ + "\b\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+ + "\u001f\u0001 \u0003 \u0188\b \u0001 \u0001 \u0001 \u0001 \u0001 \u0001"+ + " \u0001!\u0001!\u0003!\u0192\b!\u0001\"\u0001\"\u0001#\u0001#\u0001$\u0001"+ + "$\u0001%\u0003%\u019b\b%\u0001%\u0001%\u0001%\u0001%\u0001%\u0004%\u01a2"+ + "\b%\u000b%\f%\u01a3\u0003%\u01a6\b%\u0001%\u0001%\u0001&\u0001&\u0001"+ + "&\u0001&\u0001&\u0001&\u0001&\u0005&\u01b1\b&\n&\f&\u01b4\t&\u0001&\u0001"+ + "&\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0001\'\u0005\'\u01bf"+ + "\b\'\n\'\f\'\u01c2\t\'\u0001\'\u0001\'\u0001(\u0001(\u0001(\u0001(\u0001"+ + "(\u0001(\u0001(\u0005(\u01cd\b(\n(\f(\u01d0\t(\u0001(\u0001(\u0001)\u0001"+ + ")\u0001)\u0001)\u0001)\u0001*\u0001*\u0001*\u0001+\u0001+\u0001+\u0001"+ + ",\u0001,\u0001-\u0001-\u0001-\u0001.\u0001.\u0001.\u0001/\u0001/\u0001"+ + "/\u00010\u00010\u00040\u01ec\b0\u000b0\f0\u01ed\u00011\u00011\u00011\u0001"+ + "1\u00011\u00012\u00012\u00032\u01f7\b2\u00013\u00013\u00013\u00013\u0001"+ + "3\u00013\u00033\u01ff\b3\u00014\u00014\u00014\u00014\u00014\u00014\u0001"+ + "5\u00015\u00015\u00016\u00016\u00016\u00016\u00016\u00016\u00017\u0001"+ + "7\u00017\u00017\u00017\u00017\u00018\u00018\u00018\u00018\u00019\u0001"+ + "9\u00019\u00019\u0001:\u0001:\u0001:\u0001:\u0001;\u0001;\u0001;\u0001"+ + ";\u0001<\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001=\u0001>\u0001"+ + ">\u0001>\u0001>\u0001?\u0001?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001"+ + "@\u0001A\u0001A\u0001A\u0001A\u0001A\u0001B\u0001B\u0001B\u0001B\u0001"+ + "C\u0001C\u0001C\u0001C\u0001D\u0001D\u0001D\u0001D\u0001E\u0001E\u0001"+ + "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001"+ + "G\u0001G\u0001H\u0001H\u0001H\u0001H\u0001H\u0001I\u0001I\u0001I\u0001"+ + "I\u0001J\u0001J\u0001J\u0001J\u0001K\u0001K\u0001K\u0001K\u0001L\u0001"+ + "L\u0001L\u0001L\u0001M\u0001M\u0001M\u0001M\u0001N\u0001N\u0001N\u0001"+ + "N\u0001O\u0001O\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0001Q\u0001"+ + "Q\u0001Q\u0001Q\u0001R\u0001R\u0001R\u0001R\u0001S\u0001S\u0001S\u0001"+ + "S\u0001T\u0001T\u0001T\u0001T\u0001U\u0001U\u0001U\u0001U\u0001V\u0001"+ + "V\u0001V\u0001V\u0001W\u0001W\u0001W\u0001W\u0001X\u0001X\u0001X\u0001"+ + "X\u0004\u00ff\u0108\u0143\u0158\u0000Y\u0004\u0001\u0006\u0002\b\u0003"+ + "\n\u0004\f\u0005\u000e\u0006\u0010\u0007\u0012\b\u0014\t\u0016\n\u0018"+ + "\u0000\u001a\u0000\u001c\u0000\u001e\u0000 \u0000\"\u000b$\f&\r(\u000e"+ + "*\u000f,\u0000.\u00100\u00002\u00004\u00116\u00008\u0012:\u0000<\u0000"+ + ">\u0000@\u0013B\u0014D\u0015F\u0000H\u0000J\u0000L\u0000N\u0016P\u0017"+ + "R\u0018T\u0019V\u0000X\u0000Z\u0000\\\u0000^\u0000`\u0000b\u0000d\u0000"+ + "f\u0000h\u0000j\u0000l\u0000n\u0000p\u001ar\u001bt\u001cv\u001dx\u001e"+ + "z\u0000|\u0000~\u001f\u0080\u0000\u0082\u0000\u0084\u0000\u0086\u0000"+ + "\u0088 \u008a\u0000\u008c\u0000\u008e\u0000\u0090\u0000\u0092\u0000\u0094"+ + "\u0000\u0096\u0000\u0098\u0000\u009a\u0000\u009c\u0000\u009e\u0000\u00a0"+ + "\u0000\u00a2\u0000\u00a4\u0000\u00a6\u0000\u00a8\u0000\u00aa\u0000\u00ac"+ + "\u0000\u00ae\u0000\u00b0\u0000\u00b2\u0000\u00b4\u0000\u0004\u0000\u0001"+ + "\u0002\u0003\u0010\u0002\u0000\t\t \u0002\u0000\n\n\r\r\u0001\u00000"+ + "9\u0002\u0000AZaz\b\u0000\"\"//\\\\bbffnnrrtt\u0003\u0000\n\n\"\"\\\\"+ + "\u0002\u0000\n\n\'\'\u0002\u0000--__\u0002\u0000\"\"\\\\\u0002\u0000E"+ + "Eee\u0002\u0000++--\u0002\u0000AFaf\u0001\u000019\u0001\u000007\u0001"+ + "\u000001\u0003\u0000 TTtt\u02a6\u0000\u0004\u0001\u0000\u0000\u0000\u0000"+ + "\u0006\u0001\u0000\u0000\u0000\u0000\b\u0001\u0000\u0000\u0000\u0000\n"+ + "\u0001\u0000\u0000\u0000\u0000\f\u0001\u0000\u0000\u0000\u0000\u000e\u0001"+ + "\u0000\u0000\u0000\u0000\u0010\u0001\u0000\u0000\u0000\u0000\u0012\u0001"+ + "\u0000\u0000\u0000\u0000\u0014\u0001\u0000\u0000\u0000\u0000\u0016\u0001"+ + "\u0000\u0000\u0000\u0000\"\u0001\u0000\u0000\u0000\u0000$\u0001\u0000"+ + "\u0000\u0000\u0000&\u0001\u0000\u0000\u0000\u0001(\u0001\u0000\u0000\u0000"+ + "\u0001*\u0001\u0000\u0000\u0000\u0001,\u0001\u0000\u0000\u0000\u0001."+ + "\u0001\u0000\u0000\u0000\u00012\u0001\u0000\u0000\u0000\u00014\u0001\u0000"+ + "\u0000\u0000\u00016\u0001\u0000\u0000\u0000\u00018\u0001\u0000\u0000\u0000"+ + "\u0001@\u0001\u0000\u0000\u0000\u0001B\u0001\u0000\u0000\u0000\u0001D"+ + "\u0001\u0000\u0000\u0000\u0001N\u0001\u0000\u0000\u0000\u0001P\u0001\u0000"+ + "\u0000\u0000\u0001R\u0001\u0000\u0000\u0000\u0001T\u0001\u0000\u0000\u0000"+ + "\u0001p\u0001\u0000\u0000\u0000\u0001r\u0001\u0000\u0000\u0000\u0001t"+ + "\u0001\u0000\u0000\u0000\u0001v\u0001\u0000\u0000\u0000\u0002x\u0001\u0000"+ + "\u0000\u0000\u0002z\u0001\u0000\u0000\u0000\u0002|\u0001\u0000\u0000\u0000"+ + "\u0002~\u0001\u0000\u0000\u0000\u0002\u0080\u0001\u0000\u0000\u0000\u0002"+ + "\u0082\u0001\u0000\u0000\u0000\u0002\u0084\u0001\u0000\u0000\u0000\u0002"+ + "\u0086\u0001\u0000\u0000\u0000\u0003\u0088\u0001\u0000\u0000\u0000\u0003"+ + "\u008a\u0001\u0000\u0000\u0000\u0003\u008c\u0001\u0000\u0000\u0000\u0003"+ + "\u008e\u0001\u0000\u0000\u0000\u0003\u0090\u0001\u0000\u0000\u0000\u0003"+ + "\u0092\u0001\u0000\u0000\u0000\u0003\u0094\u0001\u0000\u0000\u0000\u0003"+ + "\u0096\u0001\u0000\u0000\u0000\u0003\u0098\u0001\u0000\u0000\u0000\u0003"+ + "\u009a\u0001\u0000\u0000\u0000\u0003\u009c\u0001\u0000\u0000\u0000\u0003"+ + "\u009e\u0001\u0000\u0000\u0000\u0003\u00a0\u0001\u0000\u0000\u0000\u0003"+ + "\u00a2\u0001\u0000\u0000\u0000\u0003\u00a4\u0001\u0000\u0000\u0000\u0003"+ + "\u00a6\u0001\u0000\u0000\u0000\u0003\u00a8\u0001\u0000\u0000\u0000\u0003"+ + "\u00aa\u0001\u0000\u0000\u0000\u0003\u00ac\u0001\u0000\u0000\u0000\u0003"+ + "\u00ae\u0001\u0000\u0000\u0000\u0003\u00b0\u0001\u0000\u0000\u0000\u0003"+ + "\u00b2\u0001\u0000\u0000\u0000\u0003\u00b4\u0001\u0000\u0000\u0000\u0004"+ + "\u00b7\u0001\u0000\u0000\u0000\u0006\u00c1\u0001\u0000\u0000\u0000\b\u00c5"+ + "\u0001\u0000\u0000\u0000\n\u00cc\u0001\u0000\u0000\u0000\f\u00ce\u0001"+ + "\u0000\u0000\u0000\u000e\u00d1\u0001\u0000\u0000\u0000\u0010\u00d3\u0001"+ + "\u0000\u0000\u0000\u0012\u00d6\u0001\u0000\u0000\u0000\u0014\u00da\u0001"+ + "\u0000\u0000\u0000\u0016\u00dc\u0001\u0000\u0000\u0000\u0018\u00e0\u0001"+ + "\u0000\u0000\u0000\u001a\u00e2\u0001\u0000\u0000\u0000\u001c\u00e4\u0001"+ + "\u0000\u0000\u0000\u001e\u00ea\u0001\u0000\u0000\u0000 \u00f0\u0001\u0000"+ + "\u0000\u0000\"\u00fa\u0001\u0000\u0000\u0000$\u0104\u0001\u0000\u0000"+ + "\u0000&\u0110\u0001\u0000\u0000\u0000(\u0114\u0001\u0000\u0000\u0000*"+ + "\u0118\u0001\u0000\u0000\u0000,\u011c\u0001\u0000\u0000\u0000.\u012a\u0001"+ + "\u0000\u0000\u00000\u0134\u0001\u0000\u0000\u00002\u0136\u0001\u0000\u0000"+ + "\u00004\u013b\u0001\u0000\u0000\u00006\u014c\u0001\u0000\u0000\u00008"+ + "\u0151\u0001\u0000\u0000\u0000:\u0161\u0001\u0000\u0000\u0000<\u0167\u0001"+ + "\u0000\u0000\u0000>\u0170\u0001\u0000\u0000\u0000@\u0173\u0001\u0000\u0000"+ + "\u0000B\u017e\u0001\u0000\u0000\u0000D\u0187\u0001\u0000\u0000\u0000F"+ + "\u0191\u0001\u0000\u0000\u0000H\u0193\u0001\u0000\u0000\u0000J\u0195\u0001"+ + "\u0000\u0000\u0000L\u0197\u0001\u0000\u0000\u0000N\u019a\u0001\u0000\u0000"+ + "\u0000P\u01a9\u0001\u0000\u0000\u0000R\u01b7\u0001\u0000\u0000\u0000T"+ + "\u01c5\u0001\u0000\u0000\u0000V\u01d3\u0001\u0000\u0000\u0000X\u01d8\u0001"+ + "\u0000\u0000\u0000Z\u01db\u0001\u0000\u0000\u0000\\\u01de\u0001\u0000"+ + "\u0000\u0000^\u01e0\u0001\u0000\u0000\u0000`\u01e3\u0001\u0000\u0000\u0000"+ + "b\u01e6\u0001\u0000\u0000\u0000d\u01e9\u0001\u0000\u0000\u0000f\u01ef"+ + "\u0001\u0000\u0000\u0000h\u01f6\u0001\u0000\u0000\u0000j\u01f8\u0001\u0000"+ + "\u0000\u0000l\u0200\u0001\u0000\u0000\u0000n\u0206\u0001\u0000\u0000\u0000"+ + "p\u0209\u0001\u0000\u0000\u0000r\u020f\u0001\u0000\u0000\u0000t\u0215"+ + "\u0001\u0000\u0000\u0000v\u0219\u0001\u0000\u0000\u0000x\u021d\u0001\u0000"+ + "\u0000\u0000z\u0221\u0001\u0000\u0000\u0000|\u0225\u0001\u0000\u0000\u0000"+ + "~\u0229\u0001\u0000\u0000\u0000\u0080\u022d\u0001\u0000\u0000\u0000\u0082"+ + "\u0231\u0001\u0000\u0000\u0000\u0084\u0235\u0001\u0000\u0000\u0000\u0086"+ + "\u0239\u0001\u0000\u0000\u0000\u0088\u023e\u0001\u0000\u0000\u0000\u008a"+ + "\u0242\u0001\u0000\u0000\u0000\u008c\u0246\u0001\u0000\u0000\u0000\u008e"+ + "\u024a\u0001\u0000\u0000\u0000\u0090\u024e\u0001\u0000\u0000\u0000\u0092"+ + "\u0253\u0001\u0000\u0000\u0000\u0094\u0258\u0001\u0000\u0000\u0000\u0096"+ + "\u025d\u0001\u0000\u0000\u0000\u0098\u0261\u0001\u0000\u0000\u0000\u009a"+ + "\u0265\u0001\u0000\u0000\u0000\u009c\u0269\u0001\u0000\u0000\u0000\u009e"+ + "\u026d\u0001\u0000\u0000\u0000\u00a0\u0271\u0001\u0000\u0000\u0000\u00a2"+ + "\u0275\u0001\u0000\u0000\u0000\u00a4\u0279\u0001\u0000\u0000\u0000\u00a6"+ + "\u027d\u0001\u0000\u0000\u0000\u00a8\u0281\u0001\u0000\u0000\u0000\u00aa"+ + "\u0285\u0001\u0000\u0000\u0000\u00ac\u0289\u0001\u0000\u0000\u0000\u00ae"+ + "\u028d\u0001\u0000\u0000\u0000\u00b0\u0291\u0001\u0000\u0000\u0000\u00b2"+ + "\u0295\u0001\u0000\u0000\u0000\u00b4\u0299\u0001\u0000\u0000\u0000\u00b6"+ + "\u00b8\u0007\u0000\u0000\u0000\u00b7\u00b6\u0001\u0000\u0000\u0000\u00b8"+ + "\u00b9\u0001\u0000\u0000\u0000\u00b9\u00b7\u0001\u0000\u0000\u0000\u00b9"+ + "\u00ba\u0001\u0000\u0000\u0000\u00ba\u00bb\u0001\u0000\u0000\u0000\u00bb"+ + "\u00bc\u0006\u0000\u0000\u0000\u00bc\u0005\u0001\u0000\u0000\u0000\u00bd"+ + "\u00bf\u0005\r\u0000\u0000\u00be\u00bd\u0001\u0000\u0000\u0000\u00be\u00bf"+ + "\u0001\u0000\u0000\u0000\u00bf\u00c0\u0001\u0000\u0000\u0000\u00c0\u00c2"+ + "\u0005\n\u0000\u0000\u00c1\u00be\u0001\u0000\u0000\u0000\u00c2\u00c3\u0001"+ + "\u0000\u0000\u0000\u00c3\u00c1\u0001\u0000\u0000\u0000\u00c3\u00c4\u0001"+ + "\u0000\u0000\u0000\u00c4\u0007\u0001\u0000\u0000\u0000\u00c5\u00c9\u0005"+ + "#\u0000\u0000\u00c6\u00c8\b\u0001\u0000\u0000\u00c7\u00c6\u0001\u0000"+ + "\u0000\u0000\u00c8\u00cb\u0001\u0000\u0000\u0000\u00c9\u00c7\u0001\u0000"+ + "\u0000\u0000\u00c9\u00ca\u0001\u0000\u0000\u0000\u00ca\t\u0001\u0000\u0000"+ + "\u0000\u00cb\u00c9\u0001\u0000\u0000\u0000\u00cc\u00cd\u0005[\u0000\u0000"+ + "\u00cd\u000b\u0001\u0000\u0000\u0000\u00ce\u00cf\u0005[\u0000\u0000\u00cf"+ + "\u00d0\u0005[\u0000\u0000\u00d0\r\u0001\u0000\u0000\u0000\u00d1\u00d2"+ + "\u0005]\u0000\u0000\u00d2\u000f\u0001\u0000\u0000\u0000\u00d3\u00d4\u0005"+ + "]\u0000\u0000\u00d4\u00d5\u0005]\u0000\u0000\u00d5\u0011\u0001\u0000\u0000"+ + "\u0000\u00d6\u00d7\u0005=\u0000\u0000\u00d7\u00d8\u0001\u0000\u0000\u0000"+ + "\u00d8\u00d9\u0006\u0007\u0001\u0000\u00d9\u0013\u0001\u0000\u0000\u0000"+ + "\u00da\u00db\u0005.\u0000\u0000\u00db\u0015\u0001\u0000\u0000\u0000\u00dc"+ + "\u00dd\u0005,\u0000\u0000\u00dd\u00de\u0001\u0000\u0000\u0000\u00de\u00df"+ + "\u0006\t\u0000\u0000\u00df\u0017\u0001\u0000\u0000\u0000\u00e0\u00e1\u0007"+ + "\u0002\u0000\u0000\u00e1\u0019\u0001\u0000\u0000\u0000\u00e2\u00e3\u0007"+ + "\u0003\u0000\u0000\u00e3\u001b\u0001\u0000\u0000\u0000\u00e4\u00e8\u0005"+ + "\\\u0000\u0000\u00e5\u00e9\u0007\u0004\u0000\u0000\u00e6\u00e9\u0003\u001e"+ + "\r\u0000\u00e7\u00e9\u0003 \u000e\u0000\u00e8\u00e5\u0001\u0000\u0000"+ + "\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e8\u00e7\u0001\u0000\u0000"+ + "\u0000\u00e9\u001d\u0001\u0000\u0000\u0000\u00ea\u00eb\u0005u\u0000\u0000"+ + "\u00eb\u00ec\u0003F!\u0000\u00ec\u00ed\u0003F!\u0000\u00ed\u00ee\u0003"+ + "F!\u0000\u00ee\u00ef\u0003F!\u0000\u00ef\u001f\u0001\u0000\u0000\u0000"+ + "\u00f0\u00f1\u0005U\u0000\u0000\u00f1\u00f2\u0003F!\u0000\u00f2\u00f3"+ + "\u0003F!\u0000\u00f3\u00f4\u0003F!\u0000\u00f4\u00f5\u0003F!\u0000\u00f5"+ + "\u00f6\u0003F!\u0000\u00f6\u00f7\u0003F!\u0000\u00f7\u00f8\u0003F!\u0000"+ + "\u00f8\u00f9\u0003F!\u0000\u00f9!\u0001\u0000\u0000\u0000\u00fa\u00ff"+ + "\u0005\"\u0000\u0000\u00fb\u00fe\u0003\u001c\f\u0000\u00fc\u00fe\b\u0005"+ + "\u0000\u0000\u00fd\u00fb\u0001\u0000\u0000\u0000\u00fd\u00fc\u0001\u0000"+ + "\u0000\u0000\u00fe\u0101\u0001\u0000\u0000\u0000\u00ff\u0100\u0001\u0000"+ + "\u0000\u0000\u00ff\u00fd\u0001\u0000\u0000\u0000\u0100\u0102\u0001\u0000"+ + "\u0000\u0000\u0101\u00ff\u0001\u0000\u0000\u0000\u0102\u0103\u0005\"\u0000"+ + "\u0000\u0103#\u0001\u0000\u0000\u0000\u0104\u0108\u0005\'\u0000\u0000"+ + "\u0105\u0107\b\u0006\u0000\u0000\u0106\u0105\u0001\u0000\u0000\u0000\u0107"+ + "\u010a\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0108"+ + "\u0106\u0001\u0000\u0000\u0000\u0109\u010b\u0001\u0000\u0000\u0000\u010a"+ + "\u0108\u0001\u0000\u0000\u0000\u010b\u010c\u0005\'\u0000\u0000\u010c%"+ + "\u0001\u0000\u0000\u0000\u010d\u0111\u0003\u001a\u000b\u0000\u010e\u0111"+ + "\u0003\u0018\n\u0000\u010f\u0111\u0007\u0007\u0000\u0000\u0110\u010d\u0001"+ + "\u0000\u0000\u0000\u0110\u010e\u0001\u0000\u0000\u0000\u0110\u010f\u0001"+ + "\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0110\u0001"+ + "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\'\u0001\u0000"+ + "\u0000\u0000\u0114\u0115\u0003\u0004\u0000\u0000\u0115\u0116\u0001\u0000"+ + "\u0000\u0000\u0116\u0117\u0006\u0012\u0000\u0000\u0117)\u0001\u0000\u0000"+ + "\u0000\u0118\u0119\u0005{\u0000\u0000\u0119\u011a\u0001\u0000\u0000\u0000"+ + "\u011a\u011b\u0006\u0013\u0002\u0000\u011b+\u0001\u0000\u0000\u0000\u011c"+ + "\u011d\u0003\n\u0003\u0000\u011d\u011e\u0001\u0000\u0000\u0000\u011e\u011f"+ + "\u0006\u0014\u0003\u0000\u011f\u0120\u0006\u0014\u0004\u0000\u0120-\u0001"+ + "\u0000\u0000\u0000\u0121\u0122\u0005t\u0000\u0000\u0122\u0123\u0005r\u0000"+ + "\u0000\u0123\u0124\u0005u\u0000\u0000\u0124\u012b\u0005e\u0000\u0000\u0125"+ + "\u0126\u0005f\u0000\u0000\u0126\u0127\u0005a\u0000\u0000\u0127\u0128\u0005"+ + "l\u0000\u0000\u0128\u0129\u0005s\u0000\u0000\u0129\u012b\u0005e\u0000"+ + "\u0000\u012a\u0121\u0001\u0000\u0000\u0000\u012a\u0125\u0001\u0000\u0000"+ + "\u0000\u012b\u012c\u0001\u0000\u0000\u0000\u012c\u012d\u0006\u0015\u0005"+ + "\u0000\u012d/\u0001\u0000\u0000\u0000\u012e\u0130\u0005\\\u0000\u0000"+ + "\u012f\u0131\u0005\r\u0000\u0000\u0130\u012f\u0001\u0000\u0000\u0000\u0130"+ + "\u0131\u0001\u0000\u0000\u0000\u0131\u0132\u0001\u0000\u0000\u0000\u0132"+ + "\u0135\u0005\n\u0000\u0000\u0133\u0135\u0003\u001c\f\u0000\u0134\u012e"+ + "\u0001\u0000\u0000\u0000\u0134\u0133\u0001\u0000\u0000\u0000\u01351\u0001"+ + "\u0000\u0000\u0000\u0136\u0137\u0003\"\u000f\u0000\u0137\u0138\u0001\u0000"+ + "\u0000\u0000\u0138\u0139\u0006\u0017\u0006\u0000\u0139\u013a\u0006\u0017"+ + "\u0005\u0000\u013a3\u0001\u0000\u0000\u0000\u013b\u013c\u0005\"\u0000"+ + "\u0000\u013c\u013d\u0005\"\u0000\u0000\u013d\u013e\u0005\"\u0000\u0000"+ + "\u013e\u0143\u0001\u0000\u0000\u0000\u013f\u0142\u00030\u0016\u0000\u0140"+ + "\u0142\b\b\u0000\u0000\u0141\u013f\u0001\u0000\u0000\u0000\u0141\u0140"+ + "\u0001\u0000\u0000\u0000\u0142\u0145\u0001\u0000\u0000\u0000\u0143\u0144"+ + "\u0001\u0000\u0000\u0000\u0143\u0141\u0001\u0000\u0000\u0000\u0144\u0146"+ + "\u0001\u0000\u0000\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0147"+ + "\u0005\"\u0000\u0000\u0147\u0148\u0005\"\u0000\u0000\u0148\u0149\u0005"+ + "\"\u0000\u0000\u0149\u014a\u0001\u0000\u0000\u0000\u014a\u014b\u0006\u0018"+ + "\u0005\u0000\u014b5\u0001\u0000\u0000\u0000\u014c\u014d\u0003$\u0010\u0000"+ + "\u014d\u014e\u0001\u0000\u0000\u0000\u014e\u014f\u0006\u0019\u0007\u0000"+ + "\u014f\u0150\u0006\u0019\u0005\u0000\u01507\u0001\u0000\u0000\u0000\u0151"+ + "\u0152\u0005\'\u0000\u0000\u0152\u0153\u0005\'\u0000\u0000\u0153\u0154"+ + "\u0005\'\u0000\u0000\u0154\u0158\u0001\u0000\u0000\u0000\u0155\u0157\t"+ + "\u0000\u0000\u0000\u0156\u0155\u0001\u0000\u0000\u0000\u0157\u015a\u0001"+ + "\u0000\u0000\u0000\u0158\u0159\u0001\u0000\u0000\u0000\u0158\u0156\u0001"+ + "\u0000\u0000\u0000\u0159\u015b\u0001\u0000\u0000\u0000\u015a\u0158\u0001"+ + "\u0000\u0000\u0000\u015b\u015c\u0005\'\u0000\u0000\u015c\u015d\u0005\'"+ + "\u0000\u0000\u015d\u015e\u0005\'\u0000\u0000\u015e\u015f\u0001\u0000\u0000"+ + "\u0000\u015f\u0160\u0006\u001a\u0005\u0000\u01609\u0001\u0000\u0000\u0000"+ + "\u0161\u0163\u0007\t\u0000\u0000\u0162\u0164\u0007\n\u0000\u0000\u0163"+ + "\u0162\u0001\u0000\u0000\u0000\u0163\u0164\u0001\u0000\u0000\u0000\u0164"+ + "\u0165\u0001\u0000\u0000\u0000\u0165\u0166\u0003<\u001c\u0000\u0166;\u0001"+ + "\u0000\u0000\u0000\u0167\u016d\u0003\u0018\n\u0000\u0168\u016c\u0003\u0018"+ + "\n\u0000\u0169\u016a\u0005_\u0000\u0000\u016a\u016c\u0003\u0018\n\u0000"+ + "\u016b\u0168\u0001\u0000\u0000\u0000\u016b\u0169\u0001\u0000\u0000\u0000"+ + "\u016c\u016f\u0001\u0000\u0000\u0000\u016d\u016b\u0001\u0000\u0000\u0000"+ + "\u016d\u016e\u0001\u0000\u0000\u0000\u016e=\u0001\u0000\u0000\u0000\u016f"+ + "\u016d\u0001\u0000\u0000\u0000\u0170\u0171\u0005.\u0000\u0000\u0171\u0172"+ + "\u0003<\u001c\u0000\u0172?\u0001\u0000\u0000\u0000\u0173\u0179\u0003N"+ + "%\u0000\u0174\u017a\u0003:\u001b\u0000\u0175\u0177\u0003>\u001d\u0000"+ + "\u0176\u0178\u0003:\u001b\u0000\u0177\u0176\u0001\u0000\u0000\u0000\u0177"+ + "\u0178\u0001\u0000\u0000\u0000\u0178\u017a\u0001\u0000\u0000\u0000\u0179"+ + "\u0174\u0001\u0000\u0000\u0000\u0179\u0175\u0001\u0000\u0000\u0000\u017a"+ + "\u017b\u0001\u0000\u0000\u0000\u017b\u017c\u0006\u001e\u0005\u0000\u017c"+ + "A\u0001\u0000\u0000\u0000\u017d\u017f\u0007\n\u0000\u0000\u017e\u017d"+ + "\u0001\u0000\u0000\u0000\u017e\u017f\u0001\u0000\u0000\u0000\u017f\u0180"+ + "\u0001\u0000\u0000\u0000\u0180\u0181\u0005i\u0000\u0000\u0181\u0182\u0005"+ + "n\u0000\u0000\u0182\u0183\u0005f\u0000\u0000\u0183\u0184\u0001\u0000\u0000"+ + "\u0000\u0184\u0185\u0006\u001f\u0005\u0000\u0185C\u0001\u0000\u0000\u0000"+ + "\u0186\u0188\u0007\n\u0000\u0000\u0187\u0186\u0001\u0000\u0000\u0000\u0187"+ + "\u0188\u0001\u0000\u0000\u0000\u0188\u0189\u0001\u0000\u0000\u0000\u0189"+ + "\u018a\u0005n\u0000\u0000\u018a\u018b\u0005a\u0000\u0000\u018b\u018c\u0005"+ + "n\u0000\u0000\u018c\u018d\u0001\u0000\u0000\u0000\u018d\u018e\u0006 \u0005"+ + "\u0000\u018eE\u0001\u0000\u0000\u0000\u018f\u0192\u0007\u000b\u0000\u0000"+ + "\u0190\u0192\u0003\u0018\n\u0000\u0191\u018f\u0001\u0000\u0000\u0000\u0191"+ + "\u0190\u0001\u0000\u0000\u0000\u0192G\u0001\u0000\u0000\u0000\u0193\u0194"+ + "\u0007\f\u0000\u0000\u0194I\u0001\u0000\u0000\u0000\u0195\u0196\u0007"+ + "\r\u0000\u0000\u0196K\u0001\u0000\u0000\u0000\u0197\u0198\u0007\u000e"+ + "\u0000\u0000\u0198M\u0001\u0000\u0000\u0000\u0199\u019b\u0007\n\u0000"+ + "\u0000\u019a\u0199\u0001\u0000\u0000\u0000\u019a\u019b\u0001\u0000\u0000"+ + "\u0000\u019b\u01a5\u0001\u0000\u0000\u0000\u019c\u01a6\u0003\u0018\n\u0000"+ + "\u019d\u01a1\u0003H\"\u0000\u019e\u01a2\u0003\u0018\n\u0000\u019f\u01a0"+ + "\u0005_\u0000\u0000\u01a0\u01a2\u0003\u0018\n\u0000\u01a1\u019e\u0001"+ + "\u0000\u0000\u0000\u01a1\u019f\u0001\u0000\u0000\u0000\u01a2\u01a3\u0001"+ + "\u0000\u0000\u0000\u01a3\u01a1\u0001\u0000\u0000\u0000\u01a3\u01a4\u0001"+ + "\u0000\u0000\u0000\u01a4\u01a6\u0001\u0000\u0000\u0000\u01a5\u019c\u0001"+ + "\u0000\u0000\u0000\u01a5\u019d\u0001\u0000\u0000\u0000\u01a6\u01a7\u0001"+ + "\u0000\u0000\u0000\u01a7\u01a8\u0006%\u0005\u0000\u01a8O\u0001\u0000\u0000"+ + "\u0000\u01a9\u01aa\u00050\u0000\u0000\u01aa\u01ab\u0005x\u0000\u0000\u01ab"+ + "\u01ac\u0001\u0000\u0000\u0000\u01ac\u01b2\u0003F!\u0000\u01ad\u01b1\u0003"+ + "F!\u0000\u01ae\u01af\u0005_\u0000\u0000\u01af\u01b1\u0003F!\u0000\u01b0"+ + "\u01ad\u0001\u0000\u0000\u0000\u01b0\u01ae\u0001\u0000\u0000\u0000\u01b1"+ + "\u01b4\u0001\u0000\u0000\u0000\u01b2\u01b0\u0001\u0000\u0000\u0000\u01b2"+ + "\u01b3\u0001\u0000\u0000\u0000\u01b3\u01b5\u0001\u0000\u0000\u0000\u01b4"+ + "\u01b2\u0001\u0000\u0000\u0000\u01b5\u01b6\u0006&\u0005\u0000\u01b6Q\u0001"+ + "\u0000\u0000\u0000\u01b7\u01b8\u00050\u0000\u0000\u01b8\u01b9\u0005o\u0000"+ + "\u0000\u01b9\u01ba\u0001\u0000\u0000\u0000\u01ba\u01c0\u0003J#\u0000\u01bb"+ + "\u01bf\u0003J#\u0000\u01bc\u01bd\u0005_\u0000\u0000\u01bd\u01bf\u0003"+ + "J#\u0000\u01be\u01bb\u0001\u0000\u0000\u0000\u01be\u01bc\u0001\u0000\u0000"+ + "\u0000\u01bf\u01c2\u0001\u0000\u0000\u0000\u01c0\u01be\u0001\u0000\u0000"+ + "\u0000\u01c0\u01c1\u0001\u0000\u0000\u0000\u01c1\u01c3\u0001\u0000\u0000"+ + "\u0000\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c4\u0006\'\u0005\u0000"+ + "\u01c4S\u0001\u0000\u0000\u0000\u01c5\u01c6\u00050\u0000\u0000\u01c6\u01c7"+ + "\u0005b\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8\u01ce\u0003"+ + "L$\u0000\u01c9\u01cd\u0003L$\u0000\u01ca\u01cb\u0005_\u0000\u0000\u01cb"+ + "\u01cd\u0003L$\u0000\u01cc\u01c9\u0001\u0000\u0000\u0000\u01cc\u01ca\u0001"+ + "\u0000\u0000\u0000\u01cd\u01d0\u0001\u0000\u0000\u0000\u01ce\u01cc\u0001"+ + "\u0000\u0000\u0000\u01ce\u01cf\u0001\u0000\u0000\u0000\u01cf\u01d1\u0001"+ + "\u0000\u0000\u0000\u01d0\u01ce\u0001\u0000\u0000\u0000\u01d1\u01d2\u0006"+ + "(\u0005\u0000\u01d2U\u0001\u0000\u0000\u0000\u01d3\u01d4\u0003\u0018\n"+ + "\u0000\u01d4\u01d5\u0003\u0018\n\u0000\u01d5\u01d6\u0003\u0018\n\u0000"+ + "\u01d6\u01d7\u0003\u0018\n\u0000\u01d7W\u0001\u0000\u0000\u0000\u01d8"+ + "\u01d9\u0003\u0018\n\u0000\u01d9\u01da\u0003\u0018\n\u0000\u01daY\u0001"+ + "\u0000\u0000\u0000\u01db\u01dc\u0003\u0018\n\u0000\u01dc\u01dd\u0003\u0018"+ + "\n\u0000\u01dd[\u0001\u0000\u0000\u0000\u01de\u01df\u0007\u000f\u0000"+ + "\u0000\u01df]\u0001\u0000\u0000\u0000\u01e0\u01e1\u0003\u0018\n\u0000"+ + "\u01e1\u01e2\u0003\u0018\n\u0000\u01e2_\u0001\u0000\u0000\u0000\u01e3"+ + "\u01e4\u0003\u0018\n\u0000\u01e4\u01e5\u0003\u0018\n\u0000\u01e5a\u0001"+ + "\u0000\u0000\u0000\u01e6\u01e7\u0003\u0018\n\u0000\u01e7\u01e8\u0003\u0018"+ + "\n\u0000\u01e8c\u0001\u0000\u0000\u0000\u01e9\u01eb\u0005.\u0000\u0000"+ + "\u01ea\u01ec\u0003\u0018\n\u0000\u01eb\u01ea\u0001\u0000\u0000\u0000\u01ec"+ + "\u01ed\u0001\u0000\u0000\u0000\u01ed\u01eb\u0001\u0000\u0000\u0000\u01ed"+ + "\u01ee\u0001\u0000\u0000\u0000\u01eee\u0001\u0000\u0000\u0000\u01ef\u01f0"+ + "\u0007\n\u0000\u0000\u01f0\u01f1\u0003^-\u0000\u01f1\u01f2\u0005:\u0000"+ + "\u0000\u01f2\u01f3\u0003`.\u0000\u01f3g\u0001\u0000\u0000\u0000\u01f4"+ + "\u01f7\u0005Z\u0000\u0000\u01f5\u01f7\u0003f1\u0000\u01f6\u01f4\u0001"+ + "\u0000\u0000\u0000\u01f6\u01f5\u0001\u0000\u0000\u0000\u01f7i\u0001\u0000"+ + "\u0000\u0000\u01f8\u01f9\u0003^-\u0000\u01f9\u01fa\u0005:\u0000\u0000"+ + "\u01fa\u01fb\u0003`.\u0000\u01fb\u01fc\u0005:\u0000\u0000\u01fc\u01fe"+ + "\u0003b/\u0000\u01fd\u01ff\u0003d0\u0000\u01fe\u01fd\u0001\u0000\u0000"+ + "\u0000\u01fe\u01ff\u0001\u0000\u0000\u0000\u01ffk\u0001\u0000\u0000\u0000"+ + "\u0200\u0201\u0003V)\u0000\u0201\u0202\u0005-\u0000\u0000\u0202\u0203"+ + "\u0003X*\u0000\u0203\u0204\u0005-\u0000\u0000\u0204\u0205\u0003Z+\u0000"+ + "\u0205m\u0001\u0000\u0000\u0000\u0206\u0207\u0003j3\u0000\u0207\u0208"+ + "\u0003h2\u0000\u0208o\u0001\u0000\u0000\u0000\u0209\u020a\u0003l4\u0000"+ + "\u020a\u020b\u0003\\,\u0000\u020b\u020c\u0003n5\u0000\u020c\u020d\u0001"+ + "\u0000\u0000\u0000\u020d\u020e\u00066\u0005\u0000\u020eq\u0001\u0000\u0000"+ + "\u0000\u020f\u0210\u0003l4\u0000\u0210\u0211\u0003\\,\u0000\u0211\u0212"+ + "\u0003j3\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213\u0214\u00067"+ + "\u0005\u0000\u0214s\u0001\u0000\u0000\u0000\u0215\u0216\u0003l4\u0000"+ + "\u0216\u0217\u0001\u0000\u0000\u0000\u0217\u0218\u00068\u0005\u0000\u0218"+ + "u\u0001\u0000\u0000\u0000\u0219\u021a\u0003j3\u0000\u021a\u021b\u0001"+ + "\u0000\u0000\u0000\u021b\u021c\u00069\u0005\u0000\u021cw\u0001\u0000\u0000"+ + "\u0000\u021d\u021e\u0003\u0004\u0000\u0000\u021e\u021f\u0001\u0000\u0000"+ + "\u0000\u021f\u0220\u0006:\u0000\u0000\u0220y\u0001\u0000\u0000\u0000\u0221"+ + "\u0222\u0003\u0014\b\u0000\u0222\u0223\u0001\u0000\u0000\u0000\u0223\u0224"+ + "\u0006;\b\u0000\u0224{\u0001\u0000\u0000\u0000\u0225\u0226\u0003\u0016"+ + "\t\u0000\u0226\u0227\u0001\u0000\u0000\u0000\u0227\u0228\u0006<\t\u0000"+ + "\u0228}\u0001\u0000\u0000\u0000\u0229\u022a\u0005}\u0000\u0000\u022a\u022b"+ + "\u0001\u0000\u0000\u0000\u022b\u022c\u0006=\u0005\u0000\u022c\u007f\u0001"+ + "\u0000\u0000\u0000\u022d\u022e\u0003\"\u000f\u0000\u022e\u022f\u0001\u0000"+ + "\u0000\u0000\u022f\u0230\u0006>\u0006\u0000\u0230\u0081\u0001\u0000\u0000"+ + "\u0000\u0231\u0232\u0003$\u0010\u0000\u0232\u0233\u0001\u0000\u0000\u0000"+ + "\u0233\u0234\u0006?\u0007\u0000\u0234\u0083\u0001\u0000\u0000\u0000\u0235"+ + "\u0236\u0003&\u0011\u0000\u0236\u0237\u0001\u0000\u0000\u0000\u0237\u0238"+ + "\u0006@\n\u0000\u0238\u0085\u0001\u0000\u0000\u0000\u0239\u023a\u0003"+ + "\u0012\u0007\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0006"+ + "A\u000b\u0000\u023c\u023d\u0006A\u0001\u0000\u023d\u0087\u0001\u0000\u0000"+ + "\u0000\u023e\u023f\u0003\u0004\u0000\u0000\u023f\u0240\u0001\u0000\u0000"+ + "\u0000\u0240\u0241\u0006B\u0000\u0000\u0241\u0089\u0001\u0000\u0000\u0000"+ + "\u0242\u0243\u0003\u0006\u0001\u0000\u0243\u0244\u0001\u0000\u0000\u0000"+ + "\u0244\u0245\u0006C\f\u0000\u0245\u008b\u0001\u0000\u0000\u0000\u0246"+ + "\u0247\u0003\b\u0002\u0000\u0247\u0248\u0001\u0000\u0000\u0000\u0248\u0249"+ + "\u0006D\r\u0000\u0249\u008d\u0001\u0000\u0000\u0000\u024a\u024b\u0003"+ + "\u0016\t\u0000\u024b\u024c\u0001\u0000\u0000\u0000\u024c\u024d\u0006E"+ + "\t\u0000\u024d\u008f\u0001\u0000\u0000\u0000\u024e\u024f\u0003*\u0013"+ + "\u0000\u024f\u0250\u0001\u0000\u0000\u0000\u0250\u0251\u0006F\u000e\u0000"+ + "\u0251\u0252\u0006F\u000f\u0000\u0252\u0091\u0001\u0000\u0000\u0000\u0253"+ + "\u0254\u0003\n\u0003\u0000\u0254\u0255\u0001\u0000\u0000\u0000\u0255\u0256"+ + "\u0006G\u0003\u0000\u0256\u0257\u0006G\u0010\u0000\u0257\u0093\u0001\u0000"+ + "\u0000\u0000\u0258\u0259\u0003\u000e\u0005\u0000\u0259\u025a\u0001\u0000"+ + "\u0000\u0000\u025a\u025b\u0006H\u0011\u0000\u025b\u025c\u0006H\u0005\u0000"+ + "\u025c\u0095\u0001\u0000\u0000\u0000\u025d\u025e\u0003.\u0015\u0000\u025e"+ + "\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0006I\u0012\u0000\u0260\u0097"+ + "\u0001\u0000\u0000\u0000\u0261\u0262\u0003\"\u000f\u0000\u0262\u0263\u0001"+ + "\u0000\u0000\u0000\u0263\u0264\u0006J\u0006\u0000\u0264\u0099\u0001\u0000"+ + "\u0000\u0000\u0265\u0266\u00034\u0018\u0000\u0266\u0267\u0001\u0000\u0000"+ + "\u0000\u0267\u0268\u0006K\u0013\u0000\u0268\u009b\u0001\u0000\u0000\u0000"+ + "\u0269\u026a\u0003$\u0010\u0000\u026a\u026b\u0001\u0000\u0000\u0000\u026b"+ + "\u026c\u0006L\u0007\u0000\u026c\u009d\u0001\u0000\u0000\u0000\u026d\u026e"+ + "\u00038\u001a\u0000\u026e\u026f\u0001\u0000\u0000\u0000\u026f\u0270\u0006"+ + "M\u0014\u0000\u0270\u009f\u0001\u0000\u0000\u0000\u0271\u0272\u0003@\u001e"+ + "\u0000\u0272\u0273\u0001\u0000\u0000\u0000\u0273\u0274\u0006N\u0015\u0000"+ + "\u0274\u00a1\u0001\u0000\u0000\u0000\u0275\u0276\u0003B\u001f\u0000\u0276"+ + "\u0277\u0001\u0000\u0000\u0000\u0277\u0278\u0006O\u0016\u0000\u0278\u00a3"+ + "\u0001\u0000\u0000\u0000\u0279\u027a\u0003D \u0000\u027a\u027b\u0001\u0000"+ + "\u0000\u0000\u027b\u027c\u0006P\u0017\u0000\u027c\u00a5\u0001\u0000\u0000"+ + "\u0000\u027d\u027e\u0003N%\u0000\u027e\u027f\u0001\u0000\u0000\u0000\u027f"+ + "\u0280\u0006Q\u0018\u0000\u0280\u00a7\u0001\u0000\u0000\u0000\u0281\u0282"+ + "\u0003P&\u0000\u0282\u0283\u0001\u0000\u0000\u0000\u0283\u0284\u0006R"+ + "\u0019\u0000\u0284\u00a9\u0001\u0000\u0000\u0000\u0285\u0286\u0003R\'"+ + "\u0000\u0286\u0287\u0001\u0000\u0000\u0000\u0287\u0288\u0006S\u001a\u0000"+ + "\u0288\u00ab\u0001\u0000\u0000\u0000\u0289\u028a\u0003T(\u0000\u028a\u028b"+ + "\u0001\u0000\u0000\u0000\u028b\u028c\u0006T\u001b\u0000\u028c\u00ad\u0001"+ + "\u0000\u0000\u0000\u028d\u028e\u0003p6\u0000\u028e\u028f\u0001\u0000\u0000"+ + "\u0000\u028f\u0290\u0006U\u001c\u0000\u0290\u00af\u0001\u0000\u0000\u0000"+ + "\u0291\u0292\u0003r7\u0000\u0292\u0293\u0001\u0000\u0000\u0000\u0293\u0294"+ + "\u0006V\u001d\u0000\u0294\u00b1\u0001\u0000\u0000\u0000\u0295\u0296\u0003"+ + "t8\u0000\u0296\u0297\u0001\u0000\u0000\u0000\u0297\u0298\u0006W\u001e"+ + "\u0000\u0298\u00b3\u0001\u0000\u0000\u0000\u0299\u029a\u0003v9\u0000\u029a"+ + "\u029b\u0001\u0000\u0000\u0000\u029b\u029c\u0006X\u001f\u0000\u029c\u00b5"+ + "\u0001\u0000\u0000\u0000)\u0000\u0001\u0002\u0003\u00b9\u00be\u00c3\u00c9"+ + "\u00e8\u00fd\u00ff\u0108\u0110\u0112\u012a\u0130\u0134\u0141\u0143\u0158"+ + "\u0163\u016b\u016d\u0177\u0179\u017e\u0187\u0191\u019a\u01a1\u01a3\u01a5"+ + "\u01b0\u01b2\u01be\u01c0\u01cc\u01ce\u01ed\u01f6\u01fe \u0006\u0000\u0000"+ + "\u0005\u0001\u0000\u0002\u0002\u0000\u0007\u0004\u0000\u0002\u0003\u0000"+ + "\u0004\u0000\u0000\u0007\u000b\u0000\u0007\f\u0000\u0007\t\u0000\u0007"+ + "\n\u0000\u0007\r\u0000\u0007\b\u0000\u0007\u0002\u0000\u0007\u0003\u0000"+ + "\u0007\u000f\u0000\u0005\u0002\u0000\u0005\u0003\u0000\u0007\u0006\u0000"+ + "\u0007\u0010\u0000\u0007\u0011\u0000\u0007\u0012\u0000\u0007\u0013\u0000"+ + "\u0007\u0014\u0000\u0007\u0015\u0000\u0007\u0016\u0000\u0007\u0017\u0000"+ + "\u0007\u0018\u0000\u0007\u0019\u0000\u0007\u001a\u0000\u0007\u001b\u0000"+ + "\u0007\u001c\u0000\u0007\u001d\u0000"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.tokens b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.tokens new file mode 100644 index 00000000000..080a49ecbae --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlLexer.tokens @@ -0,0 +1,41 @@ +WS=1 +NL=2 +COMMENT=3 +L_BRACKET=4 +DOUBLE_L_BRACKET=5 +R_BRACKET=6 +DOUBLE_R_BRACKET=7 +EQUALS=8 +DOT=9 +COMMA=10 +BASIC_STRING=11 +LITERAL_STRING=12 +UNQUOTED_KEY=13 +VALUE_WS=14 +L_BRACE=15 +BOOLEAN=16 +ML_BASIC_STRING=17 +ML_LITERAL_STRING=18 +FLOAT=19 +INF=20 +NAN=21 +DEC_INT=22 +HEX_INT=23 +OCT_INT=24 +BIN_INT=25 +OFFSET_DATE_TIME=26 +LOCAL_DATE_TIME=27 +LOCAL_DATE=28 +LOCAL_TIME=29 +INLINE_TABLE_WS=30 +R_BRACE=31 +ARRAY_WS=32 +'['=4 +'[['=5 +']'=6 +']]'=7 +'='=8 +'.'=9 +','=10 +'{'=15 +'}'=31 diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.interp b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.interp new file mode 100644 index 00000000000..314ece3ce49 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.interp @@ -0,0 +1,96 @@ +token literal names: +null +null +null +null +'[' +'[[' +']' +']]' +'=' +'.' +',' +null +null +null +null +'{' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +'}' +null + +token symbolic names: +null +WS +NL +COMMENT +L_BRACKET +DOUBLE_L_BRACKET +R_BRACKET +DOUBLE_R_BRACKET +EQUALS +DOT +COMMA +BASIC_STRING +LITERAL_STRING +UNQUOTED_KEY +VALUE_WS +L_BRACE +BOOLEAN +ML_BASIC_STRING +ML_LITERAL_STRING +FLOAT +INF +NAN +DEC_INT +HEX_INT +OCT_INT +BIN_INT +OFFSET_DATE_TIME +LOCAL_DATE_TIME +LOCAL_DATE +LOCAL_TIME +INLINE_TABLE_WS +R_BRACE +ARRAY_WS + +rule names: +document +expression +comment +keyValue +key +simpleKey +unquotedKey +quotedKey +dottedKey +value +string +integer +floatingPoint +bool +dateTime +commentOrNl +array +table +standardTable +inlineTable +arrayTable + + +atn: +[4, 1, 32, 235, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 1, 0, 3, 0, 44, 8, 0, 1, 0, 1, 0, 3, 0, 48, 8, 0, 5, 0, 50, 8, 0, 10, 0, 12, 0, 53, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 59, 8, 1, 1, 1, 1, 1, 3, 1, 63, 8, 1, 1, 1, 3, 1, 66, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 76, 8, 4, 1, 5, 1, 5, 3, 5, 80, 8, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 4, 8, 89, 8, 8, 11, 8, 12, 8, 90, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 100, 8, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 3, 15, 113, 8, 15, 1, 15, 1, 15, 1, 16, 1, 16, 5, 16, 119, 8, 16, 10, 16, 12, 16, 122, 9, 16, 1, 16, 1, 16, 1, 16, 5, 16, 127, 8, 16, 10, 16, 12, 16, 130, 9, 16, 1, 16, 1, 16, 1, 16, 5, 16, 135, 8, 16, 10, 16, 12, 16, 138, 9, 16, 1, 16, 1, 16, 3, 16, 142, 8, 16, 5, 16, 144, 8, 16, 10, 16, 12, 16, 147, 9, 16, 1, 16, 5, 16, 150, 8, 16, 10, 16, 12, 16, 153, 9, 16, 1, 16, 1, 16, 3, 16, 157, 8, 16, 1, 17, 1, 17, 3, 17, 161, 8, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 167, 8, 18, 10, 18, 12, 18, 170, 9, 18, 1, 18, 5, 18, 173, 8, 18, 10, 18, 12, 18, 176, 9, 18, 1, 19, 1, 19, 5, 19, 180, 8, 19, 10, 19, 12, 19, 183, 9, 19, 1, 19, 1, 19, 1, 19, 5, 19, 188, 8, 19, 10, 19, 12, 19, 191, 9, 19, 1, 19, 1, 19, 1, 19, 5, 19, 196, 8, 19, 10, 19, 12, 19, 199, 9, 19, 1, 19, 1, 19, 3, 19, 203, 8, 19, 5, 19, 205, 8, 19, 10, 19, 12, 19, 208, 9, 19, 1, 19, 5, 19, 211, 8, 19, 10, 19, 12, 19, 214, 9, 19, 1, 19, 1, 19, 3, 19, 218, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 224, 8, 20, 10, 20, 12, 20, 227, 9, 20, 1, 20, 5, 20, 230, 8, 20, 10, 20, 12, 20, 233, 9, 20, 1, 20, 0, 0, 21, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 0, 5, 1, 0, 11, 12, 2, 0, 11, 12, 17, 18, 1, 0, 22, 25, 1, 0, 19, 21, 1, 0, 26, 29, 249, 0, 43, 1, 0, 0, 0, 2, 65, 1, 0, 0, 0, 4, 67, 1, 0, 0, 0, 6, 69, 1, 0, 0, 0, 8, 75, 1, 0, 0, 0, 10, 79, 1, 0, 0, 0, 12, 81, 1, 0, 0, 0, 14, 83, 1, 0, 0, 0, 16, 85, 1, 0, 0, 0, 18, 99, 1, 0, 0, 0, 20, 101, 1, 0, 0, 0, 22, 103, 1, 0, 0, 0, 24, 105, 1, 0, 0, 0, 26, 107, 1, 0, 0, 0, 28, 109, 1, 0, 0, 0, 30, 112, 1, 0, 0, 0, 32, 156, 1, 0, 0, 0, 34, 160, 1, 0, 0, 0, 36, 162, 1, 0, 0, 0, 38, 217, 1, 0, 0, 0, 40, 219, 1, 0, 0, 0, 42, 44, 3, 2, 1, 0, 43, 42, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 51, 1, 0, 0, 0, 45, 47, 5, 2, 0, 0, 46, 48, 3, 2, 1, 0, 47, 46, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 50, 1, 0, 0, 0, 49, 45, 1, 0, 0, 0, 50, 53, 1, 0, 0, 0, 51, 49, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 54, 1, 0, 0, 0, 53, 51, 1, 0, 0, 0, 54, 55, 5, 0, 0, 1, 55, 1, 1, 0, 0, 0, 56, 58, 3, 6, 3, 0, 57, 59, 3, 4, 2, 0, 58, 57, 1, 0, 0, 0, 58, 59, 1, 0, 0, 0, 59, 66, 1, 0, 0, 0, 60, 62, 3, 34, 17, 0, 61, 63, 3, 4, 2, 0, 62, 61, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 66, 1, 0, 0, 0, 64, 66, 3, 4, 2, 0, 65, 56, 1, 0, 0, 0, 65, 60, 1, 0, 0, 0, 65, 64, 1, 0, 0, 0, 66, 3, 1, 0, 0, 0, 67, 68, 5, 3, 0, 0, 68, 5, 1, 0, 0, 0, 69, 70, 3, 8, 4, 0, 70, 71, 5, 8, 0, 0, 71, 72, 3, 18, 9, 0, 72, 7, 1, 0, 0, 0, 73, 76, 3, 10, 5, 0, 74, 76, 3, 16, 8, 0, 75, 73, 1, 0, 0, 0, 75, 74, 1, 0, 0, 0, 76, 9, 1, 0, 0, 0, 77, 80, 3, 14, 7, 0, 78, 80, 3, 12, 6, 0, 79, 77, 1, 0, 0, 0, 79, 78, 1, 0, 0, 0, 80, 11, 1, 0, 0, 0, 81, 82, 5, 13, 0, 0, 82, 13, 1, 0, 0, 0, 83, 84, 7, 0, 0, 0, 84, 15, 1, 0, 0, 0, 85, 88, 3, 10, 5, 0, 86, 87, 5, 9, 0, 0, 87, 89, 3, 10, 5, 0, 88, 86, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 88, 1, 0, 0, 0, 90, 91, 1, 0, 0, 0, 91, 17, 1, 0, 0, 0, 92, 100, 3, 20, 10, 0, 93, 100, 3, 22, 11, 0, 94, 100, 3, 24, 12, 0, 95, 100, 3, 26, 13, 0, 96, 100, 3, 28, 14, 0, 97, 100, 3, 32, 16, 0, 98, 100, 3, 38, 19, 0, 99, 92, 1, 0, 0, 0, 99, 93, 1, 0, 0, 0, 99, 94, 1, 0, 0, 0, 99, 95, 1, 0, 0, 0, 99, 96, 1, 0, 0, 0, 99, 97, 1, 0, 0, 0, 99, 98, 1, 0, 0, 0, 100, 19, 1, 0, 0, 0, 101, 102, 7, 1, 0, 0, 102, 21, 1, 0, 0, 0, 103, 104, 7, 2, 0, 0, 104, 23, 1, 0, 0, 0, 105, 106, 7, 3, 0, 0, 106, 25, 1, 0, 0, 0, 107, 108, 5, 16, 0, 0, 108, 27, 1, 0, 0, 0, 109, 110, 7, 4, 0, 0, 110, 29, 1, 0, 0, 0, 111, 113, 5, 3, 0, 0, 112, 111, 1, 0, 0, 0, 112, 113, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 115, 5, 2, 0, 0, 115, 31, 1, 0, 0, 0, 116, 120, 5, 4, 0, 0, 117, 119, 3, 30, 15, 0, 118, 117, 1, 0, 0, 0, 119, 122, 1, 0, 0, 0, 120, 118, 1, 0, 0, 0, 120, 121, 1, 0, 0, 0, 121, 123, 1, 0, 0, 0, 122, 120, 1, 0, 0, 0, 123, 157, 5, 6, 0, 0, 124, 128, 5, 4, 0, 0, 125, 127, 3, 30, 15, 0, 126, 125, 1, 0, 0, 0, 127, 130, 1, 0, 0, 0, 128, 126, 1, 0, 0, 0, 128, 129, 1, 0, 0, 0, 129, 131, 1, 0, 0, 0, 130, 128, 1, 0, 0, 0, 131, 145, 3, 18, 9, 0, 132, 136, 5, 10, 0, 0, 133, 135, 3, 30, 15, 0, 134, 133, 1, 0, 0, 0, 135, 138, 1, 0, 0, 0, 136, 134, 1, 0, 0, 0, 136, 137, 1, 0, 0, 0, 137, 139, 1, 0, 0, 0, 138, 136, 1, 0, 0, 0, 139, 141, 3, 18, 9, 0, 140, 142, 5, 10, 0, 0, 141, 140, 1, 0, 0, 0, 141, 142, 1, 0, 0, 0, 142, 144, 1, 0, 0, 0, 143, 132, 1, 0, 0, 0, 144, 147, 1, 0, 0, 0, 145, 143, 1, 0, 0, 0, 145, 146, 1, 0, 0, 0, 146, 151, 1, 0, 0, 0, 147, 145, 1, 0, 0, 0, 148, 150, 3, 30, 15, 0, 149, 148, 1, 0, 0, 0, 150, 153, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 151, 152, 1, 0, 0, 0, 152, 154, 1, 0, 0, 0, 153, 151, 1, 0, 0, 0, 154, 155, 5, 6, 0, 0, 155, 157, 1, 0, 0, 0, 156, 116, 1, 0, 0, 0, 156, 124, 1, 0, 0, 0, 157, 33, 1, 0, 0, 0, 158, 161, 3, 36, 18, 0, 159, 161, 3, 40, 20, 0, 160, 158, 1, 0, 0, 0, 160, 159, 1, 0, 0, 0, 161, 35, 1, 0, 0, 0, 162, 163, 5, 4, 0, 0, 163, 164, 3, 8, 4, 0, 164, 174, 5, 6, 0, 0, 165, 167, 3, 30, 15, 0, 166, 165, 1, 0, 0, 0, 167, 170, 1, 0, 0, 0, 168, 166, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169, 171, 1, 0, 0, 0, 170, 168, 1, 0, 0, 0, 171, 173, 3, 6, 3, 0, 172, 168, 1, 0, 0, 0, 173, 176, 1, 0, 0, 0, 174, 172, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 37, 1, 0, 0, 0, 176, 174, 1, 0, 0, 0, 177, 181, 5, 15, 0, 0, 178, 180, 3, 30, 15, 0, 179, 178, 1, 0, 0, 0, 180, 183, 1, 0, 0, 0, 181, 179, 1, 0, 0, 0, 181, 182, 1, 0, 0, 0, 182, 184, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 184, 218, 5, 31, 0, 0, 185, 189, 5, 15, 0, 0, 186, 188, 3, 30, 15, 0, 187, 186, 1, 0, 0, 0, 188, 191, 1, 0, 0, 0, 189, 187, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 192, 1, 0, 0, 0, 191, 189, 1, 0, 0, 0, 192, 206, 3, 6, 3, 0, 193, 197, 5, 10, 0, 0, 194, 196, 3, 30, 15, 0, 195, 194, 1, 0, 0, 0, 196, 199, 1, 0, 0, 0, 197, 195, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 200, 1, 0, 0, 0, 199, 197, 1, 0, 0, 0, 200, 202, 3, 6, 3, 0, 201, 203, 5, 10, 0, 0, 202, 201, 1, 0, 0, 0, 202, 203, 1, 0, 0, 0, 203, 205, 1, 0, 0, 0, 204, 193, 1, 0, 0, 0, 205, 208, 1, 0, 0, 0, 206, 204, 1, 0, 0, 0, 206, 207, 1, 0, 0, 0, 207, 212, 1, 0, 0, 0, 208, 206, 1, 0, 0, 0, 209, 211, 3, 30, 15, 0, 210, 209, 1, 0, 0, 0, 211, 214, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 215, 1, 0, 0, 0, 214, 212, 1, 0, 0, 0, 215, 216, 5, 31, 0, 0, 216, 218, 1, 0, 0, 0, 217, 177, 1, 0, 0, 0, 217, 185, 1, 0, 0, 0, 218, 39, 1, 0, 0, 0, 219, 220, 5, 5, 0, 0, 220, 221, 3, 8, 4, 0, 221, 231, 5, 7, 0, 0, 222, 224, 3, 30, 15, 0, 223, 222, 1, 0, 0, 0, 224, 227, 1, 0, 0, 0, 225, 223, 1, 0, 0, 0, 225, 226, 1, 0, 0, 0, 226, 228, 1, 0, 0, 0, 227, 225, 1, 0, 0, 0, 228, 230, 3, 6, 3, 0, 229, 225, 1, 0, 0, 0, 230, 233, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 231, 232, 1, 0, 0, 0, 232, 41, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, 30, 43, 47, 51, 58, 62, 65, 75, 79, 90, 99, 112, 120, 128, 136, 141, 145, 151, 156, 160, 168, 174, 181, 189, 197, 202, 206, 212, 217, 225, 231] \ No newline at end of file diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.java new file mode 100644 index 00000000000..13f6a167f1b --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.java @@ -0,0 +1,1879 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +// Generated from java-escape by ANTLR 4.11.1 +package org.openrewrite.toml.internal.grammar; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.util.List; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) +public class TomlParser extends Parser { + static { RuntimeMetaData.checkVersion("4.11.1", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + WS=1, NL=2, COMMENT=3, L_BRACKET=4, DOUBLE_L_BRACKET=5, R_BRACKET=6, DOUBLE_R_BRACKET=7, + EQUALS=8, DOT=9, COMMA=10, BASIC_STRING=11, LITERAL_STRING=12, UNQUOTED_KEY=13, + VALUE_WS=14, L_BRACE=15, BOOLEAN=16, ML_BASIC_STRING=17, ML_LITERAL_STRING=18, + FLOAT=19, INF=20, NAN=21, DEC_INT=22, HEX_INT=23, OCT_INT=24, BIN_INT=25, + OFFSET_DATE_TIME=26, LOCAL_DATE_TIME=27, LOCAL_DATE=28, LOCAL_TIME=29, + INLINE_TABLE_WS=30, R_BRACE=31, ARRAY_WS=32; + public static final int + RULE_document = 0, RULE_expression = 1, RULE_comment = 2, RULE_keyValue = 3, + RULE_key = 4, RULE_simpleKey = 5, RULE_unquotedKey = 6, RULE_quotedKey = 7, + RULE_dottedKey = 8, RULE_value = 9, RULE_string = 10, RULE_integer = 11, + RULE_floatingPoint = 12, RULE_bool = 13, RULE_dateTime = 14, RULE_commentOrNl = 15, + RULE_array = 16, RULE_table = 17, RULE_standardTable = 18, RULE_inlineTable = 19, + RULE_arrayTable = 20; + private static String[] makeRuleNames() { + return new String[] { + "document", "expression", "comment", "keyValue", "key", "simpleKey", + "unquotedKey", "quotedKey", "dottedKey", "value", "string", "integer", + "floatingPoint", "bool", "dateTime", "commentOrNl", "array", "table", + "standardTable", "inlineTable", "arrayTable" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, null, null, "'['", "'[['", "']'", "']]'", "'='", "'.'", "','", + null, null, null, null, "'{'", null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, "'}'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "WS", "NL", "COMMENT", "L_BRACKET", "DOUBLE_L_BRACKET", "R_BRACKET", + "DOUBLE_R_BRACKET", "EQUALS", "DOT", "COMMA", "BASIC_STRING", "LITERAL_STRING", + "UNQUOTED_KEY", "VALUE_WS", "L_BRACE", "BOOLEAN", "ML_BASIC_STRING", + "ML_LITERAL_STRING", "FLOAT", "INF", "NAN", "DEC_INT", "HEX_INT", "OCT_INT", + "BIN_INT", "OFFSET_DATE_TIME", "LOCAL_DATE_TIME", "LOCAL_DATE", "LOCAL_TIME", + "INLINE_TABLE_WS", "R_BRACE", "ARRAY_WS" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + @Override + public String getGrammarFileName() { return "java-escape"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public ATN getATN() { return _ATN; } + + public TomlParser(TokenStream input) { + super(input); + _interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @SuppressWarnings("CheckReturnValue") + public static class DocumentContext extends ParserRuleContext { + public TerminalNode EOF() { return getToken(TomlParser.EOF, 0); } + public List expression() { + return getRuleContexts(ExpressionContext.class); + } + public ExpressionContext expression(int i) { + return getRuleContext(ExpressionContext.class,i); + } + public List NL() { return getTokens(TomlParser.NL); } + public TerminalNode NL(int i) { + return getToken(TomlParser.NL, i); + } + public DocumentContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_document; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterDocument(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitDocument(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitDocument(this); + else return visitor.visitChildren(this); + } + } + + public final DocumentContext document() throws RecognitionException { + DocumentContext _localctx = new DocumentContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_document); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(43); + _errHandler.sync(this); + _la = _input.LA(1); + if (((_la) & ~0x3f) == 0 && ((1L << _la) & 14392L) != 0) { + { + setState(42); + expression(); + } + } + + setState(51); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL) { + { + { + setState(45); + match(NL); + setState(47); + _errHandler.sync(this); + _la = _input.LA(1); + if (((_la) & ~0x3f) == 0 && ((1L << _la) & 14392L) != 0) { + { + setState(46); + expression(); + } + } + + } + } + setState(53); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(54); + match(EOF); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ExpressionContext extends ParserRuleContext { + public KeyValueContext keyValue() { + return getRuleContext(KeyValueContext.class,0); + } + public CommentContext comment() { + return getRuleContext(CommentContext.class,0); + } + public TableContext table() { + return getRuleContext(TableContext.class,0); + } + public ExpressionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_expression; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterExpression(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitExpression(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitExpression(this); + else return visitor.visitChildren(this); + } + } + + public final ExpressionContext expression() throws RecognitionException { + ExpressionContext _localctx = new ExpressionContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_expression); + int _la; + try { + setState(65); + _errHandler.sync(this); + switch (_input.LA(1)) { + case BASIC_STRING: + case LITERAL_STRING: + case UNQUOTED_KEY: + enterOuterAlt(_localctx, 1); + { + setState(56); + keyValue(); + setState(58); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==COMMENT) { + { + setState(57); + comment(); + } + } + + } + break; + case L_BRACKET: + case DOUBLE_L_BRACKET: + enterOuterAlt(_localctx, 2); + { + setState(60); + table(); + setState(62); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==COMMENT) { + { + setState(61); + comment(); + } + } + + } + break; + case COMMENT: + enterOuterAlt(_localctx, 3); + { + setState(64); + comment(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class CommentContext extends ParserRuleContext { + public TerminalNode COMMENT() { return getToken(TomlParser.COMMENT, 0); } + public CommentContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_comment; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterComment(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitComment(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitComment(this); + else return visitor.visitChildren(this); + } + } + + public final CommentContext comment() throws RecognitionException { + CommentContext _localctx = new CommentContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_comment); + try { + enterOuterAlt(_localctx, 1); + { + setState(67); + match(COMMENT); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class KeyValueContext extends ParserRuleContext { + public KeyContext key() { + return getRuleContext(KeyContext.class,0); + } + public TerminalNode EQUALS() { return getToken(TomlParser.EQUALS, 0); } + public ValueContext value() { + return getRuleContext(ValueContext.class,0); + } + public KeyValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_keyValue; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterKeyValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitKeyValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitKeyValue(this); + else return visitor.visitChildren(this); + } + } + + public final KeyValueContext keyValue() throws RecognitionException { + KeyValueContext _localctx = new KeyValueContext(_ctx, getState()); + enterRule(_localctx, 6, RULE_keyValue); + try { + enterOuterAlt(_localctx, 1); + { + setState(69); + key(); + setState(70); + match(EQUALS); + setState(71); + value(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class KeyContext extends ParserRuleContext { + public SimpleKeyContext simpleKey() { + return getRuleContext(SimpleKeyContext.class,0); + } + public DottedKeyContext dottedKey() { + return getRuleContext(DottedKeyContext.class,0); + } + public KeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_key; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitKey(this); + else return visitor.visitChildren(this); + } + } + + public final KeyContext key() throws RecognitionException { + KeyContext _localctx = new KeyContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_key); + try { + setState(75); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(73); + simpleKey(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(74); + dottedKey(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class SimpleKeyContext extends ParserRuleContext { + public QuotedKeyContext quotedKey() { + return getRuleContext(QuotedKeyContext.class,0); + } + public UnquotedKeyContext unquotedKey() { + return getRuleContext(UnquotedKeyContext.class,0); + } + public SimpleKeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_simpleKey; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterSimpleKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitSimpleKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitSimpleKey(this); + else return visitor.visitChildren(this); + } + } + + public final SimpleKeyContext simpleKey() throws RecognitionException { + SimpleKeyContext _localctx = new SimpleKeyContext(_ctx, getState()); + enterRule(_localctx, 10, RULE_simpleKey); + try { + setState(79); + _errHandler.sync(this); + switch (_input.LA(1)) { + case BASIC_STRING: + case LITERAL_STRING: + enterOuterAlt(_localctx, 1); + { + setState(77); + quotedKey(); + } + break; + case UNQUOTED_KEY: + enterOuterAlt(_localctx, 2); + { + setState(78); + unquotedKey(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class UnquotedKeyContext extends ParserRuleContext { + public TerminalNode UNQUOTED_KEY() { return getToken(TomlParser.UNQUOTED_KEY, 0); } + public UnquotedKeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_unquotedKey; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterUnquotedKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitUnquotedKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitUnquotedKey(this); + else return visitor.visitChildren(this); + } + } + + public final UnquotedKeyContext unquotedKey() throws RecognitionException { + UnquotedKeyContext _localctx = new UnquotedKeyContext(_ctx, getState()); + enterRule(_localctx, 12, RULE_unquotedKey); + try { + enterOuterAlt(_localctx, 1); + { + setState(81); + match(UNQUOTED_KEY); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class QuotedKeyContext extends ParserRuleContext { + public TerminalNode BASIC_STRING() { return getToken(TomlParser.BASIC_STRING, 0); } + public TerminalNode LITERAL_STRING() { return getToken(TomlParser.LITERAL_STRING, 0); } + public QuotedKeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_quotedKey; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterQuotedKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitQuotedKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitQuotedKey(this); + else return visitor.visitChildren(this); + } + } + + public final QuotedKeyContext quotedKey() throws RecognitionException { + QuotedKeyContext _localctx = new QuotedKeyContext(_ctx, getState()); + enterRule(_localctx, 14, RULE_quotedKey); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(83); + _la = _input.LA(1); + if ( !(_la==BASIC_STRING || _la==LITERAL_STRING) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class DottedKeyContext extends ParserRuleContext { + public List simpleKey() { + return getRuleContexts(SimpleKeyContext.class); + } + public SimpleKeyContext simpleKey(int i) { + return getRuleContext(SimpleKeyContext.class,i); + } + public List DOT() { return getTokens(TomlParser.DOT); } + public TerminalNode DOT(int i) { + return getToken(TomlParser.DOT, i); + } + public DottedKeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_dottedKey; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterDottedKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitDottedKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitDottedKey(this); + else return visitor.visitChildren(this); + } + } + + public final DottedKeyContext dottedKey() throws RecognitionException { + DottedKeyContext _localctx = new DottedKeyContext(_ctx, getState()); + enterRule(_localctx, 16, RULE_dottedKey); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(85); + simpleKey(); + setState(88); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(86); + match(DOT); + setState(87); + simpleKey(); + } + } + setState(90); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( _la==DOT ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ValueContext extends ParserRuleContext { + public StringContext string() { + return getRuleContext(StringContext.class,0); + } + public IntegerContext integer() { + return getRuleContext(IntegerContext.class,0); + } + public FloatingPointContext floatingPoint() { + return getRuleContext(FloatingPointContext.class,0); + } + public BoolContext bool() { + return getRuleContext(BoolContext.class,0); + } + public DateTimeContext dateTime() { + return getRuleContext(DateTimeContext.class,0); + } + public ArrayContext array() { + return getRuleContext(ArrayContext.class,0); + } + public InlineTableContext inlineTable() { + return getRuleContext(InlineTableContext.class,0); + } + public ValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_value; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitValue(this); + else return visitor.visitChildren(this); + } + } + + public final ValueContext value() throws RecognitionException { + ValueContext _localctx = new ValueContext(_ctx, getState()); + enterRule(_localctx, 18, RULE_value); + try { + setState(99); + _errHandler.sync(this); + switch (_input.LA(1)) { + case BASIC_STRING: + case LITERAL_STRING: + case ML_BASIC_STRING: + case ML_LITERAL_STRING: + enterOuterAlt(_localctx, 1); + { + setState(92); + string(); + } + break; + case DEC_INT: + case HEX_INT: + case OCT_INT: + case BIN_INT: + enterOuterAlt(_localctx, 2); + { + setState(93); + integer(); + } + break; + case FLOAT: + case INF: + case NAN: + enterOuterAlt(_localctx, 3); + { + setState(94); + floatingPoint(); + } + break; + case BOOLEAN: + enterOuterAlt(_localctx, 4); + { + setState(95); + bool(); + } + break; + case OFFSET_DATE_TIME: + case LOCAL_DATE_TIME: + case LOCAL_DATE: + case LOCAL_TIME: + enterOuterAlt(_localctx, 5); + { + setState(96); + dateTime(); + } + break; + case L_BRACKET: + enterOuterAlt(_localctx, 6); + { + setState(97); + array(); + } + break; + case L_BRACE: + enterOuterAlt(_localctx, 7); + { + setState(98); + inlineTable(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StringContext extends ParserRuleContext { + public TerminalNode BASIC_STRING() { return getToken(TomlParser.BASIC_STRING, 0); } + public TerminalNode ML_BASIC_STRING() { return getToken(TomlParser.ML_BASIC_STRING, 0); } + public TerminalNode LITERAL_STRING() { return getToken(TomlParser.LITERAL_STRING, 0); } + public TerminalNode ML_LITERAL_STRING() { return getToken(TomlParser.ML_LITERAL_STRING, 0); } + public StringContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_string; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterString(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitString(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitString(this); + else return visitor.visitChildren(this); + } + } + + public final StringContext string() throws RecognitionException { + StringContext _localctx = new StringContext(_ctx, getState()); + enterRule(_localctx, 20, RULE_string); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(101); + _la = _input.LA(1); + if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 399360L) != 0) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class IntegerContext extends ParserRuleContext { + public TerminalNode DEC_INT() { return getToken(TomlParser.DEC_INT, 0); } + public TerminalNode HEX_INT() { return getToken(TomlParser.HEX_INT, 0); } + public TerminalNode OCT_INT() { return getToken(TomlParser.OCT_INT, 0); } + public TerminalNode BIN_INT() { return getToken(TomlParser.BIN_INT, 0); } + public IntegerContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_integer; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterInteger(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitInteger(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitInteger(this); + else return visitor.visitChildren(this); + } + } + + public final IntegerContext integer() throws RecognitionException { + IntegerContext _localctx = new IntegerContext(_ctx, getState()); + enterRule(_localctx, 22, RULE_integer); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(103); + _la = _input.LA(1); + if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 62914560L) != 0) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FloatingPointContext extends ParserRuleContext { + public TerminalNode FLOAT() { return getToken(TomlParser.FLOAT, 0); } + public TerminalNode INF() { return getToken(TomlParser.INF, 0); } + public TerminalNode NAN() { return getToken(TomlParser.NAN, 0); } + public FloatingPointContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_floatingPoint; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterFloatingPoint(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitFloatingPoint(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitFloatingPoint(this); + else return visitor.visitChildren(this); + } + } + + public final FloatingPointContext floatingPoint() throws RecognitionException { + FloatingPointContext _localctx = new FloatingPointContext(_ctx, getState()); + enterRule(_localctx, 24, RULE_floatingPoint); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(105); + _la = _input.LA(1); + if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 3670016L) != 0) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class BoolContext extends ParserRuleContext { + public TerminalNode BOOLEAN() { return getToken(TomlParser.BOOLEAN, 0); } + public BoolContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_bool; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterBool(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitBool(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitBool(this); + else return visitor.visitChildren(this); + } + } + + public final BoolContext bool() throws RecognitionException { + BoolContext _localctx = new BoolContext(_ctx, getState()); + enterRule(_localctx, 26, RULE_bool); + try { + enterOuterAlt(_localctx, 1); + { + setState(107); + match(BOOLEAN); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class DateTimeContext extends ParserRuleContext { + public TerminalNode OFFSET_DATE_TIME() { return getToken(TomlParser.OFFSET_DATE_TIME, 0); } + public TerminalNode LOCAL_DATE_TIME() { return getToken(TomlParser.LOCAL_DATE_TIME, 0); } + public TerminalNode LOCAL_DATE() { return getToken(TomlParser.LOCAL_DATE, 0); } + public TerminalNode LOCAL_TIME() { return getToken(TomlParser.LOCAL_TIME, 0); } + public DateTimeContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_dateTime; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterDateTime(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitDateTime(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitDateTime(this); + else return visitor.visitChildren(this); + } + } + + public final DateTimeContext dateTime() throws RecognitionException { + DateTimeContext _localctx = new DateTimeContext(_ctx, getState()); + enterRule(_localctx, 28, RULE_dateTime); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(109); + _la = _input.LA(1); + if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 1006632960L) != 0) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class CommentOrNlContext extends ParserRuleContext { + public TerminalNode NL() { return getToken(TomlParser.NL, 0); } + public TerminalNode COMMENT() { return getToken(TomlParser.COMMENT, 0); } + public CommentOrNlContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_commentOrNl; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterCommentOrNl(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitCommentOrNl(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitCommentOrNl(this); + else return visitor.visitChildren(this); + } + } + + public final CommentOrNlContext commentOrNl() throws RecognitionException { + CommentOrNlContext _localctx = new CommentOrNlContext(_ctx, getState()); + enterRule(_localctx, 30, RULE_commentOrNl); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(112); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==COMMENT) { + { + setState(111); + match(COMMENT); + } + } + + setState(114); + match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ArrayContext extends ParserRuleContext { + public TerminalNode L_BRACKET() { return getToken(TomlParser.L_BRACKET, 0); } + public TerminalNode R_BRACKET() { return getToken(TomlParser.R_BRACKET, 0); } + public List commentOrNl() { + return getRuleContexts(CommentOrNlContext.class); + } + public CommentOrNlContext commentOrNl(int i) { + return getRuleContext(CommentOrNlContext.class,i); + } + public List value() { + return getRuleContexts(ValueContext.class); + } + public ValueContext value(int i) { + return getRuleContext(ValueContext.class,i); + } + public List COMMA() { return getTokens(TomlParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(TomlParser.COMMA, i); + } + public ArrayContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_array; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterArray(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitArray(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitArray(this); + else return visitor.visitChildren(this); + } + } + + public final ArrayContext array() throws RecognitionException { + ArrayContext _localctx = new ArrayContext(_ctx, getState()); + enterRule(_localctx, 32, RULE_array); + int _la; + try { + setState(156); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,17,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(116); + match(L_BRACKET); + setState(120); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(117); + commentOrNl(); + } + } + setState(122); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(123); + match(R_BRACKET); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(124); + match(L_BRACKET); + setState(128); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(125); + commentOrNl(); + } + } + setState(130); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(131); + value(); + setState(145); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==COMMA) { + { + { + setState(132); + match(COMMA); + setState(136); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(133); + commentOrNl(); + } + } + setState(138); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(139); + value(); + setState(141); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) { + case 1: + { + setState(140); + match(COMMA); + } + break; + } + } + } + setState(147); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(151); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(148); + commentOrNl(); + } + } + setState(153); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(154); + match(R_BRACKET); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class TableContext extends ParserRuleContext { + public StandardTableContext standardTable() { + return getRuleContext(StandardTableContext.class,0); + } + public ArrayTableContext arrayTable() { + return getRuleContext(ArrayTableContext.class,0); + } + public TableContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_table; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterTable(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitTable(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitTable(this); + else return visitor.visitChildren(this); + } + } + + public final TableContext table() throws RecognitionException { + TableContext _localctx = new TableContext(_ctx, getState()); + enterRule(_localctx, 34, RULE_table); + try { + setState(160); + _errHandler.sync(this); + switch (_input.LA(1)) { + case L_BRACKET: + enterOuterAlt(_localctx, 1); + { + setState(158); + standardTable(); + } + break; + case DOUBLE_L_BRACKET: + enterOuterAlt(_localctx, 2); + { + setState(159); + arrayTable(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StandardTableContext extends ParserRuleContext { + public TerminalNode L_BRACKET() { return getToken(TomlParser.L_BRACKET, 0); } + public KeyContext key() { + return getRuleContext(KeyContext.class,0); + } + public TerminalNode R_BRACKET() { return getToken(TomlParser.R_BRACKET, 0); } + public List keyValue() { + return getRuleContexts(KeyValueContext.class); + } + public KeyValueContext keyValue(int i) { + return getRuleContext(KeyValueContext.class,i); + } + public List commentOrNl() { + return getRuleContexts(CommentOrNlContext.class); + } + public CommentOrNlContext commentOrNl(int i) { + return getRuleContext(CommentOrNlContext.class,i); + } + public StandardTableContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_standardTable; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterStandardTable(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitStandardTable(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitStandardTable(this); + else return visitor.visitChildren(this); + } + } + + public final StandardTableContext standardTable() throws RecognitionException { + StandardTableContext _localctx = new StandardTableContext(_ctx, getState()); + enterRule(_localctx, 36, RULE_standardTable); + int _la; + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(162); + match(L_BRACKET); + setState(163); + key(); + setState(164); + match(R_BRACKET); + setState(174); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,20,_ctx); + while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { + if ( _alt==1 ) { + { + { + setState(168); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(165); + commentOrNl(); + } + } + setState(170); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(171); + keyValue(); + } + } + } + setState(176); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,20,_ctx); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class InlineTableContext extends ParserRuleContext { + public TerminalNode L_BRACE() { return getToken(TomlParser.L_BRACE, 0); } + public TerminalNode R_BRACE() { return getToken(TomlParser.R_BRACE, 0); } + public List commentOrNl() { + return getRuleContexts(CommentOrNlContext.class); + } + public CommentOrNlContext commentOrNl(int i) { + return getRuleContext(CommentOrNlContext.class,i); + } + public List keyValue() { + return getRuleContexts(KeyValueContext.class); + } + public KeyValueContext keyValue(int i) { + return getRuleContext(KeyValueContext.class,i); + } + public List COMMA() { return getTokens(TomlParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(TomlParser.COMMA, i); + } + public InlineTableContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_inlineTable; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterInlineTable(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitInlineTable(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitInlineTable(this); + else return visitor.visitChildren(this); + } + } + + public final InlineTableContext inlineTable() throws RecognitionException { + InlineTableContext _localctx = new InlineTableContext(_ctx, getState()); + enterRule(_localctx, 38, RULE_inlineTable); + int _la; + try { + setState(217); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,27,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(177); + match(L_BRACE); + setState(181); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(178); + commentOrNl(); + } + } + setState(183); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(184); + match(R_BRACE); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(185); + match(L_BRACE); + setState(189); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(186); + commentOrNl(); + } + } + setState(191); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(192); + keyValue(); + setState(206); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==COMMA) { + { + { + setState(193); + match(COMMA); + setState(197); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(194); + commentOrNl(); + } + } + setState(199); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(200); + keyValue(); + setState(202); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,24,_ctx) ) { + case 1: + { + setState(201); + match(COMMA); + } + break; + } + } + } + setState(208); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(212); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(209); + commentOrNl(); + } + } + setState(214); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(215); + match(R_BRACE); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ArrayTableContext extends ParserRuleContext { + public TerminalNode DOUBLE_L_BRACKET() { return getToken(TomlParser.DOUBLE_L_BRACKET, 0); } + public KeyContext key() { + return getRuleContext(KeyContext.class,0); + } + public TerminalNode DOUBLE_R_BRACKET() { return getToken(TomlParser.DOUBLE_R_BRACKET, 0); } + public List keyValue() { + return getRuleContexts(KeyValueContext.class); + } + public KeyValueContext keyValue(int i) { + return getRuleContext(KeyValueContext.class,i); + } + public List commentOrNl() { + return getRuleContexts(CommentOrNlContext.class); + } + public CommentOrNlContext commentOrNl(int i) { + return getRuleContext(CommentOrNlContext.class,i); + } + public ArrayTableContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_arrayTable; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).enterArrayTable(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TomlParserListener ) ((TomlParserListener)listener).exitArrayTable(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof TomlParserVisitor ) return ((TomlParserVisitor)visitor).visitArrayTable(this); + else return visitor.visitChildren(this); + } + } + + public final ArrayTableContext arrayTable() throws RecognitionException { + ArrayTableContext _localctx = new ArrayTableContext(_ctx, getState()); + enterRule(_localctx, 40, RULE_arrayTable); + int _la; + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(219); + match(DOUBLE_L_BRACKET); + setState(220); + key(); + setState(221); + match(DOUBLE_R_BRACKET); + setState(231); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,29,_ctx); + while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { + if ( _alt==1 ) { + { + { + setState(225); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NL || _la==COMMENT) { + { + { + setState(222); + commentOrNl(); + } + } + setState(227); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(228); + keyValue(); + } + } + } + setState(233); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,29,_ctx); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static final String _serializedATN = + "\u0004\u0001 \u00eb\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ + "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ + "\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b\u0002"+ + "\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007\u000f"+ + "\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007\u0012"+ + "\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014\u0001\u0000\u0003\u0000"+ + ",\b\u0000\u0001\u0000\u0001\u0000\u0003\u00000\b\u0000\u0005\u00002\b"+ + "\u0000\n\u0000\f\u00005\t\u0000\u0001\u0000\u0001\u0000\u0001\u0001\u0001"+ + "\u0001\u0003\u0001;\b\u0001\u0001\u0001\u0001\u0001\u0003\u0001?\b\u0001"+ + "\u0001\u0001\u0003\u0001B\b\u0001\u0001\u0002\u0001\u0002\u0001\u0003"+ + "\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0003\u0004"+ + "L\b\u0004\u0001\u0005\u0001\u0005\u0003\u0005P\b\u0005\u0001\u0006\u0001"+ + "\u0006\u0001\u0007\u0001\u0007\u0001\b\u0001\b\u0001\b\u0004\bY\b\b\u000b"+ + "\b\f\bZ\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0003"+ + "\td\b\t\u0001\n\u0001\n\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0001"+ + "\r\u0001\r\u0001\u000e\u0001\u000e\u0001\u000f\u0003\u000fq\b\u000f\u0001"+ + "\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0005\u0010w\b\u0010\n\u0010"+ + "\f\u0010z\t\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0005\u0010\u007f"+ + "\b\u0010\n\u0010\f\u0010\u0082\t\u0010\u0001\u0010\u0001\u0010\u0001\u0010"+ + "\u0005\u0010\u0087\b\u0010\n\u0010\f\u0010\u008a\t\u0010\u0001\u0010\u0001"+ + "\u0010\u0003\u0010\u008e\b\u0010\u0005\u0010\u0090\b\u0010\n\u0010\f\u0010"+ + "\u0093\t\u0010\u0001\u0010\u0005\u0010\u0096\b\u0010\n\u0010\f\u0010\u0099"+ + "\t\u0010\u0001\u0010\u0001\u0010\u0003\u0010\u009d\b\u0010\u0001\u0011"+ + "\u0001\u0011\u0003\u0011\u00a1\b\u0011\u0001\u0012\u0001\u0012\u0001\u0012"+ + "\u0001\u0012\u0005\u0012\u00a7\b\u0012\n\u0012\f\u0012\u00aa\t\u0012\u0001"+ + "\u0012\u0005\u0012\u00ad\b\u0012\n\u0012\f\u0012\u00b0\t\u0012\u0001\u0013"+ + "\u0001\u0013\u0005\u0013\u00b4\b\u0013\n\u0013\f\u0013\u00b7\t\u0013\u0001"+ + "\u0013\u0001\u0013\u0001\u0013\u0005\u0013\u00bc\b\u0013\n\u0013\f\u0013"+ + "\u00bf\t\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0005\u0013\u00c4\b"+ + "\u0013\n\u0013\f\u0013\u00c7\t\u0013\u0001\u0013\u0001\u0013\u0003\u0013"+ + "\u00cb\b\u0013\u0005\u0013\u00cd\b\u0013\n\u0013\f\u0013\u00d0\t\u0013"+ + "\u0001\u0013\u0005\u0013\u00d3\b\u0013\n\u0013\f\u0013\u00d6\t\u0013\u0001"+ + "\u0013\u0001\u0013\u0003\u0013\u00da\b\u0013\u0001\u0014\u0001\u0014\u0001"+ + "\u0014\u0001\u0014\u0005\u0014\u00e0\b\u0014\n\u0014\f\u0014\u00e3\t\u0014"+ + "\u0001\u0014\u0005\u0014\u00e6\b\u0014\n\u0014\f\u0014\u00e9\t\u0014\u0001"+ + "\u0014\u0000\u0000\u0015\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012"+ + "\u0014\u0016\u0018\u001a\u001c\u001e \"$&(\u0000\u0005\u0001\u0000\u000b"+ + "\f\u0002\u0000\u000b\f\u0011\u0012\u0001\u0000\u0016\u0019\u0001\u0000"+ + "\u0013\u0015\u0001\u0000\u001a\u001d\u00f9\u0000+\u0001\u0000\u0000\u0000"+ + "\u0002A\u0001\u0000\u0000\u0000\u0004C\u0001\u0000\u0000\u0000\u0006E"+ + "\u0001\u0000\u0000\u0000\bK\u0001\u0000\u0000\u0000\nO\u0001\u0000\u0000"+ + "\u0000\fQ\u0001\u0000\u0000\u0000\u000eS\u0001\u0000\u0000\u0000\u0010"+ + "U\u0001\u0000\u0000\u0000\u0012c\u0001\u0000\u0000\u0000\u0014e\u0001"+ + "\u0000\u0000\u0000\u0016g\u0001\u0000\u0000\u0000\u0018i\u0001\u0000\u0000"+ + "\u0000\u001ak\u0001\u0000\u0000\u0000\u001cm\u0001\u0000\u0000\u0000\u001e"+ + "p\u0001\u0000\u0000\u0000 \u009c\u0001\u0000\u0000\u0000\"\u00a0\u0001"+ + "\u0000\u0000\u0000$\u00a2\u0001\u0000\u0000\u0000&\u00d9\u0001\u0000\u0000"+ + "\u0000(\u00db\u0001\u0000\u0000\u0000*,\u0003\u0002\u0001\u0000+*\u0001"+ + "\u0000\u0000\u0000+,\u0001\u0000\u0000\u0000,3\u0001\u0000\u0000\u0000"+ + "-/\u0005\u0002\u0000\u0000.0\u0003\u0002\u0001\u0000/.\u0001\u0000\u0000"+ + "\u0000/0\u0001\u0000\u0000\u000002\u0001\u0000\u0000\u00001-\u0001\u0000"+ + "\u0000\u000025\u0001\u0000\u0000\u000031\u0001\u0000\u0000\u000034\u0001"+ + "\u0000\u0000\u000046\u0001\u0000\u0000\u000053\u0001\u0000\u0000\u0000"+ + "67\u0005\u0000\u0000\u00017\u0001\u0001\u0000\u0000\u00008:\u0003\u0006"+ + "\u0003\u00009;\u0003\u0004\u0002\u0000:9\u0001\u0000\u0000\u0000:;\u0001"+ + "\u0000\u0000\u0000;B\u0001\u0000\u0000\u0000<>\u0003\"\u0011\u0000=?\u0003"+ + "\u0004\u0002\u0000>=\u0001\u0000\u0000\u0000>?\u0001\u0000\u0000\u0000"+ + "?B\u0001\u0000\u0000\u0000@B\u0003\u0004\u0002\u0000A8\u0001\u0000\u0000"+ + "\u0000A<\u0001\u0000\u0000\u0000A@\u0001\u0000\u0000\u0000B\u0003\u0001"+ + "\u0000\u0000\u0000CD\u0005\u0003\u0000\u0000D\u0005\u0001\u0000\u0000"+ + "\u0000EF\u0003\b\u0004\u0000FG\u0005\b\u0000\u0000GH\u0003\u0012\t\u0000"+ + "H\u0007\u0001\u0000\u0000\u0000IL\u0003\n\u0005\u0000JL\u0003\u0010\b"+ + "\u0000KI\u0001\u0000\u0000\u0000KJ\u0001\u0000\u0000\u0000L\t\u0001\u0000"+ + "\u0000\u0000MP\u0003\u000e\u0007\u0000NP\u0003\f\u0006\u0000OM\u0001\u0000"+ + "\u0000\u0000ON\u0001\u0000\u0000\u0000P\u000b\u0001\u0000\u0000\u0000"+ + "QR\u0005\r\u0000\u0000R\r\u0001\u0000\u0000\u0000ST\u0007\u0000\u0000"+ + "\u0000T\u000f\u0001\u0000\u0000\u0000UX\u0003\n\u0005\u0000VW\u0005\t"+ + "\u0000\u0000WY\u0003\n\u0005\u0000XV\u0001\u0000\u0000\u0000YZ\u0001\u0000"+ + "\u0000\u0000ZX\u0001\u0000\u0000\u0000Z[\u0001\u0000\u0000\u0000[\u0011"+ + "\u0001\u0000\u0000\u0000\\d\u0003\u0014\n\u0000]d\u0003\u0016\u000b\u0000"+ + "^d\u0003\u0018\f\u0000_d\u0003\u001a\r\u0000`d\u0003\u001c\u000e\u0000"+ + "ad\u0003 \u0010\u0000bd\u0003&\u0013\u0000c\\\u0001\u0000\u0000\u0000"+ + "c]\u0001\u0000\u0000\u0000c^\u0001\u0000\u0000\u0000c_\u0001\u0000\u0000"+ + "\u0000c`\u0001\u0000\u0000\u0000ca\u0001\u0000\u0000\u0000cb\u0001\u0000"+ + "\u0000\u0000d\u0013\u0001\u0000\u0000\u0000ef\u0007\u0001\u0000\u0000"+ + "f\u0015\u0001\u0000\u0000\u0000gh\u0007\u0002\u0000\u0000h\u0017\u0001"+ + "\u0000\u0000\u0000ij\u0007\u0003\u0000\u0000j\u0019\u0001\u0000\u0000"+ + "\u0000kl\u0005\u0010\u0000\u0000l\u001b\u0001\u0000\u0000\u0000mn\u0007"+ + "\u0004\u0000\u0000n\u001d\u0001\u0000\u0000\u0000oq\u0005\u0003\u0000"+ + "\u0000po\u0001\u0000\u0000\u0000pq\u0001\u0000\u0000\u0000qr\u0001\u0000"+ + "\u0000\u0000rs\u0005\u0002\u0000\u0000s\u001f\u0001\u0000\u0000\u0000"+ + "tx\u0005\u0004\u0000\u0000uw\u0003\u001e\u000f\u0000vu\u0001\u0000\u0000"+ + "\u0000wz\u0001\u0000\u0000\u0000xv\u0001\u0000\u0000\u0000xy\u0001\u0000"+ + "\u0000\u0000y{\u0001\u0000\u0000\u0000zx\u0001\u0000\u0000\u0000{\u009d"+ + "\u0005\u0006\u0000\u0000|\u0080\u0005\u0004\u0000\u0000}\u007f\u0003\u001e"+ + "\u000f\u0000~}\u0001\u0000\u0000\u0000\u007f\u0082\u0001\u0000\u0000\u0000"+ + "\u0080~\u0001\u0000\u0000\u0000\u0080\u0081\u0001\u0000\u0000\u0000\u0081"+ + "\u0083\u0001\u0000\u0000\u0000\u0082\u0080\u0001\u0000\u0000\u0000\u0083"+ + "\u0091\u0003\u0012\t\u0000\u0084\u0088\u0005\n\u0000\u0000\u0085\u0087"+ + "\u0003\u001e\u000f\u0000\u0086\u0085\u0001\u0000\u0000\u0000\u0087\u008a"+ + "\u0001\u0000\u0000\u0000\u0088\u0086\u0001\u0000\u0000\u0000\u0088\u0089"+ + "\u0001\u0000\u0000\u0000\u0089\u008b\u0001\u0000\u0000\u0000\u008a\u0088"+ + "\u0001\u0000\u0000\u0000\u008b\u008d\u0003\u0012\t\u0000\u008c\u008e\u0005"+ + "\n\u0000\u0000\u008d\u008c\u0001\u0000\u0000\u0000\u008d\u008e\u0001\u0000"+ + "\u0000\u0000\u008e\u0090\u0001\u0000\u0000\u0000\u008f\u0084\u0001\u0000"+ + "\u0000\u0000\u0090\u0093\u0001\u0000\u0000\u0000\u0091\u008f\u0001\u0000"+ + "\u0000\u0000\u0091\u0092\u0001\u0000\u0000\u0000\u0092\u0097\u0001\u0000"+ + "\u0000\u0000\u0093\u0091\u0001\u0000\u0000\u0000\u0094\u0096\u0003\u001e"+ + "\u000f\u0000\u0095\u0094\u0001\u0000\u0000\u0000\u0096\u0099\u0001\u0000"+ + "\u0000\u0000\u0097\u0095\u0001\u0000\u0000\u0000\u0097\u0098\u0001\u0000"+ + "\u0000\u0000\u0098\u009a\u0001\u0000\u0000\u0000\u0099\u0097\u0001\u0000"+ + "\u0000\u0000\u009a\u009b\u0005\u0006\u0000\u0000\u009b\u009d\u0001\u0000"+ + "\u0000\u0000\u009ct\u0001\u0000\u0000\u0000\u009c|\u0001\u0000\u0000\u0000"+ + "\u009d!\u0001\u0000\u0000\u0000\u009e\u00a1\u0003$\u0012\u0000\u009f\u00a1"+ + "\u0003(\u0014\u0000\u00a0\u009e\u0001\u0000\u0000\u0000\u00a0\u009f\u0001"+ + "\u0000\u0000\u0000\u00a1#\u0001\u0000\u0000\u0000\u00a2\u00a3\u0005\u0004"+ + "\u0000\u0000\u00a3\u00a4\u0003\b\u0004\u0000\u00a4\u00ae\u0005\u0006\u0000"+ + "\u0000\u00a5\u00a7\u0003\u001e\u000f\u0000\u00a6\u00a5\u0001\u0000\u0000"+ + "\u0000\u00a7\u00aa\u0001\u0000\u0000\u0000\u00a8\u00a6\u0001\u0000\u0000"+ + "\u0000\u00a8\u00a9\u0001\u0000\u0000\u0000\u00a9\u00ab\u0001\u0000\u0000"+ + "\u0000\u00aa\u00a8\u0001\u0000\u0000\u0000\u00ab\u00ad\u0003\u0006\u0003"+ + "\u0000\u00ac\u00a8\u0001\u0000\u0000\u0000\u00ad\u00b0\u0001\u0000\u0000"+ + "\u0000\u00ae\u00ac\u0001\u0000\u0000\u0000\u00ae\u00af\u0001\u0000\u0000"+ + "\u0000\u00af%\u0001\u0000\u0000\u0000\u00b0\u00ae\u0001\u0000\u0000\u0000"+ + "\u00b1\u00b5\u0005\u000f\u0000\u0000\u00b2\u00b4\u0003\u001e\u000f\u0000"+ + "\u00b3\u00b2\u0001\u0000\u0000\u0000\u00b4\u00b7\u0001\u0000\u0000\u0000"+ + "\u00b5\u00b3\u0001\u0000\u0000\u0000\u00b5\u00b6\u0001\u0000\u0000\u0000"+ + "\u00b6\u00b8\u0001\u0000\u0000\u0000\u00b7\u00b5\u0001\u0000\u0000\u0000"+ + "\u00b8\u00da\u0005\u001f\u0000\u0000\u00b9\u00bd\u0005\u000f\u0000\u0000"+ + "\u00ba\u00bc\u0003\u001e\u000f\u0000\u00bb\u00ba\u0001\u0000\u0000\u0000"+ + "\u00bc\u00bf\u0001\u0000\u0000\u0000\u00bd\u00bb\u0001\u0000\u0000\u0000"+ + "\u00bd\u00be\u0001\u0000\u0000\u0000\u00be\u00c0\u0001\u0000\u0000\u0000"+ + "\u00bf\u00bd\u0001\u0000\u0000\u0000\u00c0\u00ce\u0003\u0006\u0003\u0000"+ + "\u00c1\u00c5\u0005\n\u0000\u0000\u00c2\u00c4\u0003\u001e\u000f\u0000\u00c3"+ + "\u00c2\u0001\u0000\u0000\u0000\u00c4\u00c7\u0001\u0000\u0000\u0000\u00c5"+ + "\u00c3\u0001\u0000\u0000\u0000\u00c5\u00c6\u0001\u0000\u0000\u0000\u00c6"+ + "\u00c8\u0001\u0000\u0000\u0000\u00c7\u00c5\u0001\u0000\u0000\u0000\u00c8"+ + "\u00ca\u0003\u0006\u0003\u0000\u00c9\u00cb\u0005\n\u0000\u0000\u00ca\u00c9"+ + "\u0001\u0000\u0000\u0000\u00ca\u00cb\u0001\u0000\u0000\u0000\u00cb\u00cd"+ + "\u0001\u0000\u0000\u0000\u00cc\u00c1\u0001\u0000\u0000\u0000\u00cd\u00d0"+ + "\u0001\u0000\u0000\u0000\u00ce\u00cc\u0001\u0000\u0000\u0000\u00ce\u00cf"+ + "\u0001\u0000\u0000\u0000\u00cf\u00d4\u0001\u0000\u0000\u0000\u00d0\u00ce"+ + "\u0001\u0000\u0000\u0000\u00d1\u00d3\u0003\u001e\u000f\u0000\u00d2\u00d1"+ + "\u0001\u0000\u0000\u0000\u00d3\u00d6\u0001\u0000\u0000\u0000\u00d4\u00d2"+ + "\u0001\u0000\u0000\u0000\u00d4\u00d5\u0001\u0000\u0000\u0000\u00d5\u00d7"+ + "\u0001\u0000\u0000\u0000\u00d6\u00d4\u0001\u0000\u0000\u0000\u00d7\u00d8"+ + "\u0005\u001f\u0000\u0000\u00d8\u00da\u0001\u0000\u0000\u0000\u00d9\u00b1"+ + "\u0001\u0000\u0000\u0000\u00d9\u00b9\u0001\u0000\u0000\u0000\u00da\'\u0001"+ + "\u0000\u0000\u0000\u00db\u00dc\u0005\u0005\u0000\u0000\u00dc\u00dd\u0003"+ + "\b\u0004\u0000\u00dd\u00e7\u0005\u0007\u0000\u0000\u00de\u00e0\u0003\u001e"+ + "\u000f\u0000\u00df\u00de\u0001\u0000\u0000\u0000\u00e0\u00e3\u0001\u0000"+ + "\u0000\u0000\u00e1\u00df\u0001\u0000\u0000\u0000\u00e1\u00e2\u0001\u0000"+ + "\u0000\u0000\u00e2\u00e4\u0001\u0000\u0000\u0000\u00e3\u00e1\u0001\u0000"+ + "\u0000\u0000\u00e4\u00e6\u0003\u0006\u0003\u0000\u00e5\u00e1\u0001\u0000"+ + "\u0000\u0000\u00e6\u00e9\u0001\u0000\u0000\u0000\u00e7\u00e5\u0001\u0000"+ + "\u0000\u0000\u00e7\u00e8\u0001\u0000\u0000\u0000\u00e8)\u0001\u0000\u0000"+ + "\u0000\u00e9\u00e7\u0001\u0000\u0000\u0000\u001e+/3:>AKOZcpx\u0080\u0088"+ + "\u008d\u0091\u0097\u009c\u00a0\u00a8\u00ae\u00b5\u00bd\u00c5\u00ca\u00ce"+ + "\u00d4\u00d9\u00e1\u00e7"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.tokens b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.tokens new file mode 100644 index 00000000000..080a49ecbae --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParser.tokens @@ -0,0 +1,41 @@ +WS=1 +NL=2 +COMMENT=3 +L_BRACKET=4 +DOUBLE_L_BRACKET=5 +R_BRACKET=6 +DOUBLE_R_BRACKET=7 +EQUALS=8 +DOT=9 +COMMA=10 +BASIC_STRING=11 +LITERAL_STRING=12 +UNQUOTED_KEY=13 +VALUE_WS=14 +L_BRACE=15 +BOOLEAN=16 +ML_BASIC_STRING=17 +ML_LITERAL_STRING=18 +FLOAT=19 +INF=20 +NAN=21 +DEC_INT=22 +HEX_INT=23 +OCT_INT=24 +BIN_INT=25 +OFFSET_DATE_TIME=26 +LOCAL_DATE_TIME=27 +LOCAL_DATE=28 +LOCAL_TIME=29 +INLINE_TABLE_WS=30 +R_BRACE=31 +ARRAY_WS=32 +'['=4 +'[['=5 +']'=6 +']]'=7 +'='=8 +'.'=9 +','=10 +'{'=15 +'}'=31 diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserBaseListener.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserBaseListener.java new file mode 100644 index 00000000000..e0944694241 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserBaseListener.java @@ -0,0 +1,307 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +// Generated from java-escape by ANTLR 4.11.1 +package org.openrewrite.toml.internal.grammar; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * This class provides an empty implementation of {@link TomlParserListener}, + * which can be extended to create a listener which only needs to handle a subset + * of the available methods. + */ +@SuppressWarnings("CheckReturnValue") +public class TomlParserBaseListener implements TomlParserListener { + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterDocument(TomlParser.DocumentContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitDocument(TomlParser.DocumentContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterExpression(TomlParser.ExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitExpression(TomlParser.ExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterComment(TomlParser.CommentContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitComment(TomlParser.CommentContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterKeyValue(TomlParser.KeyValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitKeyValue(TomlParser.KeyValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterKey(TomlParser.KeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitKey(TomlParser.KeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterSimpleKey(TomlParser.SimpleKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitSimpleKey(TomlParser.SimpleKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterUnquotedKey(TomlParser.UnquotedKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitUnquotedKey(TomlParser.UnquotedKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterQuotedKey(TomlParser.QuotedKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitQuotedKey(TomlParser.QuotedKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterDottedKey(TomlParser.DottedKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitDottedKey(TomlParser.DottedKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterValue(TomlParser.ValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitValue(TomlParser.ValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterString(TomlParser.StringContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitString(TomlParser.StringContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterInteger(TomlParser.IntegerContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitInteger(TomlParser.IntegerContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFloatingPoint(TomlParser.FloatingPointContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFloatingPoint(TomlParser.FloatingPointContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterBool(TomlParser.BoolContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitBool(TomlParser.BoolContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterDateTime(TomlParser.DateTimeContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitDateTime(TomlParser.DateTimeContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterCommentOrNl(TomlParser.CommentOrNlContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitCommentOrNl(TomlParser.CommentOrNlContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterArray(TomlParser.ArrayContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitArray(TomlParser.ArrayContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterTable(TomlParser.TableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitTable(TomlParser.TableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStandardTable(TomlParser.StandardTableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStandardTable(TomlParser.StandardTableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterInlineTable(TomlParser.InlineTableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitInlineTable(TomlParser.InlineTableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterArrayTable(TomlParser.ArrayTableContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitArrayTable(TomlParser.ArrayTableContext ctx) { } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitTerminal(TerminalNode node) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitErrorNode(ErrorNode node) { } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserBaseVisitor.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserBaseVisitor.java new file mode 100644 index 00000000000..f4cfa1ebb80 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserBaseVisitor.java @@ -0,0 +1,177 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +// Generated from java-escape by ANTLR 4.11.1 +package org.openrewrite.toml.internal.grammar; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; + +/** + * This class provides an empty implementation of {@link TomlParserVisitor}, + * which can be extended to create a visitor which only needs to handle a subset + * of the available methods. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +@SuppressWarnings("CheckReturnValue") +public class TomlParserBaseVisitor extends AbstractParseTreeVisitor implements TomlParserVisitor { + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDocument(TomlParser.DocumentContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitExpression(TomlParser.ExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitComment(TomlParser.CommentContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitKeyValue(TomlParser.KeyValueContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitKey(TomlParser.KeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSimpleKey(TomlParser.SimpleKeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitUnquotedKey(TomlParser.UnquotedKeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitQuotedKey(TomlParser.QuotedKeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDottedKey(TomlParser.DottedKeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitValue(TomlParser.ValueContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitString(TomlParser.StringContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitInteger(TomlParser.IntegerContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFloatingPoint(TomlParser.FloatingPointContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitBool(TomlParser.BoolContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDateTime(TomlParser.DateTimeContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitCommentOrNl(TomlParser.CommentOrNlContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArray(TomlParser.ArrayContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitTable(TomlParser.TableContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStandardTable(TomlParser.StandardTableContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitInlineTable(TomlParser.InlineTableContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArrayTable(TomlParser.ArrayTableContext ctx) { return visitChildren(ctx); } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserListener.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserListener.java new file mode 100644 index 00000000000..5b428a22f42 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserListener.java @@ -0,0 +1,235 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +// Generated from java-escape by ANTLR 4.11.1 +package org.openrewrite.toml.internal.grammar; +import org.antlr.v4.runtime.tree.ParseTreeListener; + +/** + * This interface defines a complete listener for a parse tree produced by + * {@link TomlParser}. + */ +public interface TomlParserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by {@link TomlParser#document}. + * @param ctx the parse tree + */ + void enterDocument(TomlParser.DocumentContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#document}. + * @param ctx the parse tree + */ + void exitDocument(TomlParser.DocumentContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#expression}. + * @param ctx the parse tree + */ + void enterExpression(TomlParser.ExpressionContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#expression}. + * @param ctx the parse tree + */ + void exitExpression(TomlParser.ExpressionContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#comment}. + * @param ctx the parse tree + */ + void enterComment(TomlParser.CommentContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#comment}. + * @param ctx the parse tree + */ + void exitComment(TomlParser.CommentContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#keyValue}. + * @param ctx the parse tree + */ + void enterKeyValue(TomlParser.KeyValueContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#keyValue}. + * @param ctx the parse tree + */ + void exitKeyValue(TomlParser.KeyValueContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#key}. + * @param ctx the parse tree + */ + void enterKey(TomlParser.KeyContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#key}. + * @param ctx the parse tree + */ + void exitKey(TomlParser.KeyContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#simpleKey}. + * @param ctx the parse tree + */ + void enterSimpleKey(TomlParser.SimpleKeyContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#simpleKey}. + * @param ctx the parse tree + */ + void exitSimpleKey(TomlParser.SimpleKeyContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#unquotedKey}. + * @param ctx the parse tree + */ + void enterUnquotedKey(TomlParser.UnquotedKeyContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#unquotedKey}. + * @param ctx the parse tree + */ + void exitUnquotedKey(TomlParser.UnquotedKeyContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#quotedKey}. + * @param ctx the parse tree + */ + void enterQuotedKey(TomlParser.QuotedKeyContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#quotedKey}. + * @param ctx the parse tree + */ + void exitQuotedKey(TomlParser.QuotedKeyContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#dottedKey}. + * @param ctx the parse tree + */ + void enterDottedKey(TomlParser.DottedKeyContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#dottedKey}. + * @param ctx the parse tree + */ + void exitDottedKey(TomlParser.DottedKeyContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#value}. + * @param ctx the parse tree + */ + void enterValue(TomlParser.ValueContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#value}. + * @param ctx the parse tree + */ + void exitValue(TomlParser.ValueContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#string}. + * @param ctx the parse tree + */ + void enterString(TomlParser.StringContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#string}. + * @param ctx the parse tree + */ + void exitString(TomlParser.StringContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#integer}. + * @param ctx the parse tree + */ + void enterInteger(TomlParser.IntegerContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#integer}. + * @param ctx the parse tree + */ + void exitInteger(TomlParser.IntegerContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#floatingPoint}. + * @param ctx the parse tree + */ + void enterFloatingPoint(TomlParser.FloatingPointContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#floatingPoint}. + * @param ctx the parse tree + */ + void exitFloatingPoint(TomlParser.FloatingPointContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#bool}. + * @param ctx the parse tree + */ + void enterBool(TomlParser.BoolContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#bool}. + * @param ctx the parse tree + */ + void exitBool(TomlParser.BoolContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#dateTime}. + * @param ctx the parse tree + */ + void enterDateTime(TomlParser.DateTimeContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#dateTime}. + * @param ctx the parse tree + */ + void exitDateTime(TomlParser.DateTimeContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#commentOrNl}. + * @param ctx the parse tree + */ + void enterCommentOrNl(TomlParser.CommentOrNlContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#commentOrNl}. + * @param ctx the parse tree + */ + void exitCommentOrNl(TomlParser.CommentOrNlContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#array}. + * @param ctx the parse tree + */ + void enterArray(TomlParser.ArrayContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#array}. + * @param ctx the parse tree + */ + void exitArray(TomlParser.ArrayContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#table}. + * @param ctx the parse tree + */ + void enterTable(TomlParser.TableContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#table}. + * @param ctx the parse tree + */ + void exitTable(TomlParser.TableContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#standardTable}. + * @param ctx the parse tree + */ + void enterStandardTable(TomlParser.StandardTableContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#standardTable}. + * @param ctx the parse tree + */ + void exitStandardTable(TomlParser.StandardTableContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#inlineTable}. + * @param ctx the parse tree + */ + void enterInlineTable(TomlParser.InlineTableContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#inlineTable}. + * @param ctx the parse tree + */ + void exitInlineTable(TomlParser.InlineTableContext ctx); + /** + * Enter a parse tree produced by {@link TomlParser#arrayTable}. + * @param ctx the parse tree + */ + void enterArrayTable(TomlParser.ArrayTableContext ctx); + /** + * Exit a parse tree produced by {@link TomlParser#arrayTable}. + * @param ctx the parse tree + */ + void exitArrayTable(TomlParser.ArrayTableContext ctx); +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserVisitor.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserVisitor.java new file mode 100644 index 00000000000..acbe51681b2 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserVisitor.java @@ -0,0 +1,154 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +// Generated from java-escape by ANTLR 4.11.1 +package org.openrewrite.toml.internal.grammar; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by {@link TomlParser}. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public interface TomlParserVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by {@link TomlParser#document}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDocument(TomlParser.DocumentContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitExpression(TomlParser.ExpressionContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#comment}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitComment(TomlParser.CommentContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#keyValue}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitKeyValue(TomlParser.KeyValueContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#key}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitKey(TomlParser.KeyContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#simpleKey}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSimpleKey(TomlParser.SimpleKeyContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#unquotedKey}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitUnquotedKey(TomlParser.UnquotedKeyContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#quotedKey}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitQuotedKey(TomlParser.QuotedKeyContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#dottedKey}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDottedKey(TomlParser.DottedKeyContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#value}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitValue(TomlParser.ValueContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#string}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitString(TomlParser.StringContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#integer}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitInteger(TomlParser.IntegerContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#floatingPoint}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFloatingPoint(TomlParser.FloatingPointContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#bool}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitBool(TomlParser.BoolContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#dateTime}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDateTime(TomlParser.DateTimeContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#commentOrNl}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitCommentOrNl(TomlParser.CommentOrNlContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#array}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArray(TomlParser.ArrayContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#table}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitTable(TomlParser.TableContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#standardTable}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStandardTable(TomlParser.StandardTableContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#inlineTable}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitInlineTable(TomlParser.InlineTableContext ctx); + /** + * Visit a parse tree produced by {@link TomlParser#arrayTable}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArrayTable(TomlParser.ArrayTableContext ctx); +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/internal/package-info.java b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/package-info.java new file mode 100644 index 00000000000..0dc7d998012 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/internal/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.toml.internal; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/marker/ArrayTable.java b/rewrite-toml/src/main/java/org/openrewrite/toml/marker/ArrayTable.java new file mode 100644 index 00000000000..d64194c1f75 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/marker/ArrayTable.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.marker; + +import lombok.Value; +import lombok.With; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + +@Value +@With +public class ArrayTable implements Marker { + UUID id; +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/marker/InlineTable.java b/rewrite-toml/src/main/java/org/openrewrite/toml/marker/InlineTable.java new file mode 100644 index 00000000000..c444fed4f1e --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/marker/InlineTable.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.marker; + +import lombok.Value; +import lombok.With; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + +@Value +@With +public class InlineTable implements Marker { + UUID id; +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/marker/package-info.java b/rewrite-toml/src/main/java/org/openrewrite/toml/marker/package-info.java new file mode 100644 index 00000000000..f660c6bdfc4 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/marker/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.toml.marker; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/package-info.java b/rewrite-toml/src/main/java/org/openrewrite/toml/package-info.java new file mode 100644 index 00000000000..17c795873d3 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.toml; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Comment.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Comment.java new file mode 100644 index 00000000000..64d0f5afc64 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Comment.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +import lombok.Value; +import lombok.With; +import org.openrewrite.marker.Markers; + +@Value +@With +public class Comment { + String text; + String suffix; + Markers markers; +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Space.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Space.java new file mode 100644 index 00000000000..caf3dcb87ee --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Space.java @@ -0,0 +1,270 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import lombok.EqualsAndHashCode; +import org.jspecify.annotations.Nullable; +import org.openrewrite.marker.Markers; + +import java.util.*; + +import static java.util.Collections.emptyList; + +/** + * Wherever whitespace can occur in TOML, so can comments (at least block style comments). + * So whitespace and comments are like peanut butter and jelly. + */ +@EqualsAndHashCode +@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@ref") +public class Space { + public static final Space EMPTY = new Space("", emptyList()); + public static final Space SINGLE_SPACE = new Space(" ", emptyList()); + + private final List comments; + + @Nullable + private final String whitespace; + + /* + * Most occurrences of spaces will have no comments or markers and will be repeated frequently throughout a source file. + * e.g.: a single space between keywords, or the common indentation of every line in a block. + * So use flyweights to avoid storing many instances of functionally identical spaces + */ + private static final Map flyweights = Collections.synchronizedMap(new WeakHashMap<>()); + + private Space(@Nullable String whitespace, List comments) { + this.comments = comments; + this.whitespace = whitespace == null || whitespace.isEmpty() ? null : whitespace; + } + + @JsonCreator + public static Space build(@Nullable String whitespace, List comments) { + if (comments.isEmpty()) { + if (whitespace == null || whitespace.isEmpty()) { + return Space.EMPTY; + } else if (whitespace.length() <= 100) { + //noinspection StringOperationCanBeSimplified + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); + } + } + return new Space(whitespace, comments); + } + + public String getIndent() { + if (!comments.isEmpty()) { + return getWhitespaceIndent(comments.get(comments.size() - 1).getSuffix()); + } + return getWhitespaceIndent(whitespace); + } + + public String getLastWhitespace() { + if (!comments.isEmpty()) { + return comments.get(comments.size() - 1).getSuffix(); + } + return whitespace == null ? "" : whitespace; + } + + private String getWhitespaceIndent(@Nullable String whitespace) { + if (whitespace == null) { + return ""; + } + int lastNewline = whitespace.lastIndexOf('\n'); + if (lastNewline >= 0) { + return whitespace.substring(lastNewline + 1); + } else if (lastNewline == whitespace.length() - 1) { + return ""; + } + return whitespace; + } + + public List getComments() { + return comments; + } + + public String getWhitespace() { + return whitespace == null ? "" : whitespace; + } + + public boolean hasComment(String comment) { + for (Comment c : comments) { + if (c.getText().equals(comment)) { + return true; + } + } + return false; + } + + public Space withComments(List comments) { + if (comments == this.comments) { + return this; + } + if (comments.isEmpty() && (whitespace == null || whitespace.isEmpty())) { + return Space.EMPTY; + } + return build(whitespace, comments); + } + + public Space withWhitespace(String whitespace) { + if (comments.isEmpty() && whitespace.isEmpty()) { + return Space.EMPTY; + } + if ((whitespace.isEmpty() && this.whitespace == null) || whitespace.equals(this.whitespace)) { + return this; + } + return build(whitespace, comments); + } + + public boolean isEmpty() { + return this == EMPTY; + } + + public static Space firstPrefix(@Nullable List trees) { + return trees == null || trees.isEmpty() ? Space.EMPTY : trees.iterator().next().getPrefix(); + } + + public static Space format(String formatting) { + return format(formatting, 0, formatting.length()); + } + + public static Space format(String formatting, int beginIndex, int toIndex) { + if (beginIndex == toIndex) { + return Space.EMPTY; + } else if (toIndex == beginIndex + 1 && ' ' == formatting.charAt(beginIndex)) { + return Space.SINGLE_SPACE; + } else { + rangeCheck(formatting.length(), beginIndex, toIndex); + } + + StringBuilder prefix = new StringBuilder(); + StringBuilder comment = new StringBuilder(); + List comments = new ArrayList<>(1); + + boolean inSingleLineComment = false; + + for (int i = beginIndex; i < toIndex; i++) { + char c = formatting.charAt(i); + switch (c) { + case '#': + if (inSingleLineComment) { + comment.append(c); + } else { + inSingleLineComment = true; + comment.setLength(0); + } + break; + case '\r': + case '\n': + if (inSingleLineComment) { + inSingleLineComment = false; + comments.add(new Comment(comment.toString(), prefix.toString(), Markers.EMPTY)); + prefix.setLength(0); + comment.setLength(0); + prefix.append(c); + } else { + prefix.append(c); + } + break; + default: + if (inSingleLineComment) { + comment.append(c); + } else { + prefix.append(c); + } + } + } + // If a file ends with a single-line comment there may be no terminating newline + if (comment.length() > 0) { + comments.add(new Comment(comment.toString(), prefix.toString(), Markers.EMPTY)); + prefix.setLength(0); + } + + // Shift the whitespace on each comment forward to be a suffix of the comment before it, and the + // whitespace on the first comment to be the whitespace of the tree element. The remaining prefix is the suffix + // of the last comment. + String whitespace = prefix.toString(); + if (!comments.isEmpty()) { + for (int i = comments.size() - 1; i >= 0; i--) { + Comment c = comments.get(i); + String next = c.getSuffix(); + comments.set(i, c.withSuffix(whitespace)); + whitespace = next; + } + } + + return build(whitespace, comments); + } + + static void rangeCheck(int arrayLength, int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IllegalArgumentException( + "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + if (fromIndex < 0) { + throw new StringIndexOutOfBoundsException(fromIndex); + } + if (toIndex > arrayLength) { + throw new StringIndexOutOfBoundsException(toIndex); + } + } + + public static List formatFirstPrefix(List trees, Space prefix) { + if (!trees.isEmpty() && !trees.get(0).getPrefix().equals(prefix)) { + List formattedTrees = new ArrayList<>(trees); + formattedTrees.set(0, formattedTrees.get(0).withPrefix(prefix)); + return formattedTrees; + } + + return trees; + } + + private static final String[] spaces = { + "·₁", "·₂", "·₃", "·₄", "·₅", "·₆", "·₇", "·₈", "·₉", "·₊" + }; + + private static final String[] tabs = { + "-₁", "-₂", "-₃", "-₄", "-₅", "-₆", "-₇", "-₈", "-₉", "-₊" + }; + + @Override + public String toString() { + StringBuilder printedWs = new StringBuilder(); + int lastNewline = 0; + if (whitespace != null) { + char[] charArray = whitespace.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + if (c == '\n') { + printedWs.append("\\n"); + lastNewline = i + 1; + } else if (c == '\r') { + printedWs.append("\\r"); + lastNewline = i + 1; + } else if (c == ' ') { + printedWs.append(spaces[(i - lastNewline) % 10]); + } else if (c == '\t') { + printedWs.append(tabs[(i - lastNewline) % 10]); + } + } + } + + return "Space(" + + "comments=<" + (comments.size() == 1 ? "1 comment" : comments.size() + " comments") + + ">, whitespace='" + printedWs + "')"; + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Toml.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Toml.java new file mode 100644 index 00000000000..f429cd99f56 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/Toml.java @@ -0,0 +1,379 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +import lombok.*; +import lombok.experimental.NonFinal; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.marker.Markers; +import org.openrewrite.toml.TomlVisitor; +import org.openrewrite.toml.internal.TomlPrinter; + +import java.lang.ref.WeakReference; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; + +public interface Toml extends Tree { + + @SuppressWarnings("unchecked") + @Override + default R accept(TreeVisitor v, P p) { + return (R) acceptToml(v.adapt(TomlVisitor.class), p); + } + + default

@Nullable Toml acceptToml(TomlVisitor

v, P p) { + return v.defaultValue(this, p); + } + + @Override + default

boolean isAcceptable(TreeVisitor v, P p) { + return v.isAdaptableTo(TomlVisitor.class); + } + + Space getPrefix(); + + J withPrefix(Space prefix); + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + class Array implements Toml { + @Nullable + @NonFinal + transient WeakReference padding; + + @With + @EqualsAndHashCode.Include + UUID id; + + @With + Space prefix; + + @With + Markers markers; + + List> values; + + public List getValues() { + return TomlRightPadded.getElements(values); + } + + public Array withValues(List values) { + return getPadding().withValues(TomlRightPadded.withElements(this.values, values)); + } + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitArray(this, p); + } + + public Padding getPadding() { + Padding p; + if (this.padding == null) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } else { + p = this.padding.get(); + if (p == null || p.t != this) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } + } + return p; + } + + @RequiredArgsConstructor + public static class Padding { + private final Array t; + + public List> getValues() { + return t.values; + } + + public Array withValues(List> values) { + return t.values == values ? t : new Array(t.id, t.prefix, t.markers, values); + } + } + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Document implements Toml, SourceFile { + @EqualsAndHashCode.Include + UUID id; + + Path sourcePath; + Space prefix; + Markers markers; + + @With(AccessLevel.PRIVATE) + String charsetName; + + boolean charsetBomMarked; + + @Nullable + Checksum checksum; + + @Nullable + FileAttributes fileAttributes; + + @Override + public Charset getCharset() { + return Charset.forName(charsetName); + } + + @Override + @SuppressWarnings("unchecked") + public Document withCharset(Charset charset) { + return withCharsetName(charset.name()); + } + + List values; + Space eof; + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitDocument(this, p); + } + + @Override + public

TreeVisitor> printer(Cursor cursor) { + return new TomlPrinter<>(); + } + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Empty implements Toml { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitEmpty(this, p); + } + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Identifier implements TomlKey { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + String source; + String name; + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitIdentifier(this, p); + } + + @Override + public String toString() { + return "Identifier{prefix=" + prefix + ", name=" + name + "}"; + } + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @With + class KeyValue implements TomlValue { + @Nullable + @NonFinal + transient WeakReference padding; + + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + + Markers markers; + + TomlRightPadded key; + + public TomlKey getKey() { + return key.getElement(); + } + + public KeyValue withKey(TomlKey key) { + return getPadding().withKey(TomlRightPadded.withElement(this.key, key)); + } + + Toml value; + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitKeyValue(this, p); + } + + public Padding getPadding() { + Padding p; + if (this.padding == null) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } else { + p = this.padding.get(); + if (p == null || p.t != this) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } + } + return p; + } + + @RequiredArgsConstructor + public static class Padding { + private final KeyValue t; + + public TomlRightPadded getKey() { + return t.key; + } + + public KeyValue withKey(TomlRightPadded key) { + return t.key == key ? t : new KeyValue(t.id, t.prefix, t.markers, key, t.value); + } + } + + @Override + public String toString() { + return "KeyValue{prefix=" + prefix + ", key=" + key.getElement() + '}'; + } + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Literal implements Toml { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + + Markers markers; + + TomlType.Primitive type; + + String source; + + Object value; + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitLiteral(this, p); + } + + @Override + public String toString() { + return "Literal{prefix=" + prefix + ", source=" + source + "}"; + } + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + class Table implements TomlValue { + @Nullable + @NonFinal + transient WeakReference padding; + + @EqualsAndHashCode.Include + @With + UUID id; + + @With + Space prefix; + + @With + Markers markers; + + @Nullable + TomlRightPadded name; + + public Toml.@Nullable Identifier getName() { + return name != null ? name.getElement() : null; + } + + public Table withName(Toml.@Nullable Identifier name) { + return getPadding().withName(TomlRightPadded.withElement(this.name, name)); + } + + List> values; + + public List getValues() { + return TomlRightPadded.getElements(values); + } + + public Table withValues(List values) { + return getPadding().withValues(TomlRightPadded.withElements(this.values, values)); + } + + @Override + public

Toml acceptToml(TomlVisitor

v, P p) { + return v.visitTable(this, p); + } + + public Table.Padding getPadding() { + Table.Padding p; + if (this.padding == null) { + p = new Table.Padding(this); + this.padding = new WeakReference<>(p); + } else { + p = this.padding.get(); + if (p == null || p.t != this) { + p = new Table.Padding(this); + this.padding = new WeakReference<>(p); + } + } + return p; + } + + @RequiredArgsConstructor + public static class Padding { + private final Table t; + + public List> getValues() { + return t.values; + } + + public Table withValues(List> values) { + return t.values == values ? t : new Table(t.id, t.prefix, t.markers, t.name, values); + } + + public @Nullable TomlRightPadded getName() { + return t.name; + } + + public Table withName(@Nullable TomlRightPadded name) { + return t.name == name ? t : new Table(t.id, t.prefix, t.markers, name, t.values); + } + } + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlKey.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlKey.java new file mode 100644 index 00000000000..bdf63eb1f0e --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlKey.java @@ -0,0 +1,19 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +public interface TomlKey extends Toml { +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlRightPadded.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlRightPadded.java new file mode 100644 index 00000000000..911bd8adf7a --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlRightPadded.java @@ -0,0 +1,117 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.With; +import lombok.experimental.FieldDefaults; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.Markers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +/** + * A Toml element that could have trailing space. + * + * @param + */ +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) +@Data +public class TomlRightPadded { + @With + T element; + + @With + Space after; + + @With + Markers markers; + + public TomlRightPadded map(UnaryOperator map) { + return withElement(map.apply(element)); + } + + public static List getElements(List> ls) { + List list = new ArrayList<>(); + for (TomlRightPadded l : ls) { + T elem = l.getElement(); + list.add(elem); + } + return list; + } + + public static

List> withElements(List> before, List

elements) { + // a cheaper check for the most common case when there are no changes + if (elements.size() == before.size()) { + boolean hasChanges = false; + for (int i = 0; i < before.size(); i++) { + if (before.get(i).getElement() != elements.get(i)) { + hasChanges = true; + break; + } + } + if (!hasChanges) { + return before; + } + } + + List> after = new ArrayList<>(elements.size()); + Map> beforeById = before.stream().collect(Collectors + .toMap(j -> j.getElement().getId(), Function.identity())); + + for (P t : elements) { + if (beforeById.get(t.getId()) != null) { + TomlRightPadded

found = beforeById.get(t.getId()); + after.add(found.withElement(t)); + } else { + after.add(new TomlRightPadded<>(t, Space.EMPTY, Markers.EMPTY)); + } + } + + return after; + } + + public static TomlRightPadded build(T element) { + return new TomlRightPadded<>(element, Space.EMPTY, Markers.EMPTY); + } + + public static @Nullable TomlRightPadded withElement(@Nullable TomlRightPadded before, @Nullable T elements) { + if (before == null) { + if (elements == null) { + return null; + } + return new TomlRightPadded<>(elements, Space.EMPTY, Markers.EMPTY); + } + if (elements == null) { + return null; + } + return before.withElement(elements); + } + + @Override + public String toString() { + return "TomlRightPadded(element=" + element.getClass().getSimpleName() + ", after=" + after + ')'; + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlType.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlType.java new file mode 100644 index 00000000000..abe678a6c1d --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlType.java @@ -0,0 +1,29 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +public interface TomlType { + enum Primitive implements TomlType { + Boolean, + Float, + Integer, + LocalDate, + LocalDateTime, + LocalTime, + OffsetDateTime, + String + } +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlValue.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlValue.java new file mode 100644 index 00000000000..767d3d382a4 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/TomlValue.java @@ -0,0 +1,19 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml.tree; + +public interface TomlValue extends Toml { +} diff --git a/rewrite-toml/src/main/java/org/openrewrite/toml/tree/package-info.java b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/package-info.java new file mode 100644 index 00000000000..2a3f1d8f923 --- /dev/null +++ b/rewrite-toml/src/main/java/org/openrewrite/toml/tree/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.toml.tree; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-toml/src/test/java/.editorconfig b/rewrite-toml/src/test/java/.editorconfig new file mode 100644 index 00000000000..a4824935e4f --- /dev/null +++ b/rewrite-toml/src/test/java/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.java] +indent_size = 4 +ij_continuation_indent_size = 2 diff --git a/rewrite-toml/src/test/java/org/openrewrite/toml/TomlParserTest.java b/rewrite-toml/src/test/java/org/openrewrite/toml/TomlParserTest.java new file mode 100644 index 00000000000..d466abf86e2 --- /dev/null +++ b/rewrite-toml/src/test/java/org/openrewrite/toml/TomlParserTest.java @@ -0,0 +1,353 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.toml.tree.Toml; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.toml.Assertions.toml; + +class TomlParserTest implements RewriteTest { + @Test + void keyValueString() { + rewriteRun( + toml( + """ + str = "I'm a string. \\"You can quote me\\". Name\\tJos\\u00E9\\nLocation\\tSF." + """ + ) + ); + } + + @Test + void keyValueInteger() { + rewriteRun( + toml( + """ + int1 = +99 + int2 = 42 + int3 = 0 + int4 = -17 + int5 = 1_000 + + # hexadecimal with prefix `0x` + hex1 = 0xDEADBEEF + hex2 = 0xdeadbeef + hex3 = 0xdead_beef + # octal with prefix `0o` + oct1 = 0o01234567 + oct2 = 0o755 # useful for Unix file permissions + + # binary with prefix `0b` + bin1 = 0b11010110 + """ + ) + ); + } + + @Test + void keyValueFloat() { + rewriteRun( + toml( + """ + # fractional + flt1 = +1.0 + flt2 = 3.1415 + flt3 = -0.01 + + # exponent + flt4 = 5e+22 + flt5 = 1e06 + flt6 = -2E-2 + + # both + flt7 = 6.626e-34 + + flt8 = 224_617.445_991_228 + + # infinity + sf1 = inf # positive infinity + sf2 = +inf # positive infinity + sf3 = -inf # negative infinity + + # not a number + sf4 = nan # actual sNaN/qNaN encoding is implementation-specific + sf5 = +nan # same as `nan` + sf6 = -nan # valid, actual encoding is implementation-specific + """ + ) + ); + } + + @Test + void keyValueBool() { + rewriteRun( + toml( + """ + bool1 = true + bool2 = false + """ + ) + ); + } + + @Test + void keyValueOffsetDateTime() { + rewriteRun( + toml( + """ + odt1 = 1979-05-27T07:32:00Z + odt2 = 1979-05-27T00:32:00-07:00 + odt3 = 1979-05-27T00:32:00.999999-07:00 + odt4 = 1979-05-27 07:32:00Z + """ + ) + ); + } + + @Test + void keyValueLocalDateTime() { + rewriteRun( + toml( + """ + ldt1 = 1979-05-27T07:32:00 + ldt2 = 1979-05-27T00:32:00.999999 + """ + ) + ); + } + + @Test + void keyValueLocalDate() { + rewriteRun( + toml( + """ + ld1 = 1979-05-27 + """ + ) + ); + } + + @Test + void keyValueLocalTime() { + rewriteRun( + toml( + """ + lt1 = 07:32:00 + lt2 = 00:32:00.999999 + """ + ) + ); + } + + @Test + void keyValueArray() { + rewriteRun( + toml( + """ + integers = [ 1, 2, 3 ] + colors = [ "red", "yellow", "green" ] + nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] + nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] + string_array = [ "all", 'strings', ""\"are the same""\", '''type''' ] + + # Mixed-type arrays are allowed + numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] + contributors = [ + "Foo Bar ", + { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } + ] + integers2 = [ + 1, 2, 3 + ] + + integers3 = [ + 1, + 2, # this is ok + ] + """ + ) + ); + } + + @Test + void table() { + rewriteRun( + toml( + """ + [table-1] + key1 = "some string" + key2 = 123 + + [table-2] + key1 = "another string" + key2 = 456 + + [dog."tater.man"] + type.name = "pug" + """, + spec -> spec.afterRecipe(doc -> { + assertThat(doc.getValues()).hasSize(3); + assertThat(doc.getValues()).allSatisfy( + v -> assertThat(v).isInstanceOf(Toml.Table.class) + ); + }) + ) + ); + } + + @Test + void arrayTable() { + rewriteRun( + toml( + """ + [[products]] + name = "Hammer" + sku = 738594937 + + [[products]] # empty table within the array + + [[products]] + name = "Nail" + sku = 284758393 + + color = "gray" + """ + ) + ); + } + + @Test + void bareKeys() { + rewriteRun( + toml( + """ + key = "value" + bare_key = "value" + bare-key = "value" + 1234 = "value" + """ + ) + ); + } + + @Test + void quotedKeys() { + rewriteRun( + toml( + """ + "127.0.0.1" = "value" + "character encoding" = "value" + "ʎǝʞ" = "value" + 'key2' = "value" + 'quoted "value"' = "value" + """ + ) + ); + } + + @Test + void dottedKeys() { + rewriteRun( + toml( + """ + physical.color = "orange" + physical.shape = "round" + site."google.com" = true + """ + ) + ); + } + + @Test + void extraWhitespaceDottedKeys() { + rewriteRun( + toml( + """ + fruit.name = "banana" # this is best practice + fruit. color = "yellow" # same as fruit.color + fruit . flavor = "banana" # same as fruit.flavor + """ + ) + ); + } + + @Test + void extraWhitespaceTable() { + rewriteRun( + toml( + """ + [a.b.c] # this is best practice + [ d.e.f ] # same as [d.e.f] + [ g . h . i ] # same as [g.h.i] + [ j . "ʞ" . 'l' ] # same as [j."ʞ".'l'] + """ + ) + ); + } + + @Test + void extraWhitespaceArrayTable() { + rewriteRun( + toml( + """ + [[a.b.c]] # this is best practice + [[ d.e.f ]] # same as [[d.e.f]] + [[ g . h . i ]] # same as [[g.h.i]] + [[ j . "ʞ" . 'l' ]] # same as [[j."ʞ".'l']] + """ + ) + ); + } + + @Test + void empty() { + rewriteRun( + toml( + "" + ) + ); + } + + @Test + void trailingComment() { + rewriteRun( + toml( + """ + str = "I'm a string. \\"You can quote me\\". Name\\tJos\\u00E9\\nLocation\\tSF." + # trailing comment + """ + ) + ); + } + + @Test + void multiBytesUnicode() { + rewriteRun( + toml( + """ + robot.name = "r2d2" # 🤖 + """ + ) + ); + } +} diff --git a/rewrite-toml/src/test/java/org/openrewrite/toml/TomlVisitorTest.java b/rewrite-toml/src/test/java/org/openrewrite/toml/TomlVisitorTest.java new file mode 100644 index 00000000000..f7698844a20 --- /dev/null +++ b/rewrite-toml/src/test/java/org/openrewrite/toml/TomlVisitorTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed 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.openrewrite.toml; + +import org.junit.jupiter.api.Test; +import org.openrewrite.*; +import org.openrewrite.marker.Markup; +import org.openrewrite.test.RewriteTest; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.test.RewriteTest.toRecipe; +import static org.openrewrite.toml.Assertions.toml; + +class TomlVisitorTest implements RewriteTest { + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/665") + @Test + void visitMarkupErrorMarkers() { + List exceptions = new ArrayList<>(); + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new TreeVisitor<>() { + @Override + public Tree preVisit(Tree tree, ExecutionContext ctx) { + // Mimics what we do in the rewrite-gradle-plugin + tree.getMarkers().findFirst(Markup.Error.class).ifPresent(e -> { + Optional sourceFile = Optional.ofNullable(getCursor().firstEnclosing(SourceFile.class)); + String sourcePath = sourceFile.map(SourceFile::getSourcePath).map(Path::toString).orElse(""); + exceptions.add(new RuntimeException("Error while visiting " + sourcePath + ": " + e.getDetail())); + }); + return tree; + } + })), + toml( + """ + [versions] + jackson = '2.14.2' + + [libraries] + jackson-annotations = { module = 'com.fasterxml.jackson.core:jackson-annotations', version.ref = 'jackson' } + jackson-core = { module = 'com.fasterxml.jackson.core:jackson-core', version.ref = 'jackson' } + + [bundles] + jackson = ['jackson-annotations', 'jackson-core'] + """ + ) + ); + assertThat(exceptions).isEmpty(); + } +} diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeTagValue.java b/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeTagValue.java index 0773c33abba..254fb705ed7 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeTagValue.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeTagValue.java @@ -18,12 +18,16 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; +import org.openrewrite.marker.AlreadyReplaced; +import org.openrewrite.marker.Markers; import org.openrewrite.xml.tree.Xml; +import java.util.regex.Pattern; + +import static java.util.Collections.singletonList; +import static org.openrewrite.Tree.randomId; + @Value @EqualsAndHashCode(callSuper = false) public class ChangeTagValue extends Recipe { @@ -34,17 +38,24 @@ public class ChangeTagValue extends Recipe { String elementName; @Option(displayName = "Old value", - description = "The old value of the tag.", + description = "The old value of the tag. Interpreted as pattern if regex is enabled.", required = false, example = "user") @Nullable String oldValue; @Option(displayName = "New value", - description = "The new value for the tag.", + description = "The new value for the tag. Supports capture groups when regex is enabled. " + + "If literal $,\\ characters are needed in newValue, with regex true, then it should be escaped.", example = "user") String newValue; + @Option(displayName = "Regex", + description = "Default false. If true, `oldValue` will be interpreted as a [Regular Expression](https://en.wikipedia.org/wiki/Regular_expression), and capture group contents will be available in `newValue`.", + required = false) + @Nullable + Boolean regex; + @Override public String getDisplayName() { return "Change XML tag value"; @@ -52,7 +63,15 @@ public String getDisplayName() { @Override public String getDescription() { - return "Alters the value of XML tags matching the provided expression."; + return "Alters the value of XML tags matching the provided expression. " + + "When regex is enabled the replacement happens only for text nodes provided the pattern matches."; + } + + @Override + public Validated validate(ExecutionContext ctx) { + //noinspection ConstantValue + return super.validate(ctx).and(Validated.test("regex", "Regex usage requires an `oldValue`", regex, + value -> value == null || oldValue != null && !oldValue.equals(newValue))); } @Override @@ -63,9 +82,9 @@ public TreeVisitor getVisitor() { @Override public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { if (xPathMatcher.matches(getCursor())) { - // if either no oldValue is supplied OR oldValue equals the value of this tag - // then change the value to the newValue supplied - if (oldValue == null || oldValue.equals(tag.getValue().orElse(null))) { + if (Boolean.TRUE.equals(regex) && oldValue != null) { + doAfterVisit(new RegexReplaceVisitor<>(tag, oldValue, newValue)); + } else if (oldValue == null || oldValue.equals(tag.getValue().orElse(null))) { doAfterVisit(new ChangeTagValueVisitor<>(tag, newValue)); } } @@ -73,4 +92,48 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { } }; } + + /** + * This visitor finds and replaces text within a specified XML tag. + * It supports both literal and regular expression replacements. + * Use {@link ChangeTagValueVisitor} if you only wish change the value, irrespective of current data. + * + * @param

+ */ + @Value + @EqualsAndHashCode(callSuper = false) + static class RegexReplaceVisitor

extends XmlVisitor

{ + + Xml.Tag scope; + String oldValue; + String newValue; + + @Override + public Xml visitTag(Xml.Tag tag, P p) { + Xml.Tag t = (Xml.Tag) super.visitTag(tag, p); + if (scope.isScope(t) && + t.getContent() != null && + t.getContent().size() == 1 && + t.getContent().get(0) instanceof Xml.CharData) { + return updateUsingRegex(t, (Xml.CharData) t.getContent().get(0)); + } + return t; + } + + private Xml.Tag updateUsingRegex(Xml.Tag t, Xml.CharData content) { + String text = content.getText(); + if (Pattern.compile(oldValue).matcher(text).find()) { + Markers oldMarkers = content.getMarkers(); + if (oldMarkers + .findAll(AlreadyReplaced.class) + .stream() + .noneMatch(m -> m.getFind().equals(oldValue) && newValue.equals(m.getReplace()))) { + return t.withContent(singletonList(content + .withText(text.replaceAll(oldValue, newValue)) + .withMarkers(oldMarkers.add(new AlreadyReplaced(randomId(), oldValue, newValue))))); + } + } + return t; + } + } } diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaksVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaksVisitor.java index 90467568ff4..758ae736820 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaksVisitor.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaksVisitor.java @@ -21,6 +21,8 @@ import org.openrewrite.xml.XmlIsoVisitor; import org.openrewrite.xml.tree.Xml; +import static org.openrewrite.format.LineBreaks.normalizeNewLines; + public class NormalizeLineBreaksVisitor

extends XmlIsoVisitor

{ @Nullable private final Tree stopAfter; @@ -51,23 +53,6 @@ public NormalizeLineBreaksVisitor(GeneralFormatStyle style, @Nullable Tree stopA return super.visit(tree, p); } - private static String normalizeNewLines(String text, boolean useCrlf) { - if (!text.contains("\n")) { - return text; - } - StringBuilder normalized = new StringBuilder(); - char[] charArray = text.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (useCrlf && c == '\n' && (i == 0 || text.charAt(i - 1) != '\r')) { - normalized.append('\r').append('\n'); - } else if (useCrlf || c != '\r') { - normalized.append(c); - } - } - return normalized.toString(); - } - @Override public @Nullable Xml postVisit(Xml tree, P p) { if (stopAfter != null && stopAfter.isScope(tree)) { diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/security/RemoveOwaspSuppressions.java b/rewrite-xml/src/main/java/org/openrewrite/xml/security/RemoveOwaspSuppressions.java index 80a22dc8596..d071a8a5acb 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/security/RemoveOwaspSuppressions.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/security/RemoveOwaspSuppressions.java @@ -17,10 +17,8 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.xml.XPathMatcher; import org.openrewrite.xml.XmlIsoVisitor; @@ -39,11 +37,18 @@ public String getDisplayName() { return "Remove out-of-date OWASP suppressions"; } + @Option(displayName = "Until date", + required = false, + description = "Suppressions will be removed if they expired before the provided date. Default will be yesterday.", + example = "2023-01-01") + @Nullable + String cutOffDate; + @Override public String getDescription() { return "Remove all OWASP suppressions with a suppression end date in the past, as these are no longer valid. " + - "For use with the OWASP `dependency-check` tool. " + - "More details on OWASP suppression files can be found [here](https://jeremylong.github.io/DependencyCheck/general/suppression.html)."; + "For use with the OWASP `dependency-check` tool. " + + "More details on OWASP suppression files can be found [here](https://jeremylong.github.io/DependencyCheck/general/suppression.html)."; } @Override @@ -69,8 +74,12 @@ private boolean isPastDueSuppression(Content content) { maybeDate = maybeDate.substring(0, maybeDate.length() - 1); } try { + LocalDate maxDate = LocalDate.now().minusDays(1); + if (cutOffDate != null) { + maxDate = LocalDate.parse(cutOffDate); + } LocalDate date = LocalDate.parse(maybeDate); - if (date.isBefore(LocalDate.now().minusDays(1))) { + if (date.isBefore(maxDate)) { return true; } } catch (DateTimeParseException e) { diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/style/Autodetect.java b/rewrite-xml/src/main/java/org/openrewrite/xml/style/Autodetect.java index 22cf238a7ae..2992a20a659 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/style/Autodetect.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/style/Autodetect.java @@ -52,12 +52,12 @@ public static class Detector { private final IndentStatistics indentStatistics = new IndentStatistics(); private final GeneralFormatStatistics generalFormatStatistics = new GeneralFormatStatistics(); private final FindIndentXmlVisitor findIndentXmlVisitor = new FindIndentXmlVisitor(); - private final FindLineFormatJavaVisitor findLineFormatJavaVisitor = new FindLineFormatJavaVisitor(); + private final FindLineFormatXmlVisitor findLineFormatXmlVisitor = new FindLineFormatXmlVisitor(); public void sample(SourceFile xml) { if(xml instanceof Xml.Document) { findIndentXmlVisitor.visit(xml, indentStatistics); - findLineFormatJavaVisitor.visit(xml, generalFormatStatistics); + findLineFormatXmlVisitor.visit(xml, generalFormatStatistics); } } @@ -131,7 +131,7 @@ private TabsAndIndentsStyle getTabsAndIndentsStyle() { } } - private static class FindLineFormatJavaVisitor extends XmlVisitor { + private static class FindLineFormatXmlVisitor extends XmlVisitor { @Override public @Nullable Xml visit(@Nullable Tree tree, GeneralFormatStatistics stats) { if (tree instanceof Xml) { diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringReference.java b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringReference.java deleted file mode 100644 index a2a78481461..00000000000 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringReference.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * Licensed 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.openrewrite.xml.trait; - -import lombok.Value; -import org.jspecify.annotations.Nullable; -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.SourceFile; -import org.openrewrite.Tree; -import org.openrewrite.trait.Reference; -import org.openrewrite.trait.SimpleTraitMatcher; -import org.openrewrite.xml.XPathMatcher; -import org.openrewrite.xml.tree.Xml; - -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.regex.Pattern; - -@Value -class SpringReference implements Reference { - Cursor cursor; - Kind kind; - - @Override - public Tree getTree() { - return Reference.super.getTree(); - } - - @Override - public Kind getKind() { - return kind; - } - - @Override - public String getValue() { - if (getTree() instanceof Xml.Attribute) { - Xml.Attribute attribute = (Xml.Attribute) getTree(); - return attribute.getValueAsString(); - } else if (getTree() instanceof Xml.Tag) { - Xml.Tag tag = (Xml.Tag) getTree(); - if (tag.getValue().isPresent()) { - return tag.getValue().get(); - } - } - throw new IllegalArgumentException("getTree() must be an Xml.Attribute or Xml.Tag: " + getTree().getClass()); - } - - @Override - public boolean supportsRename() { - return true; - } - - @Override - public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { - Tree tree = cursor.getValue(); - if (tree instanceof Xml.Attribute) { - Xml.Attribute attribute = (Xml.Attribute) tree; - String renamed = renamer.rename(this); - return attribute.withValue(attribute.getValue().withValue(renamed)); - } else if (tree instanceof Xml.Tag && ((Xml.Tag) tree).getValue().isPresent()) { - String renamed = renamer.rename(this); - return ((Xml.Tag) tree).withValue(renamed); - } - return tree; - } - - static class Matcher extends SimpleTraitMatcher { - private final Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*"); - private final XPathMatcher classXPath = new XPathMatcher("//@class"); - private final XPathMatcher typeXPath = new XPathMatcher("//@type"); - private final XPathMatcher keyTypeXPath = new XPathMatcher("//@key-type"); - private final XPathMatcher valueTypeXPath = new XPathMatcher("//@value-type"); - private final XPathMatcher tags = new XPathMatcher("//value"); - - @Override - protected @Nullable SpringReference test(Cursor cursor) { - Object value = cursor.getValue(); - if (value instanceof Xml.Attribute) { - Xml.Attribute attrib = (Xml.Attribute) value; - if (classXPath.matches(cursor) || typeXPath.matches(cursor) || keyTypeXPath.matches(cursor) || valueTypeXPath.matches(cursor)) { - String stringVal = attrib.getValueAsString(); - if (referencePattern.matcher(stringVal).matches()) { - return new SpringReference(cursor, determineKind(stringVal)); - } - } - } else if (value instanceof Xml.Tag) { - Xml.Tag tag = (Xml.Tag) value; - if (tags.matches(cursor)) { - Optional stringVal = tag.getValue(); - if (stringVal.isPresent() && referencePattern.matcher(stringVal.get()).matches()) { - return new SpringReference(cursor, determineKind(stringVal.get())); - } - } - } - return null; - } - - Kind determineKind(String value) { - return Character.isUpperCase(value.charAt(value.lastIndexOf('.') + 1)) ? Kind.TYPE : Kind.PACKAGE; - } - } - - @SuppressWarnings("unused") - public static class Provider implements Reference.Provider { - - @Override - public Set getReferences(SourceFile sourceFile) { - Set references = new HashSet<>(); - new Matcher().asVisitor(reference -> { - references.add(reference); - return reference.getTree(); - }).visit(sourceFile, 0); - return references; - } - - @Override - public boolean isAcceptable(SourceFile sourceFile) { - if (sourceFile instanceof Xml.Document) { - Xml.Document doc = (Xml.Document) sourceFile; - //noinspection ConstantValue - if (doc.getRoot() != null) { - for (Xml.Attribute attrib : doc.getRoot().getAttributes()) { - if (attrib.getKeyAsString().equals("xsi:schemaLocation") && attrib.getValueAsString().contains("www.springframework.org/schema/beans")) { - return true; - } - } - } - } - return false; - } - } -} diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java new file mode 100644 index 00000000000..0d0db0a51da --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java @@ -0,0 +1,101 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.xml.trait; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.SourceFile; +import org.openrewrite.trait.Reference; +import org.openrewrite.trait.SimpleTraitMatcher; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Optional; +import java.util.regex.Pattern; + +@Value +@EqualsAndHashCode(callSuper = false) +public class SpringXmlReference extends XmlReference { + + Cursor cursor; + Kind kind; + + @Override + public Kind getKind() { + return kind; + } + + public static class Provider extends AbstractProvider { + private static final SimpleTraitMatcher matcher = new SimpleTraitMatcher() { + private final Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*"); + private final XPathMatcher classXPath = new XPathMatcher("//@class"); + private final XPathMatcher typeXPath = new XPathMatcher("//@type"); + private final XPathMatcher keyTypeXPath = new XPathMatcher("//@key-type"); + private final XPathMatcher valueTypeXPath = new XPathMatcher("//@value-type"); + private final XPathMatcher tags = new XPathMatcher("//value"); + + @Override + protected @Nullable SpringXmlReference test(Cursor cursor) { + Object value = cursor.getValue(); + if (value instanceof Xml.Attribute) { + Xml.Attribute attrib = (Xml.Attribute) value; + if (classXPath.matches(cursor) || typeXPath.matches(cursor) || keyTypeXPath.matches(cursor) || valueTypeXPath.matches(cursor)) { + String stringVal = attrib.getValueAsString(); + if (referencePattern.matcher(stringVal).matches()) { + return new SpringXmlReference(cursor, determineKind(stringVal)); + } + } + } else if (value instanceof Xml.Tag) { + Xml.Tag tag = (Xml.Tag) value; + if (tags.matches(cursor)) { + Optional stringVal = tag.getValue(); + if (stringVal.isPresent() && referencePattern.matcher(stringVal.get()).matches()) { + return new SpringXmlReference(cursor, determineKind(stringVal.get())); + } + } + } + return null; + } + + Reference.Kind determineKind(String value) { + return Character.isUpperCase(value.charAt(value.lastIndexOf('.') + 1)) ? Reference.Kind.TYPE : Reference.Kind.PACKAGE; + } + }; + + @Override + public boolean isAcceptable(SourceFile sourceFile) { + if (sourceFile instanceof Xml.Document) { + Xml.Document doc = (Xml.Document) sourceFile; + //noinspection ConstantValue + if (doc.getRoot() != null) { + for (Xml.Attribute attrib : doc.getRoot().getAttributes()) { + if (attrib.getKeyAsString().equals("xsi:schemaLocation") && attrib.getValueAsString().contains("www.springframework.org/schema/beans")) { + return true; + } + } + } + } + return false; + } + + @Override + public SimpleTraitMatcher getMatcher() { + return matcher; + } + } +} diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/trait/XmlReference.java b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/XmlReference.java new file mode 100644 index 00000000000..25b87149d2d --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/XmlReference.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.xml.trait; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.trait.Reference; +import org.openrewrite.xml.tree.Xml; + +public abstract class XmlReference implements Reference { + + @Override + public Tree getTree() { + return Reference.super.getTree(); + } + + @Override + public String getValue() { + if (getTree() instanceof Xml.Attribute) { + Xml.Attribute attribute = (Xml.Attribute) getTree(); + return attribute.getValueAsString(); + } else if (getTree() instanceof Xml.Tag) { + Xml.Tag tag = (Xml.Tag) getTree(); + if (tag.getValue().isPresent()) { + return tag.getValue().get(); + } + } + throw new IllegalArgumentException("getTree() must be an Xml.Attribute or Xml.Tag: " + getTree().getClass()); + } + + @Override + public boolean supportsRename() { + return true; + } + + @Override + public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { + Tree tree = cursor.getValue(); + if (tree instanceof Xml.Attribute) { + Xml.Attribute attribute = (Xml.Attribute) tree; + String renamed = renamer.rename(this); + return attribute.withValue(attribute.getValue().withValue(renamed)); + } else if (tree instanceof Xml.Tag && ((Xml.Tag) tree).getValue().isPresent()) { + String renamed = renamer.rename(this); + return ((Xml.Tag) tree).withValue(renamed); + } + return tree; + } + +} diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java b/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java index 1fabf24388c..138f470ab7a 100755 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java @@ -31,15 +31,11 @@ import org.openrewrite.xml.internal.XmlPrinter; import org.openrewrite.xml.internal.XmlWhitespaceValidationService; -import java.beans.Transient; import java.lang.ref.SoftReference; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import static java.util.Collections.emptyList; @@ -171,23 +167,11 @@ public T service(Class service) { @NonFinal transient SoftReference references; - @Transient @Override public References getReferences() { - References cache; - if (this.references == null) { - cache = References.build(this); - this.references = new SoftReference<>(cache); - } else { - cache = this.references.get(); - if (cache == null || cache.getSourceFile() != this) { - cache = References.build(this); - this.references = new SoftReference<>(cache); - } - } - return cache; + this.references = build(this.references); + return Objects.requireNonNull(this.references.get()); } - } @Value diff --git a/rewrite-xml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider b/rewrite-xml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider index 72d76d75ee4..e8a9ab1dc42 100644 --- a/rewrite-xml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider +++ b/rewrite-xml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider @@ -1 +1 @@ -org.openrewrite.xml.trait.SpringReference$Provider \ No newline at end of file +org.openrewrite.xml.trait.SpringXmlReference$Provider \ No newline at end of file diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueTest.java new file mode 100755 index 00000000000..aaa97e84ae6 --- /dev/null +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueTest.java @@ -0,0 +1,314 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed 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.openrewrite.xml; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.ExecutionContext; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.xml.tree.Xml; + +import static java.util.Objects.requireNonNull; +import static org.openrewrite.test.RewriteTest.toRecipe; +import static org.openrewrite.xml.Assertions.xml; + +class ChangeTagValueTest implements RewriteTest { + + @DocumentExample + @Test + void rewriteEmptyTagValue() { + rewriteRun( + spec -> spec.recipe( + new ChangeTagValue("/dependency/version", + null, "2.0", null)), + xml( + """ + + + + """, + """ + + 2.0 + + """) + ); + } + + @Test + void matchExistingTagValue() { + rewriteRun( + spec -> spec.recipe( + new ChangeTagValue("/dependency/version", + "1.0", "2.0", null)), + xml( + """ + + 1.0 + + """, + """ + + 2.0 + + """) + ); + } + + @Test + void noMatchExistingTagValue() { + rewriteRun( + spec -> spec.recipe( + new ChangeTagValue("/dependency/version", + "1.0", "2.0", null)), + xml( + """ + + 3.0 + + """) + ); + } + + @Test + void rewriteTagValueSubstring() { + rewriteRun( + spec -> spec.recipe( + new ChangeTagValue("/dependency/version", + "SNAPSHOT", "RELEASE", true) + ), + xml( + """ + + com.company.project + artifact + 1.2.3-SNAPSHOT + + """, + """ + + com.company.project + artifact + 1.2.3-RELEASE + + """ + ) + ); + } + + + @Test + void appendTagValue() { + rewriteRun( + spec -> spec.recipe( + new ChangeTagValue("/dependency/version", + "$", "-RELEASE", true) + ), + xml( + """ + + com.company.project + artifact + 1.2.3 + + """, + """ + + com.company.project + artifact + 1.2.3-RELEASE + + """ + ) + ); + } + + + @Test + void replaceWithCapturingGroups() { + rewriteRun( + spec -> spec.recipe( + new ChangeTagValue("/dependency/version", + "(\\d).(\\d).(\\d)", "$1.$3.4", true) + ), + xml( + """ + + com.company.project + artifact + 1.2.3 + + """, + """ + + com.company.project + artifact + 1.3.4 + + """ + ) + ); + } + + @Test + void replacedExactlyOnce() { + rewriteRun( + spec -> spec.recipe(new ChangeTagValue( + "/tag", + "(aaa)", + "$1-aaa", + true)), + xml( + "aaa", + "aaa-aaa" + ) + ); + } + + @Nested + class FindAndReplaceTagTextVisitorTest { + + @Test + void findAndReplace() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new ChangeTagValue.RegexReplaceVisitor<>( + (Xml.Tag) requireNonNull(x.getRoot().getContent()).get(0), + "2.0", + "3.0") + ); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + 2.0 + + + """, + """ + + + 3.0 + + + """ + ) + ); + } + + @Test + void changeContentSubstring() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new ChangeTagValue.RegexReplaceVisitor<>( + requireNonNull(x.getRoot()), + "SNAPSHOT", + "RELEASE") + ); + return super.visitDocument(x, ctx); + } + })), + xml( + "1.2.3-SNAPSHOT", + "1.2.3-RELEASE" + ) + ); + } + + @Test + void doNothingIfPatternDoesNotMatch() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new ChangeTagValue.RegexReplaceVisitor<>( + (Xml.Tag) requireNonNull(x.getRoot().getContent()).get(0), + "7.0", + "8.0") + ); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + + + + + """ + ) + ); + } + + @Test + void doNothingForNonTextNotFound() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new ChangeTagValue.RegexReplaceVisitor<>( + (Xml.Tag) requireNonNull(x.getRoot().getContent()).get(0), + "7.0", + "8.0") + ); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + 2.0 + + """ + ) + ); + } + + @Test + void skipsChildIfNotText() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new ChangeTagValue.RegexReplaceVisitor<>( + (Xml.Tag) requireNonNull(x.getRoot().getContent()).get(0), + "invalid", + "2.0") + ); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + + """ + ) + ); + } + } +} diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueVisitorTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueVisitorTest.java index 8d844e515bc..808c19e7eb2 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueVisitorTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeTagValueVisitorTest.java @@ -53,6 +53,26 @@ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { ); } + @Test + void noChangeIfAlreadyPresent() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new ChangeTagValueVisitor<>((Xml.Tag) requireNonNull(x.getRoot().getContent()).get(0), "2.0")); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + 2.0 + + """ + ) + ); + } + @Test void preserveOriginalFormatting() { rewriteRun( diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/XmlParserTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/XmlParserTest.java index cff46087276..57ae47a0905 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/XmlParserTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/XmlParserTest.java @@ -146,8 +146,8 @@ void javaReferenceDocument() { """, spec -> spec.afterRecipe(doc -> { assertThat(doc.getReferences().getReferences().stream().anyMatch(typeRef -> typeRef.getValue().equals("java.lang.String"))).isTrue(); - assertThat(doc.getReferences().getReferences().stream().anyMatch(typeRef -> typeRef.getKind().equals(Reference.Kind.TYPE))).isTrue(); - assertThat(doc.getReferences().getReferences().stream().anyMatch(typeRef -> typeRef.getKind().equals(Reference.Kind.PACKAGE))).isTrue(); + assertThat(doc.getReferences().getReferences().stream().anyMatch(typeRef -> typeRef.getKind() == Reference.Kind.TYPE)).isTrue(); + assertThat(doc.getReferences().getReferences().stream().anyMatch(typeRef -> typeRef.getKind() == Reference.Kind.PACKAGE)).isTrue(); }) ) ); diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/security/RemoveOwaspSuppressionsTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/security/RemoveOwaspSuppressionsTest.java index d2c06eb121b..27f004ae1de 100644 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/security/RemoveOwaspSuppressionsTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/security/RemoveOwaspSuppressionsTest.java @@ -27,7 +27,7 @@ class RemoveOwaspSuppressionsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new RemoveOwaspSuppressions()); + spec.recipe(new RemoveOwaspSuppressions(null)); } @Test @@ -159,4 +159,40 @@ void ignoresMalformedDates() { spec -> spec.path("suppressions.xml")) ); } + + @Test + void provideExplicitDateRange() { + rewriteRun( + spec -> spec.recipe(new RemoveOwaspSuppressions("2023-02-01")), + xml(""" + + + + + + + """, + """ + + + """, + spec -> spec.path("suppressions.xml")) + ); + } + + @Test + void provideExplicitDateRangeDoNotRemove() { + rewriteRun( + spec -> spec.recipe(new RemoveOwaspSuppressions("2023-01-01")), + xml(""" + + + + + + + """, + spec -> spec.path("suppressions.xml")) + ); + } } diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/trait/SpringReferenceTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/trait/SpringXmlReferenceTest.java similarity index 96% rename from rewrite-xml/src/test/java/org/openrewrite/xml/trait/SpringReferenceTest.java rename to rewrite-xml/src/test/java/org/openrewrite/xml/trait/SpringXmlReferenceTest.java index d60f5a2e8a9..1eec75fbb0a 100644 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/trait/SpringReferenceTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/trait/SpringXmlReferenceTest.java @@ -23,11 +23,11 @@ import static org.openrewrite.xml.Assertions.xml; -class SpringReferenceTest implements RewriteTest { +class SpringXmlReferenceTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(RewriteTest.toRecipe(() -> new SpringReference.Matcher() + spec.recipe(RewriteTest.toRecipe(() -> new SpringXmlReference.Provider().getMatcher() .asVisitor(springJavaTypeReference -> SearchResult.found(springJavaTypeReference.getTree(), springJavaTypeReference.getValue())))); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java index 83d5563bc10..8d94aa04c3b 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java @@ -17,7 +17,11 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.*; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; import org.openrewrite.yaml.tree.Yaml; import java.util.ArrayDeque; @@ -40,6 +44,12 @@ public class CommentOutProperty extends Recipe { example = "The `foo` property is deprecated, please migrate") String commentText; + @Option(example = "true", displayName = "Comment out property", + description = "If false, property wouldn't be commented out, only comment will be added. By default, set to true", + required = false) + @Nullable + Boolean commentOutProperty; + @Override public String getDisplayName() { return "Comment out property"; @@ -61,6 +71,7 @@ public TreeVisitor getVisitor() { private boolean nextDocNeedsNewline; private String comment = ""; private String indentation = ""; + private boolean isBlockCommentExists; @Override public Yaml.Document visitDocument(Yaml.Document document, ExecutionContext ctx) { @@ -72,7 +83,7 @@ public Yaml.Document visitDocument(Yaml.Document document, ExecutionContext ctx) } // Add any leftover comment to the end of document - if (!comment.isEmpty()) { + if (!comment.isEmpty() && !Boolean.FALSE.equals(commentOutProperty)) { String newPrefix = String.format("%s# %s%s%s", indentation, commentText, @@ -89,7 +100,9 @@ public Yaml.Document visitDocument(Yaml.Document document, ExecutionContext ctx) public Yaml.Sequence.Entry visitSequenceEntry(Yaml.Sequence.Entry entry, ExecutionContext ctx) { indentation = entry.getPrefix(); - if (!comment.isEmpty()) { + if (Boolean.FALSE.equals(commentOutProperty)) { + return addBockCommentIfNecessary(entry, ctx); + } else if (!comment.isEmpty()) { // add comment and return String newPrefix = entry.getPrefix() + "# " + commentText + comment + entry.getPrefix(); comment = ""; @@ -103,20 +116,13 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC String lastIndentation = indentation; indentation = entry.getPrefix(); - if (!comment.isEmpty()) { + if (!comment.isEmpty() && !Boolean.FALSE.equals(commentOutProperty)) { String newPrefix = entry.getPrefix() + "# " + commentText + comment + entry.getPrefix(); comment = ""; return entry.withPrefix(newPrefix); } - Deque propertyEntries = getCursor().getPathAsStream() - .filter(Yaml.Mapping.Entry.class::isInstance) - .map(Yaml.Mapping.Entry.class::cast) - .collect(Collectors.toCollection(ArrayDeque::new)); - - String prop = stream(spliteratorUnknownSize(propertyEntries.descendingIterator(), 0), false) - .map(e2 -> e2.getKey().getValue()) - .collect(Collectors.joining(".")); + String prop = calculateCurrentKeyPath(); if (prop.equals(propertyKey)) { String prefix = entry.getPrefix(); @@ -128,12 +134,50 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC comment = lastIndentation + "#" + entry.print(getCursor().getParentTreeCursor()); } - doAfterVisit(new DeleteProperty(propertyKey, null, null, null).getVisitor()); + if (Boolean.FALSE.equals(commentOutProperty)) { + if (!entry.getPrefix().contains(commentText) && !isBlockCommentExists) { + return entry.withPrefix(entry.getPrefix() + "# " + commentText + (entry.getPrefix().contains("\n") ? entry.getPrefix() : "\n" + entry.getPrefix())); + } + } else { + doAfterVisit(new DeleteProperty(propertyKey, null, null, null).getVisitor()); + } return entry; } return super.visitMappingEntry(entry, ctx); } + + private Yaml.Sequence.Entry addBockCommentIfNecessary(Yaml.Sequence.Entry entry, ExecutionContext ctx) { + boolean propertyExistsInSequence = isPropertyExistsInSequence(entry); + if (propertyExistsInSequence) { + isBlockCommentExists = true; + if (!entry.getPrefix().contains(commentText)) { + return entry.withPrefix(entry.getPrefix() + "# " + commentText + (entry.getPrefix().contains("\n") ? entry.getPrefix() : "\n" + entry.getPrefix())); + } + } + return super.visitSequenceEntry(entry, ctx); + } + + private boolean isPropertyExistsInSequence(Yaml.Sequence.Entry entry) { + if (!(entry.getBlock() instanceof Yaml.Mapping)) { + return false; + } + Yaml.Mapping mapping = (Yaml.Mapping) entry.getBlock(); + String prop = calculateCurrentKeyPath(); + return mapping.getEntries().stream() + .anyMatch(e -> propertyKey.equals(prop + "." + e.getKey().getValue())); + } + + private String calculateCurrentKeyPath() { + Deque propertyEntries = getCursor().getPathAsStream() + .filter(Yaml.Mapping.Entry.class::isInstance) + .map(Yaml.Mapping.Entry.class::cast) + .collect(Collectors.toCollection(ArrayDeque::new)); + + return stream(spliteratorUnknownSize(propertyEntries.descendingIterator(), 0), false) + .map(e2 -> e2.getKey().getValue()) + .collect(Collectors.joining(".")); + } }; } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CreateYamlFile.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CreateYamlFile.java index 157cb2f7db3..adbcf45ecd0 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CreateYamlFile.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CreateYamlFile.java @@ -46,7 +46,10 @@ public class CreateYamlFile extends ScanningRecipe { @Language("yml") @Option(displayName = "File contents", description = "Multiline text content for the file.", - example = "a:\nproperty: value\nanother:\nproperty: value", + example = "a:\n" + + " property: value\n" + + "another:\n" + + " property: value", required = false) @Nullable String fileContents; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index ca7685f37ba..f548f021801 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -52,7 +52,7 @@ import static org.openrewrite.Tree.randomId; public class YamlParser implements org.openrewrite.Parser { - private static final Pattern VARIABLE_PATTERN = Pattern.compile(":\\s*(@[^\n\r@]+@)"); + private static final Pattern VARIABLE_PATTERN = Pattern.compile(":\\s+(@[^\n\r@]+@)"); @Override public Stream parse(@Language("yml") String... sources) { @@ -194,7 +194,6 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr } case Scalar: { String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; ScalarEvent scalar = (ScalarEvent) event; @@ -207,6 +206,9 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr } else { valueStart = lastEnd + fmt.length(); } + valueStart = valueStart - newLine.length(); + newLine = ""; + String scalarValue; switch (scalar.getScalarStyle()) { diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/format/NormalizeLineBreaksVisitor.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/format/NormalizeLineBreaksVisitor.java index b50e8e5db17..1077bae9366 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/format/NormalizeLineBreaksVisitor.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/format/NormalizeLineBreaksVisitor.java @@ -21,6 +21,8 @@ import org.openrewrite.yaml.YamlIsoVisitor; import org.openrewrite.yaml.tree.Yaml; +import static org.openrewrite.format.LineBreaks.normalizeNewLines; + public class NormalizeLineBreaksVisitor

extends YamlIsoVisitor

{ private final GeneralFormatStyle generalFormatStyle; @@ -55,22 +57,4 @@ public NormalizeLineBreaksVisitor(GeneralFormatStyle generalFormatStyle, @Nullab } return y; } - - private static String normalizeNewLines(String text, boolean useCrlf) { - if (!text.contains("\n")) { - return text; - } - - StringBuilder normalized = new StringBuilder(); - char[] charArray = text.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (useCrlf && c == '\n' && (i == 0 || text.charAt(i - 1) != '\r')) { - normalized.append('\r').append('\n'); - } else if (useCrlf || c != '\r') { - normalized.append(c); - } - } - return normalized.toString(); - } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/style/Autodetect.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/style/Autodetect.java index b57ec6692e6..d6a9e1920de 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/style/Autodetect.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/style/Autodetect.java @@ -35,7 +35,7 @@ public static IndentsStyle tabsAndIndents(Yaml yaml, IndentsStyle orElse) { } public static GeneralFormatStyle generalFormat(Yaml yaml) { - FindLineFormatJavaVisitor findLineFormat = new FindLineFormatJavaVisitor<>(); + FindLineFormatYamlVisitor findLineFormat = new FindLineFormatYamlVisitor<>(); //noinspection ConstantConditions findLineFormat.visit(yaml, 0); @@ -43,7 +43,7 @@ public static GeneralFormatStyle generalFormat(Yaml yaml) { return new GeneralFormatStyle(!findLineFormat.isIndentedWithLFNewLines()); } - private static class FindLineFormatJavaVisitor

extends YamlIsoVisitor

{ + private static class FindLineFormatYamlVisitor

extends YamlIsoVisitor

{ private int linesWithCRLFNewLines = 0; private int linesWithLFNewLines = 0; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java new file mode 100644 index 00000000000..693f6bcdc77 --- /dev/null +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.yaml.trait; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.SourceFile; +import org.openrewrite.trait.SimpleTraitMatcher; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.function.Predicate; +import java.util.regex.Pattern; + +@Value +@EqualsAndHashCode(callSuper = false) +public class YamlApplicationConfigReference extends YamlReference { + Cursor cursor; + @Getter + Kind kind; + + public static class Provider extends YamlProvider { + private static final Predicate applicationPropertiesMatcher = Pattern.compile("^application(-\\w+)?\\.(yaml|yml)$").asPredicate(); + private static final SimpleTraitMatcher matcher = new SimpleTraitMatcher() { + private final Predicate javaFullyQualifiedTypePattern = Pattern.compile( + "^\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" + + "\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" + + "(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*$").asPredicate(); + + @Override + protected @Nullable YamlReference test(Cursor cursor) { + Object value = cursor.getValue(); + if (value instanceof Yaml.Scalar && javaFullyQualifiedTypePattern.test(((Yaml.Scalar) value).getValue())) { + return new YamlApplicationConfigReference(cursor, determineKind(((Yaml.Scalar) value).getValue())); + } + return null; + } + + private Kind determineKind(String value) { + return Character.isUpperCase(value.charAt(value.lastIndexOf('.') + 1)) ? Kind.TYPE : Kind.PACKAGE; + } + }; + + @Override + public boolean isAcceptable(SourceFile sourceFile) { + return super.isAcceptable(sourceFile) && applicationPropertiesMatcher.test(sourceFile.getSourcePath().getFileName().toString()); + } + + @Override + public SimpleTraitMatcher getMatcher() { + return matcher; + } + } +} diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java new file mode 100644 index 00000000000..18ced348a74 --- /dev/null +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.yaml.trait; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.trait.Reference; +import org.openrewrite.yaml.tree.Yaml; + +public abstract class YamlReference implements Reference { + @Override + public String getValue() { + if (getTree() instanceof Yaml.Scalar) { + return ((Yaml.Scalar) getTree()).getValue(); + } + throw new IllegalArgumentException("getTree() must be an Yaml.Scalar: " + getTree().getClass()); + } + + @Override + public boolean supportsRename() { + return true; + } + + @Override + public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { + Tree tree = cursor.getValue(); + if (tree instanceof Yaml.Scalar) { + return ((Yaml.Scalar) tree).withValue(renamer.rename(this)); + } + throw new IllegalArgumentException("cursor.getValue() must be an Yaml.Scalar but is: " + tree.getClass()); + } + + public static abstract class YamlProvider extends AbstractProvider { + @Override + public boolean isAcceptable(SourceFile sourceFile) { + return sourceFile instanceof Yaml.Documents; + } + } +} diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/package-info.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/package-info.java new file mode 100644 index 00000000000..b2cf0ce8763 --- /dev/null +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.yaml.trait; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java index 5245d9ab397..13ab313924b 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java @@ -17,16 +17,19 @@ import lombok.*; import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.marker.Markers; import org.openrewrite.yaml.YamlVisitor; import org.openrewrite.yaml.internal.YamlPrinter; +import java.lang.ref.SoftReference; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; +import java.util.Objects; import java.util.UUID; import static java.util.stream.Collectors.toList; @@ -60,8 +63,10 @@ default

boolean isAcceptable(TreeVisitor v, P p) { @Value @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) @With - class Documents implements Yaml, SourceFile { + class Documents implements Yaml, SourceFileWithReferences { @EqualsAndHashCode.Include UUID id; @@ -125,6 +130,16 @@ public Documents withPrefix(String prefix) { public

TreeVisitor> printer(Cursor cursor) { return new YamlPrinter<>(); } + + @Nullable + @NonFinal + transient SoftReference references; + + @Override + public References getReferences() { + this.references = build(this.references); + return Objects.requireNonNull(this.references.get()); + } } @Value diff --git a/rewrite-yaml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider b/rewrite-yaml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider new file mode 100644 index 00000000000..a51673a11b4 --- /dev/null +++ b/rewrite-yaml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider @@ -0,0 +1 @@ +org.openrewrite.yaml.trait.YamlApplicationConfigReference$Provider diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/CommentOutPropertyTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/CommentOutPropertyTest.java index 57334b24734..9f5d7dcf239 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/CommentOutPropertyTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/CommentOutPropertyTest.java @@ -17,24 +17,17 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.yaml.Assertions.yaml; class CommentOutPropertyTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new CommentOutProperty("management.metrics.binders.files.enabled", "some comments")); - } - @DocumentExample("comment out a map entry") @Test void regular() { rewriteRun( - spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence.propertyA", - "Some comments")), + spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence.propertyA", "Some comments", null)), yaml( """ foo: @@ -63,8 +56,7 @@ void regular() { @Test void commentSequence() { rewriteRun( - spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence", - "Some comments")), + spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence", "Some comments", null)), yaml( """ foo: @@ -92,8 +84,7 @@ void commentSequence() { @Test void commentSingleProperty() { rewriteRun( - spec -> spec.recipe(new CommentOutProperty("with.java-version", - "Some comments")), + spec -> spec.recipe(new CommentOutProperty("with.java-version", "Some comments", null)), yaml( """ with: @@ -115,8 +106,7 @@ void commentSingleProperty() { @Test void commentLastPropertyWithIndent() { rewriteRun( - spec -> spec.recipe(new CommentOutProperty("with.java-version", - "Some comments")), + spec -> spec.recipe(new CommentOutProperty("with.java-version", "Some comments", null)), yaml( """ with: @@ -136,8 +126,7 @@ void commentLastPropertyWithIndent() { @Test void commentLastPropertyOfFirstDocument() { rewriteRun( - spec -> spec.recipe(new CommentOutProperty("with.java-version", - "Some comments")), + spec -> spec.recipe(new CommentOutProperty("with.java-version", "Some comments", null)), yaml( """ with: @@ -159,8 +148,7 @@ void commentLastPropertyOfFirstDocument() { @Test void commentLastProperty() { rewriteRun( - spec -> spec.recipe(new CommentOutProperty("test", - "Some comments")), + spec -> spec.recipe(new CommentOutProperty("test", "Some comments", null)), yaml( """ test: foo @@ -172,4 +160,172 @@ void commentLastProperty() { ) ); } + + @DocumentExample("comment out a map entry") + @Test + void sequenceKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence.propertyA", + "Some comments", false)), + yaml( + """ + foo: + bar: + sequence: + - name: name + - propertyA: fieldA + - propertyB: fieldB + scalar: value + """, + """ + foo: + bar: + sequence: + - name: name + # Some comments + - propertyA: fieldA + - propertyB: fieldB + scalar: value + """ + ) + ); + } + + @DocumentExample("comment out a map entry") + @Test + void sequenceFirstKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence.name", "Some comments", false)), + yaml( + """ + foo: + bar: + sequence: + - name: name + - propertyA: fieldA + - propertyB: fieldB + scalar: value + """, + """ + foo: + bar: + sequence: + # Some comments + - name: name + - propertyA: fieldA + - propertyB: fieldB + scalar: value + """ + ) + ); + } + + @DocumentExample("comment out entire sequence") + @Test + void commentSequenceKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("foo.bar.sequence", "Some comments", false)), + yaml( + """ + foo: + bar: + sequence: + - name: name + - propertyA: fieldA + - propertyB: fieldB + scalar: value + """, + """ + foo: + bar: + # Some comments + sequence: + - name: name + - propertyA: fieldA + - propertyB: fieldB + scalar: value + """ + ) + ); + } + + @Test + void commentSinglePropertyKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("with.java-version", "Some comments", false)), + yaml( + """ + with: + java-cache: 'maven' + java-version: 11 + test: 'bar' + """, + """ + with: + java-cache: 'maven' + # Some comments + java-version: 11 + test: 'bar' + """ + ) + ); + } + + @Test + void commentLastPropertyWithIndentKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("with.java-version", "Some comments", false)), + yaml( + """ + with: + java-cache: 'maven' + java-version: 11 + """, + """ + with: + java-cache: 'maven' + # Some comments + java-version: 11 + """ + ) + ); + } + + @Test + void commentLastPropertyOfFirstDocumentKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("with.java-version", "Some comments", false)), + yaml( + """ + with: + java-cache: 'maven' + java-version: 11 + --- + """, + """ + with: + java-cache: 'maven' + # Some comments + java-version: 11 + --- + """ + ) + ); + } + + @Test + void commentLastPropertyKeepProperty() { + rewriteRun( + spec -> spec.recipe(new CommentOutProperty("test", "Some comments", false)), + yaml( + """ + test: foo + """, + """ + # Some comments + test: foo + """ + ) + ); + } } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index e3033a4b827..73dba6f1336 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -216,4 +216,33 @@ void troublesomeYaml() { ) ); } + + @Test + void atSymbols() { + rewriteRun( + yaml( + // BTW, the @ sign is forbidden as the first character of a scalar value by the YAML spec: + // https://github.com/yaml/yaml-spec/blob/1b1a1be43bd6e0cfec45caf0e40af3b5d2bb7f8a/spec/1.2.2/spec.md#L1877 + """ + root: + specifier: npm:@testing-library/vue@5.0.4 + date: @build.timestamp@ + version: @project.version@ + """ + ) + ); + } + + @Test + void pipeLiteralInASequenceWithDoubleQuotes() { + rewriteRun( + yaml( + """ + - "one": | + two + "three": "four" + """ + ) + ); + } } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/YamlReferenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/YamlReferenceTest.java new file mode 100644 index 00000000000..ddc9abdeff9 --- /dev/null +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/YamlReferenceTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed 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.openrewrite.yaml.trait; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.openrewrite.Issue; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.trait.Reference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.yaml.Assertions.yaml; + +class YamlReferenceTest implements RewriteTest { + @Language("yml") + private static final String YAML = """ + root: + a: java.lang.String + b: java.lang + c: String + recipelist: + - org.openrewrite.java.DoSomething: + option: 'org.foo.Bar' + """; + + + @ParameterizedTest + @CsvSource({ + "application.yaml", + "application.yml", + "application-test.yaml", + "application-test.yml", + "/foo/bar/application-test.yaml", + "/foo/bar/application-test.yml", + }) + void findJavaReferencesInYamlProperties(String filename) { + rewriteRun( + yaml( + YAML, + spec -> spec.path(filename).afterRecipe(doc -> + assertThat(doc.getReferences().getReferences()).satisfiesExactlyInAnyOrder( + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("java.lang.String"); + }, + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.PACKAGE); + assertThat(ref.getValue()).isEqualTo("java.lang"); + }, + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("org.openrewrite.java.DoSomething"); + }, + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("org.foo.Bar"); + }))) + ); + } + + @ParameterizedTest + @CsvSource({ + "application-.yaml", + "application-.yml", + "application.test.yaml", + "application.test.yml", + "other-application.yaml", + "other-application.yml", + "other.yaml", + "other.yml", + "/foo/bar/other.yaml", + "/foo/bar/other.yml" + }) + void noReferencesInMismatchedFilenames(String filename) { + rewriteRun( + yaml( + YAML, + spec -> spec.path(filename).afterRecipe(doc -> assertThat(doc.getReferences().getReferences()).isEmpty()) + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/4817") + @Test + void endsWithDot() { + rewriteRun( + yaml( + """ + root: + recipelist: + - org.openrewrite.java.DoSomething: + option: 'org.foo.' + """, + spec -> spec + .path("application.yml") + .afterRecipe(doc -> assertThat(doc.getReferences().getReferences()).singleElement().satisfies( + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("org.openrewrite.java.DoSomething"); + }) + ) + ) + ); + } +} diff --git a/rewrite.yml b/rewrite.yml new file mode 100644 index 00000000000..3a067936a62 --- /dev/null +++ b/rewrite.yml @@ -0,0 +1,79 @@ +# +# Copyright 2024 the original author or authors. +#

+# Licensed 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. +# +--- +# Apply a subset of best practices to OpenRewrite recipes; typically run before committing changes. +# Any differences produced by this recipe will result in code suggestion comments on pull requests. +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.recipes.OpenRewriteBestPracticesSubset +displayName: OpenRewrite best practices +description: Best practices for OpenRewrite recipe development. +recipeList: + - org.openrewrite.recipes.JavaRecipeBestPracticesSubset + - org.openrewrite.recipes.RecipeTestingBestPracticesSubset + - org.openrewrite.recipes.RecipeNullabilityBestPracticesSubset + #- org.openrewrite.java.OrderImports + - org.openrewrite.java.format.EmptyNewlineAtEndOfFile + - org.openrewrite.staticanalysis.CompareEnumsWithEqualityOperator + - org.openrewrite.staticanalysis.InlineVariable + - org.openrewrite.staticanalysis.MissingOverrideAnnotation + - org.openrewrite.staticanalysis.UseDiamondOperator +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.recipes.JavaRecipeBestPracticesSubset +displayName: Java Recipe best practices +description: Best practices for Java recipe development. +preconditions: + - org.openrewrite.java.search.FindTypes: + fullyQualifiedTypeName: org.openrewrite.Recipe + checkAssignability: true +recipeList: + - org.openrewrite.java.recipes.BlankLinesAroundFieldsWithAnnotations + - org.openrewrite.java.recipes.ExecutionContextParameterName + - org.openrewrite.java.recipes.MissingOptionExample + - org.openrewrite.java.recipes.RecipeEqualsAndHashCodeCallSuper + - org.openrewrite.java.recipes.UseTreeRandomId + - org.openrewrite.staticanalysis.NeedBraces + #- org.openrewrite.staticanalysis.RemoveSystemOutPrintln +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.recipes.RecipeTestingBestPracticesSubset +displayName: Recipe testing best practices +description: Best practices for testing recipes. +preconditions: + - org.openrewrite.java.search.FindTypes: + fullyQualifiedTypeName: org.openrewrite.test.RewriteTest + checkAssignability: true +recipeList: + - org.openrewrite.java.recipes.RewriteTestClassesShouldNotBePublic + #- org.openrewrite.java.recipes.SelectRecipeExamples + - org.openrewrite.java.recipes.SourceSpecTextBlockIndentation + - org.openrewrite.java.testing.cleanup.RemoveTestPrefix + - org.openrewrite.java.testing.cleanup.TestsShouldNotBePublic + - org.openrewrite.staticanalysis.NeedBraces + #- org.openrewrite.staticanalysis.RemoveSystemOutPrintln +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.recipes.RecipeNullabilityBestPracticesSubset +displayName: Recipe nullability best practices +description: Use OpenRewrite internal nullability annotations; drop JetBrains annotations; use `package-info.java` instead. +recipeList: + - org.openrewrite.staticanalysis.NullableOnMethodReturnType + - org.openrewrite.java.RemoveAnnotation: + annotationPattern: '@org.jetbrains.annotations.NotNull' + - org.openrewrite.java.RemoveAnnotation: + annotationPattern: '@jakarta.annotation.Nonnull' + #- org.openrewrite.java.jspecify.MigrateToJspecify diff --git a/settings.gradle.kts b/settings.gradle.kts index f280b567997..1cc35fdf9ba 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,7 @@ val allProjects = listOf( "rewrite-properties", "rewrite-protobuf", "rewrite-test", + "rewrite-toml", "rewrite-xml", "rewrite-yaml", ) diff --git a/suppressions.xml b/suppressions.xml index 0376a3d984e..aab8ffb8ba0 100644 --- a/suppressions.xml +++ b/suppressions.xml @@ -1,15 +1,6 @@ - - - ^pkg:maven/com\.squareup\.okio/okio@.*$ - CVE-2023-3635 - - + CVE-2023-45163 CVE-2023-5964 - + @@ -32,11 +23,12 @@ CVE-2020-15773 CVE-2020-15767 - + ^pkg:maven/org\.glassfish/javax\.json@.*$ CVE-2023-7272 - \ No newline at end of file +