提交 5b28fca6 编写于 作者: M Manish Vasani

Remove unused expressions/values/parameters analyzer and fixer

上级 dfa91051
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeStyle
{
......@@ -56,39 +54,41 @@ internal abstract class AbstractCodeStyleDiagnosticAnalyzer : DiagnosticAnalyzer
Descriptor, UnnecessaryWithoutSuggestionDescriptor, UnnecessaryWithSuggestionDescriptor);
}
protected AbstractCodeStyleDiagnosticAnalyzer(ImmutableArray<DiagnosticDescriptor> supportedDiagnostics)
{
SupportedDiagnostics = supportedDiagnostics;
}
protected DiagnosticDescriptor CreateUnnecessaryDescriptor()
=> CreateUnnecessaryDescriptor(DescriptorId);
protected DiagnosticDescriptor CreateUnnecessaryDescriptor(string descriptorId)
=> CreateDescriptorWithId(
descriptorId, _localizableTitle, _localizableMessageFormat,
DiagnosticCustomTags.Unnecessary);
isUnneccessary: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
protected DiagnosticDescriptor CreateDescriptor(params string[] customTags)
=> CreateDescriptorWithId(DescriptorId, _localizableTitle, _localizableMessageFormat, customTags);
=> CreateDescriptorWithId(DescriptorId, _localizableTitle, _localizableMessageFormat, customTags: customTags);
protected DiagnosticDescriptor CreateDescriptorWithTitle(LocalizableString title, params string[] customTags)
=> CreateDescriptorWithId(DescriptorId, title, title, customTags);
protected DiagnosticDescriptor CreateDescriptorWithId(
string id, LocalizableString title, LocalizableString messageFormat,
=> CreateDescriptorWithId(DescriptorId, title, title, customTags: customTags);
protected static DiagnosticDescriptor CreateDescriptorWithId(
string id,
LocalizableString title,
LocalizableString messageFormat,
bool isUnneccessary = false,
bool isConfigurable = true,
params string[] customTags)
{
if (!_configurable)
{
customTags = customTags.Concat(new[] { WellKnownDiagnosticTags.NotConfigurable }).ToArray();
}
return new DiagnosticDescriptor(
id, title, messageFormat,
DiagnosticCategory.Style,
DiagnosticSeverity.Hidden,
isEnabledByDefault: true,
customTags: customTags);
}
=> new DiagnosticDescriptor(
id, title, messageFormat,
DiagnosticCategory.Style,
DiagnosticSeverity.Hidden,
isEnabledByDefault: true,
customTags: DiagnosticCustomTags.Create(isUnneccessary, isConfigurable, customTags));
public sealed override void Initialize(AnalysisContext context)
{
// Code style analyzers should not run on generated code.
......
......@@ -142,6 +142,9 @@
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
</ItemGroup>
<ItemGroup>
<Folder Include="InternalUtilities\FlowAnalysis\" />
</ItemGroup>
<Import Project="..\AnalyzerDriver\AnalyzerDriver.projitems" Label="Shared" />
<Import Project="..\..\..\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems" Label="Shared" />
<Import Project="..\..\..\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems" Label="Shared" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.FlowAnalysis
{
internal sealed class BasicBlockBuilder
{
public int Ordinal;
public readonly BasicBlockKind Kind;
private ArrayBuilder<IOperation> _statements;
// The most common case is that we have one, or two predecessors.
// Let's avoid allocating a HashSet for these cases.
private BasicBlockBuilder _predecessor1;
private BasicBlockBuilder _predecessor2;
private PooledHashSet<BasicBlockBuilder> _predecessors;
public IOperation BranchValue;
public ControlFlowConditionKind ConditionKind;
public Branch ConditionalSuccessor;
public Branch FallThroughSuccessor;
public bool IsReachable;
public ControlFlowRegion EnclosingRegion;
public BasicBlockBuilder(BasicBlockKind kind)
{
Kind = kind;
Ordinal = -1;
IsReachable = false;
}
public bool HasStatements => _statements?.Count > 0;
public ArrayBuilder<IOperation> StatementsOpt => _statements;
public void AddStatement(IOperation operation)
{
Debug.Assert(operation != null);
if (_statements == null)
{
_statements = ArrayBuilder<IOperation>.GetInstance();
}
_statements.Add(operation);
}
public void MoveStatementsFrom(BasicBlockBuilder other)
{
if (other._statements == null)
{
return;
}
else if (_statements == null)
{
_statements = other._statements;
other._statements = null;
}
else
{
_statements.AddRange(other._statements);
other._statements.Clear();
}
}
public BasicBlock ToImmutable()
{
var block = new BasicBlock(Kind,
_statements?.ToImmutableAndFree() ?? ImmutableArray<IOperation>.Empty,
BranchValue,
ConditionKind,
Ordinal,
IsReachable,
EnclosingRegion);
_statements = null;
return block;
}
public bool HasPredecessors
{
get
{
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
return _predecessors.Count > 0;
}
else
{
return _predecessor1 != null || _predecessor2 != null;
}
}
}
public bool HasCondition
{
get
{
bool result = ConditionKind != ControlFlowConditionKind.None;
Debug.Assert(!result || BranchValue != null);
return result;
}
}
public BasicBlockBuilder GetSingletonPredecessorOrDefault()
{
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
return _predecessors.AsSingleton();
}
else if (_predecessor2 == null)
{
return _predecessor1;
}
else if (_predecessor1 == null)
{
return _predecessor2;
}
else
{
return null;
}
}
public void AddPredecessor(BasicBlockBuilder predecessor)
{
Debug.Assert(predecessor != null);
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
_predecessors.Add(predecessor);
}
else if (_predecessor1 == predecessor)
{
return;
}
else if (_predecessor2 == predecessor)
{
return;
}
else if (_predecessor1 == null)
{
_predecessor1 = predecessor;
}
else if (_predecessor2 == null)
{
_predecessor2 = predecessor;
}
else
{
_predecessors = PooledHashSet<BasicBlockBuilder>.GetInstance();
_predecessors.Add(_predecessor1);
_predecessors.Add(_predecessor2);
_predecessors.Add(predecessor);
Debug.Assert(_predecessors.Count == 3);
_predecessor1 = null;
_predecessor2 = null;
}
}
public void RemovePredecessor(BasicBlockBuilder predecessor)
{
Debug.Assert(predecessor != null);
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
_predecessors.Remove(predecessor);
}
else if (_predecessor1 == predecessor)
{
_predecessor1 = null;
}
else if (_predecessor2 == predecessor)
{
_predecessor2 = null;
}
}
public void GetPredecessors(ArrayBuilder<BasicBlockBuilder> builder)
{
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
foreach (BasicBlockBuilder predecessor in _predecessors)
{
builder.Add(predecessor);
}
return;
}
if (_predecessor1 != null)
{
builder.Add(_predecessor1);
}
if (_predecessor2 != null)
{
builder.Add(_predecessor2);
}
}
public ImmutableArray<ControlFlowBranch> ConvertPredecessorsToBranches(ArrayBuilder<BasicBlock> blocks)
{
if (!HasPredecessors)
{
_predecessors?.Free();
_predecessors = null;
return ImmutableArray<ControlFlowBranch>.Empty;
}
BasicBlock block = blocks[Ordinal];
var branches = ArrayBuilder<ControlFlowBranch>.GetInstance(_predecessors?.Count ?? 2);
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
foreach (BasicBlockBuilder predecessorBlockBuilder in _predecessors)
{
addBranches(predecessorBlockBuilder);
}
_predecessors.Free();
_predecessors = null;
}
else
{
if (_predecessor1 != null)
{
addBranches(_predecessor1);
_predecessor1 = null;
}
if (_predecessor2 != null)
{
addBranches(_predecessor2);
_predecessor2 = null;
}
}
// Order predecessors by source ordinal and conditional first to ensure deterministic predecessor ordering.
branches.Sort((x, y) =>
{
int result = x.Source.Ordinal - y.Source.Ordinal;
if (result == 0 && x.IsConditionalSuccessor != y.IsConditionalSuccessor)
{
if (x.IsConditionalSuccessor)
{
result = -1;
}
else
{
result = 1;
}
}
return result;
});
return branches.ToImmutableAndFree();
void addBranches(BasicBlockBuilder predecessorBlockBuilder)
{
BasicBlock predecessor = blocks[predecessorBlockBuilder.Ordinal];
if (predecessor.FallThroughSuccessor.Destination == block)
{
branches.Add(predecessor.FallThroughSuccessor);
}
if (predecessor.ConditionalSuccessor?.Destination == block)
{
branches.Add(predecessor.ConditionalSuccessor);
}
}
}
public void Free()
{
Ordinal = -1;
_statements?.Free();
_statements = null;
_predecessors?.Free();
_predecessors = null;
_predecessor1 = null;
_predecessor2 = null;
}
internal struct Branch
{
public ControlFlowBranchSemantics Semantics { get; set; }
public BasicBlockBuilder Destination { get; set; }
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.FlowAnalysis
{
internal partial class ControlFlowGraphBuilder
{
internal sealed class BasicBlockBuilder
{
public int Ordinal;
public readonly BasicBlockKind Kind;
private ArrayBuilder<IOperation> _statements;
// The most common case is that we have one, or two predecessors.
// Let's avoid allocating a HashSet for these cases.
private BasicBlockBuilder _predecessor1;
private BasicBlockBuilder _predecessor2;
private PooledHashSet<BasicBlockBuilder> _predecessors;
public IOperation BranchValue;
public ControlFlowConditionKind ConditionKind;
public Branch Conditional;
public Branch FallThrough;
public bool IsReachable;
public ControlFlowRegion Region;
public BasicBlockBuilder(BasicBlockKind kind)
{
Kind = kind;
Ordinal = -1;
IsReachable = false;
}
public bool HasStatements => _statements?.Count > 0;
public ArrayBuilder<IOperation> StatementsOpt => _statements;
public void AddStatement(IOperation operation)
{
Debug.Assert(operation != null);
if (_statements == null)
{
_statements = ArrayBuilder<IOperation>.GetInstance();
}
_statements.Add(operation);
}
public void MoveStatementsFrom(BasicBlockBuilder other)
{
if (other._statements == null)
{
return;
}
else if (_statements == null)
{
_statements = other._statements;
other._statements = null;
}
else
{
_statements.AddRange(other._statements);
other._statements.Clear();
}
}
public BasicBlock ToImmutable()
{
var block = new BasicBlock(Kind,
_statements?.ToImmutableAndFree() ?? ImmutableArray<IOperation>.Empty,
BranchValue,
ConditionKind,
Ordinal,
IsReachable,
Region);
_statements = null;
return block;
}
public bool HasPredecessors
{
get
{
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
return _predecessors.Count > 0;
}
else
{
return _predecessor1 != null || _predecessor2 != null;
}
}
}
public bool HasCondition
{
get
{
bool result = ConditionKind != ControlFlowConditionKind.None;
Debug.Assert(!result || BranchValue != null);
return result;
}
}
public BasicBlockBuilder GetSingletonPredecessorOrDefault()
{
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
return _predecessors.AsSingleton();
}
else if (_predecessor2 == null)
{
return _predecessor1;
}
else if (_predecessor1 == null)
{
return _predecessor2;
}
else
{
return null;
}
}
public void AddPredecessor(BasicBlockBuilder predecessor)
{
Debug.Assert(predecessor != null);
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
_predecessors.Add(predecessor);
}
else if (_predecessor1 == predecessor)
{
return;
}
else if (_predecessor2 == predecessor)
{
return;
}
else if (_predecessor1 == null)
{
_predecessor1 = predecessor;
}
else if (_predecessor2 == null)
{
_predecessor2 = predecessor;
}
else
{
_predecessors = PooledHashSet<BasicBlockBuilder>.GetInstance();
_predecessors.Add(_predecessor1);
_predecessors.Add(_predecessor2);
_predecessors.Add(predecessor);
Debug.Assert(_predecessors.Count == 3);
_predecessor1 = null;
_predecessor2 = null;
}
}
public void RemovePredecessor(BasicBlockBuilder predecessor)
{
Debug.Assert(predecessor != null);
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
_predecessors.Remove(predecessor);
}
else if (_predecessor1 == predecessor)
{
_predecessor1 = null;
}
else if (_predecessor2 == predecessor)
{
_predecessor2 = null;
}
}
public void GetPredecessors(ArrayBuilder<BasicBlockBuilder> builder)
{
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
foreach (BasicBlockBuilder predecessor in _predecessors)
{
builder.Add(predecessor);
}
return;
}
if (_predecessor1 != null)
{
builder.Add(_predecessor1);
}
if (_predecessor2 != null)
{
builder.Add(_predecessor2);
}
}
public ImmutableArray<ControlFlowBranch> ConvertPredecessorsToBranches(ArrayBuilder<BasicBlock> blocks)
{
if (!HasPredecessors)
{
_predecessors?.Free();
_predecessors = null;
return ImmutableArray<ControlFlowBranch>.Empty;
}
BasicBlock block = blocks[Ordinal];
var branches = ArrayBuilder<ControlFlowBranch>.GetInstance(_predecessors?.Count ?? 2);
if (_predecessors != null)
{
Debug.Assert(_predecessor1 == null);
Debug.Assert(_predecessor2 == null);
foreach (BasicBlockBuilder predecessorBlockBuilder in _predecessors)
{
addBranches(predecessorBlockBuilder);
}
_predecessors.Free();
_predecessors = null;
}
else
{
if (_predecessor1 != null)
{
addBranches(_predecessor1);
_predecessor1 = null;
}
if (_predecessor2 != null)
{
addBranches(_predecessor2);
_predecessor2 = null;
}
}
// Order predecessors by source ordinal and conditional first to ensure deterministic predecessor ordering.
branches.Sort((x, y) =>
{
int result = x.Source.Ordinal - y.Source.Ordinal;
if (result == 0 && x.IsConditionalSuccessor != y.IsConditionalSuccessor)
{
if (x.IsConditionalSuccessor)
{
result = -1;
}
else
{
result = 1;
}
}
return result;
});
return branches.ToImmutableAndFree();
void addBranches(BasicBlockBuilder predecessorBlockBuilder)
{
BasicBlock predecessor = blocks[predecessorBlockBuilder.Ordinal];
if (predecessor.FallThroughSuccessor.Destination == block)
{
branches.Add(predecessor.FallThroughSuccessor);
}
if (predecessor.ConditionalSuccessor?.Destination == block)
{
branches.Add(predecessor.ConditionalSuccessor);
}
}
}
public void Free()
{
Ordinal = -1;
_statements?.Free();
_statements = null;
_predecessors?.Free();
_predecessors = null;
_predecessor1 = null;
_predecessor2 = null;
}
internal struct Branch
{
public ControlFlowBranchSemantics Kind { get; set; }
public BasicBlockBuilder Destination { get; set; }
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.FlowAnalysis
{
internal sealed partial class ControlFlowGraphBuilder
{
internal sealed class ReachabilityAnalyzer : IDataFlowAnalyzer<bool>
{
private BitVector _visited = BitVector.Empty;
private ReachabilityAnalyzer() { }
public static void Run(ArrayBuilder<BasicBlockBuilder> blocks, CancellationToken cancellationToken)
=> CustomDataFlowAnalysis<ReachabilityAnalyzer, bool>.Run(blocks.ToImmutable(), new ReachabilityAnalyzer(), cancellationToken);
public bool AnalyzeUnreachableBlocks => false;
public bool AnalyzeBlock(BasicBlockBuilder basicBlock, CancellationToken cancellationToken)
{
SetCurrentAnalysisData(basicBlock, isReachable: true);
return true;
}
public bool AnalyzeNonConditionalBranch(BasicBlockBuilder basicBlock, bool currentAnalysisData, CancellationToken cancellationToken)
=> currentAnalysisData;
public (bool fallThroughSuccessorData, bool conditionalSuccessorData) AnalyzeConditionalBranch(
BasicBlockBuilder basicBlock,
bool currentAnalysisData,
CancellationToken cancellationToken)
=> (currentAnalysisData, currentAnalysisData);
public void SetCurrentAnalysisData(BasicBlockBuilder basicBlock, bool isReachable)
{
_visited[basicBlock.Ordinal] = isReachable;
basicBlock.IsReachable = isReachable;
}
public bool GetCurrentAnalysisData(BasicBlockBuilder basicBlock) => _visited[basicBlock.Ordinal];
public bool GetEmptyAnalysisData() => false;
public bool Merge(bool analysisData1, bool analysisData2, CancellationToken cancellationToken)
=> analysisData1 || analysisData2;
public bool IsEqual(bool analysisData1, bool analysisData2)
=> analysisData1 == analysisData2;
public void Dispose()
{
}
}
}
}
......@@ -351,8 +351,8 @@ public void Free()
void setRegion(BasicBlockBuilder block)
{
Debug.Assert(block.Region == null);
block.Region = result;
Debug.Assert(block.EnclosingRegion == null);
block.EnclosingRegion = result;
// Populate the map of IFlowAnonymousFunctionOperation nodes, if we have any
if (anonymousFunctionsMapOpt != null)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.FlowAnalysis
{
internal static partial class ControlFlowRegionExtensions
{
internal static bool ContainsBlock(this ControlFlowRegion region, int destinationOrdinal)
{
return region.FirstBlockOrdinal <= destinationOrdinal && region.LastBlockOrdinal >= destinationOrdinal;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FlowAnalysis
{
#if WORKSPACE
using TBasicBlock = BasicBlock;
using TControlFlowBranch = ControlFlowBranch;
#else
using TBasicBlock = BasicBlockBuilder;
using TControlFlowBranch = BasicBlockBuilder.Branch;
#endif
internal static class CustomDataFlowAnalysis<TDataFlowAnalyzer, TBlockAnalysisData>
where TDataFlowAnalyzer: IDataFlowAnalyzer<TBlockAnalysisData>
{
/// <summary>
/// Runs dataflow analysis for the given <paramref name="analyzer"/> on the given <paramref name="blocks"/>.
/// </summary>
/// <param name="blocks">Blocks on which to execute analysis.</param>
/// <param name="analyzer">Dataflow analyzer.</param>
/// <returns>Block analysis data for the last block.</returns>
internal static TBlockAnalysisData Run(ImmutableArray<TBasicBlock> blocks, TDataFlowAnalyzer analyzer, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var continueDispatchAfterFinally = PooledDictionary<ControlFlowRegion, bool>.GetInstance();
var dispatchedExceptionsFromRegions = PooledHashSet<ControlFlowRegion>.GetInstance();
int firstBlockOrdinal = 0;
int lastBlockOrdinal = blocks.Length - 1;
var unreachableBlocksToVisit = ArrayBuilder<TBasicBlock>.GetInstance();
if (analyzer.AnalyzeUnreachableBlocks)
{
for (int i = firstBlockOrdinal; i <= lastBlockOrdinal; i++)
{
if (!blocks[i].IsReachable)
{
unreachableBlocksToVisit.Add(blocks[i]);
}
}
}
var initialAnalysisData = analyzer.GetCurrentAnalysisData(blocks[0]);
if (initialAnalysisData == default)
{
initialAnalysisData = analyzer.GetEmptyAnalysisData();
}
var result = RunCore(blocks, analyzer, firstBlockOrdinal, lastBlockOrdinal,
initialAnalysisData,
unreachableBlocksToVisit,
outOfRangeBlocksToVisit: null,
continueDispatchAfterFinally,
dispatchedExceptionsFromRegions,
cancellationToken);
Debug.Assert(unreachableBlocksToVisit.Count == 0);
unreachableBlocksToVisit.Free();
continueDispatchAfterFinally.Free();
dispatchedExceptionsFromRegions.Free();
return result;
}
private static TBlockAnalysisData RunCore(
ImmutableArray<TBasicBlock> blocks,
TDataFlowAnalyzer analyzer,
int firstBlockOrdinal,
int lastBlockOrdinal,
TBlockAnalysisData initialAnalysisData,
ArrayBuilder<TBasicBlock> unreachableBlocksToVisit,
SortedSet<int> outOfRangeBlocksToVisit,
PooledDictionary<ControlFlowRegion, bool> continueDispatchAfterFinally,
PooledHashSet<ControlFlowRegion> dispatchedExceptionsFromRegions,
CancellationToken cancellationToken)
{
var toVisit = new SortedSet<int>();
var firstBlock = blocks[firstBlockOrdinal];
analyzer.SetCurrentAnalysisData(firstBlock, initialAnalysisData);
toVisit.Add(firstBlock.Ordinal);
var processedBlocks = PooledHashSet<TBasicBlock>.GetInstance();
TBlockAnalysisData resultAnalysisData = default;
do
{
cancellationToken.ThrowIfCancellationRequested();
TBasicBlock current;
if (toVisit.Count > 0)
{
var min = toVisit.Min;
toVisit.Remove(min);
current = blocks[min];
}
else
{
int index;
current = null;
for (index = 0; index < unreachableBlocksToVisit.Count; index++)
{
var unreachableBlock = unreachableBlocksToVisit[index];
if (unreachableBlock.Ordinal >= firstBlockOrdinal && unreachableBlock.Ordinal <= lastBlockOrdinal)
{
current = unreachableBlock;
break;
}
}
if (current == null)
{
continue;
}
unreachableBlocksToVisit.RemoveAt(index);
if (processedBlocks.Contains(current))
{
// Already processed from a branch from another unreachable block.
continue;
}
analyzer.SetCurrentAnalysisData(current, analyzer.GetEmptyAnalysisData());
}
if (current.Ordinal < firstBlockOrdinal || current.Ordinal > lastBlockOrdinal)
{
outOfRangeBlocksToVisit.Add(current.Ordinal);
continue;
}
if (current.Ordinal == current.EnclosingRegion.FirstBlockOrdinal)
{
// We are revisiting first block of a region, so we need to again dispatch exceptions from region.
dispatchedExceptionsFromRegions.Remove(current.EnclosingRegion);
}
TBlockAnalysisData fallThroughAnalysisData = analyzer.AnalyzeBlock(current, cancellationToken);
bool fallThroughSuccessorIsReachable = true;
if (current.ConditionKind != ControlFlowConditionKind.None)
{
TBlockAnalysisData conditionalSuccessorAnalysisData;
(fallThroughAnalysisData, conditionalSuccessorAnalysisData) = analyzer.AnalyzeConditionalBranch(current, fallThroughAnalysisData, cancellationToken);
bool conditionalSuccesorIsReachable = true;
if (current.BranchValue.ConstantValue.HasValue && current.BranchValue.ConstantValue.Value is bool constant)
{
if (constant == (current.ConditionKind == ControlFlowConditionKind.WhenTrue))
{
fallThroughSuccessorIsReachable = false;
}
else
{
conditionalSuccesorIsReachable = false;
}
}
if (conditionalSuccesorIsReachable || analyzer.AnalyzeUnreachableBlocks)
{
followBranch(current, current.ConditionalSuccessor, conditionalSuccessorAnalysisData);
}
}
else
{
fallThroughAnalysisData = analyzer.AnalyzeNonConditionalBranch(current, fallThroughAnalysisData, cancellationToken);
}
if (fallThroughSuccessorIsReachable || analyzer.AnalyzeUnreachableBlocks)
{
TControlFlowBranch branch = current.FallThroughSuccessor;
followBranch(current, branch, fallThroughAnalysisData);
if (current.EnclosingRegion.Kind == ControlFlowRegionKind.Finally &&
current.Ordinal == lastBlockOrdinal)
{
continueDispatchAfterFinally[current.EnclosingRegion] = branch.Semantics != ControlFlowBranchSemantics.Throw &&
branch.Semantics != ControlFlowBranchSemantics.Rethrow &&
current.FallThroughSuccessor.Semantics == ControlFlowBranchSemantics.StructuredExceptionHandling;
}
}
if (current.Ordinal == lastBlockOrdinal)
{
resultAnalysisData = fallThroughAnalysisData;
}
// We are using very simple approach:
// If try block is reachable, we should dispatch an exception from it, even if it is empty.
// To simplify implementation, we dispatch exception from every reachable basic block and rely
// on dispatchedExceptionsFromRegions cache to avoid doing duplicate work.
dispatchException(current.EnclosingRegion);
processedBlocks.Add(current);
}
while (toVisit.Count != 0 || unreachableBlocksToVisit.Count != 0);
return resultAnalysisData;
void followBranch(TBasicBlock current, TControlFlowBranch branch, TBlockAnalysisData currentAnalsisData)
{
#if WORKSPACE
if (branch == null)
{
return;
}
#endif
switch (branch.Semantics)
{
case ControlFlowBranchSemantics.None:
case ControlFlowBranchSemantics.ProgramTermination:
case ControlFlowBranchSemantics.StructuredExceptionHandling:
case ControlFlowBranchSemantics.Throw:
case ControlFlowBranchSemantics.Rethrow:
case ControlFlowBranchSemantics.Error:
Debug.Assert(branch.Destination == null);
return;
case ControlFlowBranchSemantics.Regular:
case ControlFlowBranchSemantics.Return:
Debug.Assert(branch.Destination != null);
if (stepThroughFinally(current.EnclosingRegion, branch.Destination, ref currentAnalsisData))
{
var destination = branch.Destination;
var currentDestinationData = analyzer.GetCurrentAnalysisData(destination);
var mergedAnalysisData = analyzer.Merge(currentDestinationData, currentAnalsisData, cancellationToken);
// We need to analyze the destination block if both the following conditions are met:
// 1. Either the current block is reachable both destination and current are non-reachable
// 2. Either the new analysis data for destination has changed or destination is the last block,
// in which case we need to analyze it so we can return the result data at end of the block.
if ((current.IsReachable || !destination.IsReachable) &&
(!analyzer.IsEqual(currentDestinationData, mergedAnalysisData) || destination.Ordinal == lastBlockOrdinal))
{
analyzer.SetCurrentAnalysisData(destination, mergedAnalysisData);
toVisit.Add(branch.Destination.Ordinal);
}
}
return;
default:
throw ExceptionUtilities.UnexpectedValue(branch.Semantics);
}
}
// Returns whether we should proceed to the destination after finallies were taken care of.
bool stepThroughFinally(ControlFlowRegion region, TBasicBlock destination, ref TBlockAnalysisData currentAnalysisData)
{
int destinationOrdinal = destination.Ordinal;
while (!region.ContainsBlock(destinationOrdinal))
{
Debug.Assert(region.Kind != ControlFlowRegionKind.Root);
ControlFlowRegion enclosing = region.EnclosingRegion;
if (region.Kind == ControlFlowRegionKind.Try && enclosing.Kind == ControlFlowRegionKind.TryAndFinally)
{
Debug.Assert(enclosing.NestedRegions[0] == region);
Debug.Assert(enclosing.NestedRegions[1].Kind == ControlFlowRegionKind.Finally);
if (!stepThroughSingleFinally(enclosing.NestedRegions[1], ref currentAnalysisData))
{
// The point that continues dispatch is not reachable. Cancel the dispatch.
return false;
}
}
region = enclosing;
}
return true;
}
// Returns whether we should proceed with dispatch after finally was taken care of.
bool stepThroughSingleFinally(ControlFlowRegion @finally, ref TBlockAnalysisData currentAnalysisData)
{
Debug.Assert(@finally.Kind == ControlFlowRegionKind.Finally);
var previousAnalysisData = analyzer.GetCurrentAnalysisData(blocks[@finally.FirstBlockOrdinal]);
var mergedAnalysisData = analyzer.Merge(previousAnalysisData, currentAnalysisData, cancellationToken);
if (!analyzer.IsEqual(previousAnalysisData, mergedAnalysisData))
{
// For simplicity, we do a complete walk of the finally/filter region in isolation
// to make sure that the resume dispatch point is reachable from its beginning.
// It could also be reachable through invalid branches into the finally and we don't want to consider
// these cases for regular finally handling.
currentAnalysisData = RunCore(blocks,
analyzer,
@finally.FirstBlockOrdinal,
@finally.LastBlockOrdinal,
mergedAnalysisData,
unreachableBlocksToVisit,
outOfRangeBlocksToVisit: toVisit,
continueDispatchAfterFinally,
dispatchedExceptionsFromRegions,
cancellationToken);
}
if (!continueDispatchAfterFinally.TryGetValue(@finally, out bool dispatch))
{
dispatch = false;
continueDispatchAfterFinally.Add(@finally, false);
}
return dispatch;
}
void dispatchException(ControlFlowRegion fromRegion)
{
do
{
if (!dispatchedExceptionsFromRegions.Add(fromRegion))
{
return;
}
ControlFlowRegion enclosing = fromRegion.Kind == ControlFlowRegionKind.Root ? null : fromRegion.EnclosingRegion;
if (fromRegion.Kind == ControlFlowRegionKind.Try)
{
switch (enclosing.Kind)
{
case ControlFlowRegionKind.TryAndFinally:
Debug.Assert(enclosing.NestedRegions[0] == fromRegion);
Debug.Assert(enclosing.NestedRegions[1].Kind == ControlFlowRegionKind.Finally);
var currentAnalysisData = analyzer.GetCurrentAnalysisData(blocks[fromRegion.FirstBlockOrdinal]);
if (!stepThroughSingleFinally(enclosing.NestedRegions[1], ref currentAnalysisData))
{
// The point that continues dispatch is not reachable. Cancel the dispatch.
return;
}
break;
case ControlFlowRegionKind.TryAndCatch:
Debug.Assert(enclosing.NestedRegions[0] == fromRegion);
dispatchExceptionThroughCatches(enclosing, startAt: 1);
break;
default:
throw ExceptionUtilities.UnexpectedValue(enclosing.Kind);
}
}
else if (fromRegion.Kind == ControlFlowRegionKind.Filter)
{
// If filter throws, dispatch is resumed at the next catch with an original exception
Debug.Assert(enclosing.Kind == ControlFlowRegionKind.FilterAndHandler);
ControlFlowRegion tryAndCatch = enclosing.EnclosingRegion;
Debug.Assert(tryAndCatch.Kind == ControlFlowRegionKind.TryAndCatch);
int index = tryAndCatch.NestedRegions.IndexOf(enclosing, startIndex: 1);
if (index > 0)
{
dispatchExceptionThroughCatches(tryAndCatch, startAt: index + 1);
fromRegion = tryAndCatch;
continue;
}
throw ExceptionUtilities.Unreachable;
}
fromRegion = enclosing;
}
while (fromRegion != null);
}
void dispatchExceptionThroughCatches(ControlFlowRegion tryAndCatch, int startAt)
{
// For simplicity, we do not try to figure out whether a catch clause definitely
// handles all exceptions.
Debug.Assert(tryAndCatch.Kind == ControlFlowRegionKind.TryAndCatch);
Debug.Assert(startAt > 0);
Debug.Assert(startAt <= tryAndCatch.NestedRegions.Length);
for (int i = startAt; i < tryAndCatch.NestedRegions.Length; i++)
{
ControlFlowRegion @catch = tryAndCatch.NestedRegions[i];
switch (@catch.Kind)
{
case ControlFlowRegionKind.Catch:
toVisit.Add(@catch.FirstBlockOrdinal);
break;
case ControlFlowRegionKind.FilterAndHandler:
TBasicBlock entryBlock = blocks[@catch.FirstBlockOrdinal];
Debug.Assert(@catch.NestedRegions[0].Kind == ControlFlowRegionKind.Filter);
Debug.Assert(entryBlock.Ordinal == @catch.NestedRegions[0].FirstBlockOrdinal);
toVisit.Add(entryBlock.Ordinal);
break;
default:
throw ExceptionUtilities.UnexpectedValue(@catch.Kind);
}
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
namespace Microsoft.CodeAnalysis.FlowAnalysis
{
#if WORKSPACE
using TBasicBlock = BasicBlock;
#else
using TBasicBlock = BasicBlockBuilder;
#endif
/// <summary>
/// Analyzer to execute custom dataflow analysis on a control flow graph.
/// </summary>
/// <typeparam name="TBlockAnalysisData">Custom data tracked for each basic block with values at start of the block.</typeparam>
internal interface IDataFlowAnalyzer<TBlockAnalysisData> : IDisposable
{
/// <summary>
/// Gets empty analysis data for first analysis pass on a basic block.
/// </summary>
TBlockAnalysisData GetEmptyAnalysisData();
/// <summary>
/// Gets current analysis data for the given basic block.
/// </summary>
TBlockAnalysisData GetCurrentAnalysisData(TBasicBlock basicBlock);
/// <summary>
/// Updates the current analysis data for the given basic block.
/// </summary>
void SetCurrentAnalysisData(TBasicBlock basicBlock, TBlockAnalysisData data);
/// <summary>
/// Analyze the given basic block and return the block analysis data at the end of the block for it's succesors.
/// </summary>
TBlockAnalysisData AnalyzeBlock(TBasicBlock basicBlock, CancellationToken cancellationToken);
/// <summary>
/// Analyze the non-conditional fallthrough successor branch for the given basic block
/// and return the block analysis data for the branch destination.
/// </summary>
TBlockAnalysisData AnalyzeNonConditionalBranch(TBasicBlock basicBlock, TBlockAnalysisData currentAnalysisData, CancellationToken cancellationToken);
/// <summary>
/// Analyze the given conditional branch for the given basic block and return the
/// block analysis data for the branch destinations for the fallthrough and
/// conditional successor branches.
/// </summary>
(TBlockAnalysisData fallThroughSuccessorData, TBlockAnalysisData conditionalSuccessorData) AnalyzeConditionalBranch(
TBasicBlock basicBlock,
TBlockAnalysisData currentAnalysisData,
CancellationToken cancellationToken);
/// <summary>
/// Merge the given block analysis data instances to produce the resultant merge data.
/// </summary>
TBlockAnalysisData Merge(TBlockAnalysisData analysisData1, TBlockAnalysisData analysisData2, CancellationToken cancellationToken);
/// <summary>
/// Returns true if both the given block analysis data instances should be considered equivalent by analysis.
/// </summary>
bool IsEqual(TBlockAnalysisData analysisData1, TBlockAnalysisData analysisData2);
/// <summary>
/// Flag indicating if the dataflow analysis should run on unreachable blocks.
/// </summary>
bool AnalyzeUnreachableBlocks { get; }
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnusedExpressionsAndParameters
{
public partial class RemoveUnusedExpressionsTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_Suppressed()
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
}
int M2() => 0;
}", options: PreferNone);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_PreferDiscard_CSharp6()
{
// Discard not supported in C# 6.0, so we fallback to unused local variable.
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
}
int M2() => 0;
}",
@"class C
{
void M()
{
var unused = M2();
}
int M2() => 0;
}", options: PreferDiscard,
parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_VariableInitialization(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
void M()
{
int x = [|M2()|];
}
int M2() => 0;
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard), "_")]
[InlineData(nameof(PreferUnusedLocal), "var unused")]
public async Task ExpressionStatement_NonConstantPrimitiveTypeValue(string optionName, string fix)
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
}
int M2() => 0;
}",
$@"class C
{{
void M()
{{
{fix} = M2();
}}
int M2() => 0;
}}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard), "_")]
[InlineData(nameof(PreferUnusedLocal), "var unused")]
public async Task ExpressionStatement_UserDefinedType(string optionName, string fix)
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
}
C M2() => new C();
}",
$@"class C
{{
void M()
{{
{fix} = M2();
}}
C M2() => new C();
}}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_ConstantValue(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
void M()
{
[|1|];
}
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SyntaxError(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2(,)|];
}
int M2() => 0;
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SemanticError(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
}
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_VoidReturningMethodCall(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
}
void M2() { }
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_BoolReturningMethodCall(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"using System.Collections.Generic;
class C
{
void AddToSet(HashSet<int> set, int i)
{
[|set.Add(i)|];
}
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SpecialCase_Interlocked(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"using System.Threading;
class C
{
int field;
void SetField(int param)
{
[|Interlocked.Exchange(ref field, param)|];
}
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard))]
[InlineData(nameof(PreferUnusedLocal))]
public async Task ExpressionStatement_SpecialCase_ImmutableInterlocked(string optionName)
{
await TestMissingInRegularAndScriptAsync(
@"using System.Collections.Immutable;
namespace System.Collections.Immutable
{
public class ImmutableInterlocked
{
public static ImmutableArray<T> InterlockedExchange<T>(ref ImmutableArray<T> p1, ImmutableArray<T> p2)
=> default;
}
public struct ImmutableArray<T>
{
}
}
class C
{
ImmutableArray<int> field;
void SetField(ImmutableArray<int> param)
{
[|ImmutableInterlocked.InterlockedExchange(ref field, param);|]
}
}", optionName);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData("=")]
[InlineData("+=")]
public async Task ExpressionStatement_AssignmentExpression(string op)
{
await TestMissingInRegularAndScriptWithAllOptionsAsync(
$@"class C
{{
void M(int x)
{{
x {op} [|M2()|];
}}
int M2() => 0;
}}");
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData("x++")]
[InlineData("x--")]
[InlineData("++x")]
[InlineData("--x")]
public async Task ExpressionStatement_IncrementOrDecrement(string incrementOrDecrement)
{
await TestMissingInRegularAndScriptWithAllOptionsAsync(
$@"class C
{{
int M(int x)
{{
[|{incrementOrDecrement}|];
return x;
}}
}}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
var unused = M2();
[|M2()|];
}
int M2() => 0;
}",
@"class C
{
void M()
{
var unused = M2();
var unused1 = M2();
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed_02()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
[|M2()|];
var unused = M2();
}
int M2() => 0;
}",
@"class C
{
void M()
{
var unused1 = M2();
var unused = M2();
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed_03()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M(int p)
{
[|M2()|];
if (p > 0)
{
var unused = M2();
}
}
int M2() => 0;
}",
@"class C
{
void M(int p)
{
var unused1 = M2();
if (p > 0)
{
var unused = M2();
}
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_UnusedLocal_NameAlreadyUsed_04()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M(int p)
{
if (p > 0)
{
[|M2()|];
}
else
{
var unused = M2();
}
}
int M2() => 0;
}",
@"class C
{
void M(int p)
{
if (p > 0)
{
var unused = M2();
}
else
{
var unused = M2();
}
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
[InlineData(nameof(PreferDiscard), "_", "_", "_")]
[InlineData(nameof(PreferUnusedLocal), "var unused", "var unused", "var unused3")]
public async Task ExpressionStatement_FixAll(string optionName, string fix1, string fix2, string fix3)
{
await TestInRegularAndScriptAsync(
@"class C
{
public C()
{
M2(); // Separate code block
}
void M(int unused1, int unused2)
{
{|FixAllInDocument:M2()|};
M2(); // Another instance in same code block
_ = M2(); // Already fixed
var x = M2(); // Different unused value diagnostic
}
int M2() => 0;
}",
$@"class C
{{
public C()
{{
{fix1} = M2(); // Separate code block
}}
void M(int unused1, int unused2)
{{
{fix2} = M2();
{fix3} = M2(); // Another instance in same code block
_ = M2(); // Already fixed
var x = M2(); // Different unused value diagnostic
}}
int M2() => 0;
}}", optionName);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_Trivia_PreferDiscard_01()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
// C1
[|M2()|]; // C2
// C3
}
int M2() => 0;
}",
@"class C
{
void M()
{
// C1
_ = M2(); // C2
// C3
}
int M2() => 0;
}", options: PreferDiscard);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_Trivia_PreferDiscard_02()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{/*C0*/
/*C1*/[|M2()|]/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}",
@"class C
{
void M()
{/*C0*/
/*C1*/
_ = M2()/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}", options: PreferDiscard);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_Trivia_PreferUnusedLocal_01()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
// C1
[|M2()|]; // C2
// C3
}
int M2() => 0;
}",
@"class C
{
void M()
{
// C1
var unused = M2(); // C2
// C3
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)]
public async Task ExpressionStatement_Trivia_PreferUnusedLocal_02()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{/*C0*/
/*C1*/[|M2()|]/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}",
@"class C
{
void M()
{/*C0*/
/*C1*/
var unused = M2()/*C2*/;/*C3*/
/*C4*/
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
}
}
......@@ -123,8 +123,7 @@ end class",
"class C
sub M()
if true
dim x as integer
= 5
dim x as integer = 5
Console.WriteLine(x)
end if
end sub
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedExpressionsAndParameters
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnusedExpressionsAndParameters
Partial Public Class RemoveUnusedExpressionsTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider)
Return (New VisualBasicRemoveUnusedExpressionsAndParametersDiagnosticAnalyzer(), New VisualBasicRemoveUnusedExpressionsAndParametersCodeFixProvider())
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function ExpressionStatement_PreferNone() As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
Private Sub M()
[|M2()|]
End Sub
Private Function M2() As Integer
Return 0
End Function
End Class", options:=PreferNone)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function ExpressionStatement_PreferDiscard() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Sub M()
[|M2()|]
End Sub
Private Function M2() As Integer
Return 0
End Function
End Class",
$"Class C
Private Sub M()
Dim unused = M2()
End Sub
Private Function M2() As Integer
Return 0
End Function
End Class", options:=PreferDiscard)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function ExpressionStatement_PreferUnusedLocal() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Sub M()
[|M2()|]
End Sub
Private Function M2() As Integer
Return 0
End Function
End Class",
$"Class C
Private Sub M()
Dim unused = M2()
End Sub
Private Function M2() As Integer
Return 0
End Function
End Class", options:=PreferUnusedLocal)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function CallStatement() As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
Private Sub M()
Call [|M2()|]
End Sub
Private Function M2() As Integer
Return 0
End Function
End Class")
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.CodeStyle
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
Imports Microsoft.CodeAnalysis.Options
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnusedExpressionsAndParameters
Partial Public Class RemoveUnusedExpressionsTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
Private ReadOnly Property PreferNone As IDictionary(Of OptionKey, Object)
Get
Return [Option](CodeStyleOptions.UnusedExpressionAssignment,
New CodeStyleOption(Of UnusedExpressionAssignmentPreference)(UnusedExpressionAssignmentPreference.None, NotificationOption.Suggestion))
End Get
End Property
Private ReadOnly Property PreferDiscard As IDictionary(Of OptionKey, Object)
Get
Return [Option](CodeStyleOptions.UnusedExpressionAssignment,
New CodeStyleOption(Of UnusedExpressionAssignmentPreference)(UnusedExpressionAssignmentPreference.DiscardVariable, NotificationOption.Suggestion))
End Get
End Property
Private ReadOnly Property PreferUnusedLocal As IDictionary(Of OptionKey, Object)
Get
Return [Option](CodeStyleOptions.UnusedExpressionAssignment,
New CodeStyleOption(Of UnusedExpressionAssignmentPreference)(UnusedExpressionAssignmentPreference.UnusedLocalVariable, NotificationOption.Suggestion))
End Get
End Property
Private Overloads Function TestMissingInRegularAndScriptAsync(initialMarkup As String, options As IDictionary(Of OptionKey, Object)) As Task
Return TestMissingInRegularAndScriptAsync(initialMarkup, New TestParameters(options:=options))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function TestPreferNone() As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] = 1
x = 2
Return x
End Function
End Class", options:=PreferNone)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function TestPreferDiscard() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] = 1
x = 2
Return x
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim x As Integer = 2
Return x
End Function
End Class", options:=PreferDiscard)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function TestPreferUnusedLocal() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] = 1
x = 2
Return x
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim x As Integer = 2
Return x
End Function
End Class", options:=PreferUnusedLocal)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Initialization_ConstantValue() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] As Integer = 1
x = 2
Return x
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim x As Integer = 2
Return x
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Initialization_ConstantValue_UnusedLocal() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] As Integer = 1
x = 2
Return 0
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim x As Integer = 2
Return 0
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Assignment_ConstantValue() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim x As Integer
[|x|] = 1
x = 2
Return x
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim x As Integer
x = 2
Return x
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Initialization_NonConstantValue() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] = M2()
x = 2
Return x
End Function
Private Function M2() As Integer
Return 0
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim unused = M2()
Dim x As Integer = 2
Return x
End Function
Private Function M2() As Integer
Return 0
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Initialization_NonConstantValue_UnusedLocal() As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim [|x|] As Integer = M2()
x = 2
Return 0
End Function
Private Function M2() As Integer
Return 0
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Assignment_NonConstantValue() As Task
Await TestInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim x As Integer
[|x|] = M2()
x = 2
Return x
End Function
Private Function M2() As Integer
Return 0
End Function
End Class",
$"Class C
Private Function M() As Integer
Dim x As Integer
Dim unused As Integer = M2()
x = 2
Return x
End Function
Private Function M2() As Integer
Return 0
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function Assignment_NonConstantValue_UnusedLocal() As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
Private Function M() As Integer
Dim x As Integer
[|x|] = M2()
x = 2
Return 0
End Function
Private Function M2() As Integer
Return 0
End Function
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function UseInLambda() As Task
Await TestMissingInRegularAndScriptAsync(
$"Imports System
Class C
Private Sub M(p As Object)
Dim lambda As Action = Sub()
Dim x = p
End Sub
[|p|] = Nothing
lambda()
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function CatchClause_ExceptionVariable_01() As Task
Await TestMissingInRegularAndScriptAsync(
$"Imports System
Class C
Private Sub M(p As Object)
Try
Catch [|ex|] As Exception
End Try
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function CatchClause_ExceptionVariable_02() As Task
Await TestMissingInRegularAndScriptAsync(
$"Imports System
Class C
Public ReadOnly Property P As Boolean
Get
Try
Return True
Catch [|ex|] As Exception
Return False
End Try
Return 0
End Get
End Property
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function CatchClause_ExceptionVariable_03() As Task
Await TestInRegularAndScriptAsync(
$"Imports System
Class C
Private Sub M(p As Object)
Try
Catch [|ex|] As Exception
ex = Nothing
Dim x = ex
End Try
End Sub
End Class",
$"Imports System
Class C
Private Sub M(p As Object)
Try
Catch unused As Exception
Dim ex As Exception = Nothing
Dim x = ex
End Try
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function ForToLoopStatement_01() As Task
Await TestMissingInRegularAndScriptAsync(
$"Imports System
Class C
Private Sub M()
For [|i|] As Integer = 0 To 10
Dim x = i
Next
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function ForToLoopStatement_02() As Task
Await TestMissingInRegularAndScriptAsync(
$"Imports System
Class C
Private Sub M()
For [|i|] As Integer = 0 To 10
i = 1
Next
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedExpressions)>
Public Async Function ForToLoopStatement_03() As Task
Await TestMissingInRegularAndScriptAsync(
$"Imports System
Class C
Private Sub M()
For i As Integer = 0 To 10
[|i|] = 1
Next
End Sub
End Class")
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedExpressionsAndParameters
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnusedExpressionsAndParameters
Public Class RemoveUnusedParametersTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider)
Return (New VisualBasicRemoveUnusedExpressionsAndParametersDiagnosticAnalyzer(), New VisualBasicRemoveUnusedExpressionsAndParametersCodeFixProvider())
End Function
' Ensure that we explicitly test missing IDE0058, which has no corresponding code fix (non-fixable diagnostic).
Private Overloads Function TestDiagnosticMissingAsync(initialMarkup As String) As Task
Return TestDiagnosticMissingAsync(initialMarkup, New TestParameters(retainNonFixableDiagnostics:=True))
End Function
Private Shared Function Diagnostic(id As String) As DiagnosticDescription
Return TestHelpers.Diagnostic(id)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function Parameter_Used() As Task
Await TestDiagnosticMissingAsync(
$"Class C
Sub M([|p|] As Integer)
Dim x = p
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function Parameter_Unused() As Task
Await TestDiagnosticsAsync(
$"Class C
Sub M([|p|] As Integer)
End Sub
End Class", parameters:=Nothing,
Diagnostic(IDEDiagnosticIds.ParameterCanBeRemovedDiagnosticId))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function Parameter_WrittenOnly() As Task
Await TestDiagnosticsAsync(
$"Class C
Sub M([|p|] As Integer)
p = 1
End Sub
End Class", parameters:=Nothing,
Diagnostic(IDEDiagnosticIds.ParameterCanBeRemovedDiagnosticId))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function Parameter_WrittenThenRead() As Task
Await TestDiagnosticsAsync(
$"Class C
Function M([|p|] As Integer) As Integer
p = 1
Return p
End Function
End Class", parameters:=Nothing,
Diagnostic(IDEDiagnosticIds.ParameterCanBeRemovedDiagnosticId))
End Function
End Class
End Namespace
......@@ -12,6 +12,7 @@
namespace Microsoft.CodeAnalysis.CSharp.MoveDeclarationNearReference
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.MoveDeclarationNearReference), Shared]
[Export(typeof(CSharpMoveDeclarationNearReferenceCodeRefactoringProvider))]
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.InlineTemporary)]
internal partial class CSharpMoveDeclarationNearReferenceCodeRefactoringProvider :
AbstractMoveDeclarationNearReferenceCodeRefactoringProvider<
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MoveDeclarationNearReference;
namespace Microsoft.CodeAnalysis.CSharp.MoveDeclarationNearReference
{
[ExportLanguageServiceFactory(typeof(IMoveDeclarationNearReferenceService), LanguageNames.CSharp), Shared]
internal partial class CSharpMoveDeclarationNearReferenceLanguageServiceFactory : ILanguageServiceFactory
{
private readonly CSharpMoveDeclarationNearReferenceCodeRefactoringProvider _refactoringProvider;
[ImportingConstructor]
public CSharpMoveDeclarationNearReferenceLanguageServiceFactory(CSharpMoveDeclarationNearReferenceCodeRefactoringProvider refactoringProvider)
{
_refactoringProvider = refactoringProvider;
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices) => _refactoringProvider;
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RemoveUnusedExpressionsAndParameters;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedExpressionsAndParameters
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnusedExpressions), Shared]
internal class CSharpRemoveUnusedExpressionsAndParametersCodeFixProvider:
AbstractRemoveUnusedExpressionsAndParametersCodeFixProvider<ExpressionSyntax, StatementSyntax, BlockSyntax,
ExpressionStatementSyntax, LocalDeclarationStatementSyntax,
VariableDeclaratorSyntax, ForEachStatementSyntax,
SwitchSectionSyntax, SwitchLabelSyntax,
CatchClauseSyntax, CatchClauseSyntax>
{
protected override BlockSyntax GenerateBlock(IEnumerable<StatementSyntax> statements)
=> SyntaxFactory.Block(statements);
protected override SyntaxToken GetForEachStatementIdentifier(ForEachStatementSyntax node)
=> node.Identifier;
protected override SyntaxNode UpdateNameForFlaggedNode(SyntaxNode node, SyntaxToken newName)
{
switch (node.Kind())
{
case SyntaxKind.IdentifierName:
var identifierName = (IdentifierNameSyntax)node;
return identifierName.WithIdentifier(newName.WithTriviaFrom(identifierName.Identifier));
case SyntaxKind.VariableDeclarator:
var variableDeclarator = (VariableDeclaratorSyntax)node;
return variableDeclarator.WithIdentifier(newName.WithTriviaFrom(variableDeclarator.Identifier));
case SyntaxKind.SingleVariableDesignation:
return SyntaxFactory.SingleVariableDesignation(newName).WithTriviaFrom(node);
case SyntaxKind.CatchDeclaration:
var catchDeclaration = (CatchDeclarationSyntax)node;
return catchDeclaration.WithIdentifier(newName.WithTriviaFrom(catchDeclaration.Identifier));
default:
throw ExceptionUtilities.Unreachable;
}
}
protected override ILocalSymbol GetSingleDeclaredLocal(LocalDeclarationStatementSyntax localDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
{
Contract.ThrowIfFalse(localDeclaration.Declaration.Variables.Count == 1);
return (ILocalSymbol)semanticModel.GetDeclaredSymbol(localDeclaration.Declaration.Variables[0]);
}
protected override void InsertAtStartOfSwitchCaseBlock(SwitchSectionSyntax switchCaseBlock, SyntaxEditor editor, LocalDeclarationStatementSyntax declarationStatement)
{
var firstStatement = switchCaseBlock.Statements.FirstOrDefault();
if (firstStatement != null)
{
editor.InsertBefore(firstStatement, declarationStatement);
}
else
{
// Switch section without any statements is an error case.
// Insert before containing switch statement.
editor.InsertBefore(switchCaseBlock.Parent, declarationStatement);
}
}
protected override Task RemoveDiscardDeclarationsAsync(
SyntaxNode memberDeclaration,
SyntaxEditor editor,
Document document,
CancellationToken cancellationToken)
{
foreach (var child in memberDeclaration.DescendantNodes(descendIntoChildren: n => !(n is ExpressionSyntax)))
{
if (child is LocalDeclarationStatementSyntax localDeclarationStatement &&
localDeclarationStatement.Declaration.Variables.Any(v => v.Identifier.Text == "_"))
{
ProcessVariableDeclarationWithDiscard(localDeclarationStatement, editor);
}
else if (child is CatchDeclarationSyntax catchDeclaration &&
catchDeclaration.Identifier.Text == "_")
{
// "catch (Exception _)" => "catch (Exception)"
var newCatchDeclaration = catchDeclaration.WithIdentifier(default)
.WithAdditionalAnnotations(Formatter.Annotation);
editor.ReplaceNode(catchDeclaration, newCatchDeclaration);
}
}
return Task.CompletedTask;
}
private static void ProcessVariableDeclarationWithDiscard(
LocalDeclarationStatementSyntax localDeclarationStatement,
SyntaxEditor editor)
{
var statementsBuilder = ArrayBuilder<StatementSyntax>.GetInstance();
var variableDeclaration = localDeclarationStatement.Declaration;
var currentNonDiscardVariables = new SeparatedSyntaxList<VariableDeclaratorSyntax>();
try
{
foreach (var variable in variableDeclaration.Variables)
{
if (variable.Identifier.Text != "_")
{
currentNonDiscardVariables = currentNonDiscardVariables.Add(variable);
}
else
{
ProcessCurrentNonDiscardVariables();
ProcessDiscardVariable(variable);
}
}
ProcessCurrentNonDiscardVariables();
if (statementsBuilder.Count == 0)
{
return;
}
var leadingTrivia = variableDeclaration.Type.GetLeadingTrivia()
.Concat(variableDeclaration.Type.GetTrailingTrivia());
statementsBuilder[0] = statementsBuilder[0].WithLeadingTrivia(leadingTrivia);
var last = statementsBuilder.Count - 1;
var trailingTrivia = localDeclarationStatement.SemicolonToken.GetAllTrivia();
statementsBuilder[last] = statementsBuilder[last].WithTrailingTrivia(trailingTrivia);
if (localDeclarationStatement.Parent is BlockSyntax)
{
editor.InsertAfter(localDeclarationStatement, statementsBuilder.Skip(1));
editor.ReplaceNode(localDeclarationStatement, statementsBuilder[0]);
}
else
{
editor.ReplaceNode(localDeclarationStatement, SyntaxFactory.Block(statementsBuilder));
}
}
finally
{
statementsBuilder.Free();
}
return;
// Local functions.
void ProcessCurrentNonDiscardVariables()
{
if (currentNonDiscardVariables.Count > 0)
{
var statement = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(variableDeclaration.Type, currentNonDiscardVariables))
.WithAdditionalAnnotations(Formatter.Annotation);
statementsBuilder.Add(statement);
currentNonDiscardVariables = new SeparatedSyntaxList<VariableDeclaratorSyntax>();
}
}
void ProcessDiscardVariable(VariableDeclaratorSyntax variable)
{
if (variable.Initializer != null)
{
statementsBuilder.Add(SyntaxFactory.ExpressionStatement(
SyntaxFactory.AssignmentExpression(
kind: SyntaxKind.SimpleAssignmentExpression,
left: SyntaxFactory.IdentifierName(variable.Identifier),
operatorToken: variable.Initializer.EqualsToken,
right: variable.Initializer.Value))
.WithAdditionalAnnotations(Formatter.Annotation));
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.RemoveUnusedExpressionsAndParameters;
namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedExpressionsAndParameters
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpRemoveUnusedExpressionsAndParametersDiagnosticAnalyzer : AbstractRemoveUnusedExpressionsAndParametersDiagnosticAnalyzer
{
protected override bool SupportsDiscard(SyntaxTree tree)
=> ((CSharpParseOptions)tree.Options).LanguageVersion >= LanguageVersion.CSharp7;
protected override Location GetDefinitionLocationToFade(IOperation unusedDefinition)
{
switch (unusedDefinition.Syntax)
{
case VariableDeclaratorSyntax variableDeclartor:
return variableDeclartor.Identifier.GetLocation();
case DeclarationPatternSyntax declarationPattern:
return declarationPattern.Designation.GetLocation();
default:
if (unusedDefinition.Syntax?.Parent is ForEachStatementSyntax forEachStatement &&
forEachStatement.Type == unusedDefinition.Syntax)
{
return forEachStatement.Identifier.GetLocation();
}
return unusedDefinition.Syntax.GetLocation();
}
}
}
}
......@@ -45,6 +45,7 @@ internal static class PredefinedCodeFixProviderNames
public const string DeclareAsNullable = nameof(DeclareAsNullable);
public const string RemoveUnnecessaryImports = nameof(RemoveUnnecessaryImports);
public const string RemoveUnreachableCode = nameof(RemoveUnreachableCode);
public const string RemoveUnusedExpressions = nameof(RemoveUnusedExpressions);
public const string RemoveUnusedLocalFunction = nameof(RemoveUnusedLocalFunction);
public const string RemoveUnusedMembers = nameof(RemoveUnusedMembers);
public const string RemoveUnusedVariable = nameof(RemoveUnusedVariable);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeQuality
{
......@@ -43,26 +40,11 @@ public sealed override void Initialize(AnalysisContext context)
bool isEnabledByDefault = true,
bool isConfigurable = true,
params string[] customTags)
{
var customTagsBuilder = ArrayBuilder<string>.GetInstance();
customTagsBuilder.AddRange(customTags.Concat(WellKnownDiagnosticTags.Telemetry));
if (!isConfigurable)
{
customTagsBuilder.Add(WellKnownDiagnosticTags.NotConfigurable);
}
if (isUnneccessary)
{
customTagsBuilder.Add(WellKnownDiagnosticTags.Unnecessary);
}
return new DiagnosticDescriptor(
id, title, messageFormat,
DiagnosticCategory.CodeQuality,
DiagnosticSeverity.Info,
isEnabledByDefault,
customTags: customTagsBuilder.ToArrayAndFree());
}
=> new DiagnosticDescriptor(
id, title, messageFormat,
DiagnosticCategory.CodeQuality,
DiagnosticSeverity.Info,
isEnabledByDefault,
customTags: DiagnosticCustomTags.Create(isUnneccessary, isConfigurable, customTags));
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.CodeStyle
......@@ -14,6 +15,11 @@ internal abstract class AbstractBuiltInCodeStyleDiagnosticAnalyzer : AbstractCod
{
}
protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<DiagnosticDescriptor> supportedDiagnostics)
: base(supportedDiagnostics)
{
}
public abstract DiagnosticAnalyzerCategory GetAnalyzerCategory();
public abstract bool OpenFileOnly(Workspace workspace);
}
......
......@@ -88,6 +88,10 @@ internal static class IDEDiagnosticIds
public const string FormattingDiagnosticId = "IDE0055";
public const string ExpressionValueIsUnusedDiagnosticId = "IDE0056";
public const string ValueAssignedIsUnusedDiagnosticId = "IDE0057";
public const string ParameterCanBeRemovedDiagnosticId = "IDE0058";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
......@@ -51,5 +53,28 @@ private static void Assert(string[] customTags, params string[] tags)
Debug.Assert(customTags[i] == tags[i]);
}
}
internal static string[] Create(bool isUnneccessary, bool isConfigurable, params string[] customTags)
{
if (customTags.Length == 0 && isConfigurable)
{
return isUnneccessary ? Unnecessary : Microsoft;
}
var customTagsBuilder = ImmutableArray.CreateBuilder<string>();
customTagsBuilder.AddRange(customTags.Concat(Microsoft));
if (!isConfigurable)
{
customTagsBuilder.Add(WellKnownDiagnosticTags.NotConfigurable);
}
if (isUnneccessary)
{
customTagsBuilder.Add(WellKnownDiagnosticTags.Unnecessary);
}
return customTagsBuilder.ToArray();
}
}
}
......@@ -835,6 +835,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to , its initial value is never used.
/// </summary>
internal static string comma_its_initial_value_is_never_used {
get {
return ResourceManager.GetString("comma_its_initial_value_is_never_used", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Compiler.
/// </summary>
......@@ -1243,6 +1252,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Expression value is never used.
/// </summary>
internal static string Expression_value_is_never_used {
get {
return ResourceManager.GetString("Expression_value_is_never_used", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extension methods.
/// </summary>
......@@ -1738,6 +1756,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to if it is not part of a shipped public API.
/// </summary>
internal static string if_it_is_not_part_of_a_shipped_public_API {
get {
return ResourceManager.GetString("if_it_is_not_part_of_a_shipped_public_API", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Implement {0}.
/// </summary>
......@@ -2834,6 +2861,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Remove redundant assignment.
/// </summary>
internal static string Remove_redundant_assignment {
get {
return ResourceManager.GetString("Remove_redundant_assignment", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove reference to &apos;{0}&apos;..
/// </summary>
......@@ -2924,6 +2960,24 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Remove unused parameter.
/// </summary>
internal static string Remove_unused_parameter {
get {
return ResourceManager.GetString("Remove_unused_parameter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove unused parameter &apos;{0}&apos;{1}.
/// </summary>
internal static string Remove_unused_parameter_0_1 {
get {
return ResourceManager.GetString("Remove_unused_parameter_0_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove unused private members.
/// </summary>
......@@ -3852,6 +3906,24 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Use discard &apos;_&apos;.
/// </summary>
internal static string Use_discard_underscore {
get {
return ResourceManager.GetString("Use_discard_underscore", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use discarded local.
/// </summary>
internal static string Use_discarded_local {
get {
return ResourceManager.GetString("Use_discarded_local", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use explicitly provided tuple name.
/// </summary>
......@@ -4042,6 +4114,24 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Value assigned here to &apos;{0}&apos; is never used.
/// </summary>
internal static string Value_assigned_here_to_0_is_never_used {
get {
return ResourceManager.GetString("Value_assigned_here_to_0_is_never_used", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value assigned here to symbol is never used.
/// </summary>
internal static string Value_assigned_here_to_symbol_is_never_used {
get {
return ResourceManager.GetString("Value_assigned_here_to_symbol_is_never_used", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Variable declaration can be deconstructed.
/// </summary>
......
......@@ -1466,6 +1466,39 @@ This version used in: {2}</value>
<data name="Failed_to_analyze_data_flow_for_0" xml:space="preserve">
<value>Failed to analyze data-flow for: {0}</value>
</data>
<data name="Expression_value_is_never_used" xml:space="preserve">
<value>Expression value is never used</value>
</data>
<data name="Value_assigned_here_to_0_is_never_used" xml:space="preserve">
<value>Value assigned here to '{0}' is never used</value>
</data>
<data name="Value_assigned_here_to_symbol_is_never_used" xml:space="preserve">
<value>Value assigned here to symbol is never used</value>
</data>
<data name="Use_discarded_local" xml:space="preserve">
<value>Use discarded local</value>
</data>
<data name="Use_discard_underscore" xml:space="preserve">
<value>Use discard '_'</value>
</data>
<data name="Remove_redundant_assignment" xml:space="preserve">
<value>Remove redundant assignment</value>
</data>
<data name="comma_its_initial_value_is_never_used" xml:space="preserve">
<value>, its initial value is never used</value>
<comment>Optional addition to Remove_unused_parameter_0_1</comment>
</data>
<data name="if_it_is_not_part_of_a_shipped_public_API" xml:space="preserve">
<value> if it is not part of a shipped public API</value>
<comment>Optional addition to Remove_unused_parameter_0_1</comment>
</data>
<data name="Remove_unused_parameter" xml:space="preserve">
<value>Remove unused parameter</value>
</data>
<data name="Remove_unused_parameter_0_1" xml:space="preserve">
<value>Remove unused parameter '{0}'{1}</value>
<comment>String {1} is an optional addition to this message, see comma_its_initial_value_is_never_used</comment>
</data>
<data name="Fix_formatting" xml:space="preserve">
<value>Fix formatting</value>
</data>
......
......@@ -33,10 +33,11 @@ private class State
TService service,
Document document,
TLocalDeclarationStatementSyntax statement,
bool skipIfInDeclarationStatementGroup,
CancellationToken cancellationToken)
{
var state = new State();
if (!await state.TryInitializeAsync(service, document, statement, cancellationToken).ConfigureAwait(false))
if (!await state.TryInitializeAsync(service, document, statement, skipIfInDeclarationStatementGroup, cancellationToken).ConfigureAwait(false))
{
return null;
}
......@@ -48,6 +49,7 @@ private class State
TService service,
Document document,
TLocalDeclarationStatementSyntax node,
bool skipIfInDeclarationStatementGroup,
CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
......@@ -130,26 +132,29 @@ private class State
return false;
}
var originalIndexInBlock = this.InnermostBlockStatements.IndexOf(this.DeclarationStatement);
var firstStatementIndexAffectedInBlock = this.InnermostBlockStatements.IndexOf(this.FirstStatementAffectedInInnermostBlock);
if (originalIndexInBlock >= 0 &&
originalIndexInBlock < firstStatementIndexAffectedInBlock)
if (skipIfInDeclarationStatementGroup)
{
// Don't want to move a decl past other decls in order to move it to the first
// affected statement. If we do we can end up in the following situation:
var originalIndexInBlock = this.InnermostBlockStatements.IndexOf(this.DeclarationStatement);
var firstStatementIndexAffectedInBlock = this.InnermostBlockStatements.IndexOf(this.FirstStatementAffectedInInnermostBlock);
if (originalIndexInBlock >= 0 &&
originalIndexInBlock < firstStatementIndexAffectedInBlock)
{
// Don't want to move a decl past other decls in order to move it to the first
// affected statement. If we do we can end up in the following situation:
#if false
int x = 0;
int y = 0;
Console.WriteLine(x + y);
#endif
// Each of these declarations will want to 'move' down to the WriteLine
// statement and we don't want to keep offering the refactoring. Note: this
// solution is overly aggressive. Technically if 'y' weren't referenced in
// Console.Writeline, then it might be a good idea to move the 'x'. But this
// gives good enough behavior most of the time.
if (InDeclarationStatementGroup(originalIndexInBlock, firstStatementIndexAffectedInBlock))
{
return false;
// Each of these declarations will want to 'move' down to the WriteLine
// statement and we don't want to keep offering the refactoring. Note: this
// solution is overly aggressive. Technically if 'y' weren't referenced in
// Console.Writeline, then it might be a good idea to move the 'x'. But this
// gives good enough behavior most of the time.
if (InDeclarationStatementGroup(originalIndexInBlock, firstStatementIndexAffectedInBlock))
{
return false;
}
}
}
......
......@@ -21,7 +21,7 @@ internal abstract partial class AbstractMoveDeclarationNearReferenceCodeRefactor
TService,
TStatementSyntax,
TLocalDeclarationStatementSyntax,
TVariableDeclaratorSyntax> : CodeRefactoringProvider
TVariableDeclaratorSyntax> : CodeRefactoringProvider, IMoveDeclarationNearReferenceService
where TService : AbstractMoveDeclarationNearReferenceCodeRefactoringProvider<TService, TStatementSyntax, TLocalDeclarationStatementSyntax, TVariableDeclaratorSyntax>
where TStatementSyntax : SyntaxNode
where TLocalDeclarationStatementSyntax : TStatementSyntax
......@@ -53,17 +53,12 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return;
}
var state = await State.GenerateAsync((TService)this, document, statement, cancellationToken).ConfigureAwait(false);
var state = await TryGetApplicableStateAsync(document, statement, skipIfInDeclarationStatementGroup: true, cancellationToken).ConfigureAwait(false);
if (state == null)
{
return;
}
if (!CanMoveToBlock(state.LocalSymbol, state.OutermostBlock, state.InnermostBlock))
{
return;
}
// Don't offer the refactoring inside the initializer for the variable.
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var initializer = syntaxFacts.GetInitializerOfVariableDeclarator(state.VariableDeclarator);
......@@ -80,32 +75,65 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
new MyCodeAction(c => MoveDeclarationNearReferenceAsync(document, state, root, c)));
}
private async Task<State> TryGetApplicableStateAsync(
Document document,
TLocalDeclarationStatementSyntax statement,
bool skipIfInDeclarationStatementGroup,
CancellationToken cancellationToken)
{
var state = await State.GenerateAsync((TService)this, document, statement, skipIfInDeclarationStatementGroup, cancellationToken).ConfigureAwait(false);
if (state == null)
{
return null;
}
if (!CanMoveToBlock(state.LocalSymbol, state.OutermostBlock, state.InnermostBlock))
{
return null;
}
return state;
}
private async Task<Document> MoveDeclarationNearReferenceAsync(
Document document, State state, SyntaxNode root, CancellationToken cancellationToken)
{
var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
await MoveDeclarationNearReferenceAsync(document, state, editor, cancellationToken).ConfigureAwait(false);
var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
private async Task MoveDeclarationNearReferenceAsync(Document document, State state, SyntaxEditor editor, CancellationToken cancellationToken)
{
var crossesMeaningfulBlock = CrossesMeaningfulBlock(state);
var warningAnnotation = crossesMeaningfulBlock
? WarningAnnotation.Create(FeaturesResources.Warning_colon_Declaration_changes_scope_and_may_change_meaning)
: null;
editor.RemoveNode(state.DeclarationStatement);
var canMergeDeclarationAndAssignment = await CanMergeDeclarationAndAssignmentAsync(document, state, cancellationToken).ConfigureAwait(false);
if (canMergeDeclarationAndAssignment)
{
editor.RemoveNode(state.DeclarationStatement);
MergeDeclarationAndAssignment(
document, state, editor, warningAnnotation);
}
else
{
var statementIndex = state.OutermostBlockStatements.IndexOf(state.DeclarationStatement);
if (statementIndex + 1 < state.OutermostBlockStatements.Count &&
state.OutermostBlockStatements[statementIndex + 1] == state.FirstStatementAffectedInInnermostBlock)
{
// Already at the correct location.
return;
}
editor.RemoveNode(state.DeclarationStatement);
await MoveDeclarationToFirstReferenceAsync(
document, state, editor, warningAnnotation, cancellationToken).ConfigureAwait(false);
}
var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
private static async Task MoveDeclarationToFirstReferenceAsync(Document document, State state, SyntaxEditor editor, SyntaxAnnotation warningAnnotation, CancellationToken cancellationToken)
......@@ -237,9 +265,28 @@ private bool CrossesMeaningfulBlock(State state)
return state.DeclarationStatement.ReplaceNode(
state.VariableDeclarator,
generator.WithInitializer(
state.VariableDeclarator,
generator.EqualsValueClause(operatorToken, right)));
state.VariableDeclarator.WithoutTrailingTrivia(),
generator.EqualsValueClause(operatorToken, right))
.WithTrailingTrivia(state.VariableDeclarator.GetTrailingTrivia()));
}
#region IMoveDeclarationNearReferenceService implementation
async Task IMoveDeclarationNearReferenceService.MoveDeclarationNearReferenceAsync(
SyntaxNode statement,
Document document,
SyntaxEditor editor,
CancellationToken cancellationToken)
{
if (statement is TLocalDeclarationStatementSyntax localDeclarationStatement)
{
var state = await TryGetApplicableStateAsync(document, localDeclarationStatement, skipIfInDeclarationStatementGroup: false, cancellationToken).ConfigureAwait(false);
if (state != null)
{
await MoveDeclarationNearReferenceAsync(document, state, editor, cancellationToken).ConfigureAwait(false);
}
}
}
#endregion
private class MyCodeAction : CodeAction.DocumentChangeAction
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.MoveDeclarationNearReference
{
internal interface IMoveDeclarationNearReferenceService: ILanguageService
{
Task MoveDeclarationNearReferenceAsync(SyntaxNode statement, Document document, SyntaxEditor editor, CancellationToken cancellationToken);
}
}
......@@ -93,7 +93,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
/// the removes the <paramref name="childDeclarators"/> from <paramref name="declarators"/>, and
/// adds the <paramref name="parentDeclaration"/> to the <paramref name="declarators"/>.
/// </summary>
protected void AdjustAndAddAppropriateDeclaratorsToRemove(SyntaxNode parentDeclaration, IEnumerable<SyntaxNode> childDeclarators, HashSet<SyntaxNode> declarators)
protected static void AdjustAndAddAppropriateDeclaratorsToRemove(SyntaxNode parentDeclaration, IEnumerable<SyntaxNode> childDeclarators, HashSet<SyntaxNode> declarators)
{
if(declarators.Contains(parentDeclaration))
{
......
......@@ -72,6 +72,11 @@
<target state="new">Failed to analyze data-flow for: {0}</target>
<note />
</trans-unit>
<trans-unit id="Expression_value_is_never_used">
<source>Expression value is never used</source>
<target state="new">Expression value is never used</target>
<note />
</trans-unit>
<trans-unit id="Fix_formatting">
<source>Fix formatting</source>
<target state="new">Fix formatting</target>
......@@ -132,6 +137,11 @@
<target state="translated">Podpisy souvisejících metod nalezené v metadatech se nebudou aktualizovat.</target>
<note />
</trans-unit>
<trans-unit id="Remove_redundant_assignment">
<source>Remove redundant assignment</source>
<target state="new">Remove redundant assignment</target>
<note />
</trans-unit>
<trans-unit id="Remove_unread_private_members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
......@@ -142,6 +152,16 @@
<target state="new">Remove unused member</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter">
<source>Remove unused parameter</source>
<target state="new">Remove unused parameter</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter_0_1">
<source>Remove unused parameter '{0}'{1}</source>
<target state="new">Remove unused parameter '{0}'{1}</target>
<note>String {1} is an optional addition to this message, see comma_its_initial_value_is_never_used</note>
</trans-unit>
<trans-unit id="Remove_unused_private_members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
......@@ -162,6 +182,16 @@
<target state="new">Use compound assignment</target>
<note />
</trans-unit>
<trans-unit id="Use_discard_underscore">
<source>Use discard '_'</source>
<target state="new">Use discard '_'</target>
<note />
</trans-unit>
<trans-unit id="Use_discarded_local">
<source>Use discarded local</source>
<target state="new">Use discarded local</target>
<note />
</trans-unit>
<trans-unit id="Use_expression_body_for_lambda_expressions">
<source>Use expression body for lambda expressions</source>
<target state="new">Use expression body for lambda expressions</target>
......@@ -172,6 +202,21 @@
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_0_is_never_used">
<source>Value assigned here to '{0}' is never used</source>
<target state="new">Value assigned here to '{0}' is never used</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_symbol_is_never_used">
<source>Value assigned here to symbol is never used</source>
<target state="new">Value assigned here to symbol is never used</target>
<note />
</trans-unit>
<trans-unit id="comma_its_initial_value_is_never_used">
<source>, its initial value is never used</source>
<target state="new">, its initial value is never used</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">obecné přetížení</target>
......@@ -182,6 +227,11 @@
<target state="translated">obecná přetížení</target>
<note />
</trans-unit>
<trans-unit id="if_it_is_not_part_of_a_shipped_public_API">
<source> if it is not part of a shipped public API</source>
<target state="new"> if it is not part of a shipped public API</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="overload">
<source>overload</source>
<target state="translated">přetížení</target>
......
......@@ -67,6 +67,11 @@
<target state="new">Convert to tuple</target>
<note />
</trans-unit>
<trans-unit id="Expression_value_is_never_used">
<source>Expression value is never used</source>
<target state="new">Expression value is never used</target>
<note />
</trans-unit>
<trans-unit id="Failed_to_analyze_data_flow_for_0">
<source>Failed to analyze data-flow for: {0}</source>
<target state="new">Failed to analyze data-flow for: {0}</target>
......@@ -132,6 +137,11 @@
<target state="translated">In Metadaten gefundene ähnliche Methodensignaturen werden nicht aktualisiert.</target>
<note />
</trans-unit>
<trans-unit id="Remove_redundant_assignment">
<source>Remove redundant assignment</source>
<target state="new">Remove redundant assignment</target>
<note />
</trans-unit>
<trans-unit id="Remove_unread_private_members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
......@@ -142,6 +152,16 @@
<target state="new">Remove unused member</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter">
<source>Remove unused parameter</source>
<target state="new">Remove unused parameter</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter_0_1">
<source>Remove unused parameter '{0}'{1}</source>
<target state="new">Remove unused parameter '{0}'{1}</target>
<note>String {1} is an optional addition to this message, see comma_its_initial_value_is_never_used</note>
</trans-unit>
<trans-unit id="Remove_unused_private_members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
......@@ -162,6 +182,16 @@
<target state="new">Use compound assignment</target>
<note />
</trans-unit>
<trans-unit id="Use_discard_underscore">
<source>Use discard '_'</source>
<target state="new">Use discard '_'</target>
<note />
</trans-unit>
<trans-unit id="Use_discarded_local">
<source>Use discarded local</source>
<target state="new">Use discarded local</target>
<note />
</trans-unit>
<trans-unit id="Use_expression_body_for_lambda_expressions">
<source>Use expression body for lambda expressions</source>
<target state="new">Use expression body for lambda expressions</target>
......@@ -172,6 +202,21 @@
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_0_is_never_used">
<source>Value assigned here to '{0}' is never used</source>
<target state="new">Value assigned here to '{0}' is never used</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_symbol_is_never_used">
<source>Value assigned here to symbol is never used</source>
<target state="new">Value assigned here to symbol is never used</target>
<note />
</trans-unit>
<trans-unit id="comma_its_initial_value_is_never_used">
<source>, its initial value is never used</source>
<target state="new">, its initial value is never used</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">generische Überladung</target>
......@@ -182,6 +227,11 @@
<target state="translated">generische Überladungen</target>
<note />
</trans-unit>
<trans-unit id="if_it_is_not_part_of_a_shipped_public_API">
<source> if it is not part of a shipped public API</source>
<target state="new"> if it is not part of a shipped public API</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="overload">
<source>overload</source>
<target state="translated">Überladung</target>
......
......@@ -67,6 +67,11 @@
<target state="new">Convert to tuple</target>
<note />
</trans-unit>
<trans-unit id="Expression_value_is_never_used">
<source>Expression value is never used</source>
<target state="new">Expression value is never used</target>
<note />
</trans-unit>
<trans-unit id="Failed_to_analyze_data_flow_for_0">
<source>Failed to analyze data-flow for: {0}</source>
<target state="new">Failed to analyze data-flow for: {0}</target>
......@@ -132,6 +137,11 @@
<target state="translated">Las signaturas de método relacionadas encontradas en los metadatos no se actualizarán.</target>
<note />
</trans-unit>
<trans-unit id="Remove_redundant_assignment">
<source>Remove redundant assignment</source>
<target state="new">Remove redundant assignment</target>
<note />
</trans-unit>
<trans-unit id="Remove_unread_private_members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
......@@ -142,6 +152,16 @@
<target state="new">Remove unused member</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter">
<source>Remove unused parameter</source>
<target state="new">Remove unused parameter</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter_0_1">
<source>Remove unused parameter '{0}'{1}</source>
<target state="new">Remove unused parameter '{0}'{1}</target>
<note>String {1} is an optional addition to this message, see comma_its_initial_value_is_never_used</note>
</trans-unit>
<trans-unit id="Remove_unused_private_members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
......@@ -162,6 +182,16 @@
<target state="new">Use compound assignment</target>
<note />
</trans-unit>
<trans-unit id="Use_discard_underscore">
<source>Use discard '_'</source>
<target state="new">Use discard '_'</target>
<note />
</trans-unit>
<trans-unit id="Use_discarded_local">
<source>Use discarded local</source>
<target state="new">Use discarded local</target>
<note />
</trans-unit>
<trans-unit id="Use_expression_body_for_lambda_expressions">
<source>Use expression body for lambda expressions</source>
<target state="new">Use expression body for lambda expressions</target>
......@@ -172,6 +202,21 @@
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_0_is_never_used">
<source>Value assigned here to '{0}' is never used</source>
<target state="new">Value assigned here to '{0}' is never used</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_symbol_is_never_used">
<source>Value assigned here to symbol is never used</source>
<target state="new">Value assigned here to symbol is never used</target>
<note />
</trans-unit>
<trans-unit id="comma_its_initial_value_is_never_used">
<source>, its initial value is never used</source>
<target state="new">, its initial value is never used</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">sobrecarga genérica</target>
......@@ -182,6 +227,11 @@
<target state="translated">sobrecargas genéricas</target>
<note />
</trans-unit>
<trans-unit id="if_it_is_not_part_of_a_shipped_public_API">
<source> if it is not part of a shipped public API</source>
<target state="new"> if it is not part of a shipped public API</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="overload">
<source>overload</source>
<target state="translated">sobrecarga</target>
......
......@@ -72,6 +72,11 @@
<target state="new">Failed to analyze data-flow for: {0}</target>
<note />
</trans-unit>
<trans-unit id="Expression_value_is_never_used">
<source>Expression value is never used</source>
<target state="new">Expression value is never used</target>
<note />
</trans-unit>
<trans-unit id="Fix_formatting">
<source>Fix formatting</source>
<target state="new">Fix formatting</target>
......@@ -132,6 +137,11 @@
<target state="translated">Les signatures de méthode associées trouvées dans les métadonnées ne seront pas mises à jour.</target>
<note />
</trans-unit>
<trans-unit id="Remove_redundant_assignment">
<source>Remove redundant assignment</source>
<target state="new">Remove redundant assignment</target>
<note />
</trans-unit>
<trans-unit id="Remove_unread_private_members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
......@@ -142,6 +152,16 @@
<target state="new">Remove unused member</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter">
<source>Remove unused parameter</source>
<target state="new">Remove unused parameter</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter_0_1">
<source>Remove unused parameter '{0}'{1}</source>
<target state="new">Remove unused parameter '{0}'{1}</target>
<note>String {1} is an optional addition to this message, see comma_its_initial_value_is_never_used</note>
</trans-unit>
<trans-unit id="Remove_unused_private_members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
......@@ -162,6 +182,16 @@
<target state="new">Use compound assignment</target>
<note />
</trans-unit>
<trans-unit id="Use_discard_underscore">
<source>Use discard '_'</source>
<target state="new">Use discard '_'</target>
<note />
</trans-unit>
<trans-unit id="Use_discarded_local">
<source>Use discarded local</source>
<target state="new">Use discarded local</target>
<note />
</trans-unit>
<trans-unit id="Use_expression_body_for_lambda_expressions">
<source>Use expression body for lambda expressions</source>
<target state="new">Use expression body for lambda expressions</target>
......@@ -172,6 +202,21 @@
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_0_is_never_used">
<source>Value assigned here to '{0}' is never used</source>
<target state="new">Value assigned here to '{0}' is never used</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_symbol_is_never_used">
<source>Value assigned here to symbol is never used</source>
<target state="new">Value assigned here to symbol is never used</target>
<note />
</trans-unit>
<trans-unit id="comma_its_initial_value_is_never_used">
<source>, its initial value is never used</source>
<target state="new">, its initial value is never used</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">surcharge générique</target>
......@@ -182,6 +227,11 @@
<target state="translated">surcharges génériques</target>
<note />
</trans-unit>
<trans-unit id="if_it_is_not_part_of_a_shipped_public_API">
<source> if it is not part of a shipped public API</source>
<target state="new"> if it is not part of a shipped public API</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="overload">
<source>overload</source>
<target state="translated">surcharge</target>
......
......@@ -67,6 +67,11 @@
<target state="new">Convert to tuple</target>
<note />
</trans-unit>
<trans-unit id="Expression_value_is_never_used">
<source>Expression value is never used</source>
<target state="new">Expression value is never used</target>
<note />
</trans-unit>
<trans-unit id="Failed_to_analyze_data_flow_for_0">
<source>Failed to analyze data-flow for: {0}</source>
<target state="new">Failed to analyze data-flow for: {0}</target>
......@@ -132,6 +137,11 @@
<target state="translated">Le firme del metodo correlate trovate nei metadati non verranno aggiornate.</target>
<note />
</trans-unit>
<trans-unit id="Remove_redundant_assignment">
<source>Remove redundant assignment</source>
<target state="new">Remove redundant assignment</target>
<note />
</trans-unit>
<trans-unit id="Remove_unread_private_members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
......@@ -142,6 +152,16 @@
<target state="new">Remove unused member</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter">
<source>Remove unused parameter</source>
<target state="new">Remove unused parameter</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_parameter_0_1">
<source>Remove unused parameter '{0}'{1}</source>
<target state="new">Remove unused parameter '{0}'{1}</target>
<note>String {1} is an optional addition to this message, see comma_its_initial_value_is_never_used</note>
</trans-unit>
<trans-unit id="Remove_unused_private_members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
......@@ -162,6 +182,16 @@
<target state="new">Use compound assignment</target>
<note />
</trans-unit>
<trans-unit id="Use_discard_underscore">
<source>Use discard '_'</source>
<target state="new">Use discard '_'</target>
<note />
</trans-unit>
<trans-unit id="Use_discarded_local">
<source>Use discarded local</source>
<target state="new">Use discarded local</target>
<note />
</trans-unit>
<trans-unit id="Use_expression_body_for_lambda_expressions">
<source>Use expression body for lambda expressions</source>
<target state="new">Use expression body for lambda expressions</target>
......@@ -172,6 +202,21 @@
<target state="new">Use interpolated verbatim string</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_0_is_never_used">
<source>Value assigned here to '{0}' is never used</source>
<target state="new">Value assigned here to '{0}' is never used</target>
<note />
</trans-unit>
<trans-unit id="Value_assigned_here_to_symbol_is_never_used">
<source>Value assigned here to symbol is never used</source>
<target state="new">Value assigned here to symbol is never used</target>
<note />
</trans-unit>
<trans-unit id="comma_its_initial_value_is_never_used">
<source>, its initial value is never used</source>
<target state="new">, its initial value is never used</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="generic_overload">
<source>generic overload</source>
<target state="translated">overload generico</target>
......@@ -182,6 +227,11 @@
<target state="translated">overload generici</target>
<note />
</trans-unit>
<trans-unit id="if_it_is_not_part_of_a_shipped_public_API">
<source> if it is not part of a shipped public API</source>
<target state="new"> if it is not part of a shipped public API</target>
<note>Optional addition to Remove_unused_parameter_0_1</note>
</trans-unit>
<trans-unit id="overload">
<source>overload</source>
<target state="translated">overload</target>
......
......@@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.MoveDeclarationNearReference
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.MoveDeclarationNearReference), [Shared]>
<Export(GetType(VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider))>
<ExtensionOrder(After:=PredefinedCodeRefactoringProviderNames.InlineTemporary)>
Friend Class VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider
Inherits AbstractMoveDeclarationNearReferenceCodeRefactoringProvider(Of
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Composition
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.MoveDeclarationNearReference
Namespace Microsoft.CodeAnalysis.VisualBasic.MoveDeclarationNearReference
<ExportLanguageServiceFactory(GetType(IMoveDeclarationNearReferenceService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicMoveDeclarationNearReferenceLanguageServiceFactory
Implements ILanguageServiceFactory
Private ReadOnly _refactoringProvider As VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider
<ImportingConstructor>
Public Sub New(refactoringProvider As VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider)
_refactoringProvider = refactoringProvider
End Sub
Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService
Return _refactoringProvider
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.RemoveUnusedExpressionsAndParameters
Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedExpressionsAndParameters
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend NotInheritable Class VisualBasicRemoveUnusedExpressionsAndParametersDiagnosticAnalyzer
Inherits AbstractRemoveUnusedExpressionsAndParametersDiagnosticAnalyzer
Protected Overrides Function SupportsDiscard(tree As SyntaxTree) As Boolean
Return False
End Function
Protected Overrides Function GetDefinitionLocationToFade(unusedDefinition As IOperation) As Location
Return unusedDefinition.Syntax.GetLocation()
End Function
End Class
End Namespace
......@@ -112,8 +112,10 @@ public static class Features
public const string CodeActionsRemoveDocCommentNode = "CodeActions.RemoveDocCommentNode";
public const string CodeActionsRemoveUnnecessaryCast = "CodeActions.RemoveUnnecessaryCast";
public const string CodeActionsRemoveUnnecessaryParentheses = "CodeActions.RemoveUnnecessaryParentheses";
public const string CodeActionsRemoveUnusedExpressions = "CodeActions.RemoveUnusedExpressions";
public const string CodeActionsRemoveUnusedLocalFunction = "CodeActions.RemoveUnusedLocalFunction";
public const string CodeActionsRemoveUnusedMembers = "CodeActions.RemoveUnusedMembers";
public const string CodeActionsRemoveUnusedParameters = "CodeActions.RemoveUnusedParameters";
public const string CodeActionsRemoveUnusedVariable = "CodeActions.RemoveUnusedVariable";
public const string CodeActionsRemoveUnnecessaryImports = "CodeActions.RemoveUnnecessaryImports";
public const string CodeActionsRemoveUnreachableCode = "CodeActions.RemoveUnreachableCode";
......
......@@ -3226,10 +3226,10 @@ public override SyntaxNode ReplaceNode(SyntaxNode root, SyntaxNode declaration,
var fullDecl = GetFullDeclaration(declaration);
// special handling for replacing at location of sub-declaration
if (fullDecl != declaration)
if (fullDecl != declaration && fullDecl.IsKind(newFullDecl.Kind()))
{
// try to replace inline if possible
if (fullDecl.IsKind(newFullDecl.Kind()) && GetDeclarationCount(newFullDecl) == 1)
if (GetDeclarationCount(newFullDecl) == 1)
{
var newSubDecl = GetSubDeclarations(newFullDecl)[0];
if (AreInlineReplaceableSubDeclarations(declaration, newSubDecl))
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册