dependabot-nuget 0.370.0 → 0.372.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Build.props +1 -0
  3. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/DotNetPackageCorrelation.csproj +1 -0
  4. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Build.props +2 -0
  5. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Build.Tasks/NuGet.Build.Tasks.csproj +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.PackageManagement/NuGet.PackageManagement.csproj +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +14 -6
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +0 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +31 -16
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/GetProperty.targets +9 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +1 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/CommitOptions_IncludeScopeConverter.cs +1 -1
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +41 -14
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +55 -51
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +62 -12
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +2 -2
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +0 -20
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +96 -5
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +0 -8
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +2 -49
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +0 -77
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +0 -1
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +0 -58
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +1 -2
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlerSelectionTests.cs +2 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlersTestsBase.cs +2 -1
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +1 -4
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +1 -1
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +2 -1
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterTestsBase.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +64 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests_GetDocumentIndentationCharactersTests.cs +120 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/SpecialFilePatcherTests.cs +2 -2
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/LinuxOnlyAttribute.cs +6 -1
  35. metadata +6 -5
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Property.cs +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a443457e2e8e24c70f4cb8a9e11a2fff6c3f9d15255b7e023c29a1ccdf4bd548
4
- data.tar.gz: 272f36da3e3a53a28cd748d21da99bf8b5c94ca94c854e549db6dc1ee8b2f40a
3
+ metadata.gz: a058182c9ce909abc8398d444a3cba984f43528a7655fd5ee96cc8c607bbfd76
4
+ data.tar.gz: ffd8f02946da91739938e89ca13e3beb4e6b3ef157bb23083251df98cf45930f
5
5
  SHA512:
6
- metadata.gz: 7e5806e6b08c76324c6ba174c1f0a4e085ab62ef8f6694b365932beefa770ea5cdcdf578574f73e61f3b08b1ff7fd2523fa7f23a9598a34f69ccf1f236397b44
7
- data.tar.gz: 7786d4b33b44eb4fe14cc7ba98d77c5ad45c9213ae80320d895de61db18e19592b72243c3a25b4bcdc182dbdb0f6e2787812b14dd8cf49c606503053c3612a3b
6
+ metadata.gz: 4d9d90b837aa4ebd014a46c1dc0a89ae29689f30bbe84fc49c16deec4c260625e263aa731f100d0ef909ad2da00a48c131e9086393866f54e83e48e6678a77f8
7
+ data.tar.gz: d8c27b9ff9c1837c191ce89ae005e3071f5fc5cd09d76d8c1e0ecc3ea1bd511ed1cd544bac6bdd0ea17aef43dc49747b2c87cc8cbe0f3e3645f6977582ec3394
@@ -4,6 +4,7 @@
4
4
  <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
5
5
  <NoWarn>$(NoWarn);NU1701</NoWarn>
6
6
  <Nullable>enable</Nullable>
7
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
7
8
  <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
8
9
  </PropertyGroup>
9
10
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  <PropertyGroup>
4
4
  <TargetFramework>$(CommonTargetFramework)</TargetFramework>
5
+ <NoWarn>$(NoWarn);NU1510</NoWarn>
5
6
  </PropertyGroup>
6
7
 
7
8
  <ItemGroup>
@@ -10,6 +10,8 @@
10
10
  <NoWarn>$(NoWarn);SYSLIB0014</NoWarn><!-- obsolete -->
11
11
  <NuGetSourceLocation>$(MSBuildThisFileDirectory)..\..\NuGet.Client</NuGetSourceLocation>
12
12
  <SharedDirectory>$(NuGetSourceLocation)\build\Shared</SharedDirectory>
13
+ <NoWarn>$(NoWarn);CA1416</NoWarn><!-- platform compatibility in vendored NuGet.Client code -->
14
+ <NoWarn>$(NoWarn);CS0436</NoWarn><!-- type conflicts with imported types in vendored NuGet.Client code -->
13
15
  <Version>6.8.0</Version>
14
16
  </PropertyGroup>
15
17
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  <PropertyGroup>
4
4
  <TargetFramework>$(CommonTargetFramework)</TargetFramework>
5
- <NoWarn>$(NoWarn);CS1591</NoWarn>
5
+ <NoWarn>$(NoWarn);CS1591;NU1510</NoWarn>
6
6
  </PropertyGroup>
7
7
 
8
8
  <ItemGroup>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <PropertyGroup>
4
4
  <TargetFramework>$(CommonTargetFramework)</TargetFramework>
5
- <NoWarn>$(NoWarn);CS1591;CS1580;CS1574;CS1573;RS0041</NoWarn>
5
+ <NoWarn>$(NoWarn);CS1591;CS1580;CS1574;CS1573;RS0041;NU1510</NoWarn>
6
6
  </PropertyGroup>
7
7
 
8
8
  <ItemGroup>
@@ -318,7 +318,20 @@ public partial class DiscoveryWorker : IDiscoveryWorker
318
318
  private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForProjectPathsAsync(string repoRootPath, string workspacePath, IEnumerable<string> projectPaths)
319
319
  {
320
320
  var normalizedProjectPaths = projectPaths.SelectMany(p => PathHelper.ResolveCaseInsensitivePathsInsideRepoRoot(p, repoRootPath) ?? []).Distinct().ToImmutableArray();
321
- var disposables = normalizedProjectPaths.Select(p => new SpecialImportsConditionPatcher(p)).ToImmutableArray();
321
+
322
+ // Find all MSBuild files that may contain special imports
323
+ var enumerationOptions = new EnumerationOptions()
324
+ {
325
+ RecurseSubdirectories = true,
326
+ IgnoreInaccessible = true,
327
+ AttributesToSkip = FileAttributes.ReparsePoint,
328
+ };
329
+ var msbuildExtensions = new[] { ".props", ".targets", ".proj", ".csproj", ".vbproj", ".fsproj" };
330
+ var filesToPatch = Directory.GetFiles(repoRootPath, "*.*", enumerationOptions)
331
+ .Where(f => msbuildExtensions.Contains(Path.GetExtension(f), StringComparer.OrdinalIgnoreCase))
332
+ .ToImmutableArray();
333
+
334
+ var disposables = filesToPatch.Select(p => new SpecialImportsConditionPatcher(p)).ToImmutableArray();
322
335
  var results = new Dictionary<string, ProjectDiscoveryResult>(StringComparer.Ordinal);
323
336
 
324
337
  try
@@ -411,10 +424,6 @@ public partial class DiscoveryWorker : IDiscoveryWorker
411
424
  var mergedDependencies = mergedDependenciesSet.Values
412
425
  .OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
413
426
  .ToImmutableArray();
414
- var mergedProperties = result1.Properties.Concat(result2.Properties)
415
- .DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
416
- .OrderBy(p => p.Name)
417
- .ToImmutableArray();
418
427
  var mergedTargetFrameworks = result1.TargetFrameworks.Concat(result2.TargetFrameworks)
419
428
  .Select(t =>
420
429
  {
@@ -450,7 +459,6 @@ public partial class DiscoveryWorker : IDiscoveryWorker
450
459
  Dependencies = mergedDependencies,
451
460
  IsSuccess = result1.IsSuccess && result2.IsSuccess,
452
461
  Error = result1.Error ?? result2.Error,
453
- Properties = mergedProperties,
454
462
  TargetFrameworks = mergedTargetFrameworks,
455
463
  ReferencedProjectPaths = mergedReferencedProjects,
456
464
  ImportedFiles = mergedImportedFiles,
@@ -10,7 +10,6 @@ public record ProjectDiscoveryResult : IDiscoveryResultWithDependencies
10
10
  public required ImmutableArray<Dependency> Dependencies { get; init; }
11
11
  public bool IsSuccess { get; init; } = true;
12
12
  public JobErrorBase? Error { get; init; } = null;
13
- public ImmutableArray<Property> Properties { get; init; } = [];
14
13
  public ImmutableArray<string> TargetFrameworks { get; init; } = [];
15
14
  public ImmutableArray<string> ReferencedProjectPaths { get; init; } = [];
16
15
  public required ImmutableArray<string> ImportedFiles { get; init; }
@@ -1,7 +1,6 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Text.Json;
3
3
  using System.Xml.Linq;
4
- using System.Xml.XPath;
5
4
 
6
5
  using Microsoft.Build.Logging.StructuredLogger;
7
6
 
@@ -63,6 +62,12 @@ internal static class SdkProjectDiscovery
63
62
  "GenerateBuildDependencyFile"
64
63
  ];
65
64
 
65
+ // these targets are required to evaluate a legacy project with a single operation
66
+ private static readonly ImmutableArray<string> LegacyProjectSingleRestoreTargetNames = ["ResolveProjectReferences"];
67
+
68
+ // this property evaluates to a version number in an SDK-style project and is unset or empty otherwise
69
+ private const string NETCoreSdkVersionPropertyName = "NETCoreSdkVersion";
70
+
66
71
  // this seems to be the maximum number of TFMs that can be restored in parallel without running into race conditions
67
72
  private const int MaximumParallelTargetFrameworkRestores = 2;
68
73
 
@@ -138,20 +143,39 @@ internal static class SdkProjectDiscovery
138
143
  var binLogPath = Path.Combine(Path.GetTempPath(), $"msbuild_{Guid.NewGuid():d}.binlog");
139
144
  try
140
145
  {
141
- // when using single restore, we can directly invoke the relevant targets
146
+ // when using single restore, we can directly invoke the relevant targets...
142
147
  var args = new List<string>() { "msbuild", startingProjectPath };
143
- var targets = await MSBuildHelper.GetProjectTargetsAsync(startingProjectPath, logger);
144
- var useDirectRestore = SingleRestoreTargetNames.All(targets.Contains);
148
+
149
+ // ...but determining what the relevant targets are can be complicated
150
+
151
+ // For SDK-style projects the targets `Restore`, `ResolveProjectReferences`, and `GenerateBuildDependencyFile`
152
+ // are necessary. If the project has a single target framework, those magic targets will all be present and can
153
+ // be directly invoked, but if the project has multiple target frameworks, those targets will _NOT_ be directly
154
+ // present and we instead have to invoke the `Build` target and specify the three magic values as `InnerTargets`.
155
+
156
+ // If the project is legacy then those three magic targets will not be present, but we shouldn't use the `Build`
157
+ // and `InnerTargets` trick because that's an SDK-only mechanism, so we instead only need to invoke
158
+ // `ResolveProjectReferences` to gather all `PackageReference` items and further down we re-build the transitive
159
+ // dependency set. Without the legacy project check, we could incorrectly invoke `Build` which eventually tries
160
+ // to call `csc.exe` which is unnecessary and can be slow.
161
+ var netCoreSdkVersionValue = await MSBuildHelper.GetProjectPropertyAsync(startingProjectPath, NETCoreSdkVersionPropertyName, logger);
162
+ var isLegacyProject = string.IsNullOrEmpty(netCoreSdkVersionValue);
163
+ var requiredTargets = isLegacyProject
164
+ ? LegacyProjectSingleRestoreTargetNames
165
+ : SingleRestoreTargetNames;
166
+
167
+ var actualTargets = await MSBuildHelper.GetProjectTargetsAsync(startingProjectPath, logger);
168
+ var useDirectRestore = requiredTargets.All(actualTargets.Contains);
145
169
  if (useDirectRestore || isIndividualTfmRestore)
146
170
  {
147
171
  // directly call the required targets
148
- args.Add($"/t:{string.Join(",", SingleRestoreTargetNames)}");
172
+ args.Add($"/t:{string.Join(",", requiredTargets)}");
149
173
  }
150
174
  else
151
175
  {
152
176
  // delegate to the inner build and call those targets
153
177
  args.Add("/t:Build");
154
- args.Add($"/p:InnerTargets=\"{string.Join(";", SingleRestoreTargetNames)}\"");
178
+ args.Add($"/p:InnerTargets=\"{string.Join(";", requiredTargets)}\"");
155
179
  }
156
180
 
157
181
  // inject various props and targets to help with discovery
@@ -385,7 +409,7 @@ internal static class SdkProjectDiscovery
385
409
  foreach (var projectPath in resolvedProperties.Keys)
386
410
  {
387
411
  var projectProperties = resolvedProperties[projectPath];
388
- var isProjectLegacy = !projectProperties.ContainsKey("NETCoreSdkVersion"); // legacy projects don't contain this property
412
+ var isProjectLegacy = !projectProperties.ContainsKey(NETCoreSdkVersionPropertyName); // legacy projects don't contain this property
389
413
  if (isProjectLegacy)
390
414
  {
391
415
  logger.Info($"Project {projectPath} is legacy");
@@ -482,9 +506,6 @@ internal static class SdkProjectDiscovery
482
506
  }
483
507
 
484
508
  var projectFullDirectory = Path.GetDirectoryName(projectPath)!;
485
- var doc = XDocument.Load(projectPath);
486
- var localPropertyDefinitionElements = doc.Root!.XPathSelectElements("/Project/PropertyGroup/*");
487
- var projectPropertyNames = localPropertyDefinitionElements.Select(e => e.Name.LocalName).ToHashSet(StringComparer.OrdinalIgnoreCase);
488
509
  var projectRelativePath = Path.GetRelativePath(workspacePath, projectPath);
489
510
 
490
511
  var propertiesForProject = resolvedProperties.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
@@ -611,11 +632,6 @@ internal static class SdkProjectDiscovery
611
632
 
612
633
  // others
613
634
  var projectProperties = resolvedProperties[projectPath];
614
- var properties = projectProperties
615
- .Where(pkvp => projectPropertyNames.Contains(pkvp.Key))
616
- .Select(pkvp => new Property(pkvp.Key, pkvp.Value, Path.GetRelativePath(repoRootPath, projectPath).NormalizePathToUnix()))
617
- .OrderBy(p => p.Name)
618
- .ToImmutableArray();
619
635
  var referenced = referencedProjects.GetOrAdd(projectPath, () => new(PathComparer.Instance))
620
636
  .Select(p => Path.GetRelativePath(projectFullDirectory, p).NormalizePathToUnix())
621
637
  .OrderBy(p => p)
@@ -640,7 +656,6 @@ internal static class SdkProjectDiscovery
640
656
  FilePath = projectRelativePath,
641
657
  Dependencies = dependencies,
642
658
  TargetFrameworks = tfms,
643
- Properties = properties,
644
659
  ReferencedProjectPaths = referenced,
645
660
  ImportedFiles = imported,
646
661
  AdditionalFiles = additional,
@@ -0,0 +1,9 @@
1
+ <Project>
2
+ <Target Name="_Dependabot_GetProperty">
3
+ <PropertyGroup>
4
+ <!-- the following value will get copied and replaced at runtime -->
5
+ <_DependabotPropertyValue>%RequestedPropertyName%</_DependabotPropertyValue>
6
+ </PropertyGroup>
7
+ <Message Importance="High" Text="__PROPERTY_VALUE:$(_DependabotPropertyValue)" />
8
+ </Target>
9
+ </Project>
@@ -11,6 +11,7 @@
11
11
  <None Include="DependencyDiscovery.props" CopyToOutputDirectory="PreserveNewest" />
12
12
  <None Include="DependencyDiscoveryTargetingPacks.props" CopyToOutputDirectory="PreserveNewest" />
13
13
  <None Include="DependencyDiscovery.targets" CopyToOutputDirectory="PreserveNewest" />
14
+ <None Include="GetProperty.targets" CopyToOutputDirectory="PreserveNewest" />
14
15
  <None Include="TargetFrameworkReporter.targets" CopyToOutputDirectory="PreserveNewest" />
15
16
  </ItemGroup>
16
17
 
@@ -1,4 +1,4 @@
1
- using System.Text.Json;
1
+ using System.Text.Json;
2
2
  using System.Text.Json.Serialization;
3
3
 
4
4
  namespace NuGetUpdater.Core.Run;
@@ -150,29 +150,35 @@ public class XmlFileWriter : IFileWriter
150
150
  // find last `<ItemGroup>` in the project...
151
151
  Action addItemGroup = () => { }; // adding an ItemGroup to the project isn't always necessary, but it's much easier to prepare for it here
152
152
  var projectDocument = filesAndContents[projectRelativePath];
153
- var lastItemGroup = projectDocument.RootSyntax.Elements
154
- .LastOrDefault(e => e.Name.Equals(ItemGroupElementName, StringComparison.OrdinalIgnoreCase));
155
- if (lastItemGroup is null)
153
+ var indentation = GetDocumentIndentationCharacters(projectDocument);
154
+ var itemGroups = projectDocument.RootSyntax.Elements
155
+ .Where(e => e.Name.Equals(ItemGroupElementName, StringComparison.OrdinalIgnoreCase))
156
+ .ToArray();
157
+ var itemGroupsWithPackageReferences = itemGroups
158
+ .Where(e => e.Elements.Any(c => c.Name.Equals(PackageReferenceElementName, StringComparison.OrdinalIgnoreCase)))
159
+ .ToArray();
160
+ var itemGroupForInsertion = itemGroupsWithPackageReferences.LastOrDefault() ?? itemGroups.LastOrDefault();
161
+ if (itemGroupForInsertion is null)
156
162
  {
157
163
  _logger.Info($"No `<{ItemGroupElementName}>` element found in project; adding one.");
158
- lastItemGroup = XmlExtensions.CreateOpenCloseXmlElementSyntax(ItemGroupElementName,
159
- new SyntaxList<SyntaxNode>([SyntaxFactory.EndOfLineTrivia("\n"), SyntaxFactory.WhitespaceTrivia(" ")]),
164
+ itemGroupForInsertion = XmlExtensions.CreateOpenCloseXmlElementSyntax(ItemGroupElementName,
165
+ new SyntaxList<SyntaxNode>([SyntaxFactory.EndOfLineTrivia("\n"), SyntaxFactory.WhitespaceTrivia(indentation)]),
160
166
  insertIntermediateNewline: false);
161
167
  addItemGroup = () =>
162
168
  {
163
169
  // add the new element
164
- var updatedRootSyntax = projectDocument.RootSyntax.AddChild(lastItemGroup);
170
+ var updatedRootSyntax = projectDocument.RootSyntax.AddChild(itemGroupForInsertion);
165
171
  var updatedProjectDocument = projectDocument.ReplaceNode(projectDocument.RootSyntax.AsNode, updatedRootSyntax.AsNode);
166
172
 
167
173
  // reset well-known variables
168
174
  projectDocument = updatedProjectDocument;
169
175
  filesAndContents[projectRelativePath] = updatedProjectDocument;
170
- lastItemGroup = updatedProjectDocument.RootSyntax.Elements.Last(e => e.Name.Equals(ItemGroupElementName, StringComparison.OrdinalIgnoreCase));
176
+ itemGroupForInsertion = updatedProjectDocument.RootSyntax.Elements.Last(e => e.Name.Equals(ItemGroupElementName, StringComparison.OrdinalIgnoreCase));
171
177
  };
172
178
  }
173
179
 
174
180
  // ...find where the new item should go...
175
- var elementsBeforeNew = GetOrderedElementsBeforeSpecified(lastItemGroup, PackageReferenceElementName, [IncludeAttributeName, UpdateAttributeName], requiredPackageVersion.Name);
181
+ var elementsBeforeNew = GetOrderedElementsBeforeSpecified(itemGroupForInsertion, PackageReferenceElementName, [IncludeAttributeName, UpdateAttributeName], requiredPackageVersion.Name);
176
182
 
177
183
  // ...prepare a new `<PackageReference>` element...
178
184
  var newElement = XmlExtensions.CreateSingleLineXmlElementSyntax(PackageReferenceElementName, leadingTrivia: new SyntaxList<SyntaxNode>())
@@ -221,18 +227,18 @@ public class XmlFileWriter : IFileWriter
221
227
  else
222
228
  {
223
229
  // no prior package references; add to the front
224
- var itemGroupTrivia = lastItemGroup.AsNode.GetLeadingTrivia().ToList();
230
+ var itemGroupTrivia = itemGroupForInsertion.AsNode.GetLeadingTrivia().ToList();
225
231
  var priorEolIndex = itemGroupTrivia.FindLastIndex(t => t.Kind == SyntaxKind.EndOfLineTrivia);
226
232
  var indentTrivia = itemGroupTrivia
227
233
  .Skip(priorEolIndex + 1)
228
234
  .Select(t => SyntaxFactory.WhitespaceTrivia(t.ToFullString()))
229
235
  .ToArray();
230
- var newTrivia = new SyntaxTriviaList([SyntaxFactory.EndOfLineTrivia("\n"), SyntaxFactory.WhitespaceTrivia(" "), .. indentTrivia]);
236
+ var newTrivia = new SyntaxTriviaList([SyntaxFactory.EndOfLineTrivia("\n"), SyntaxFactory.WhitespaceTrivia(indentation), .. indentTrivia]);
231
237
  newElement = (IXmlElementSyntax)newElement.AsNode.WithLeadingTrivia(newTrivia);
232
238
  var updatedItemGroup = (IXmlElementSyntax)ReplaceNode(
233
239
  projectRelativePath,
234
- lastItemGroup.AsNode,
235
- lastItemGroup.InsertChild(newElement, 0).AsNode
240
+ itemGroupForInsertion.AsNode,
241
+ itemGroupForInsertion.InsertChild(newElement, 0).AsNode
236
242
  );
237
243
  newElement = (IXmlElementSyntax)updatedItemGroup.Content[0];
238
244
  }
@@ -353,7 +359,9 @@ public class XmlFileWriter : IFileWriter
353
359
  .Skip(priorEolIndex + 1)
354
360
  .Select(t => SyntaxFactory.WhitespaceTrivia(t.ToFullString()))
355
361
  .ToArray();
356
- var newTrivia = new SyntaxTriviaList([SyntaxFactory.EndOfLineTrivia("\n"), SyntaxFactory.WhitespaceTrivia(" "), .. indentTrivia]);
362
+ var packageVersionDocument = filesAndContents[filePath];
363
+ var packageVersionIndentation = GetDocumentIndentationCharacters(packageVersionDocument);
364
+ var newTrivia = new SyntaxTriviaList([SyntaxFactory.EndOfLineTrivia("\n"), SyntaxFactory.WhitespaceTrivia(packageVersionIndentation), .. indentTrivia]);
357
365
  newVersionElement = (IXmlElementSyntax)newVersionElement.AsNode.WithLeadingTrivia(newTrivia).WithoutTrailingTrivia();
358
366
  var insertionIndex = 0;
359
367
  var replacementPackageVersionGroup = packageVersionGroup
@@ -581,7 +589,7 @@ public class XmlFileWriter : IFileWriter
581
589
  return elementsBeforeNew;
582
590
  }
583
591
 
584
- private static async Task<XmlDocumentSyntax> ReadFileContentsAsync(DirectoryInfo repoContentsPath, string path)
592
+ internal static async Task<XmlDocumentSyntax> ReadFileContentsAsync(DirectoryInfo repoContentsPath, string path)
585
593
  {
586
594
  var fullPath = Path.Join(repoContentsPath.FullName, path);
587
595
  var contents = await File.ReadAllTextAsync(fullPath);
@@ -696,4 +704,23 @@ public class XmlFileWriter : IFileWriter
696
704
 
697
705
  return newRange.ToString();
698
706
  }
707
+
708
+ public static string GetDocumentIndentationCharacters(XmlDocumentSyntax document)
709
+ {
710
+ // find the first element with leading whitespace and assume that's the document indentation
711
+ var nodeLeadingLineTrivias = document.DescendantNodes()
712
+ .Select(n => n.GetLeadingTrivia().ToList())
713
+ .Select(l =>
714
+ {
715
+ var priorEolIndex = l.FindLastIndex(t => t.Kind == SyntaxKind.EndOfLineTrivia);
716
+ var leadingTriviaParts = l.Skip(priorEolIndex + 1)
717
+ .Select(t => t.ToFullString());
718
+ var leadingTrivia = string.Concat(leadingTriviaParts);
719
+ return leadingTrivia;
720
+ })
721
+ .ToArray();
722
+ var nodeLeadingLineTrivia = nodeLeadingLineTrivias
723
+ .FirstOrDefault(t => !string.IsNullOrEmpty(t));
724
+ return nodeLeadingLineTrivia ?? " ";
725
+ }
699
726
  }