From 2b1e922263e5d4efc75e6529343cfab31af1d55c Mon Sep 17 00:00:00 2001 From: Lia Ferguson Date: Fri, 26 Jul 2024 12:22:04 -0400 Subject: [PATCH 1/7] Adding recipe FindAndReplaceLiteral to find and replace string literals in HCL files --- .../hcl/search/FindAndReplaceLiteral.java | 158 ++++++++++ .../hcl/search/FindAndReplaceLiteralTest.java | 290 ++++++++++++++++++ 2 files changed, 448 insertions(+) create mode 100644 rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java create mode 100644 rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java new file mode 100644 index 00000000000..f5555c8e328 --- /dev/null +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java @@ -0,0 +1,158 @@ +/* + * Copyright 2022 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.hcl.search; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.openrewrite.ExecutionContext; +import org.openrewrite.FindSourceFiles; +import org.openrewrite.Option; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.hcl.HclIsoVisitor; +import org.openrewrite.hcl.tree.Hcl; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.AlreadyReplaced; +import org.openrewrite.marker.Marker; + +import java.util.Arrays; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.openrewrite.Tree.randomId; + +public class FindAndReplaceLiteral extends Recipe { + + @Override + public String getDisplayName() { + return "Find and replace string literals in HCL files"; + } + + @Override + public String getDescription() { + return "Find and replace string literal values in HCL files. This recipe parses the source files on which it runs" + + "as HCL, meaning you can execute HCL language-specific recipes before and after this recipe in a single recipe run."; + } + + @Option( + displayName = "Find", description= "The string literal to find (and replace)", example = "blacklist" + ) + private final String find; + + @Option(displayName = "Replace", + description = "The replacement string literal for `find`. This snippet can be multiline.", + example = "denylist", + required = false) + private final @Nullable String replace; + + @Option(displayName = "Regex", + description = "Default false. If true, `find` will be interpreted as a Regular Expression, and capture group contents will be available in `replace`.", + required = false) + private final @Nullable Boolean regex; + + @Option(displayName = "Case sensitive", + description = "If `true` the search will be sensitive to case. Default `false`.", required = false) + private final @Nullable Boolean caseSensitive; + + @Option(displayName = "Regex multiline mode", description = + "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " + + "When performing a regex search when this is `false` \"^\" and \"$\" will match only the beginning and ending of the entire source file, respectively." + + "Has no effect when not performing a regex search. Default `false`.", required = false) + private final @Nullable Boolean multiline; + + @Option(displayName = "Regex dot all", description = + "When performing a regex search setting this to `true` allows \".\" to match line terminators." + + "Has no effect when not performing a regex search. Default `false`.", required = false) + private final @Nullable Boolean dotAll; + + @Option(displayName = "File pattern", 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. ", required = false, example = "**/*.tf") + private final @Nullable String filePattern; + + @JsonCreator + public FindAndReplaceLiteral(@JsonProperty("find") final String find, @JsonProperty("replace") final @Nullable String replace, @JsonProperty("regex") final @Nullable Boolean regex, + @JsonProperty("caseSensitive") final @Nullable Boolean caseSensitive, @JsonProperty("multiline") final @Nullable Boolean multiline, @JsonProperty("dotAll") final @Nullable Boolean dotAll, + @JsonProperty("filePattern") final @Nullable String filePattern) { + this.find = find; + this.replace = replace; + this.regex = regex; + this.caseSensitive = caseSensitive; + this.multiline = multiline; + this.dotAll = dotAll; + this.filePattern = filePattern; + } + + @Override + public TreeVisitor getVisitor() { + TreeVisitor visitor = new HclIsoVisitor(){ + + @Override + public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContext executionContext) { + for(Marker marker : literal.getMarkers().getMarkers()) { + if (marker instanceof AlreadyReplaced) { + AlreadyReplaced alreadyReplaced = (AlreadyReplaced) marker; + if (Objects.equals(find, alreadyReplaced.getFind()) && Objects.equals(replace, + alreadyReplaced.getReplace())) { + return literal; + } + } + } + String searchStr = find; + if (!Boolean.TRUE.equals(regex)) { + searchStr = Pattern.quote(searchStr); + } + int patternOptions = 0; + if (!Boolean.TRUE.equals(caseSensitive)) { + patternOptions |= Pattern.CASE_INSENSITIVE; + } + if (Boolean.TRUE.equals(multiline)) { + patternOptions |= Pattern.MULTILINE; + } + if (Boolean.TRUE.equals(dotAll)) { + patternOptions |= Pattern.DOTALL; + } + Pattern pattern = Pattern.compile(searchStr, patternOptions); + Matcher matcher = pattern.matcher(literal.getValue().toString()); + if(!matcher.find()){ + return literal; + } + String replacement = replace == null ? "" : replace; + if (!Boolean.TRUE.equals(regex)) { + replacement = replacement.replace("$", "\\$"); + } + String newLiteral = matcher.replaceAll(replacement); + return literal.withValue(newLiteral).withValueSource(newLiteral) + .withMarkers(literal.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); + } + return visitor; + } +} \ No newline at end of file diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java new file mode 100644 index 00000000000..d2b2d02f633 --- /dev/null +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java @@ -0,0 +1,290 @@ +/* + * Copyright 2022 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.hcl.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.junit.jupiter.api.Test; +import org.openrewrite.Recipe; +import org.openrewrite.test.RewriteTest; + +import java.util.Arrays; +import java.util.List; + +import static org.openrewrite.hcl.Assertions.hcl; + +class FindAndReplaceLiteralTest implements RewriteTest { + + @Test + void defaultNonRegexReplace() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral("app-cluster", "new-app-cluster", null, null, null, null, null)), + hcl( + + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "new-app-cluster" + } + } + """ + ) + ); + } + + @Test + void removeWhenReplaceIsNullOrEmpty() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral("prefix-", null, null, null, null, null, null)), + hcl( + + """ + config = { + app_deployment = { + cluster_name = "prefix-app-cluster" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + } + } + """ + ) + ); + } + + @Test + void regexReplace() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral(".", "a", true, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "aaaaaaaaaaa" + } + } + """ + ) + ); + } + + @Test + void captureGroupsReplace() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral("old-([^.]+)", "new-$1", true, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "old-app-cluster" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "new-app-cluster" + } + } + """ + ) + ); + } + + @Test + void noRecursiveReplace() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral("app", "application", null, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "application-cluster" + } + } + """ + ) + ); + } + + @Test + void compatibleWithDollarSigns() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral("$${app-cluster}", "$${new-app-cluster}", null, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "$${app-cluster}" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "$${new-app-cluster}" + } + } + """ + ) + ); + } + + @Test + void doesNotReplaceStringTemplate() { + rewriteRun( + spec -> spec.recipe(new FindAndReplaceLiteral("app-name", "new-app-name", null, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "old-${app-name}-cluster" + } + } + """ + ) + ); + } + + @Test + void doesNothingIfLiteralNotFound() { + rewriteRun( + spec -> spec.recipe(new FindAndReplaceLiteral("hello", "goodbye", null, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + } + } + """ + ) + ); + } + + @Test + void doesNotReplaceVariableNames() { + rewriteRun( + spec -> spec.recipe(new FindAndReplaceLiteral("app_deployment", "replacement_deployment_name", null, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + } + } + """ + ) + ); + } + + @Test + void replacesNumericLiterals() { + rewriteRun( + spec -> spec.recipe( new FindAndReplaceLiteral("2", "1",null, null, null, null, null)), + hcl( + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + app_replica = 2 + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "app-cluster" + app_replica = 1 + } + } + """ + ) + ); + } + + @Value + @EqualsAndHashCode(callSuper = false) + static class MultiFindAndReplaceLiteral extends Recipe { + + @Override + public String getDisplayName() { + return "Replaces \"cluster-1\" with \"cluster-2\" then \"cluster-3\" then \"cluster-4\""; + } + + @Override + public String getDescription() { + return "Replaces \"cluster-1\" with \"cluster-2\" then \"cluster-3\" then \"cluster-4\"."; + } + + @Override + public List getRecipeList() { + return Arrays.asList( + new FindAndReplaceLiteral("cluster-1", "cluster-2", null, null, null, null, null), + new FindAndReplaceLiteral("cluster-2", "cluster-3", null, null, null, null, null), + new FindAndReplaceLiteral("cluster-3", "cluster-4", null, null, null, null, null) + ); + } + } + + @Test + void successiveReplacement() { + rewriteRun( + spec -> spec.recipe(new MultiFindAndReplaceLiteral()), + hcl( + """ + config = { + app_deployment = { + cluster_name = "cluster-1" + } + } + """, + """ + config = { + app_deployment = { + cluster_name = "cluster-4" + } + } + """ + ) + ); + } +} From de28503bf2d44acb4f67bbe9159bac9d37553ea9 Mon Sep 17 00:00:00 2001 From: Lia Ferguson Date: Fri, 26 Jul 2024 12:28:36 -0400 Subject: [PATCH 2/7] Removing "string" qualifier from recipe and field descriptions since recipe handles literals of other types as well --- .../org/openrewrite/hcl/search/FindAndReplaceLiteral.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java index f5555c8e328..a7e8de77f6d 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java @@ -40,22 +40,22 @@ public class FindAndReplaceLiteral extends Recipe { @Override public String getDisplayName() { - return "Find and replace string literals in HCL files"; + return "Find and replace literals in HCL files"; } @Override public String getDescription() { - return "Find and replace string literal values in HCL files. This recipe parses the source files on which it runs" + return "Find and replace literal values in HCL files. This recipe parses the source files on which it runs" + "as HCL, meaning you can execute HCL language-specific recipes before and after this recipe in a single recipe run."; } @Option( - displayName = "Find", description= "The string literal to find (and replace)", example = "blacklist" + displayName = "Find", description= "The literal to find (and replace)", example = "blacklist" ) private final String find; @Option(displayName = "Replace", - description = "The replacement string literal for `find`. This snippet can be multiline.", + description = "The replacement literal for `find`. This snippet can be multiline.", example = "denylist", required = false) private final @Nullable String replace; From d06a324311f93ebd0162a7599cf1750f72d2fc6a Mon Sep 17 00:00:00 2001 From: Lia Ferguson Date: Fri, 26 Jul 2024 12:43:09 -0400 Subject: [PATCH 3/7] Fixing spacing in FindAndReplaceLiteralTest.java --- .../org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java index d2b2d02f633..16bcc160e58 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java @@ -241,8 +241,8 @@ void replacesNumericLiterals() { ); } - @Value - @EqualsAndHashCode(callSuper = false) + @Value + @EqualsAndHashCode(callSuper = false) static class MultiFindAndReplaceLiteral extends Recipe { @Override From 0935e0f964671a5dc3aa0195f7afb44c46097249 Mon Sep 17 00:00:00 2001 From: Lia Ferguson Date: Fri, 26 Jul 2024 17:30:53 -0400 Subject: [PATCH 4/7] Fixing spacing and variable naming to comply with coding standards --- .../openrewrite/hcl/search/FindAndReplaceLiteral.java | 2 +- .../hcl/search/FindAndReplaceLiteralTest.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java index a7e8de77f6d..1fee5ab7b23 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java @@ -105,7 +105,7 @@ public TreeVisitor getVisitor() { TreeVisitor visitor = new HclIsoVisitor(){ @Override - public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContext executionContext) { + public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContext ctx) { for(Marker marker : literal.getMarkers().getMarkers()) { if (marker instanceof AlreadyReplaced) { AlreadyReplaced alreadyReplaced = (AlreadyReplaced) marker; diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java index 16bcc160e58..2c50fd30061 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java @@ -278,11 +278,11 @@ void successiveReplacement() { } """, """ - config = { - app_deployment = { - cluster_name = "cluster-4" - } - } + config = { + app_deployment = { + cluster_name = "cluster-4" + } + } """ ) ); From 604448361e64e3b6741928ecf3e4c81b2109086b Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 29 Jul 2024 21:12:09 +0200 Subject: [PATCH 5/7] Polish --- .../hcl/search/FindAndReplaceLiteral.java | 79 +++--- .../hcl/search/FindAndReplaceLiteralTest.java | 228 +++++++++--------- 2 files changed, 152 insertions(+), 155 deletions(-) diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java index 1fee5ab7b23..2401c3728d7 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -15,14 +15,9 @@ */ package org.openrewrite.hcl.search; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.openrewrite.ExecutionContext; -import org.openrewrite.FindSourceFiles; -import org.openrewrite.Option; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; import org.openrewrite.hcl.HclIsoVisitor; import org.openrewrite.hcl.tree.Hcl; import org.openrewrite.internal.lang.Nullable; @@ -36,6 +31,8 @@ import static org.openrewrite.Tree.randomId; +@Value +@EqualsAndHashCode(callSuper = false) public class FindAndReplaceLiteral extends Recipe { @Override @@ -45,68 +42,58 @@ public String getDisplayName() { @Override public String getDescription() { - return "Find and replace literal values in HCL files. This recipe parses the source files on which it runs" - + "as HCL, meaning you can execute HCL language-specific recipes before and after this recipe in a single recipe run."; + return "Find and replace literal values in HCL files. This recipe parses the source files on which it runs as HCL, " + + "meaning you can execute HCL language-specific recipes before and after this recipe in a single recipe run."; } - @Option( - displayName = "Find", description= "The literal to find (and replace)", example = "blacklist" - ) - private final String find; + @Option(displayName = "Find", description = "The literal to find (and replace)", example = "blacklist") + String find; @Option(displayName = "Replace", description = "The replacement literal for `find`. This snippet can be multiline.", example = "denylist", required = false) - private final @Nullable String replace; + @Nullable + String replace; @Option(displayName = "Regex", description = "Default false. If true, `find` will be interpreted as a Regular Expression, and capture group contents will be available in `replace`.", required = false) - private final @Nullable Boolean regex; + @Nullable + Boolean regex; @Option(displayName = "Case sensitive", description = "If `true` the search will be sensitive to case. Default `false`.", required = false) - private final @Nullable Boolean caseSensitive; + @Nullable + Boolean caseSensitive; @Option(displayName = "Regex multiline mode", description = "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " - + "When performing a regex search when this is `false` \"^\" and \"$\" will match only the beginning and ending of the entire source file, respectively." - + "Has no effect when not performing a regex search. Default `false`.", required = false) - private final @Nullable Boolean multiline; + + "When performing a regex search when this is `false` \"^\" and \"$\" will match only the beginning and ending of the entire source file, respectively." + + "Has no effect when not performing a regex search. Default `false`.", required = false) + @Nullable + Boolean multiline; @Option(displayName = "Regex dot all", description = "When performing a regex search setting this to `true` allows \".\" to match line terminators." - + "Has no effect when not performing a regex search. Default `false`.", required = false) - private final @Nullable Boolean dotAll; + + "Has no effect when not performing a regex search. Default `false`.", required = false) + @Nullable + Boolean dotAll; @Option(displayName = "File pattern", 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. ", required = false, example = "**/*.tf") - private final @Nullable String filePattern; - - @JsonCreator - public FindAndReplaceLiteral(@JsonProperty("find") final String find, @JsonProperty("replace") final @Nullable String replace, @JsonProperty("regex") final @Nullable Boolean regex, - @JsonProperty("caseSensitive") final @Nullable Boolean caseSensitive, @JsonProperty("multiline") final @Nullable Boolean multiline, @JsonProperty("dotAll") final @Nullable Boolean dotAll, - @JsonProperty("filePattern") final @Nullable String filePattern) { - this.find = find; - this.replace = replace; - this.regex = regex; - this.caseSensitive = caseSensitive; - this.multiline = multiline; - this.dotAll = dotAll; - this.filePattern = filePattern; - } + + "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. ", required = false, example = "**/*.tf") + @Nullable + String filePattern; @Override public TreeVisitor getVisitor() { - TreeVisitor visitor = new HclIsoVisitor(){ - + TreeVisitor visitor = new HclIsoVisitor() { @Override public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContext ctx) { - for(Marker marker : literal.getMarkers().getMarkers()) { + for (Marker marker : literal.getMarkers().getMarkers()) { if (marker instanceof AlreadyReplaced) { AlreadyReplaced alreadyReplaced = (AlreadyReplaced) marker; if (Objects.equals(find, alreadyReplaced.getFind()) && Objects.equals(replace, @@ -131,10 +118,10 @@ public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContex } Pattern pattern = Pattern.compile(searchStr, patternOptions); Matcher matcher = pattern.matcher(literal.getValue().toString()); - if(!matcher.find()){ + if (!matcher.find()) { return literal; } - String replacement = replace == null ? "" : replace; + String replacement = replace == null ? "" : replace; if (!Boolean.TRUE.equals(regex)) { replacement = replacement.replace("$", "\\$"); } @@ -155,4 +142,4 @@ public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContex } return visitor; } -} \ No newline at end of file +} diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java index 2c50fd30061..a460db5c7d0 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -18,10 +18,10 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.Recipe; import org.openrewrite.test.RewriteTest; -import java.util.Arrays; import java.util.List; import static org.openrewrite.hcl.Assertions.hcl; @@ -29,25 +29,26 @@ class FindAndReplaceLiteralTest implements RewriteTest { @Test + @DocumentExample void defaultNonRegexReplace() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral("app-cluster", "new-app-cluster", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("app-cluster", "new-app-cluster", null, null, null, null, null)), + //language=hcl hcl( - """ - config = { - app_deployment = { - cluster_name = "app-cluster" + config = { + app_deployment = { + cluster_name = "app-cluster" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "new-app-cluster" + config = { + app_deployment = { + cluster_name = "new-app-cluster" + } } - } - """ + """ ) ); } @@ -55,23 +56,23 @@ void defaultNonRegexReplace() { @Test void removeWhenReplaceIsNullOrEmpty() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral("prefix-", null, null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("prefix-", null, null, null, null, null, null)), + //language=hcl hcl( - """ - config = { - app_deployment = { - cluster_name = "prefix-app-cluster" + config = { + app_deployment = { + cluster_name = "prefix-app-cluster" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "app-cluster" + config = { + app_deployment = { + cluster_name = "app-cluster" + } } - } - """ + """ ) ); } @@ -79,22 +80,23 @@ void removeWhenReplaceIsNullOrEmpty() { @Test void regexReplace() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral(".", "a", true, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral(".", "a", true, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "app-cluster" + config = { + app_deployment = { + cluster_name = "app-cluster" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "aaaaaaaaaaa" + config = { + app_deployment = { + cluster_name = "aaaaaaaaaaa" + } } - } - """ + """ ) ); } @@ -102,22 +104,23 @@ void regexReplace() { @Test void captureGroupsReplace() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral("old-([^.]+)", "new-$1", true, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("old-([^.]+)", "new-$1", true, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "old-app-cluster" + config = { + app_deployment = { + cluster_name = "old-app-cluster" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "new-app-cluster" + config = { + app_deployment = { + cluster_name = "new-app-cluster" + } } - } - """ + """ ) ); } @@ -125,22 +128,23 @@ void captureGroupsReplace() { @Test void noRecursiveReplace() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral("app", "application", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("app", "application", null, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "app-cluster" + config = { + app_deployment = { + cluster_name = "app-cluster" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "application-cluster" + config = { + app_deployment = { + cluster_name = "application-cluster" + } } - } - """ + """ ) ); } @@ -148,22 +152,23 @@ void noRecursiveReplace() { @Test void compatibleWithDollarSigns() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral("$${app-cluster}", "$${new-app-cluster}", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("$${app-cluster}", "$${new-app-cluster}", null, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "$${app-cluster}" + config = { + app_deployment = { + cluster_name = "$${app-cluster}" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "$${new-app-cluster}" + config = { + app_deployment = { + cluster_name = "$${new-app-cluster}" + } } - } - """ + """ ) ); } @@ -172,14 +177,15 @@ void compatibleWithDollarSigns() { void doesNotReplaceStringTemplate() { rewriteRun( spec -> spec.recipe(new FindAndReplaceLiteral("app-name", "new-app-name", null, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "old-${app-name}-cluster" + config = { + app_deployment = { + cluster_name = "old-${app-name}-cluster" + } } - } - """ + """ ) ); } @@ -188,14 +194,15 @@ void doesNotReplaceStringTemplate() { void doesNothingIfLiteralNotFound() { rewriteRun( spec -> spec.recipe(new FindAndReplaceLiteral("hello", "goodbye", null, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "app-cluster" + config = { + app_deployment = { + cluster_name = "app-cluster" + } } - } - """ + """ ) ); } @@ -204,14 +211,15 @@ void doesNothingIfLiteralNotFound() { void doesNotReplaceVariableNames() { rewriteRun( spec -> spec.recipe(new FindAndReplaceLiteral("app_deployment", "replacement_deployment_name", null, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "app-cluster" + config = { + app_deployment = { + cluster_name = "app-cluster" + } } - } - """ + """ ) ); } @@ -219,24 +227,25 @@ void doesNotReplaceVariableNames() { @Test void replacesNumericLiterals() { rewriteRun( - spec -> spec.recipe( new FindAndReplaceLiteral("2", "1",null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("2", "1", null, null, null, null, null)), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "app-cluster" - app_replica = 2 + config = { + app_deployment = { + cluster_name = "app-cluster" + app_replica = 2 + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "app-cluster" - app_replica = 1 + config = { + app_deployment = { + cluster_name = "app-cluster" + app_replica = 1 + } } - } - """ + """ ) ); } @@ -257,7 +266,7 @@ public String getDescription() { @Override public List getRecipeList() { - return Arrays.asList( + return List.of( new FindAndReplaceLiteral("cluster-1", "cluster-2", null, null, null, null, null), new FindAndReplaceLiteral("cluster-2", "cluster-3", null, null, null, null, null), new FindAndReplaceLiteral("cluster-3", "cluster-4", null, null, null, null, null) @@ -269,21 +278,22 @@ public List getRecipeList() { void successiveReplacement() { rewriteRun( spec -> spec.recipe(new MultiFindAndReplaceLiteral()), + //language=hcl hcl( """ - config = { - app_deployment = { - cluster_name = "cluster-1" + config = { + app_deployment = { + cluster_name = "cluster-1" + } } - } - """, + """, """ - config = { - app_deployment = { - cluster_name = "cluster-4" + config = { + app_deployment = { + cluster_name = "cluster-4" + } } - } - """ + """ ) ); } From 3a06f9d934fd04582c1e90f20efb86df690a7900 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 29 Jul 2024 21:22:56 +0200 Subject: [PATCH 6/7] Simplify by stripping unlikely options --- .../hcl/search/FindAndReplaceLiteral.java | 58 +++---------------- .../hcl/search/FindAndReplaceLiteralTest.java | 55 +++++------------- 2 files changed, 24 insertions(+), 89 deletions(-) diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java index 2401c3728d7..68e9798fb6f 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/search/FindAndReplaceLiteral.java @@ -17,14 +17,15 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; import org.openrewrite.hcl.HclIsoVisitor; import org.openrewrite.hcl.tree.Hcl; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.AlreadyReplaced; -import org.openrewrite.marker.Marker; -import java.util.Arrays; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -67,39 +68,15 @@ public String getDescription() { @Nullable Boolean caseSensitive; - @Option(displayName = "Regex multiline mode", description = - "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " - + "When performing a regex search when this is `false` \"^\" and \"$\" will match only the beginning and ending of the entire source file, respectively." - + "Has no effect when not performing a regex search. Default `false`.", required = false) - @Nullable - Boolean multiline; - - @Option(displayName = "Regex dot all", description = - "When performing a regex search setting this to `true` allows \".\" to match line terminators." - + "Has no effect when not performing a regex search. Default `false`.", required = false) - @Nullable - Boolean dotAll; - - @Option(displayName = "File pattern", 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. ", required = false, example = "**/*.tf") - @Nullable - String filePattern; - @Override public TreeVisitor getVisitor() { - TreeVisitor visitor = new HclIsoVisitor() { + return new HclIsoVisitor() { @Override public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContext ctx) { - for (Marker marker : literal.getMarkers().getMarkers()) { - if (marker instanceof AlreadyReplaced) { - AlreadyReplaced alreadyReplaced = (AlreadyReplaced) marker; - if (Objects.equals(find, alreadyReplaced.getFind()) && Objects.equals(replace, - alreadyReplaced.getReplace())) { - return literal; - } + for (AlreadyReplaced alreadyReplaced : literal.getMarkers().findAll(AlreadyReplaced.class)) { + if (Objects.equals(find, alreadyReplaced.getFind()) && + Objects.equals(replace, alreadyReplaced.getReplace())) { + return literal; } } String searchStr = find; @@ -110,12 +87,6 @@ public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContex if (!Boolean.TRUE.equals(caseSensitive)) { patternOptions |= Pattern.CASE_INSENSITIVE; } - if (Boolean.TRUE.equals(multiline)) { - patternOptions |= Pattern.MULTILINE; - } - if (Boolean.TRUE.equals(dotAll)) { - patternOptions |= Pattern.DOTALL; - } Pattern pattern = Pattern.compile(searchStr, patternOptions); Matcher matcher = pattern.matcher(literal.getValue().toString()); if (!matcher.find()) { @@ -130,16 +101,5 @@ public Hcl.Literal visitLiteral(final Hcl.Literal literal, final ExecutionContex .withMarkers(literal.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); - } - return visitor; } } diff --git a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java index a460db5c7d0..d5493a7022b 100644 --- a/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java +++ b/rewrite-hcl/src/test/java/org/openrewrite/hcl/search/FindAndReplaceLiteralTest.java @@ -15,15 +15,10 @@ */ package org.openrewrite.hcl.search; -import lombok.EqualsAndHashCode; -import lombok.Value; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; -import org.openrewrite.Recipe; import org.openrewrite.test.RewriteTest; -import java.util.List; - import static org.openrewrite.hcl.Assertions.hcl; class FindAndReplaceLiteralTest implements RewriteTest { @@ -32,7 +27,7 @@ class FindAndReplaceLiteralTest implements RewriteTest { @DocumentExample void defaultNonRegexReplace() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("app-cluster", "new-app-cluster", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("app-cluster", "new-app-cluster", null, null)), //language=hcl hcl( """ @@ -56,7 +51,7 @@ void defaultNonRegexReplace() { @Test void removeWhenReplaceIsNullOrEmpty() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("prefix-", null, null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("prefix-", null, null, null)), //language=hcl hcl( """ @@ -80,7 +75,7 @@ void removeWhenReplaceIsNullOrEmpty() { @Test void regexReplace() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral(".", "a", true, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral(".", "a", true, null)), //language=hcl hcl( """ @@ -104,7 +99,7 @@ void regexReplace() { @Test void captureGroupsReplace() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("old-([^.]+)", "new-$1", true, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("old-([^.]+)", "new-$1", true, null)), //language=hcl hcl( """ @@ -128,7 +123,7 @@ void captureGroupsReplace() { @Test void noRecursiveReplace() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("app", "application", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("app", "application", null, null)), //language=hcl hcl( """ @@ -152,7 +147,7 @@ void noRecursiveReplace() { @Test void compatibleWithDollarSigns() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("$${app-cluster}", "$${new-app-cluster}", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("$${app-cluster}", "$${new-app-cluster}", null, null)), //language=hcl hcl( """ @@ -176,7 +171,7 @@ void compatibleWithDollarSigns() { @Test void doesNotReplaceStringTemplate() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("app-name", "new-app-name", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("app-name", "new-app-name", null, null)), //language=hcl hcl( """ @@ -193,7 +188,7 @@ void doesNotReplaceStringTemplate() { @Test void doesNothingIfLiteralNotFound() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("hello", "goodbye", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("hello", "goodbye", null, null)), //language=hcl hcl( """ @@ -210,7 +205,7 @@ void doesNothingIfLiteralNotFound() { @Test void doesNotReplaceVariableNames() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("app_deployment", "replacement_deployment_name", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("app_deployment", "replacement_deployment_name", null, null)), //language=hcl hcl( """ @@ -227,7 +222,7 @@ void doesNotReplaceVariableNames() { @Test void replacesNumericLiterals() { rewriteRun( - spec -> spec.recipe(new FindAndReplaceLiteral("2", "1", null, null, null, null, null)), + spec -> spec.recipe(new FindAndReplaceLiteral("2", "1", null, null)), //language=hcl hcl( """ @@ -250,34 +245,14 @@ void replacesNumericLiterals() { ); } - @Value - @EqualsAndHashCode(callSuper = false) - static class MultiFindAndReplaceLiteral extends Recipe { - - @Override - public String getDisplayName() { - return "Replaces \"cluster-1\" with \"cluster-2\" then \"cluster-3\" then \"cluster-4\""; - } - - @Override - public String getDescription() { - return "Replaces \"cluster-1\" with \"cluster-2\" then \"cluster-3\" then \"cluster-4\"."; - } - - @Override - public List getRecipeList() { - return List.of( - new FindAndReplaceLiteral("cluster-1", "cluster-2", null, null, null, null, null), - new FindAndReplaceLiteral("cluster-2", "cluster-3", null, null, null, null, null), - new FindAndReplaceLiteral("cluster-3", "cluster-4", null, null, null, null, null) - ); - } - } - @Test void successiveReplacement() { rewriteRun( - spec -> spec.recipe(new MultiFindAndReplaceLiteral()), + spec -> spec.recipes( + new FindAndReplaceLiteral("cluster-1", "cluster-2", null, null), + new FindAndReplaceLiteral("cluster-2", "cluster-3", null, null), + new FindAndReplaceLiteral("cluster-3", "cluster-4", null, null) + ), //language=hcl hcl( """ From cbc0c0d27b77c17f9a3b16988957b7cb1dde4a7d Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 29 Jul 2024 21:25:08 +0200 Subject: [PATCH 7/7] Drop MultiFindAndReplace from FindAndReplaceTest --- .../openrewrite/text/FindAndReplaceTest.java | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java index b46b458501f..bf77a7f6ee4 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java @@ -15,16 +15,10 @@ */ package org.openrewrite.text; -import lombok.EqualsAndHashCode; -import lombok.Value; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; -import org.openrewrite.Recipe; import org.openrewrite.test.RewriteTest; -import java.util.Arrays; -import java.util.List; - import static org.openrewrite.test.SourceSpecs.text; class FindAndReplaceTest implements RewriteTest { @@ -57,7 +51,7 @@ void removeWhenNullOrEmpty() { """, """ Foo - + Quz """ ) @@ -127,33 +121,14 @@ void dollarSignsTolerated() { ); } - @Value - @EqualsAndHashCode(callSuper = false) - static class MultiFindAndReplace extends Recipe { - - @Override - public String getDisplayName() { - return "Replaces \"one\" with \"two\" then \"three\" then \"four\""; - } - - @Override - public String getDescription() { - return "Replaces \"one\" with \"two\" then \"three\" then \"four\"."; - } - - @Override - public List getRecipeList() { - return Arrays.asList( - new FindAndReplace("one", "two", null, null, null, null, null, null), - new FindAndReplace("two", "three", null, null, null, null, null, null), - new FindAndReplace("three", "four", null, null, null, null, null, null)); - } - } - @Test void successiveReplacement() { rewriteRun( - spec -> spec.recipe(new MultiFindAndReplace()), + spec -> spec.recipes( + new FindAndReplace("one", "two", null, null, null, null, null, null), + new FindAndReplace("two", "three", null, null, null, null, null, null), + new FindAndReplace("three", "four", null, null, null, null, null, null) + ), text( """ one