-
Notifications
You must be signed in to change notification settings - Fork 391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement WarningsNotAsErrors in the new property pages #6971
Implement WarningsNotAsErrors in the new property pages #6971
Conversation
Related to dotnet#6720. ### Background The C# compiler provides a switch allowing the user to specify whether or not warnings _in general_ should be treated as errors. In addition to that, specific warnings can be treated as errors, or _blocked_ from being treated as errors. In the project file, these settings are stored in the `TreatWarningsAsErrors`, `WarningsAsErrors`, and `WarningsNotAsErrors` properties, respectively. Currently, the project property pages only expose the first two of these. This change adds support for the third, `WarningsNotAsErrors`. ### Explanation The first thing to note is that all we have to do to get the new control to show up in the UI *and* to get the property persisted to the right place in the project file is to add a new `StringProperty` element to BuildPropertyPage.xaml. The `Name` attribute uniquely identifies the property within the Build page, _and_ specifies the property name that is read and written in the project file. Second, note the `VisibilityCondition` metadata on the new `WarningsNotAsErrors` and existing `WarningsAsErrors` property. We only want to show one or the other of these properties in the UI, not both. If warnings _in general_ are being treated as errors it makes sense to only show `WarningsNotAsErrors`; but if warnings are not being treated as errors then we should only show `WarningsAsErrors`. The `VisibilityCondition` contains an expression that controls whether or not the control should be displayed. Before I get into specifics of the expressions implemented here, it is important to note that `TreatWarningsAsErrors`, `WarningsAsErrors`, and `WarningsNotAsErrors` are all configuration dependent properties. That is, their values could vary from one configuration to another, and so the property effectively has multiple values--one per configuration. There are also many configuration independent properties; these have at most one value. We want the `WarningsAsErrors` control to show up if `TreatWarningsAsErrors` is false in _any_ configuration, and this is achieved with the following expression: ``` (has-evaluated-value "Build" "TreatWarningsAsErrors" "false") ``` This looks at all of the evaluated values of the `TreatWarningsAsErrors` property on the `Build` page and checks if any of them is "false"; if so, the overall expression evaluates to "true" and the control is displayed. If the properties were configuration independent we could have gone with ``` (eq (evaluated "Build" "TreatWarningsAsErrors") "true") ``` to check if the evaluated value of TreatWarningsAsErrors is "true", or even just ``` (evaluated "Build" "TreatWarningsAsErrors") ``` Third, let's leave the .xaml file for the moment and look and the one piece of code, TreatWarningsAsErrorsValueProvider.cs. When the user toggles the value of `TreatWarningsAsErrors` we don't just want to hide or show `WarningsAsErrors` and/or `WarningsNotAsErrors`, we want to clear out the property values that no longer make sense. E.g., if we're setting `TreatWarningsAsErrors` to false, we can delete `WarningsNotAsErrors` entirely as it has no effect. We accomplish this by implementing an `IInterceptingPropertyValueProvider` for `TreatWarningsAsErrors`. An intercepting value provider gets a chance to handle a property set before it is passed on for default handling; this allows it to manipulate the value or even other properties. It also has a chance to manipulate the values _read_ from the default handler before they are passed on to the property pages, but we don't need this ability here. However, if the user toggles `TreatWarningsAsErrors` back and forth we don't want to permanently delete whatever values they had for the other two properties. Rather, we squirrel them away in `ITemporaryPropertyStorage` before deleting them, and restore the value from there when switching back. Fourth, go back to the .xaml file and take a look at the `Rule.DataSource` entry at the top of the page. The `Persistence` attribute indicates that the property values will read from and written to the "ProjectFile" handler which, of course, reads and writes to the project file itself (as opposed to the .user file or some other .props or .targets file). However, as we want to intercept the `TreatWarningsAsErrors` property we update its `DataSource` entry to instead specify the "ProjectFileWithInterception" persistence handler. This handler runs the property through an applicable interceptors before handing it off to the "ProjectFile" handler. Without this, the TreatWarningsAsErrorsValueProvider would never be called. Fifth and finally, note that the values of `WarningsAsErrors` and `WarningsNotAsErrors` now depend on the value of `TreatWarningsAsErrors`. We need to tell the UI that when `TreatWarningsAsErrors` is updated, it should re-query the values of the other two. This is done via the `DependsOn` metadata added to both of those properties' entries. Without this, the UI may become out of sync with the actual values in the project as the user makes changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only hesitation with this change is that a user who wants "All except ..." has to know to select "All" before they'll be given the option to specify exclusions. The inverse is true ("None" except).
Another way of implementing this is to change the enum property to have four values ("None", "Only specific", "All except specific", "All"). That would be a more complicated implementation, involving a hidden synthetic property. It might also be an interesting example to document.
Please add a link to this PR in https://github.com/dotnet/project-system/blob/main/docs/repo/property-pages/property-specification.md. We should add a section for good reference PRs.
Yeah, once we everything implemented we're going to want to go through all the properties and figure out if we're providing the right user experience around them. For example, we should probably update the display names and descriptions for these and |
Add a link to dotnet#6971 to the property page documentation.
Related to #6720.
Background
The C# compiler provides a switch allowing the user to specify whether or not warnings in general should be treated as errors. In addition to that, specific warnings can be treated as errors, or blocked from being treated as errors. In the project file, these settings are stored in the
TreatWarningsAsErrors
,WarningsAsErrors
, andWarningsNotAsErrors
properties, respectively.Currently, the project property pages only expose the first two of these. This change adds support for the third,
WarningsNotAsErrors
.Explanation
The first thing to note is that all we have to do to get the new control to show up in the UI and to get the property persisted to the right place in the project file is to add a new
StringProperty
element to BuildPropertyPage.xaml. TheName
attribute uniquely identifies the property within the Build page, and specifies the property name that is read and written in the project file.Second, note the
VisibilityCondition
metadata on the newWarningsNotAsErrors
and existingWarningsAsErrors
property. We only want to show one or the other of these properties in the UI, not both. If warnings in general are being treated as errors it makes sense to only showWarningsNotAsErrors
; but if warnings are not being treated as errors then we should only showWarningsAsErrors
. TheVisibilityCondition
contains an expression that controls whether or not the control should be displayed.Before I get into specifics of the expressions implemented here, it is important to note that
TreatWarningsAsErrors
,WarningsAsErrors
, andWarningsNotAsErrors
are all configuration dependent properties. That is, their values could vary from one configuration to another, and so the property effectively has multiple values--one per configuration. There are also many configuration independent properties; these have at most one value.We want the
WarningsAsErrors
control to show up ifTreatWarningsAsErrors
is false in any configuration, and this is achieved with the following expression:This looks at all of the evaluated values of the
TreatWarningsAsErrors
property on theBuild
page and checks if any of them is "false"; if so, the overall expression evaluates to "true" and the control is displayed.If the properties were configuration independent we could have gone with
to check if the evaluated value of TreatWarningsAsErrors is "true", or even just
Third, let's leave the .xaml file for the moment and look and the one piece of code, TreatWarningsAsErrorsValueProvider.cs. When the user toggles the value of
TreatWarningsAsErrors
we don't just want to hide or showWarningsAsErrors
and/orWarningsNotAsErrors
, we want to clear out the property values that no longer make sense. E.g., if we're settingTreatWarningsAsErrors
to false, we can deleteWarningsNotAsErrors
entirely as it has no effect.We accomplish this by implementing an
IInterceptingPropertyValueProvider
forTreatWarningsAsErrors
. An intercepting value provider gets a chance to handle a property set before it is passed on for default handling; this allows it to manipulate the value or even other properties. It also has a chance to manipulate the values read from the default handler before they are passed on to the property pages, but we don't need this ability here.However, if the user toggles
TreatWarningsAsErrors
back and forth we don't want to permanently delete whatever values they had for the other two properties. Rather, we squirrel them away inITemporaryPropertyStorage
before deleting them, and restore the value from there when switching back.Fourth, go back to the .xaml file and take a look at the
Rule.DataSource
entry at the top of the page. ThePersistence
attribute indicates that the property values will read from and written to the "ProjectFile" handler which, of course, reads and writes to the project file itself (as opposed to the .user file or some other .props or .targets file). However, as we want to intercept theTreatWarningsAsErrors
property we update itsDataSource
entry to instead specify the "ProjectFileWithInterception" persistence handler. This handler runs the property through an applicable interceptors before handing it off to the "ProjectFile" handler. Without this, the TreatWarningsAsErrorsValueProvider would never be called.Fifth and finally, note that the values of
WarningsAsErrors
andWarningsNotAsErrors
now depend on the value ofTreatWarningsAsErrors
. We need to tell the UI that whenTreatWarningsAsErrors
is updated, it should re-query the values of the other two. This is done via theDependsOn
metadata added to both of those properties' entries. Without this, the UI may become out of sync with the actual values in the project as the user makes changes.Microsoft Reviewers: Open in CodeFlow