From 51c06c80f34eb97c012bdd62b63d0b16c0ef1123 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 3 Apr 2026 11:48:52 -0700 Subject: [PATCH 1/2] AddProperty add options for controlling where new properties get inserted --- .../org/openrewrite/gradle/AddProperty.java | 6 +- .../openrewrite/properties/AddProperty.java | 94 +++- .../resources/META-INF/rewrite/examples.yml | 2 + .../resources/META-INF/rewrite/recipes.csv | 2 +- .../properties/AddPropertyTest.java | 421 ++++++++++++++---- 5 files changed, 408 insertions(+), 117 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java index 5774fa1f7a7..9397076bd80 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java @@ -124,7 +124,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { sourceFile : new ChangePropertyValue(key, value, null, false, null) .getVisitor().visitNonNull(sourceFile, ctx); - return new org.openrewrite.properties.AddProperty(key, value, null, null, null) + return org.openrewrite.properties.AddProperty.builder() + .property(key).value(value).build() .getVisitor() .visitNonNull(t, ctx); } @@ -133,7 +134,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { sourceFile : new ChangePropertyValue(key, value, null, false, null) .getVisitor().visitNonNull(sourceFile, ctx); - return new org.openrewrite.properties.AddProperty(key, value, null, null, null) + return org.openrewrite.properties.AddProperty.builder() + .property(key).value(value).build() .getVisitor() .visitNonNull(t, ctx); } diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java b/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java index 3d74d12d22b..f295c2a2d3e 100644 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java @@ -15,6 +15,9 @@ */ package org.openrewrite.properties; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; @@ -34,9 +37,12 @@ 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.isBlank; @Value +@AllArgsConstructor(onConstructor_ = {@JsonCreator}) @EqualsAndHashCode(callSuper = false) +@Builder public class AddProperty extends Recipe { @Option(displayName = "Property key", @@ -70,17 +76,40 @@ public class AddProperty extends Recipe { @Nullable Boolean orderedInsertion; + @Option(displayName = "Insert mode", + description = "Choose an insertion point relative to an existing property. Default is `Last`. " + + "Takes precedence over `orderedInsertion`. " + + "If the referenced property does not exist, falls back to default behavior.", + valid = {"Before", "After", "Last"}, + required = false) + @Nullable + InsertMode insertMode; + + @Option(displayName = "Insert property", + description = "The key of an existing property to use as the reference point for the insert mode. " + + "Required when `insertMode` is `Before` or `After`.", + required = false, + example = "server.port") + @Nullable + String insertProperty; + + public enum InsertMode {Before, After, Last} + String displayName = "Add a new property"; String description = "Adds a new property to a property file. " + - "Attempts to place the new property in alphabetical order by the property keys. " + - "Whitespace before and after the `=` must be included in the property and value."; + "Attempts to place the new property in alphabetical order by the property keys. " + + "Whitespace before and after the `=` must be included in the property and value."; @Override public Validated validate() { return Validated.none() .and(Validated.required("property", property)) - .and(Validated.required("value", value)); + .and(Validated.required("value", value)) + .and(Validated.test("insertProperty", + "Insert property must be provided when `insertMode` is `Before` or `After`.", + insertProperty, + s -> insertMode == null || insertMode == InsertMode.Last || !isBlank(s))); } @Override @@ -104,7 +133,7 @@ public Properties.File visitFile(Properties.File file, ExecutionContext ctx) { List newContents; - if(StringUtils.isBlank(comment)) { + if (StringUtils.isBlank(comment)) { newContents = singletonList(entry); } else { newContents = Arrays.asList( @@ -113,28 +142,44 @@ public Properties.File visitFile(Properties.File file, ExecutionContext ctx) { "\n", Markers.EMPTY, Properties.Comment.Delimiter.HASH_TAG, - " " + comment.trim()), + " " + comment.trim()), entry); } - List contentList = new ArrayList<>(p.getContent().size() + 1); - if (orderedInsertion == null || orderedInsertion) { - int insertionIndex = sortedInsertionIndex(entry, p.getContent()); - contentList.addAll(p.getContent().subList(0, insertionIndex)); - contentList.addAll(newContents); - contentList.addAll(p.getContent().subList(insertionIndex, p.getContent().size())); + List contentList = new ArrayList<>(p.getContent().size() + newContents.size()); + boolean inserted = false; + + if (insertMode != null && insertMode != InsertMode.Last && !isBlank(insertProperty)) { + int refIndex = findEntryIndex(p.getContent(), p, insertProperty); + if (refIndex >= 0) { + int insertIndex = insertMode == InsertMode.Before ? + findCommentBlockStart(p.getContent(), refIndex) : + refIndex + 1; + contentList.addAll(p.getContent().subList(0, insertIndex)); + contentList.addAll(newContents); + contentList.addAll(p.getContent().subList(insertIndex, p.getContent().size())); + inserted = true; + } } - else { - contentList.addAll(p.getContent()); - contentList.addAll(newContents); + + if (!inserted) { + if (orderedInsertion == null || orderedInsertion) { + int insertionIndex = sortedInsertionIndex(entry, p.getContent()); + contentList.addAll(p.getContent().subList(0, insertionIndex)); + contentList.addAll(newContents); + contentList.addAll(p.getContent().subList(insertionIndex, p.getContent().size())); + } else { + contentList.addAll(p.getContent()); + contentList.addAll(newContents); + } } // First entry in the file does not need a newline, but every other entry does contentList = ListUtils.map(contentList, (i, c) -> { - if(i == 0) { + if (i == 0) { return (Properties.Content) c.withPrefix(""); - } else if(!c.getPrefix().contains("\n")) { + } else if (!c.getPrefix().contains("\n")) { return (Properties.Content) c.withPrefix("\n" + c.getPrefix()); } return c; @@ -145,6 +190,23 @@ public Properties.File visitFile(Properties.File file, ExecutionContext ctx) { }; } + private static int findEntryIndex(List contentList, Properties.File file, String referenceKey) { + Set matches = FindProperties.find(file, referenceKey, false); + if (matches.isEmpty()) { + return -1; + } + Properties.Entry matched = matches.iterator().next(); + return contentList.indexOf(matched); + } + + private static int findCommentBlockStart(List contentList, int entryIndex) { + int start = entryIndex; + while (start > 0 && contentList.get(start - 1) instanceof Properties.Comment) { + start--; + } + return start; + } + private static int sortedInsertionIndex(Properties.Entry entry, List contentsList) { if (contentsList.isEmpty()) { return 0; diff --git a/rewrite-properties/src/main/resources/META-INF/rewrite/examples.yml b/rewrite-properties/src/main/resources/META-INF/rewrite/examples.yml index 45d092c27ed..1952b40988a 100644 --- a/rewrite-properties/src/main/resources/META-INF/rewrite/examples.yml +++ b/rewrite-properties/src/main/resources/META-INF/rewrite/examples.yml @@ -22,6 +22,8 @@ examples: - 'null' - 'null' - 'null' + - 'null' + - 'null' sources: - before: | management=true diff --git a/rewrite-properties/src/main/resources/META-INF/rewrite/recipes.csv b/rewrite-properties/src/main/resources/META-INF/rewrite/recipes.csv index 0516ec05ac9..71ebe0293ba 100644 --- a/rewrite-properties/src/main/resources/META-INF/rewrite/recipes.csv +++ b/rewrite-properties/src/main/resources/META-INF/rewrite/recipes.csv @@ -1,5 +1,5 @@ ecosystem,packageName,name,displayName,description,recipeCount,category1,category2,options -maven,org.openrewrite:rewrite-properties,org.openrewrite.properties.AddProperty,Add a new property,Adds a new property to a property file. Attempts to place the new property in alphabetical order by the property keys. Whitespace before and after the `=` must be included in the property and value.,1,,Properties,"[{""name"":""property"",""type"":""String"",""displayName"":""Property key"",""description"":""The property key to add."",""example"":""management.metrics.enable.process.files"",""required"":true},{""name"":""value"",""type"":""String"",""displayName"":""Property value"",""description"":""The value of the new property key."",""example"":""newPropValue"",""required"":true},{""name"":""comment"",""type"":""String"",""displayName"":""Optional comment to be prepended to the property"",""description"":""A comment that will be added to the new property."",""example"":""This is a comment""},{""name"":""delimiter"",""type"":""String"",""displayName"":""Optional delimiter"",""description"":""Property entries support different delimiters (`=`, `:`, or whitespace). The default value is `=` unless provided the delimiter of the new property entry."",""example"":"":""},{""name"":""orderedInsertion"",""type"":""Boolean"",""displayName"":""Ordered property insertion"",""description"":""Whether to attempt adding the property in an order following alphabetic sorting. The default value is `true`."",""example"":""false""}]" +maven,org.openrewrite:rewrite-properties,org.openrewrite.properties.AddProperty,Add a new property,Adds a new property to a property file. Attempts to place the new property in alphabetical order by the property keys. Whitespace before and after the `=` must be included in the property and value.,1,,Properties,"[{""name"":""property"",""type"":""String"",""displayName"":""Property key"",""description"":""The property key to add."",""example"":""management.metrics.enable.process.files"",""required"":true},{""name"":""value"",""type"":""String"",""displayName"":""Property value"",""description"":""The value of the new property key."",""example"":""newPropValue"",""required"":true},{""name"":""comment"",""type"":""String"",""displayName"":""Optional comment to be prepended to the property"",""description"":""A comment that will be added to the new property."",""example"":""This is a comment""},{""name"":""delimiter"",""type"":""String"",""displayName"":""Optional delimiter"",""description"":""Property entries support different delimiters (`=`, `:`, or whitespace). The default value is `=` unless provided the delimiter of the new property entry."",""example"":"":""},{""name"":""orderedInsertion"",""type"":""Boolean"",""displayName"":""Ordered property insertion"",""description"":""Whether to attempt adding the property in an order following alphabetic sorting. The default value is `true`."",""example"":""false""},{""name"":""insertMode"",""type"":""org.openrewrite.properties.AddProperty$InsertMode"",""displayName"":""Insert mode"",""description"":""Choose an insertion point relative to an existing property. Default is `Last`. Takes precedence over `orderedInsertion`. If the referenced property does not exist, falls back to default behavior."",""valid"":[""Before"",""After"",""Last""]},{""name"":""insertProperty"",""type"":""String"",""displayName"":""Insert property"",""description"":""The key of an existing property to use as the reference point for the insert mode. Required when `insertMode` is `Before` or `After`."",""example"":""server.port""}]" maven,org.openrewrite:rewrite-properties,org.openrewrite.properties.AddPropertyComment,Add comment before property key,"Add a new comment before a property key if not already present, optionally commenting out the property.",1,,Properties,"[{""name"":""propertyKey"",""type"":""String"",""displayName"":""Property key"",""description"":""The name of the property to add comment."",""example"":""management.metrics.binders"",""required"":true},{""name"":""comment"",""type"":""String"",""displayName"":""Comment"",""description"":""The comment to be added."",""example"":""comment"",""required"":true},{""name"":""commentOutProperty"",""type"":""Boolean"",""displayName"":""Comment out property"",""description"":""If true, property will be commented out."",""example"":""true""}]" maven,org.openrewrite:rewrite-properties,org.openrewrite.properties.CopyValue,Copy property value,"Copies a property value from one key to another. The existing key/value pair remains unaffected by this change. If the destination key already exists, its value will be replaced. By default, creates the destination key if it does not exist.",1,,Properties,"[{""name"":""oldPropertyKey"",""type"":""String"",""displayName"":""Old property key"",""description"":""The property key to copy the value from. Supports glob patterns."",""example"":""app.source.property"",""required"":true},{""name"":""oldFilePath"",""type"":""String"",""displayName"":""Old file path"",""description"":""The file path to the properties file to copy the value from. If `null` then the value will be copied from any properties file it appears within."",""example"":""src/main/resources/application.properties""},{""name"":""newPropertyKey"",""type"":""String"",""displayName"":""New property key"",""description"":""The property key to copy the value to."",""example"":""app.destination.property"",""required"":true},{""name"":""newFilePath"",""type"":""String"",""displayName"":""New file path"",""description"":""The file path to the properties file to copy the value to. If `null` then the value will be copied only into the same file it was found in."",""example"":""src/main/resources/application.properties""},{""name"":""createNewKeys"",""type"":""Boolean"",""displayName"":""Create new keys"",""description"":""When the destination key does _not_ already exist, create it. Default is `true`.""},{""name"":""relaxedBinding"",""type"":""Boolean"",""displayName"":""Use relaxed binding"",""description"":""Whether to match the `oldPropertyKey` using [relaxed binding](https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) rules. Default is `true`. Set to `false` to use exact matching.""}]" maven,org.openrewrite:rewrite-properties,org.openrewrite.properties.ChangePropertyKey,Change property key,Change a property key leaving the value intact.,1,,Properties,"[{""name"":""oldPropertyKey"",""type"":""String"",""displayName"":""Old property key"",""description"":""The property key to rename."",""example"":""management.metrics.binders.files.enabled"",""required"":true},{""name"":""newPropertyKey"",""type"":""String"",""displayName"":""New property key"",""description"":""The new name for the key identified by `oldPropertyKey`."",""example"":""management.metrics.enable.process.files"",""required"":true},{""name"":""relaxedBinding"",""type"":""Boolean"",""displayName"":""Use relaxed binding"",""description"":""Whether to match the `oldPropertyKey` using [relaxed binding](https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) rules. Default is `true`. Set to `false` to use exact matching.""},{""name"":""regex"",""type"":""Boolean"",""displayName"":""Regex"",""description"":""Default false. If enabled, `oldPropertyKey` will be interpreted as a Regular Expression, and capture group contents will be available in `newPropertyKey`""}]" diff --git a/rewrite-properties/src/test/java/org/openrewrite/properties/AddPropertyTest.java b/rewrite-properties/src/test/java/org/openrewrite/properties/AddPropertyTest.java index 57cab16f70c..a10b726aa8a 100755 --- a/rewrite-properties/src/test/java/org/openrewrite/properties/AddPropertyTest.java +++ b/rewrite-properties/src/test/java/org/openrewrite/properties/AddPropertyTest.java @@ -22,6 +22,7 @@ import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.properties.AddProperty.InsertMode; import static org.openrewrite.properties.Assertions.properties; @SuppressWarnings("UnusedProperty") @@ -31,13 +32,10 @@ class AddPropertyTest implements RewriteTest { @Test void newProperty() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - null, - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .build()), properties( """ management=true @@ -53,13 +51,10 @@ void newProperty() { @Test void emptyProperty() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "", - "true", - null, - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("") + .value("true") + .build()), properties( """ management.metrics.enable.process.files=true @@ -71,13 +66,10 @@ void emptyProperty() { @Test void emptyValue() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "", - null, - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("") + .build()), properties( """ management.metrics.enable.process.files=true @@ -89,13 +81,12 @@ void emptyValue() { @Test void containsProperty() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - null, - null, - null - )), + spec -> spec.recipe( + AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .build() + ), properties( """ management.metrics.enable.process.files=true @@ -108,13 +99,11 @@ void containsProperty() { @Test void delimitedByColon() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - null, - ":", - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .delimiter(":") + .build()), properties( """ management=true @@ -131,13 +120,11 @@ void delimitedByColon() { @Test void delimitedByWhitespace() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - null, - " ", - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .delimiter(" ") + .build()), properties( """ management=true @@ -153,13 +140,10 @@ void delimitedByWhitespace() { @Test void addToEmptyFile() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - null, - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .build()), properties( "", """ @@ -172,13 +156,11 @@ void addToEmptyFile() { @Test void addCommentedPropertyToEmptyFile() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - "Management metrics", - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .comment("Management metrics") + .build()), properties( "", """ @@ -192,13 +174,11 @@ void addCommentedPropertyToEmptyFile() { @Test void addCommentedPropertyToExistingFile() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - "Management metrics", - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .comment("Management metrics") + .build()), properties( "management=true", """ @@ -213,13 +193,10 @@ void addCommentedPropertyToExistingFile() { @Test void keepPropertyValueWithLineContinuations() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "management.metrics.enable.process.files", - "true", - null, - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("management.metrics.enable.process.files") + .value("true") + .build()), properties( """ management=tr\\ @@ -242,13 +219,11 @@ void keepPropertyValueWithLineContinuations() { @Test void orderedInsertionBeginning() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "com.sam", - "true", - "sam", - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("com.sam") + .value("true") + .comment("sam") + .build()), properties( """ com.zoe=true @@ -265,13 +240,11 @@ void orderedInsertionBeginning() { @Test void orderedInsertionMiddle() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "com.sam", - "true", - "sam", - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("com.sam") + .value("true") + .comment("sam") + .build()), properties( """ # amy @@ -302,13 +275,11 @@ void orderedInsertionMiddle() { @Test void orderedInsertionEnd() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "com.sam", - "true", - "sam", - null, - null - )), + spec -> spec.recipe(AddProperty.builder() + .property("com.sam") + .value("true") + .comment("sam") + .build()), properties( """ com.amy=true @@ -325,13 +296,12 @@ void orderedInsertionEnd() { @Test void unorderedInsertion() { rewriteRun( - spec -> spec.recipe(new AddProperty( - "com.sam", - "true", - "sam", - null, - false - )), + spec -> spec.recipe(AddProperty.builder() + .property("com.sam") + .value("true") + .comment("sam") + .orderedInsertion(false) + .build()), properties( """ # amy @@ -359,4 +329,259 @@ void unorderedInsertion() { ); } + @Test + void insertBeforeProperty() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("new.prop") + .value("val") + .insertMode(InsertMode.Before) + .insertProperty("com.bea") + .build()), + properties( + """ + com.amy=true + com.bea=true + com.zoe=true + """, + """ + com.amy=true + new.prop=val + com.bea=true + com.zoe=true + """ + ) + ); + } + + @Test + void insertAfterProperty() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("new.prop") + .value("val") + .insertMode(InsertMode.After) + .insertProperty("com.bea") + .build()), + properties( + """ + com.amy=true + com.bea=true + com.zoe=true + """, + """ + com.amy=true + com.bea=true + new.prop=val + com.zoe=true + """ + ) + ); + } + + @Test + void insertBeforePropertyWithPrecedingComment() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("new.prop") + .value("val") + .insertMode(InsertMode.Before) + .insertProperty("com.bea") + .build()), + properties( + """ + com.amy=true + # bea comment + com.bea=true + com.zoe=true + """, + """ + com.amy=true + new.prop=val + # bea comment + com.bea=true + com.zoe=true + """ + ) + ); + } + + @Test + void insertBeforePropertyWithNewPropertyComment() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("new.prop") + .value("val") + .comment("new property comment") + .insertMode(InsertMode.Before) + .insertProperty("com.bea") + .build()), + properties( + """ + com.amy=true + com.bea=true + com.zoe=true + """, + """ + com.amy=true + # new property comment + new.prop=val + com.bea=true + com.zoe=true + """ + ) + ); + } + + @Test + void insertAfterPropertyWithNewPropertyComment() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("new.prop") + .value("val") + .comment("new property comment") + .insertMode(InsertMode.After) + .insertProperty("com.bea") + .build()), + properties( + """ + com.amy=true + com.bea=true + com.zoe=true + """, + """ + com.amy=true + com.bea=true + # new property comment + new.prop=val + com.zoe=true + """ + ) + ); + } + + @Test + void insertBeforePropertyNotFoundFallsBackToAlphabetical() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("com.sam") + .value("true") + .insertMode(InsertMode.Before) + .insertProperty("nonexistent.key") + .build()), + properties( + """ + com.amy=true + com.zoe=true + """, + """ + com.amy=true + com.sam=true + com.zoe=true + """ + ) + ); + } + + @Test + void insertAfterPropertyNotFoundUnordered() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("com.sam") + .value("true") + .orderedInsertion(false) + .insertMode(InsertMode.After) + .insertProperty("nonexistent.key") + .build()), + properties( + """ + com.amy=true + com.zoe=true + """, + """ + com.amy=true + com.zoe=true + com.sam=true + """ + ) + ); + } + + @Test + void insertAfterLastEntry() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("zzz.last") + .value("val") + .insertMode(InsertMode.After) + .insertProperty("com.zoe") + .build()), + properties( + """ + com.amy=true + com.zoe=true + """, + """ + com.amy=true + com.zoe=true + zzz.last=val + """ + ) + ); + } + + @Test + void insertBeforeOverridesOrderedInsertion() { + rewriteRun( + spec -> spec.recipe(AddProperty.builder() + .property("zzz.prop") + .value("val") + .orderedInsertion(true) + .insertMode(InsertMode.Before) + .insertProperty("com.amy") + .build()), + properties( + """ + com.amy=true + com.zoe=true + """, + """ + zzz.prop=val + com.amy=true + com.zoe=true + """ + ) + ); + } + + @Test + void insertPropertyRequiredForBeforeMode() { + AddProperty recipe = AddProperty.builder() + .property("new.prop") + .value("val") + .insertMode(InsertMode.Before) + .build(); + assertThat(recipe.validate().isInvalid()).isTrue(); + } + + @Test + void insertPropertyRequiredForAfterMode() { + AddProperty recipe = AddProperty.builder() + .property("new.prop") + .value("val") + .insertMode(InsertMode.After) + .build(); + assertThat(recipe.validate().isInvalid()).isTrue(); + } + + @Test + void insertPropertyNotRequiredForLastMode() { + AddProperty recipe = AddProperty.builder() + .property("new.prop") + .value("val") + .insertMode(InsertMode.Last) + .build(); + assertThat(recipe.validate().isValid()).isTrue(); + } + } From ebd5bb45ae7abe32bdb83257dcf1d6f7b0e34599 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 3 Apr 2026 13:18:53 -0700 Subject: [PATCH 2/2] Stupid javadoc --- rewrite-properties/build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rewrite-properties/build.gradle.kts b/rewrite-properties/build.gradle.kts index 3074daa6347..8a8abed55e7 100755 --- a/rewrite-properties/build.gradle.kts +++ b/rewrite-properties/build.gradle.kts @@ -2,6 +2,10 @@ plugins { id("org.openrewrite.build.language-library") } +tasks.withType().configureEach { + exclude("**/AddProperty**") +} + dependencies { api(project(":rewrite-core")) api("org.jetbrains:annotations:latest.release")