Skip to content

Conversation

drewnoakes
Copy link
Member

Fixes #257
Fixes #287
Fixes #317

New analyzers for:

  • Missing [ImportingConstructor]
  • Duplicate [ImportingConstructor]
  • Duplicate [Import]
  • Mismatched C# nullability and AllowDefault

Mostly coded using GitHub Copilot.

# Conflicts:
#	doc/analyzers/index.md
#	src/Microsoft.VisualStudio.Composition.Analyzers/AnalyzerReleases.Unshipped.md
#	src/Microsoft.VisualStudio.Composition.Analyzers/README.md
#	src/Microsoft.VisualStudio.Composition.Analyzers/Strings.Designer.cs
#	src/Microsoft.VisualStudio.Composition.Analyzers/Strings.resx
Copy link
Member

@AArnott AArnott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good. Thanks so much.
There's a lot of code here though, so I have quite a few comments.

using System.ComponentModel.Composition;
[Export]
[method: ImportingConstructor]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL: I had no idea how to apply an attribute to a primary constructor, and copilot had told me there wasn't a way.

[Export]
class Foo
{
public {|#0:Foo|}(string parameter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Make it easier on yourself by using this syntax:

Suggested change
public {|#0:Foo|}(string parameter)
public {|VSMEF004:Foo|}(string parameter)

Then you don't have to do the "expected" stuff in your assertion area. This would be enough:

await VerifyCS.VerifyAnalyzerAsync(test);

Although then I guess you lose the assertion about the formatting argument.

[Export]
public static string StaticField = "test";
[Export]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📖 Information you wish you didn't know: the VS editor defines exports like these for their metadata. They might be literally non-activatable, yet useful because the editor only wants the metadata.

But that's a very unusual case and I fully support a (suppressible) warning diagnostic as you have here.

[Export]
class Service
{
[System.Composition.ImportingConstructor]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code fix should apply the Simplifier and/or ImportUsing (or whatever it's called) annotation so that Roslyn removes this redundant qualifier.

This example shows both how to annotate all the qualified names you emit and how to apply the modification based on those annotations.
https://github.com/AArnott/Nerdbank.MessagePack/blob/bfc5c068337baf693dc9aea6a4f50a94fa6b43fe/src/Nerdbank.MessagePack.Analyzers.CodeFixes/MigrationCodeFix.cs#L144-L153

await VerifyCS.VerifyCodeFixAsync(testCode, expected, fixedCode);
}

// TODO: Add test for second code action (parameterless constructor) when framework supports testing multiple code actions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
var symbol = (INamedTypeSymbol)context.Symbol;

// Only analyze classes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not structs? They can have too many importing constructors, can't they?

foreach (var parameter in method.Parameters)
{
var importAttribute = Utils.GetImportAttribute(parameter.GetAttributes());
if (importAttribute is not null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An importing constructor parameter is still an import even if it lacks this attribute. Is this a blindspot in the analyzer?

}
}

private static string? GetImportContract(AttributeData importAttribute, ITypeSymbol importType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually far more complicated than this (see ContractNameServices), but since analyzers can't reference the library and I wouldn't want to reproduce all that logic here, a rough approximation is fine since it's not about what the contract name is exactly as much as just identifying duplicates. But for clarity, that's probably worth commenting here.

/// <summary>
/// Provides code fixes for VSMEF004: Exported type missing importing constructor.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(VSMEF004ExportWithoutImportingConstructorCodeFixProvider))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the Name property on this attribute do? I've never bothered setting it.

return document.WithSyntaxRoot(newRoot);
}

private static MefVersion DetermineMefVersion(ClassDeclarationSyntax classDeclaration, SemanticModel semanticModel)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember that a MEF part may have no attributes from either of these namespaces at all, if the only MEF attributes it has are custom ones derived from an ExportAttribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants