dependabot-nuget 0.387.0 → 0.388.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b6442680ffbd904b76c8f2da10bf879900b426d17344f1306c5b68f989e1f99
4
- data.tar.gz: b6131bc3dd8b9026d5cc304f9d7cec4da6643143995ff0a3e6b31db676c9dd3c
3
+ metadata.gz: ab3a09f44990043a0aea2f247f1319e0f47f3d82a5dc185062cf5f524256e24c
4
+ data.tar.gz: 785af3809b07b9e07628389cfe491b8bfa1509f7a438cd512b25b78650bab691
5
5
  SHA512:
6
- metadata.gz: 0f992808734c45a92cce97321d5dcfaefbdb8e0ebf41152e455e963718133d45a1ead0beda7f0a3b09417e0fa2db7bb6a711c21960b327f68417b244d7e80fa9
7
- data.tar.gz: 5bc4da0439ee92ec1021f908b159471c7483e18b38625fc0c18cb5f63099b117db45a98a6468b6adde87206697cf65c6be5137c32dcf44579c2db25e6f18bfcc
6
+ metadata.gz: e426620a0669b8c692d7de4d556a7f39bfc58d833168bbad8ba6f24b359e53d793240d3e0cdeb2ef74f83b89f4c1f2820a068532147379c49a3feddaa4a7139d
7
+ data.tar.gz: 35adfa4d731c4cc3c72279531b44bf5ce00eb5c06bf0e83c263ed1509c276ec742d3932718d0e01f800bd702233b419d43f97a36ba6b3d0fd38fc7382c754b95
@@ -8,7 +8,7 @@ namespace NuGetUpdater.Core.Updater
8
8
  {
9
9
  internal class SpecialImportsConditionPatcher : IDisposable
10
10
  {
11
- private readonly List<string?> _capturedConditions = new List<string?>();
11
+ private readonly List<IXmlElementSyntax> _capturedElements = [];
12
12
  private readonly XmlFilePreAndPostProcessor _processor;
13
13
 
14
14
  // These files only ship with a full Visual Studio install
@@ -60,20 +60,48 @@ namespace NuGetUpdater.Core.Updater
60
60
  preProcessor: (i, n) =>
61
61
  {
62
62
  var element = (IXmlElementSyntax)n;
63
- _capturedConditions.Add(element.GetAttributeValue("Condition"));
63
+ _capturedElements.Add(element);
64
64
  return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false");
65
65
  },
66
66
  postProcessor: (i, n) =>
67
67
  {
68
68
  var element = (IXmlElementSyntax)n;
69
- var newElement = element.RemoveAttributeByName("Condition");
70
- var capturedCondition = _capturedConditions[i];
71
- if (capturedCondition is not null)
69
+ var originalElement = _capturedElements[i];
70
+
71
+ // copy over attribute values from the potentially updated element EXCEPT for `Condition="false"`
72
+ var updatedAttributeValues = element.Attributes.ToDictionary(e => e.Name, e => e.Value);
73
+ var originalRestoredElement = originalElement;
74
+ foreach (var (attributeName, attributeValue) in updatedAttributeValues)
75
+ {
76
+ if (attributeName == "Condition" && attributeValue == "false")
77
+ {
78
+ // this was the attribute we manually patched so it shouldn't be copied over
79
+ // note that if the update operation change the Condition value, then we _will_ copy it over, which is what we want
80
+ continue;
81
+ }
82
+
83
+ var oldAttribute = originalRestoredElement.GetAttribute(attributeName);
84
+ if (oldAttribute is null)
85
+ {
86
+ // the attribute was added by an operation and just needs to be added
87
+ originalRestoredElement = originalRestoredElement.WithAttribute(attributeName, attributeValue);
88
+ }
89
+ else
90
+ {
91
+ // the attribute was updated by an operation and needs to be maintained
92
+ var updatedAttribute = oldAttribute.WithValue(updatedAttributeValues[attributeName]);
93
+ originalRestoredElement = originalRestoredElement.ReplaceAttribute(oldAttribute, updatedAttribute);
94
+ }
95
+ }
96
+
97
+ // remove attributes not present in the updated element
98
+ var attributeNamesToRemove = originalRestoredElement.Attributes.Select(a => a.Name).Except(updatedAttributeValues.Keys);
99
+ foreach (var attributeNameToRemove in attributeNamesToRemove)
72
100
  {
73
- newElement = newElement.WithAttribute("Condition", capturedCondition);
101
+ originalRestoredElement = originalRestoredElement.RemoveAttributeByName(attributeNameToRemove);
74
102
  }
75
103
 
76
- return (XmlNodeSyntax)newElement;
104
+ return (XmlNodeSyntax)originalRestoredElement;
77
105
  }
78
106
  );
79
107
  }
@@ -66,15 +66,25 @@ public static class XmlExtensions
66
66
 
67
67
  public static IXmlElementSyntax WithAttribute(this IXmlElementSyntax parent, string name, string value)
68
68
  {
69
- var singleSpanceTrivia = SyntaxFactory.WhitespaceTrivia(" ");
70
-
71
- return parent.AddAttribute(SyntaxFactory.XmlAttribute(
69
+ var singleSpaceTrivia = SyntaxFactory.WhitespaceTrivia(" ");
70
+ var newAttribute = SyntaxFactory.XmlAttribute(
72
71
  SyntaxFactory.XmlName(null, SyntaxFactory.XmlNameToken(name, null, null)),
73
72
  SyntaxFactory.Punctuation(SyntaxKind.EqualsToken, "=", null, null),
74
73
  SyntaxFactory.XmlString(
75
74
  SyntaxFactory.Punctuation(SyntaxKind.SingleQuoteToken, "\"", null, null),
76
75
  SyntaxFactory.XmlTextLiteralToken(value, null, null),
77
- SyntaxFactory.Punctuation(SyntaxKind.SingleQuoteToken, "\"", null, singleSpanceTrivia))));
76
+ SyntaxFactory.Punctuation(SyntaxKind.SingleQuoteToken, "\"", null, singleSpaceTrivia)));
77
+ var lastAttribute = parent.Attributes.LastOrDefault();
78
+ if (lastAttribute is not null &&
79
+ string.IsNullOrEmpty(lastAttribute.GetTrailingTrivia().ToFullString()))
80
+ {
81
+ // ensure there's a space at the end of the previous attribute so we don't end up with this:
82
+ // <Element ExistingAttribute="ExistingValue"NewAttribute="NewValue" />
83
+ parent = parent.RemoveAttribute(lastAttribute)
84
+ .AddAttribute(lastAttribute.WithTrailingTrivia(singleSpaceTrivia));
85
+ }
86
+
87
+ return parent.AddAttribute(newAttribute);
78
88
  }
79
89
 
80
90
  public static XmlElementSyntax CreateOpenCloseXmlElementSyntax(string name, int indentation = 2, bool spaces = true)
@@ -54,6 +54,120 @@ public class SpecialFilePatcherTests
54
54
  Assert.Equal(restoredContent.Replace("\r", ""), fileContent.Replace("\r", ""));
55
55
  }
56
56
 
57
+ [Fact]
58
+ public async Task ModifiedAttributesAreRetainedThroughConditionPatching()
59
+ {
60
+ // it's possible that an operation updated a `<Import Project="..." />` element after we patched it with `Condition="false"`
61
+ // this test ensures that if that did happen, we don't lose the updated attributes when we restore the original content
62
+
63
+ // arrange
64
+ var projectFileName = "project.csproj";
65
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync((projectFileName, """
66
+ <Project Sdk="Microsoft.NET.Sdk">
67
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" />
68
+ </Project>
69
+ """));
70
+ var projectPath = Path.Join(tempDir.DirectoryPath, projectFileName);
71
+
72
+ // act
73
+ using (var patcher = new SpecialImportsConditionPatcher(projectPath))
74
+ {
75
+ var initialPatchedContent = await File.ReadAllTextAsync(projectPath, TestContext.Current.CancellationToken);
76
+ Assert.Contains("Condition=\"false\"", initialPatchedContent);
77
+
78
+ // this simulates an operation that updated the `<Import Project="..."` attribute that we still want to be present when we're all done
79
+ await File.WriteAllTextAsync(projectPath, """
80
+ <Project Sdk="Microsoft.NET.Sdk">
81
+ <Import Project="UPDATED\Path\Microsoft.WebApplication.targets" Condition="false" />
82
+ </Project>
83
+ """, TestContext.Current.CancellationToken);
84
+ }
85
+
86
+ // assert
87
+ var restoredAndUpdatedContent = await File.ReadAllTextAsync(projectPath, TestContext.Current.CancellationToken);
88
+ Assert.Equal("""
89
+ <Project Sdk="Microsoft.NET.Sdk">
90
+ <Import Project="UPDATED\Path\Microsoft.WebApplication.targets" />
91
+ </Project>
92
+ """.Replace("\r", ""), restoredAndUpdatedContent.Replace("\r", ""));
93
+ }
94
+
95
+ [Fact]
96
+ public async Task NewAttributesAreRetainedThroughConditionPatching()
97
+ {
98
+ // it's possible that an operation updated a `<Import Project="..." />` element after we patched it with `Condition="false"`
99
+ // this test ensures that if that did happen, we don't lose any attributes that were added
100
+
101
+ // arrange
102
+ var projectFileName = "project.csproj";
103
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync((projectFileName, """
104
+ <Project Sdk="Microsoft.NET.Sdk">
105
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" />
106
+ </Project>
107
+ """));
108
+ var projectPath = Path.Join(tempDir.DirectoryPath, projectFileName);
109
+
110
+ // act
111
+ using (var patcher = new SpecialImportsConditionPatcher(projectPath))
112
+ {
113
+ var initialPatchedContent = await File.ReadAllTextAsync(projectPath, TestContext.Current.CancellationToken);
114
+ Assert.Contains("Condition=\"false\"", initialPatchedContent);
115
+
116
+ // this simulates an operation that added the `NewAttribute="..."` attribute that we still want to be present when we're all done
117
+ await File.WriteAllTextAsync(projectPath, """
118
+ <Project Sdk="Microsoft.NET.Sdk">
119
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" NewAttribute="NewValue" Condition="false" />
120
+ </Project>
121
+ """, TestContext.Current.CancellationToken);
122
+ }
123
+
124
+ // assert
125
+ var restoredAndUpdatedContent = await File.ReadAllTextAsync(projectPath, TestContext.Current.CancellationToken);
126
+ Assert.Equal("""
127
+ <Project Sdk="Microsoft.NET.Sdk">
128
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" NewAttribute="NewValue" />
129
+ </Project>
130
+ """.Replace("\r", ""), restoredAndUpdatedContent.Replace("\r", ""));
131
+ }
132
+
133
+ [Fact]
134
+ public async Task RemovedAttributesAreNotReAddedThroughConditionPatching()
135
+ {
136
+ // it's possible that an operation updated a `<Import Project="..." />` element after we patched it with `Condition="false"`
137
+ // this test ensures that if that did happen, we don't re-add any attributes that were removed
138
+
139
+ // arrange
140
+ var projectFileName = "project.csproj";
141
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync((projectFileName, """
142
+ <Project Sdk="Microsoft.NET.Sdk">
143
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" UnrelatedAttribute="UnrelatedValue" />
144
+ </Project>
145
+ """));
146
+ var projectPath = Path.Join(tempDir.DirectoryPath, projectFileName);
147
+
148
+ // act
149
+ using (var patcher = new SpecialImportsConditionPatcher(projectPath))
150
+ {
151
+ var initialPatchedContent = await File.ReadAllTextAsync(projectPath, TestContext.Current.CancellationToken);
152
+ Assert.Contains("Condition=\"false\"", initialPatchedContent);
153
+
154
+ // this simulates an operation that removed the `UnrelatedAttribute="..."` attribute that should remain absent when we're all done
155
+ await File.WriteAllTextAsync(projectPath, """
156
+ <Project Sdk="Microsoft.NET.Sdk">
157
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="false" />
158
+ </Project>
159
+ """, TestContext.Current.CancellationToken);
160
+ }
161
+
162
+ // assert
163
+ var restoredAndUpdatedContent = await File.ReadAllTextAsync(projectPath, TestContext.Current.CancellationToken);
164
+ Assert.Equal("""
165
+ <Project Sdk="Microsoft.NET.Sdk">
166
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" />
167
+ </Project>
168
+ """.Replace("\r", ""), restoredAndUpdatedContent.Replace("\r", ""));
169
+ }
170
+
57
171
  public static IEnumerable<object[]> SpecialImportsConditionPatcherTestData()
58
172
  {
59
173
  // magic file names
@@ -79,6 +193,27 @@ public class SpecialFilePatcherTests
79
193
  """
80
194
  ];
81
195
 
196
+ // one-off test to verify no whitespace before end tag is supported
197
+ yield return
198
+ [
199
+ // fileContent
200
+ """
201
+ <Project>
202
+ <Import Project="Unrelated.One.targets" />
203
+ <Import Project="Some\Path\Microsoft.WebApplication.targets"/>
204
+ <Import Project="Unrelated.Two.targets" />
205
+ </Project>
206
+ """,
207
+ // expectedPatchedContent
208
+ """
209
+ <Project>
210
+ <Import Project="Unrelated.One.targets" />
211
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="false" />
212
+ <Import Project="Unrelated.Two.targets" />
213
+ </Project>
214
+ """
215
+ ];
216
+
82
217
  // one-off test to verify existing conditions are restored
83
218
  yield return
84
219
  [
@@ -100,6 +235,28 @@ public class SpecialFilePatcherTests
100
235
  """
101
236
  ];
102
237
 
238
+ // one-off test to verify existing conditions are restored when on a different line
239
+ yield return
240
+ [
241
+ // fileContent
242
+ """
243
+ <Project>
244
+ <Import Project="Unrelated.One.targets" />
245
+ <Import Project="Some\Path\Microsoft.WebApplication.targets"
246
+ Condition="existing condition on a different line" />
247
+ <Import Project="Unrelated.Two.targets" />
248
+ </Project>
249
+ """,
250
+ // expectedPatchedContent
251
+ """
252
+ <Project>
253
+ <Import Project="Unrelated.One.targets" />
254
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="false" />
255
+ <Import Project="Unrelated.Two.targets" />
256
+ </Project>
257
+ """
258
+ ];
259
+
103
260
  // all file variations - by its nature, also verifies that multiple replacements can occur
104
261
  yield return
105
262
  [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.387.0
4
+ version: 0.388.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.387.0
18
+ version: 0.388.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.387.0
25
+ version: 0.388.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -581,7 +581,7 @@ licenses:
581
581
  - MIT
582
582
  metadata:
583
583
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
584
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.387.0
584
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.388.0
585
585
  rdoc_options: []
586
586
  require_paths:
587
587
  - lib