diff --git a/lib/manager/nuget/__fixtures__/sample.csproj b/lib/manager/nuget/__fixtures__/sample.csproj index 168c0ae5c0c43a8e9c3df32890c064aecb27d52a..1989dcb75d03fb1348b6268d12e4e62e45136ed4 100644 --- a/lib/manager/nuget/__fixtures__/sample.csproj +++ b/lib/manager/nuget/__fixtures__/sample.csproj @@ -18,13 +18,17 @@ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" /> <PackageReference Include="Newtonsoft.Json" VersionOverride="10.0.2" /> - <PackageReference Include="Serilog" Version="2.4.0" /> + <PackageReference Include="Serilog"> + <VersionOverride> + 2.4.0 + </VersionOverride> + </PackageReference> <PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" /> <PackageReference Include="Serilog.Sinks.Literate" Version="2.1.0" /> <PackageReference Include="Stateless" Version="3.1.0.5" /> <PackageReference Include="Range1" Version="[1.2.3]" /> <PackageReference Include="Range2" Version="[1.2.3,]" /> - <PackageReference Include="Range3" Version="[1.2.3,)" /> + <PackageReference Include="Range3"><Version>[1.2.3,)</Version></PackageReference> <PackageReference Include="NotUpdatable1" Version="[,1.2.3)" /> <PackageReference Include="NotUpdatable2" Version="(1.2.3,)" /> <PackageReference Include="NotUpdatable3" Version="(1.2.3, 3.2.1)" /> diff --git a/lib/manager/nuget/extract.ts b/lib/manager/nuget/extract.ts index db0e4af48d01615124de3a64fce162af728e371a..1273ec1cc4c61fe58ba0c38f52ebbbc89511a7c9 100644 --- a/lib/manager/nuget/extract.ts +++ b/lib/manager/nuget/extract.ts @@ -74,7 +74,52 @@ async function determineRegistryUrls( return registryUrls; } -const packageRe = /<(?:PackageReference|DotNetCliToolReference|GlobalPackageReference).*(?:Include|Update)\s*=\s*"(?<depName>[^"]+)".*(?:Version|VersionOverride)\s*=\s*"(?:[[])?(?:(?<currentValue>[^"(,[\]]+)\s*(?:,\s*[)\]]|])?)"/; +/** + * https://docs.microsoft.com/en-us/nuget/concepts/package-versioning + * This article mentions that Nuget 3.x and later tries to restore the lowest possible version + * regarding to given version range. + * 1.3.4 equals [1.3.4,) + * Due to guarantee that an update of package version will result in its usage by the next restore + build operation, + * only following constrained versions make sense + * 1.3.4, [1.3.4], [1.3.4, ], [1.3.4, ) + * The update of the right boundary does not make sense regarding to the lowest version restore rule, + * so we don't include it in the extracting regexp + */ +const checkVersion = /^\s*(?:[[])?(?:(?<currentValue>[^"(,[\]]+)\s*(?:,\s*[)\]]|])?)\s*$/; + +function extractDepsFromXml(xmlNode: XmlDocument): PackageDependency[] { + const results = []; + const itemGroups = xmlNode.childrenNamed('ItemGroup'); + for (const itemGroup of itemGroups) { + const relevantChildren = [ + ...itemGroup.childrenNamed('PackageReference'), + ...itemGroup.childrenNamed('DotNetCliToolReference'), + ...itemGroup.childrenNamed('GlobalPackageReference'), + ]; + for (const child of relevantChildren) { + const { attr } = child; + const depName = attr?.Include || attr?.Update; + const version = + attr?.Version || + child.valueWithPath('Version') || + attr?.VersionOverride || + child.valueWithPath('VersionOverride'); + const currentValue = version + ?.match(checkVersion) + ?.groups?.currentValue?.trim(); + if (depName && currentValue) { + results.push({ + datasource: datasourceNuget.id, + depType: 'nuget', + depName, + currentValue, + }); + } + } + } + return results; +} + export async function extractPackageFile( content: string, packageFile: string, @@ -82,7 +127,6 @@ export async function extractPackageFile( ): Promise<PackageFile | null> { logger.trace({ packageFile }, 'nuget.extractPackageFile()'); const versioning = get(config.versioning || semverVersioning.id); - const deps: PackageDependency[] = []; const registryUrls = await determineRegistryUrls( packageFile, @@ -90,6 +134,7 @@ export async function extractPackageFile( ); if (packageFile.endsWith('.config/dotnet-tools.json')) { + const deps: PackageDependency[] = []; let manifest: DotnetToolsManifest; try { @@ -123,36 +168,19 @@ export async function extractPackageFile( return { deps }; } - for (const line of content.split('\n')) { - /** - * https://docs.microsoft.com/en-us/nuget/concepts/package-versioning - * This article mentions that Nuget 3.x and later tries to restore the lowest possible version - * regarding to given version range. - * 1.3.4 equals [1.3.4,) - * Due to guarantee that an update of package version will result in its usage by the next restore + build operation, - * only following constrained versions make sense - * 1.3.4, [1.3.4], [1.3.4, ], [1.3.4, ) - * The update of the right boundary does not make sense regarding to the lowest version restore rule, - * so we don't include it in the extracting regexp - */ - - const match = packageRe.exec(line); - if (match) { - const { currentValue, depName } = match.groups; - const dep: PackageDependency = { - depType: 'nuget', - depName, - currentValue, - datasource: datasourceNuget.id, - }; - if (registryUrls) { - dep.registryUrls = registryUrls; - } - if (!versioning.isVersion(currentValue)) { - dep.skipReason = SkipReason.NotAVersion; - } - deps.push(dep); - } + let deps: PackageDependency[] = []; + try { + const parsedXml = new XmlDocument(content); + deps = extractDepsFromXml(parsedXml).map((dep) => ({ + ...dep, + ...(registryUrls && { registryUrls }), + ...(!versioning.isVersion(dep.currentValue) && { + skipReason: SkipReason.NotAVersion, + }), + })); + return { deps }; + } catch (err) { + logger.debug({ err }, `Failed to parse ${packageFile}`); } return { deps }; }