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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 19 additions & 25 deletions src/REstate.Analyzers/REstateAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,46 @@ namespace REstate.Analyzers
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class REstateAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = nameof(REstateAnalyzer);

// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.RESTATE001_Title), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.RESTATE001_Format), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private const string Category = "Usage";

private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
private static DiagnosticDescriptor RESTATE001 = new DiagnosticDescriptor("RESTATE001", Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);


public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(RESTATE001); } }

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleBaseType);
}

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var baseDeclaration = (SimpleBaseTypeSyntax)context.Node;

var baseItem = baseDeclaration.ChildNodes().OfType<GenericNameSyntax>().FirstOrDefault();

// only consider IAcceptSignal
if (baseItem?.Identifier.ValueText != "IAcceptSignal") return;

//const check example.
//var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
var symbol = context.SemanticModel.GetSymbolInfo(baseItem);
if (symbol.Symbol?.OriginalDefinition?.ToDisplayString() != "REstate.Natural.IAcceptSignal<TSignal>") return;

//// make sure the declaration isn't already const:
//if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
//{
// return;
//}
var signalNode = baseItem.TypeArgumentList.ChildNodes().OfType<IdentifierNameSyntax>().FirstOrDefault();

//// Perform data flow analysis on the local declaration.
//var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);
if(signalNode is null) return;

//// Retrieve the local symbol for each variable in the local declaration
//// and ensure that it is not written outside of the data flow analysis region.
//var variable = localDeclaration.Declaration.Variables.Single();
//var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
//if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
//{
// return;
//}
var signalType = context.SemanticModel.GetTypeInfo(signalNode);
if(signalType.Type?.TypeKind != TypeKind.Interface) return;

//context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(RESTATE001, context.Node.GetLocation(), signalType.Type.ToDisplayString()));
}
}
}
14 changes: 7 additions & 7 deletions src/REstate.Analyzers/REstateCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class REstateCodeFixProvider : CodeFixProvider

public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(REstateAnalyzer.DiagnosticId); }
get { return ImmutableArray.Create("RESTATE001"); }
}

public sealed override FixAllProvider GetFixAllProvider()
Expand All @@ -43,12 +43,12 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();

// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c),
equivalenceKey: title),
diagnostic);
//context.RegisterCodeFix(
// CodeAction.Create(
// title: title,
// createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c),
// equivalenceKey: title),
// diagnostic);
}

private async Task<Solution> MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
Expand Down
14 changes: 7 additions & 7 deletions src/REstate.Analyzers/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/REstate.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AnalyzerDescription" xml:space="preserve">
<value>locals should be const if possible</value>
<value>Transitioning on Interfaces may cause unexpected actions and paths when signals are casted to an interface they implement.</value>
<comment>An optional longer localizable description of the diagnostic.</comment>
</data>
<data name="AnalyzerMessageFormat" xml:space="preserve">
<value>variable '{0}' can be const</value>
<data name="RESTATE001_Format" xml:space="preserve">
<value>State accepts the interface {0} as a signal</value>
<comment>The format-able message the diagnostic displays.</comment>
</data>
<data name="AnalyzerTitle" xml:space="preserve">
<value>Can be const</value>
<data name="RESTATE001_Title" xml:space="preserve">
<value>Accepting interfaces as signals may be unsafe</value>
<comment>The title of the diagnostic.</comment>
</data>
</root>