dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.
https://docs.microsoft.com/visualstudio/msbuild/msbuild
MIT License
5.17k stars 1.34k forks source link

MSBuild linter #1777

Open rainersigwald opened 7 years ago

rainersigwald commented 7 years ago

MSBuild could have an opt-in mode that would apply rules and heuristics to give suggestions about project "health".

Possible warnings:

rainersigwald commented 7 years ago

Thanks, @danmosemsft: https://github.com/Microsoft/msbuild/issues/1774#issuecomment-283475320.

stan-sz commented 2 years ago

My favorite linter rule would be to catch ItemGroups with wildcards that scan the entire drive:

<ItemGroup>
  <MyItem Include="$(SomeProperty)/**" />
</ItemGroup>

Where SomeProperty resolves to empty sting.

rainersigwald commented 2 years ago

@stan-sz that is a great linter use case but also see https://github.com/dotnet/msbuild/issues/3642#issue-352653387j and the upcoming https://github.com/dotnet/msbuild/pull/7029.

stan-sz commented 1 year ago

Another case are conditions in form of

Condition="$(SomeProperty)">

where at least the linter should signal the missing single quotes and comparison with empty string like:

Condition=" '$(SomeProperty)' != '' ">
stan-sz commented 1 year ago

Some of the items listed in the initial scope for linter are captured in Feature: Warning Waves

hknielsen commented 11 months ago

We had an interesting issue today in our team, where:

<ProjectReference...><Properties></Properties>

Broke Publishing, and was not trivial what was happening, until we remembered that ProjectReference mapped to MsBuild task and removed the Properties delegated trough, removing the TargetFramework.

Interesting case as well for a linter warning, suggest to use AdditinalProperties instead

stan-sz commented 9 months ago

For the record: a linter to catch missing single quotes in string comparison

Condition=" $(SomeProperty) != 'true' ">
KalleOlaviNiemitalo commented 9 months ago

What's the difference between $(SomeProperty) and '$(SomeProperty)'? It looks like Microsoft.Build.Evaluation.Parser.Arg(string expression) creates a StringExpressionNode in both cases.

stan-sz commented 9 months ago

This is to handle case when a property evaluates to an empty value. From: https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions?view=vs-2022

Single quotes are not required for simple alphanumeric strings or boolean values. However, single quotes are required for empty values. This check is case insensitive.

KalleOlaviNiemitalo commented 9 months ago

quoted.proj:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">
  <PropertyGroup>
    <Property />
  </PropertyGroup>

  <Target Name="Run">
    <Message Condition="$(Property) == ''" Importance="high" Text="Is empty without quotation marks" />
    <Message Condition="$(Property) != ''" Importance="high" Text="Is not empty without quotation marks" />
    <Message Condition="'$(Property)' == ''" Importance="high" Text="Is empty with quotation marks" />
    <Message Condition="'$(Property)' != ''" Importance="high" Text="Is not empty with quotation marks" />
  </Target>
</Project>
C:\Projects\quoted>C:\Windows\Microsoft.NET\Framework64\v2.0.50727\MSBuild.exe quoted.proj
Microsoft (R) Build Engine Version 2.0.50727.9149
[Microsoft .NET Framework, Version 2.0.50727.9174]
Copyright (C) Microsoft Corporation 2005. All rights reserved.

Build started 12.9.2023 21.12.13.
__________________________________________________
Project "C:\Projects\quoted\quoted.proj" (default targets):

Target Run:
    Is empty without quotation marks
    Is empty with quotation marks

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.26
$ dotnet msbuild quoted.proj
MSBuild version 17.8.0-preview-23367-03+0ff2a83e9 for .NET
  Is empty without quotation marks
  Is empty with quotation marks

Perhaps the documentation means you cannot compare to an empty unquoted string literal like Condition="$(Property) == ".

Piedone commented 8 months ago

This could also include formatting checks like Prettier for XML does.

Ideally, I'm thinking of full static code analysis for csproj, props, and targets files.

danmoseley commented 8 months ago

Another possibility (in strict mode?) could be to help with Boolean properties. Properties of course have no type but in practice many are Boolean existing in a world where empty string is default. This can lead to ambiguity about what blank means, and since often we don't want to add a line to explicitly set a default, blank is treated as default. If that means blank is true, then properties end up with confusing negative names like say "DoNotOptimize". The obvious sources of errors here are the confusion of seeing a negative property to false, or assuming blank means true (or false) when it doesn't, or comparing against blank when it was set explicitly, or setting anything other than true or false as the value.

Years ago when we wrote the original "targets" I tried to have a rule that either blank was assumed to be false or else the value was explicitly defaulted to "true", then comparisons were always and only against "true", "false" and "" never appeared in conditionals on these Booleans, and hopefully property names were not negative (in practice some were inherited from the old format and already were).

A linter could possibly help by

This would of course be noisy. Perhaps the real answer is to have some way to annotate a property as Boolean with a default.

Note of course that the conditional parser IIRC does have some special casing for Booleans for example you can negate: "'$(Optimize)' == !'$(Debug)'" ...IIRC

stan-sz commented 5 months ago

Another linting ideas:

stan-sz commented 5 months ago

Another linter idea: #6277

stan-sz commented 4 months ago

Another idea: #3976

JustinSchneiderPBI commented 4 months ago

Fail\warn on property overwrite without Overwrite="true" metadata.

MattKotsenas commented 4 months ago

Another idea:

A colleague just ran into a case where a project specified <TargetFrameworks> (plural) that was silently overwritten by an imported <TargetFramework> (singular).

DaveCousineau commented 3 months ago

Not sure if it's a bug, something to catch with a linter, or maybe if there's already a workaround, but a problem I'm noticing is that this is an error (MSB4035):

<ProjectReference Include="" />

But these are not errors or even warnings:

<ProjectReference Include="$(empty)" />
<ProjectReference Include=" " />