Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions doc/UNT0043.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# UNT0043 Possible typo in conditional compilation symbol

Mistyped conditional compilation symbols in `#if` and `#elif` directives are treated as undefined, so the affected branch can be compiled or skipped unexpectedly without a compiler error.

## Examples of patterns that are flagged by this analyzer

```csharp
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITTY_STANDALONE_OSX
InitializeServiceLocator();
#endif
```

If the project defines `UNITY_STANDALONE_OSX`, `UNITTY_STANDALONE_OSX` is reported as very close to that project-level preprocessor symbol. The analyzer gets this symbol set from Roslyn's parse options for the project.

The same applies to custom project symbols:

```csharp
#if FEATURE_RELEAS
EnableReleaseFeature();
#endif
```

If the project defines `FEATURE_RELEASE`, this is reported as a likely typo.

## Solution

Use the intended symbol name:

```csharp
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX
InitializeServiceLocator();
#endif
```

A code fix is offered for this diagnostic to replace the mistyped symbol with the suggested project-level symbol.
1 change: 1 addition & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ID | Title | Category
[UNT0040](UNT0040.md) | `GameObject.isStatic` is editor-only | Correctness
[UNT0041](UNT0041.md) | Use `Animator.StringToHash` for repeated `Animator` method calls | Performance
[UNT0042](UNT0042.md) | `Mesh` array property accessed in loop | Performance
[UNT0043](UNT0043.md) | Possible typo in conditional compilation symbol | Correctness

# Diagnostic Suppressors

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*-------------------------------------------------------------------------------------------*/

using System.Threading.Tasks;
using Xunit;

namespace Microsoft.Unity.Analyzers.Tests;

public class ConditionalCompilationSymbolTypoTests : BaseCodeFixVerifierTest<ConditionalCompilationSymbolTypoAnalyzer, ConditionalCompilationSymbolTypoCodeFix>
{
[Fact]
public async Task UnityPlatformSymbolTypo()
{
const string test = @"
class Test
{
void Method()
{
#if UNITTY_STANDALONE_OSX
return;
#endif
}
}
";

var diagnostic = ExpectDiagnostic()
.WithLocation(6, 5)
.WithArguments("UNITTY_STANDALONE_OSX", "UNITY_STANDALONE_OSX");

var context = AnalyzerVerificationContext.Default
.WithPreprocessorSymbols("UNITY_STANDALONE_OSX");

await VerifyCSharpDiagnosticAsync(context, test, diagnostic);

const string fixedTest = @"
class Test
{
void Method()
{
#if UNITY_STANDALONE_OSX
return;
#endif
}
}
";

await VerifyCSharpFixAsync(context, test, fixedTest);
}

[Fact]
public async Task CompoundConditionTypoTrivia()
{
const string test = @"
class Test
{
void Method()
{
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || !UNITTY_STANDALONE_OSX
return;
#endif
}
}
";

var diagnostic = ExpectDiagnostic()
.WithLocation(6, 72)
.WithArguments("UNITTY_STANDALONE_OSX", "UNITY_STANDALONE_OSX");

var context = AnalyzerVerificationContext.Default
.WithPreprocessorSymbols("UNITY_EDITOR", "UNITY_STANDALONE_WIN", "UNITY_STANDALONE_LINUX", "UNITY_STANDALONE_OSX");

await VerifyCSharpDiagnosticAsync(context, test, diagnostic);

const string fixedTest = @"
class Test
{
void Method()
{
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || !UNITY_STANDALONE_OSX
return;
#endif
}
}
";

await VerifyCSharpFixAsync(context, test, fixedTest);
}

[Fact]
public async Task ElifDirectiveTypo()
{
const string test = @"
class Test
{
void Method()
{
#if UNITY_EDITOR
return;
#elif UNITTY_STANDALONE_OSX
return;
#endif
}
}
";

var diagnostic = ExpectDiagnostic()
.WithLocation(8, 7)
.WithArguments("UNITTY_STANDALONE_OSX", "UNITY_STANDALONE_OSX");

var context = AnalyzerVerificationContext.Default
.WithPreprocessorSymbols("UNITY_EDITOR", "UNITY_STANDALONE_OSX");

await VerifyCSharpDiagnosticAsync(context, test, diagnostic);
}

[Fact]
public async Task ProjectSymbolTypo()
{
const string test = @"
class Test
{
void Method()
{
#if FEATURE_RELEAS
return;
#endif
}
}
";

var context = AnalyzerVerificationContext.Default
.WithPreprocessorSymbols("FEATURE_RELEASE");

var diagnostic = ExpectDiagnostic()
.WithLocation(6, 5)
.WithArguments("FEATURE_RELEAS", "FEATURE_RELEASE");

await VerifyCSharpDiagnosticAsync(context, test, diagnostic);
}

[Fact]
public async Task DefinedProjectSymbol()
{
const string test = @"
class Test
{
void Method()
{
#if FEATURE_RELEASE
return;
#endif
}
}
";

var context = AnalyzerVerificationContext.Default
.WithPreprocessorSymbols("FEATURE_RELEASE");

await VerifyCSharpDiagnosticAsync(context, test);
}

[Fact]
public async Task DistantUndefinedSymbol()
{
const string test = @"
class Test
{
void Method()
{
#if OTHER_SYMBOL
return;
#endif
}
}
";

var context = AnalyzerVerificationContext.Default
.WithPreprocessorSymbols("FEATURE_RELEASE");

await VerifyCSharpDiagnosticAsync(context, test);
}

[Fact]
public async Task LocallyDefinedSymbol()
{
const string test = @"
#define UNITTY_STANDALONE_OSX

class Test
{
void Method()
{
#if UNITTY_STANDALONE_OSX
return;
#endif
}
}
";

await VerifyCSharpDiagnosticAsync(test);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,53 @@

namespace Microsoft.Unity.Analyzers.Tests;

public readonly struct AnalyzerVerificationContext(ImmutableDictionary<string, string> options, ImmutableArray<string> filters, LanguageVersion languageVersion)
public readonly struct AnalyzerVerificationContext(ImmutableDictionary<string, string> options, ImmutableArray<string> filters, LanguageVersion languageVersion, ImmutableArray<string> preprocessorSymbols)
{
public ImmutableDictionary<string, string> Options { get; } = options;
public ImmutableArray<string> Filters { get; } = filters;
public LanguageVersion LanguageVersion { get; } = languageVersion;
public ImmutableArray<string> PreprocessorSymbols { get; } = preprocessorSymbols;

// CS0414 - cf. IDE0051
public static AnalyzerVerificationContext Default = new(
[],
["CS0414"],
LanguageVersion.Latest);
LanguageVersion.Latest,
[]);

public AnalyzerVerificationContext WithAnalyzerOption(string key, string value)
{
return new(
Options.Add(key, value),
Filters,
LanguageVersion);
LanguageVersion,
PreprocessorSymbols);
}

public AnalyzerVerificationContext WithAnalyzerFilter(string value)
{
return new(
Options,
Filters.Add(value),
LanguageVersion);
LanguageVersion,
PreprocessorSymbols);
}

public AnalyzerVerificationContext WithLanguageVersion(LanguageVersion languageVersion)
{
return new(
Options,
Filters,
languageVersion);
languageVersion,
PreprocessorSymbols);
}

public AnalyzerVerificationContext WithPreprocessorSymbols(params string[] symbols)
{
return new(
Options,
Filters,
LanguageVersion,
[.. symbols]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,10 @@ private static Project CreateProject(AnalyzerVerificationContext context, string
.AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp);

solution = UnityAssemblies().Aggregate(solution, (current, dll) => current.AddMetadataReference(projectId, MetadataReference.CreateFromFile(dll)));
solution = solution.WithProjectParseOptions(projectId, new CSharpParseOptions(context.LanguageVersion));

var parseOptions = new CSharpParseOptions(context.LanguageVersion)
.WithPreprocessorSymbols(context.PreprocessorSymbols);
solution = solution.WithProjectParseOptions(projectId, parseOptions);

var count = 0;
foreach (var source in sources)
Expand Down
Loading
Loading