public void AllocatingReferenceTypeFromSecondLevelNoFix2() { var testCode = @" namespace RoslynSandbox { public class Foo { public Foo(int a, int b, int c, int d) { this.A = a; this.B = b; this.C = c; this.D = d; this.Bar1 = new Foo(a, b, c, d); } public int A { get; } public int B { get; } public int C { get; } public int D { get; } public Foo Bar1 { get; set; } public Foo Bar2 ↓=> new Foo(this.A, this.B, this.C, this.Bar1.D); } }"; var fixedCode = @" namespace RoslynSandbox { public class Foo { public Foo(int a, int b, int c, int d) { this.A = a; this.B = b; this.C = c; this.D = d; this.Bar1 = new Foo(a, b, c, d); this.Bar2 = new Foo(this.A, this.B, this.C, this.Bar1.D); } public int A { get; } public int B { get; } public int C { get; } public int D { get; } public Foo Bar1 { get; set; } public Foo Bar2 { get; } } }"; AnalyzerAssert.CodeFix <GU0021CalculatedPropertyAllocates, UseGetOnlyCodeFixProvider>(testCode, fixedCode); }
public void WhenInjectingCondition() { var fooCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private int value; public event PropertyChangedEventHandler PropertyChanged; public int Value { get { return this.value; } set { if (value == this.value) { return; } this.value = value; this.OnPropertyChanged(); } } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; var conditionCode = @" namespace RoslynSandbox { using System.Reactive.Linq; using Gu.Reactive; public class FooCondition : Condition { public FooCondition(Foo foo) : base( foo.ObservePropertyChangedSlim(x => x.Value), () => foo.Value == 2) { } } }"; var testCode = @" namespace RoslynSandbox { using System; using Gu.Reactive; public class Bar { private readonly ICondition condition; public Bar(FooCondition condition) { var foo = new Foo(); this.condition = condition; } } }"; AnalyzerAssert.NoDiagnostics <GUREA06DontNewCondition>(fooCode, conditionCode, testCode); }
public void OperatorEqualsInternalClassInternalProperty() { var testCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; internal class ViewModel : INotifyPropertyChanged { private Foo bar; public event PropertyChangedEventHandler PropertyChanged; internal Foo Bar { get { return this.bar; } set { ↓if (value == this.bar) { return; } this.bar = value; this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Bar))); } } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }"; var fixedCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; internal class ViewModel : INotifyPropertyChanged { private Foo bar; public event PropertyChangedEventHandler PropertyChanged; internal Foo Bar { get { return this.bar; } set { if (ReferenceEquals(value, this.bar)) { return; } this.bar = value; this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Bar))); } } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }"; AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { FooCode, testCode }, fixedCode); AnalyzerAssert.FixAll(Analyzer, Fix, ExpectedDiagnostic, new[] { FooCode, testCode }, fixedCode); }
public void WhenPrivateSetAssignedInLambdaInCtor(string assignCode) { var testCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { public Foo() { Bar += (_, __) => this.Value = 1; } public event EventHandler Bar; public event PropertyChangedEventHandler PropertyChanged; ↓public int Value { get; private set; } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; testCode = testCode.AssertReplace("this.Value = 1", assignCode); var fixedCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private int value; public Foo() { Bar += (_, __) => this.Value = 1; } public event EventHandler Bar; public event PropertyChangedEventHandler PropertyChanged; public int Value { get => this.value; private set { if (value == this.value) { return; } this.value = value; this.OnPropertyChanged(); } } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; fixedCode = fixedCode.AssertReplace("this.Value = 1", assignCode); AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, testCode, fixedCode); }
public void WhenSettingNestedFieldNotify() { var barCode = @" namespace RoslynSandbox { public class Bar { public int BarValue; } }"; var testCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private readonly Bar bar = new Bar(); public event PropertyChangedEventHandler PropertyChanged; ↓public int Value { get => this.bar.BarValue; set => this.bar.BarValue = value; } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; var fixedCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private readonly Bar bar = new Bar(); public event PropertyChangedEventHandler PropertyChanged; public int Value { get => this.bar.BarValue; set { this.bar.BarValue = value; this.OnPropertyChanged(); } } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { barCode, testCode }, fixedCode, fixTitle: "Notify."); AnalyzerAssert.FixAll(Analyzer, Fix, ExpectedDiagnostic, new[] { barCode, testCode }, fixedCode, fixTitle: "Notify."); }
public void IgnoreRegex() { var fooCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private string text; public event PropertyChangedEventHandler PropertyChanged; public string Text { get => this.text; set { if (value == this.text) { return; } this.text = value; this.OnPropertyChanged(); } } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; var testCode = @" namespace RoslynSandbox { using System.Text.RegularExpressions; using Gu.Reactive; public class FooCondition : Condition { public FooCondition(Foo foo) : base( foo.ObservePropertyChanged(x => x.Text), () => Criteria(foo)) { } private static bool? Criteria(Foo foo) { if (foo.Text is string text) { var match = Regex.Match(text, string.Empty); return match.Success && int.TryParse(match.Groups[string.Empty].Value, out var value) && value > 10; } return false; } } }"; AnalyzerAssert.Valid(Analyzer, fooCode, testCode); }
public void FactoryMethodCallingPrivateCtorWithCreatedDisposable() { var testCode = @" namespace RoslynSandbox { using System; public sealed class Foo { private Foo(IDisposable value) { this.Value = value; } ↓public IDisposable Value { get; } public static Foo Create() => new Foo(new Disposable()); } }"; var fixedCode = @" namespace RoslynSandbox { using System; public sealed class Foo : IDisposable { private bool disposed; private Foo(IDisposable value) { this.Value = value; } public IDisposable Value { get; } public static Foo Create() => new Foo(new Disposable()); public void Dispose() { if (this.disposed) { return; } this.disposed = true; } private void ThrowIfDisposed() { if (this.disposed) { throw new ObjectDisposedException(this.GetType().FullName); } } } }"; AnalyzerAssert.CodeFix(Analyzer, CodeFix, ExpectedDiagnostic, new[] { DisposableCode, testCode }, fixedCode); AnalyzerAssert.FixAll(Analyzer, CodeFix, ExpectedDiagnostic, new[] { DisposableCode, testCode }, fixedCode); }
public void Event() { var testCode = @" namespace RoslynSandbox { using System; [Serializable] public class Foo { public Foo(int a, int b, int c, int d) { this.A = a; this.B = b; this.C = c; this.D = d; } ↓public event EventHandler SomeEvent; public int A { get; } public int B { get; protected set;} public int C { get; internal set; } public int D { get; set; } public int E => A; } }"; var fixedCode = @" namespace RoslynSandbox { using System; [Serializable] public class Foo { public Foo(int a, int b, int c, int d) { this.A = a; this.B = b; this.C = c; this.D = d; } [field: NonSerialized] public event EventHandler SomeEvent; public int A { get; } public int B { get; protected set;} public int C { get; internal set; } public int D { get; set; } public int E => A; } }"; AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, testCode, fixedCode); AnalyzerAssert.FixAll(Analyzer, Fix, ExpectedDiagnostic, testCode, fixedCode); }
public void RecursiveSample(DiagnosticAnalyzer analyzer) { var testCode = @" namespace RoslynSandbox { using System.Collections.Generic; internal abstract class Foo { public Foo() : this() { var value = this.RecursiveExpressionBodyProperty; value = this.RecursiveStatementBodyProperty; value = this.RecursiveExpressionBodyMethod(); value = this.RecursiveExpressionBodyMethod(1); value = this.RecursiveStatementBodyMethod(); value = this.RecursiveStatementBodyMethod(1); value = RecursiveStatementBodyMethodWithOptionalParameter(1); // value = value; } public int RecursiveExpressionBodyProperty => this.RecursiveExpressionBodyProperty; public int RecursiveStatementBodyProperty { get { return this.RecursiveStatementBodyProperty; } } public int RecursiveExpressionBodyMethod() => this.RecursiveExpressionBodyMethod(); public int RecursiveExpressionBodyMethod(int value) => this.RecursiveExpressionBodyMethod(value); public int RecursiveStatementBodyMethod() { return this.RecursiveStatementBodyMethod(); } public int RecursiveStatementBodyMethod(int value) { return this.RecursiveStatementBodyMethod(value); } public void Meh() { var value = this.RecursiveExpressionBodyProperty; value = this.RecursiveStatementBodyProperty; value = this.RecursiveExpressionBodyMethod(); value = this.RecursiveExpressionBodyMethod(1); value = this.RecursiveStatementBodyMethod(); value = this.RecursiveStatementBodyMethod(1); value = RecursiveStatementBodyMethodWithOptionalParameter(1); // value = value; } private static int RecursiveStatementBodyMethodWithOptionalParameter(int value, IEnumerable<int> values = null) { if (values == null) { return RecursiveStatementBodyMethodWithOptionalParameter(value, new[] { value }); } return value; } } }"; var converterCode = @" namespace RoslynSandbox { using System; using System.Globalization; using System.Windows.Controls; using System.Windows.Data; internal class ValidationErrorToStringConverter : IValueConverter { /// <summary> Gets the default instance </summary> public static readonly ValidationErrorToStringConverter Default = new ValidationErrorToStringConverter(); #pragma warning disable GU0012 /// <inheritdoc /> public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string text) { return text; } if (value is ValidationResult result) { return this.Convert(result.ErrorContent, targetType, parameter, culture); } if (value is ValidationError error) { return this.Convert(error.ErrorContent, targetType, parameter, culture); } return value; } /// <inheritdoc /> object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException($""{this.GetType().Name} only supports one-way conversion.""); } #pragma warning restore GU0012 } }"; AnalyzerAssert.Valid(analyzer, testCode, converterCode); }
public void GetNestedType(string type, string flags, string expected) { var code = @" namespace RoslynSandbox { using System.Reflection; class C { public C() { var typeInfo = typeof(C).GetNestedType(nameof(PublicStatic), ↓BindingFlags.Public | BindingFlags.Static); } public static class PublicStatic { } public class Public { } private static class PrivateStatic { } private class Private { } } }".AssertReplace("nameof(PublicStatic)", $"nameof({type})") .AssertReplace("BindingFlags.Public | BindingFlags.Static", flags); var fixedCode = @" namespace RoslynSandbox { using System.Reflection; class C { public C() { var typeInfo = typeof(C).GetNestedType(nameof(PublicStatic), BindingFlags.Public | BindingFlags.DeclaredOnly); } public static class PublicStatic { } public class Public { } private static class PrivateStatic { } private class Private { } } }".AssertReplace("nameof(PublicStatic)", $"nameof({type})") .AssertReplace("BindingFlags.Public | BindingFlags.DeclaredOnly", expected); var message = $"There is no member matching the filter. Expected: {expected}."; AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic.WithMessage(message), code, fixedCode); }
public void RecursiveSample(DiagnosticAnalyzer analyzer) { var testCode = @" namespace RoslynSandbox { using System.Collections.Generic; internal abstract class Foo { public Foo() : this() { var value = this.RecursiveExpressionBodyProperty; value = this.RecursiveStatementBodyProperty; value = this.RecursiveExpressionBodyMethod(); value = this.RecursiveExpressionBodyMethod(1); value = this.RecursiveStatementBodyMethod(); value = this.RecursiveStatementBodyMethod(1); value = RecursiveStatementBodyMethodWithOptionalParameter(1); // value = value; } public int RecursiveExpressionBodyProperty => this.RecursiveExpressionBodyProperty; public int RecursiveStatementBodyProperty { get { return this.RecursiveStatementBodyProperty; } } public int RecursiveExpressionBodyMethod() => this.RecursiveExpressionBodyMethod(); public int RecursiveExpressionBodyMethod(int value) => this.RecursiveExpressionBodyMethod(value); public int RecursiveStatementBodyMethod() { return this.RecursiveStatementBodyMethod(); } public int RecursiveStatementBodyMethod(int value) { return this.RecursiveStatementBodyMethod(value); } public void Meh() { var value = this.RecursiveExpressionBodyProperty; value = this.RecursiveStatementBodyProperty; value = this.RecursiveExpressionBodyMethod(); value = this.RecursiveExpressionBodyMethod(1); value = this.RecursiveStatementBodyMethod(); value = this.RecursiveStatementBodyMethod(1); value = RecursiveStatementBodyMethodWithOptionalParameter(1); // value = value; } private static int RecursiveStatementBodyMethodWithOptionalParameter(int value, IEnumerable<int> values = null) { if (values == null) { return RecursiveStatementBodyMethodWithOptionalParameter(value, new[] { value }); } return value; } } }"; AnalyzerAssert.Valid(analyzer, testCode); }
public void RealisticExtensionMethodClass() { var testCode = @" namespace RoslynSandbox { using System; using System.Collections.Generic; internal static class EnumerableExt { internal static bool TryElementAt<TCollection, TItem>(this TCollection source, int index, out TItem result) where TCollection : IReadOnlyList<TItem> { result = default(TItem); if (source == null) { return false; } if (source.Count <= index) { return false; } result = source[index]; return true; } internal static bool TrySingle<TCollection, TItem>(this TCollection source, out TItem result) where TCollection : IReadOnlyList<TItem> { if (source.Count == 1) { result = source[0]; return true; } result = default(TItem); return false; } internal static bool TrySingle<TCollection, TItem>(this TCollection source, Func<TItem, bool> selector, out TItem result) where TCollection : IReadOnlyList<TItem> { foreach (var item in source) { if (selector(item)) { result = item; return true; } } result = default(TItem); return false; } internal static bool TryFirst<TCollection, TItem>(this TCollection source, out TItem result) where TCollection : IReadOnlyList<TItem> { if (source.Count == 0) { result = default(TItem); return false; } result = source[0]; return true; } internal static bool TryFirst<TCollection, TItem>(this TCollection source, Func<TItem, bool> selector, out TItem result) where TCollection : IReadOnlyList<TItem> { foreach (var item in source) { if (selector(item)) { result = item; return true; } } result = default(TItem); return false; } internal static bool TryLast<TCollection, TItem>(this TCollection source, out TItem result) where TCollection : IReadOnlyList<TItem> { if (source.Count == 0) { result = default(TItem); return false; } result = source[source.Count - 1]; return true; } internal static bool TryLast<TCollection, TItem>(this TCollection source, Func<TItem, bool> selector, out TItem result) where TCollection : IReadOnlyList<TItem> { for (var i = source.Count - 1; i >= 0; i--) { var item = source[i]; if (selector(item)) { result = item; return true; } } result = default(TItem); return false; } } }"; AnalyzerAssert.Valid(Analyzer, testCode); }
public void ImplementIDisposableWithVirtualDisposeMethod() { var testCode = @" namespace RoslynSandbox { using System.IO; public class Foo { ↓private readonly Stream stream = File.OpenRead(string.Empty); public Foo() { } public int Value { get; } public int this[int value] { get { return value; } } protected virtual void Bar() { } private void Meh() { } } }"; var fixedCode = @" namespace RoslynSandbox { using System.IO; public class Foo : System.IDisposable { private readonly Stream stream = File.OpenRead(string.Empty); private bool disposed; public Foo() { } public int Value { get; } public int this[int value] { get { return value; } } public void Dispose() { this.Dispose(true); } protected virtual void Bar() { } protected virtual void Dispose(bool disposing) { if (this.disposed) { return; } this.disposed = true; if (disposing) { } } protected virtual void ThrowIfDisposed() { if (this.disposed) { throw new System.ObjectDisposedException(this.GetType().FullName); } } private void Meh() { } } }"; AnalyzerAssert.CodeFix <IDISP006ImplementIDisposable, ImplementIDisposableCodeFixProvider>(testCode, fixedCode, "Implement IDisposable with virtual dispose method."); }
public void DisposingInjectedPropertyInBaseClass() { var fooBaseCode = @" namespace RoslynSandbox { using System; public class FooBase : IDisposable { private bool disposed = false; public FooBase() : this(null) { } public FooBase(object bar) { this.Bar = bar; } public object Bar { get; } public void Dispose() { this.Dispose(true); } protected virtual void Dispose(bool disposing) { if (this.disposed) { return; } this.disposed = true; } } }"; var fooImplCode = @" namespace RoslynSandbox { using System; using System.IO; public class Foo : FooBase { public Foo(Stream stream) : base(stream) { } protected override void Dispose(bool disposing) { if (disposing) { ↓(this.Bar as IDisposable)?.Dispose(); } base.Dispose(disposing); } } }"; AnalyzerAssert.Diagnostics(Analyzer, ExpectedDiagnostic, fooBaseCode, fooImplCode); }
public void WhenUsingPrivateSetPropertyOnlyAssignedInCtor() { var fooCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public sealed class Foo : INotifyPropertyChanged { private Bar bar; public event PropertyChangedEventHandler PropertyChanged; public Bar Bar { get => this.bar; set { if (ReferenceEquals(value, this.bar)) { return; } this.bar = value; this.OnPropertyChanged(); } } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; var barCode = @" namespace RoslynSandbox { public class Bar { public Bar(int value) { Value = value; } public int Value { get; private set; } } }"; var testCode = @" namespace RoslynSandbox { using Gu.Reactive; public class FooCondition : Condition { public FooCondition(Foo foo) : base( foo.ObservePropertyChangedSlim(x => x.Bar), () => foo.Bar.Value == 2) { } } }"; AnalyzerAssert.Valid(Analyzer, fooCode, barCode, testCode); }
public void OneTimeSetUp() { AnalyzerAssert.AddTransitiveMetadataReferences(typeof(GalaSoft.MvvmLight.ViewModelBase).Assembly); }
public void IgnoreUsageInThrowTwoLevels() { var fooCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.Runtime.CompilerServices; public sealed class Foo : INotifyPropertyChanged, IDisposable { private bool disposed; private Bar bar; public event PropertyChangedEventHandler PropertyChanged; public Bar Bar { get { ThrowIfDisposed(); return this.bar; } set { ThrowIfDisposed(); if (ReferenceEquals(value, this.bar)) { return; } this.bar = value; this.OnPropertyChanged(); } } public void Dispose() { if (this.disposed) { return; } this.disposed = true; } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void ThrowIfDisposed() { if (this.disposed) { throw new ObjectDisposedException(this.GetType().FullName); } } } }"; var barCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.Runtime.CompilerServices; public class Bar : INotifyPropertyChanged, IDisposable { private int value; private bool disposed; public event PropertyChangedEventHandler PropertyChanged; public int Value { get { ThrowIfDisposed(); return this.value; } set { ThrowIfDisposed(); if (value == this.value) { return; } this.value = value; this.OnPropertyChanged(); } } public void Dispose() { } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void ThrowIfDisposed() { if (this.disposed) { throw new ObjectDisposedException(this.GetType().FullName); } } } }"; var testCode = @" namespace RoslynSandbox { using Gu.Reactive; public class FooCondition : Condition { public FooCondition(Foo foo) : base( foo.ObservePropertyChangedSlim(x => x.Bar.Value), () => foo.Bar?.Value == 2) { } } }"; AnalyzerAssert.Valid(Analyzer, fooCode, barCode, testCode); }
public void TwoLevels() { var fooCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private Bar bar; public Bar Bar { get { return this.bar; } set { if (value == this.bar) { return; } this.bar = value; this.OnPropertyChanged(); } } } "; var barCode = @" namespace RoslynSandbox { using System.ComponentModel; using System.Runtime.CompilerServices; public class Bar : INotifyPropertyChanged { private int value; public event PropertyChangedEventHandler PropertyChanged; public int Value { get { return this.value; } set { if (value == this.value) { return; } this.value = value; this.OnPropertyChanged(); } } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; var testCode = @" namespace RoslynSandbox { using System; using Gu.Reactive; public class Baz { public Baz() { var foo = new Foo(); foo.ObservePropertyChanged(x => x.Bar.Value) .Subscribe(x => Console.WriteLine(x)); } } }"; AnalyzerAssert.Valid(Analyzer, fooCode, barCode, testCode); }
public void ImplementIDisposableAndMakeSealed() { var testCode = @" namespace RoslynSandbox { using System.IO; public class Foo { public Foo() { } ↓public Stream Stream { get; } = File.OpenRead(string.Empty); public int Value { get; } protected virtual void Bar() { } private void Meh() { } } }"; var fixedCode = @" namespace RoslynSandbox { using System.IO; public sealed class Foo : System.IDisposable { private bool disposed; public Foo() { } public Stream Stream { get; } = File.OpenRead(string.Empty); public int Value { get; } public void Dispose() { if (this.disposed) { return; } this.disposed = true; } private void Bar() { } private void Meh() { } private void ThrowIfDisposed() { if (this.disposed) { throw new System.ObjectDisposedException(this.GetType().FullName); } } } }"; AnalyzerAssert.CodeFix(Analyzer, CodeFix, ExpectedDiagnostic, testCode, fixedCode, "Implement IDisposable and make class sealed."); }
public void Issue63() { var viewModelBaseCode = @" namespace MVVM { using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; /// <summary> /// Base class for all ViewModel classes in the application. /// It provides support for property change notifications /// and has a DisplayName property. This class is abstract. /// </summary> public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable { private readonly List<IDisposable> disposables = new List<IDisposable>(); private readonly object disposeLock = new object(); private bool isDisposed; protected ViewModelBase() { } public event PropertyChangedEventHandler PropertyChanged; public void Dispose() { lock (disposeLock) { this.OnDispose(); if (isDisposed) return; foreach (var disposable in disposables) disposable.Dispose(); isDisposed = true; } } protected virtual void OnDispose() { } } }"; var popupViewModelCode = @" namespace ProjectX.ViewModel { using System; using MVVM; public class PopupViewModel : ViewModelBase { public PopupViewModel() { ClosePopupCommand = new ClosePopupCommand(this); } // Gives an IDISP006 warning (need to implement IDispose) public ClosePopupCommand ClosePopupCommand { get; } protected override void OnDispose() { ClosePopupCommand.Dispose(); CloseProgramCommand.Dispose(); } } }"; var closePopupCommandCode = @" namespace ProjectX.Commands { using System; public sealed class ClosePopupCommand : IDisposable { private readonly object disposeLock = new object(); private bool isDisposed; private bool isBusy = false; internal ClosePopupCommand() { } public event EventHandler CanExecuteChanged; public void Dispose() { lock (disposeLock) { if (isDisposed) return; // Here we have code that actually needs to be disposed off... isDisposed = true; } } } }"; var solution = CodeFactory.CreateSolution( new[] { viewModelBaseCode, popupViewModelCode, closePopupCommandCode }, CodeFactory.DefaultCompilationOptions(Analyzer, AnalyzerAssert.SuppressedDiagnostics), AnalyzerAssert.MetadataReferences); AnalyzerAssert.NoDiagnostics(Analyze.GetDiagnostics(Analyzer, solution)); }
public void ImplementIDisposableWithVirtualDisposeMethod() { var testCode = @" namespace RoslynSandbox { using System.IO; public class Foo { public Foo() { } ↓public Stream Stream { get; } = File.OpenRead(string.Empty); public int Value { get; } public int this[int value] { get { return value; } } protected virtual void Bar() { } private void Meh() { } } }"; var fixedCode = @" namespace RoslynSandbox { using System; using System.IO; public class Foo : IDisposable { private bool disposed; public Foo() { } public Stream Stream { get; } = File.OpenRead(string.Empty); public int Value { get; } public int this[int value] { get { return value; } } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Bar() { } protected virtual void Dispose(bool disposing) { if (this.disposed) { return; } this.disposed = true; if (disposing) { } } protected virtual void ThrowIfDisposed() { if (this.disposed) { throw new ObjectDisposedException(this.GetType().FullName); } } private void Meh() { } } }"; AnalyzerAssert.CodeFix(Analyzer, CodeFix, ExpectedDiagnostic, testCode, fixedCode, "Implement IDisposable with virtual dispose method."); }
public void SomewhatRealisticSample(DiagnosticAnalyzer analyzer) { var disposableCode = @" namespace RoslynSandbox { using System; internal class Disposable : IDisposable { public Disposable(string meh) : this() { if (meh == null) throw new ArgumentNullException(nameof(meh)); } public Disposable() { } public void Dispose() { } } }"; var fooListCode = @" namespace RoslynSandbox { using System.Collections; using System.Collections.Generic; internal class FooList<T> : IReadOnlyList<T> { private readonly List<T> inner = new List<T>(); public int Count => this.inner.Count; public T this[int index] => this.inner[index]; public IEnumerator<T> GetEnumerator() { return this.inner.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this.inner).GetEnumerator(); } } }"; var fooCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.IO; using System.Reactive.Disposables; internal class Foo1 : IDisposable { private static readonly PropertyChangedEventArgs IsDirtyPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(IsDirty)); private readonly SingleAssignmentDisposable subscription = new SingleAssignmentDisposable(); private IDisposable meh1; private IDisposable meh2; private bool isDirty; public Foo1() { this.meh1 = this.RecursiveProperty; this.meh2 = this.RecursiveMethod(); this.subscription.Disposable = File.OpenRead(string.Empty); } public event PropertyChangedEventHandler PropertyChanged { add { this.PropertyChangedCore += value; } remove { this.PropertyChangedCore -= value; } } private event PropertyChangedEventHandler PropertyChangedCore; public Disposable RecursiveProperty => RecursiveProperty; public IDisposable Disposable => subscription.Disposable; public bool IsDirty { get { return this.isDirty; } private set { if (value == this.isDirty) { return; } this.isDirty = value; this.PropertyChangedCore?.Invoke(this, IsDirtyPropertyChangedEventArgs); } } public Disposable RecursiveMethod() => RecursiveMethod(); public void Meh() { using (var item = new Disposable()) { } using (var item = RecursiveProperty) { } using (RecursiveProperty) { } using (var item = RecursiveMethod()) { } using (RecursiveMethod()) { } } public void Dispose() { this.subscription.Dispose(); } } }"; var fooBaseCode = @" namespace RoslynSandbox { using System; using System.IO; internal abstract class FooBase : IDisposable { private readonly Stream stream = File.OpenRead(string.Empty); private bool disposed = false; public void Dispose() { this.Dispose(true); } protected virtual void Dispose(bool disposing) { if (this.disposed) { return; } this.disposed = true; if (disposing) { this.stream.Dispose(); } } } }"; var fooImplCode = @" namespace RoslynSandbox { using System; using System.IO; internal class FooImpl : FooBase { private readonly Stream stream = File.OpenRead(string.Empty); private bool disposed; protected override void Dispose(bool disposing) { if (this.disposed) { return; } this.disposed = true; if (disposing) { this.stream.Dispose(); } base.Dispose(disposing); } } }"; var withOptionalParameterCode = @" namespace RoslynSandbox { using System; using System.Collections.Generic; internal class Foo { private IDisposable disposable; public Foo(IDisposable disposable) { if (disposable == null) throw new ArgumentNullException(nameof(disposable)); this.disposable = Bar(disposable); } private static IDisposable Bar(IDisposable disposable, IEnumerable<IDisposable> disposables = null) { if (disposables == null) { return Bar(disposable, new[] { disposable }); } return disposable; } } }"; var reactiveCode = @" namespace RoslynSandbox { using System; using System.IO; using System.Reactive.Disposables; using System.Reactive.Linq; internal abstract class RxFoo : IDisposable { private readonly IDisposable subscription; private readonly SingleAssignmentDisposable singleAssignmentDisposable = new SingleAssignmentDisposable(); public RxFoo(int no) : this(Create(no)) { } public RxFoo(IObservable<object> observable) { if (observable == null) throw new ArgumentNullException(nameof(observable)); this.subscription = observable.Subscribe(_ => { }); this.singleAssignmentDisposable.Disposable = observable.Subscribe(_ => { }); } public void Dispose() { this.subscription.Dispose(); this.singleAssignmentDisposable.Dispose(); } private static IObservable<object> Create(int i) { return Observable.Empty<object>(); } } }"; var sources = new[] { disposableCode, fooListCode, fooCode, fooBaseCode, fooImplCode, withOptionalParameterCode, reactiveCode }; AnalyzerAssert.Valid(analyzer, sources); }
public void WhenBackingFieldNotifyWhenValueChanged() { var testCode = @" namespace RoslynSandbox { public class Foo : System.ComponentModel.INotifyPropertyChanged { private int value; public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; ↓public int Value { get => this.value; set { this.value = value; } } protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } } }"; var fixedCode = @" namespace RoslynSandbox { public class Foo : System.ComponentModel.INotifyPropertyChanged { private int value; public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; public int Value { get => this.value; set { if (value == this.value) { return; } this.value = value; this.OnPropertyChanged(); } } protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } } }"; AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, testCode, fixedCode, fixTitle: "Notify when value changes."); AnalyzerAssert.FixAll(Analyzer, Fix, ExpectedDiagnostic, testCode, fixedCode, fixTitle: "Notify when value changes."); }
public void IgnoreUsageInThrowOneLevel() { var fooCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.Runtime.CompilerServices; public sealed class Foo : INotifyPropertyChanged, IDisposable { private int value; private bool disposed; public event PropertyChangedEventHandler PropertyChanged; public int Value { get { ThrowIfDisposed(); return this.value; } set { ThrowIfDisposed(); if (value == this.value) { return; } this.value = value; this.OnPropertyChanged(); } } public void Dispose() { if (this.disposed) { return; } this.disposed = true; } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void ThrowIfDisposed() { if (this.disposed) { throw new ObjectDisposedException(this.GetType().FullName); } } } }"; var testCode = @" namespace RoslynSandbox { using Gu.Reactive; public class FooCondition : Condition { public FooCondition(Foo foo) : base( foo.ObservePropertyChangedSlim(x => x.Value), () => foo.Value == 2) { } } }"; AnalyzerAssert.NoDiagnostics <GUREA02ObservableAndCriteriaMustMatch>(fooCode, testCode); }
public async Task ReferencesMemberThatAnalyzerCannotSee() { var code = @" namespace RoslynSandbox { using System.Reflection; public class C { public object Get => typeof(BinaryReferencedAssembly.Foo).GetMethod(""add_Bar"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); } }"; var fixedCode = @" namespace RoslynSandbox { using System.Reflection; public class C { public object Get => typeof(BinaryReferencedAssembly.Foo).GetEvent(""Bar"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly).AddMethod; } }"; var binaryReferencedCode = @" namespace RoslynSandbox.BinaryReferencedAssembly { using System; public interface IFoo { event EventHandler Bar; } public class Foo : IFoo { #pragma warning disable CS0067 internal event EventHandler Bar; event EventHandler IFoo.Bar { add { } remove { } } } }"; var binaryReference = TestHelper.CompileBinaryReference(binaryReferencedCode); var solution = CodeFactory.CreateSolution( code, CodeFactory.DefaultCompilationOptions(new[] { Analyzer }) .WithMetadataImportOptions(MetadataImportOptions.Public), AnalyzerAssert.MetadataReferences.Append(binaryReference)); // To make sure the test is effective, assert that ReflectionAnalyzers *can’t* see Foo.Bar. var compilation = await solution.Projects.Single() .GetCompilationAsync() .ConfigureAwait(true); var fooType = compilation.GetTypeByMetadataName("RoslynSandbox.BinaryReferencedAssembly.Foo"); Assert.That(fooType.GetMembers(), Has.None.With.Property("Name") .EqualTo("Bar")); var message = @"Prefer typeof(BinaryReferencedAssembly.Foo).GetEvent(""Bar"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly).AddMethod."; AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic.WithMessage(message), solution, fixedCode); }
public void Run(DiagnosticAnalyzer analyzer) { var diagnostics = Analyze.GetDiagnostics(Solution, analyzer); AnalyzerAssert.NoDiagnostics(diagnostics); }
public void TearDown() { AnalyzerAssert.ResetAll(); }
public void ReproIssue71() { var code = @" using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TaxonomyWpf { public class IndexedList<T> : IList<KeyValuePair<int, T>> { protected IList<T> decorated; public IndexedList(IList<T> decorated) { if(decorated == null) throw new ArgumentNullException(nameof(decorated)); this.decorated = decorated; } public IEnumerator<KeyValuePair<int, T>> GetEnumerator() { return decorated.Select((element, index) => new KeyValuePair<int, T>(index, element)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void ICollection<KeyValuePair<int, T>>.Add(KeyValuePair<int, T> item) { Add(item.Value); } public void Add(T item) { decorated.Add(item); } public void Clear() { decorated.Clear(); } bool ICollection<KeyValuePair<int, T>>.Contains(KeyValuePair<int, T> item) { return Contains(item.Value); } public bool Contains(T item) { return decorated.Contains(item); } public void CopyTo(KeyValuePair<int, T>[] array, int arrayIndex) { throw new NotImplementedException(); } public bool Remove(KeyValuePair<int, T> item) { return decorated.Remove(item.Value); } public int Count => decorated.Count; public bool IsReadOnly => decorated.IsReadOnly; public int IndexOf(KeyValuePair<int, T> item) { return decorated.IndexOf(item.Value); } void IList<KeyValuePair<int, T>>.Insert(int index, KeyValuePair<int, T> item) { Insert(index, item.Value); } public void Insert(int index, T item) { decorated.Insert(index, item); } public void RemoveAt(int index) { decorated.RemoveAt(index); } public KeyValuePair<int, T> this[int index] { get { return new KeyValuePair<int, T>(index, decorated[index]); } set { decorated[index] = value.Value; } } } public class ObservableIndexedList<T> : IndexedList<T>, INotifyCollectionChanged { public ObservableIndexedList(ObservableCollection<T> decorated) : base(decorated) { } public event NotifyCollectionChangedEventHandler CollectionChanged { add { ((ObservableCollection<T>)decorated).CollectionChanged += value; } remove { ((ObservableCollection<T>)decorated).CollectionChanged -= value; } } } }"; AnalyzerAssert.Valid <IDISP003DisposeBeforeReassigning>(code); }
public void Check(TestCase check) { var testCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; public class ViewModel : INotifyPropertyChanged { private Foo bar; public event PropertyChangedEventHandler PropertyChanged; public Foo Bar { get { return this.bar; } set { ↓if (Equals(value, this.bar)) { return; } this.bar = value; this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Bar))); } } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }"; var fixedCode = @" namespace RoslynSandbox { using System; using System.ComponentModel; public class ViewModel : INotifyPropertyChanged { private Foo bar; public event PropertyChangedEventHandler PropertyChanged; public Foo Bar { get { return this.bar; } set { if (Equals(value, this.bar)) { return; } this.bar = value; this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Bar))); } } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }"; testCode = testCode.AssertReplace("Equals(value, this.bar)", check.Call); fixedCode = check.FixedCall == null ? fixedCode.AssertReplace("Equals(value, this.bar)", check.Call) : fixedCode.AssertReplace("Equals(value, this.bar)", check.FixedCall); AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { FooCode, testCode }, fixedCode); AnalyzerAssert.FixAll(Analyzer, Fix, ExpectedDiagnostic, new[] { FooCode, testCode }, fixedCode); }
public void AllocatingReferenceTypeFromMutableMembersObjectInitializerNoFix() { var testCode = @" namespace RoslynSandbox { public class Foo { public Foo(int a, int b, int c, int d) { this.A = a; this.B = b; this.C = c; this.D = d; } public int A { get; } public int B { get; } public int C { get; } public int D { get; set; } public Foo Bar ↓=> new Foo(this.A, this.B, this.C, 0) { D = this.D }; } }"; var fixedCode = @" namespace RoslynSandbox { public class Foo { public Foo(int a, int b, int c, int d) { this.A = a; this.B = b; this.C = c; this.D = d; this.Bar = new Foo(this.A, this.B, this.C, 0) { D = this.D }; } public int A { get; } public int B { get; } public int C { get; } public int D { get; set; } public Foo Bar { get; } } }"; AnalyzerAssert.CodeFix <GU0021CalculatedPropertyAllocates, UseGetOnlyCodeFixProvider>(testCode, fixedCode); }