Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Add nullable annotations to NuGet.Common - #5333

#5333
Merged
zivkan merged 4 commits into
devNuGet/NuGet.Client:devfrom
dev-zivkan-nullable-NuGet.CommonNuGet/NuGet.Client:dev-zivkan-nullable-NuGet.CommonCopy head branch name to clipboard
Aug 3, 2023
Merged

Add nullable annotations to NuGet.Common#5333
zivkan merged 4 commits into
devNuGet/NuGet.Client:devfrom
dev-zivkan-nullable-NuGet.CommonNuGet/NuGet.Client:dev-zivkan-nullable-NuGet.CommonCopy head branch name to clipboard

Conversation

@zivkan

@zivkan zivkan commented Jul 25, 2023

Copy link
Copy Markdown
Member

Bug

Fixes: NuGet/Home#12775

Regression? Last working version:

Description

  • Add <Nullable>enable</Nullable> to NuGet.Common and test project.
  • Annotate all public APIs
  • Fix all remaining compiler errors

PR Checklist

  • PR has a meaningful title

  • PR has a linked issue.

  • Described changes

  • Tests

    • Automated tests added
    • OR
    • Test exception: no functionality changes, existing tests are sufficient
    • OR
    • N/A
  • Documentation

    • Documentation PR or issue filled
    • OR
    • N/A

@zivkan
zivkan requested a review from a team as a code owner July 25, 2023 16:31
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

// BCL annotations on Environment.GetEnvironmentVariable makes this file difficult to annotate in .NET 5+
#nullable disable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file nearly made me give up. .NET Core 1.0 didn't have Environment.GetFolderPath, so this file has #if to compile different code for .NET Framework and everything else. On .NET Framework it uses GetFolderPath, which is annotated in .NET 5+ to not return nulls. But in our .NET Standard (and .NET (Core) if we add those TFMs) it will use Environment.GetEnvironmentVariable, which can return null.

However, .NET Core 2.0 added GetFolderPath, so I think that porting the .NET Standard code to use that. However, it's significantly higher risk than the rest of this PR, so I want to do it in a separate PR in case it needs to be reverted.

{
throw new ArgumentException(paramName: nameof(telemetryEvent), message: "Property 'Name' must not be null");
}
_telemetryActivity = NuGetTelemetryService?.StartActivity(telemetryEvent.Name);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exposes an API design problem. We have code creating instances of TelemetryEvent with a null name, but this code will throw a NullReferenceException if such an instance is used here.

}
else
{
Debug.Fail("Looking at all references to the static Create methods, I don't think this code path is possible.");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't feel comfortable deleting the if != null statement, but looking at usage it should never be null. I thought maybe this Debug.Fail will give us confidence in a few weeks if none of us hit this debug point, so we can make the change later.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.

martinrrm
martinrrm previously approved these changes Jul 26, 2023

@martinrrm martinrrm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't review the tests but I trust they are good since CI is passing.

@aortiz-msft

Copy link
Copy Markdown
Collaborator

Reviewed until src/NuGet.Core/NuGet.Common/Migrations/Migration1.cs. Might need an extra day to go through the rest.

jeffkl
jeffkl previously approved these changes Jul 31, 2023
string? destFileDirectory = Path.GetDirectoryName(destFilePath);
if (destFileDirectory is null)
{
throw new ArgumentException(paramName: destFilePath, message: "Path.GetDirectoryName(destFilePath) returned null");

@aortiz-msft aortiz-msft Aug 1, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path.GetDirectoryName(destFilePath) returned null

Does this need to be localized? At the minimum, we should have a string defined somewhere so that the exception text is guaranteed to be the same for folks who are parsing the text. Same for all other instances.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't expect any customers to ever see this message, so no, I don't think it needs to be localized. It's only feasible if someone using NuGet.Common (part of the Client SDK) as part of their app calls the API with bad input (or if someone modifying NuGet starts calling this in a way that is buggy). Frankly, I think it's a bad API, NuGet shouldn't be in the business of proving and supporting trivial file utility APIs.

I also think it's a bad idea to parse exception messages. .NET Core changed some exception messages from what they were on the .NET Framework (I think ArgumentNullException's message is an example), and exception messages that are localized will have unexpected results when running on machines not using the same language as the developer who wrote code trying to parse the exception message. Even for tests this is a bad idea, since I don't want to exclude people running their devbox in something other than en-US from being able to run tests locally.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with the answer. Suggestion for message: "Not a valid file path".

Comment thread src/NuGet.Core/NuGet.Common/PathUtil/PathUtility.cs Outdated
Comment thread test/NuGet.Core.Tests/NuGet.Common.Test/SynchronizationTest.cs

@aortiz-msft aortiz-msft left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

@nkolev92 nkolev92 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. Nicely done.
TelemetryEvent is really the only type where I wish we can do better.

private static ILogger _instance;
private static ILogger? _instance;

public static ILogger Instance

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh this optimization seems pointless

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What optimization? I was annotating exactly what was implemented

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the lazy creation of the null logger. It's not really heavy so no need to delay it.
My bad, I should've pointed out that it was the existing code.

Up to you whether you change it or not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, since I had to annotate hundreds of APIs, I didn't pay attention to most of what I was annotating. Trying to resolve all the compiler errors, and minimize risk of regression by avoiding unnecessary code changes.

Comment thread src/NuGet.Core/NuGet.Common/Telemetry/TelemetryEvent.cs Outdated
Comment thread src/NuGet.Core/NuGet.Common/Telemetry/TelemetryEvent.cs Outdated
Comment thread src/NuGet.Core/NuGet.Common/Telemetry/TelemetryEvent.cs
@zivkan
zivkan dismissed stale reviews from jeffkl and martinrrm via 3582fc9 August 2, 2023 08:44
@zivkan
zivkan force-pushed the dev-zivkan-nullable-NuGet.Common branch from d246c7b to 3582fc9 Compare August 2, 2023 08:44
@zivkan
zivkan requested review from aortiz-msft and nkolev92 August 2, 2023 08:45
if (firstParentDirectory is not null)
{
isCaseInsensitive &= result;
isCaseInsensitive &= CheckIfFileSystemIsCaseInsensitive(firstParentDirectory);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isCaseInsensitive &= CheckIfFileSystemIsCaseInsensitive(firstParentDirectory);

Just for my own learning, why wouldn't it be sufficient to check any one of these paths? I.e. why not return immediately?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know.

Technically, the whole thing is flawed, because individual filesystems can be case sensitive or not. On Windows I can configure a specific partition to be case sensitive, even though Windows' default is case insensitive filesystems. I expect there are filesystems on Linux where you can enable insensitivity. But I imagine that the number of people who change the defaults is very close to zero, and NuGet wouldn't be the only software that breaks when those defaults are changed.


var tempPath = GetTempFilePath(Path.GetDirectoryName(destFilePath));
string? destFileDirectory = Path.GetDirectoryName(destFilePath);
if (destFileDirectory is null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also raise an exception if the destFileDirectory is an Empty string? https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getdirectoryname?view=net-7.0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like an exception will be thrown either way (either by us, or by the BCL). The difference is that the ArgumentException's ParamName value will be a different name, a name that doesn't exist in our API. But given our code is open source and this API only has a single path parameter, it's trivial to discover which parameter had an input error.

So, it's still the caller's responsibility to pass in sensible input. It doesn't cause the API to produce incorrect results. Since pushing changes resets approvals, unfortunately I don't feel motivated to add such a minor change (same goes for reverting my "see if this fixes E2E tests" commit)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could do a follow up PR :)

@aortiz-msft aortiz-msft left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@zivkan
zivkan merged commit 267b938 into dev Aug 3, 2023
@zivkan
zivkan deleted the dev-zivkan-nullable-NuGet.Common branch August 3, 2023 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add nullable annotations to NuGet.Common

5 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.