Skip to content

Commit 4e2c4f8

Browse files
drewnoakesAArnott
andauthored
Add several new analyzers (#603)
* Add VSMEF004 for missing ImportingConstructor * Add codefix for VSMEF004 * Add copilot-instructions.md * Add VSMEF005 for multiple importing constructors * Add VSMEF006 to align nullable and AllowDefault * Use file-scoped namespace * Extract duplicated util code * Test missing attribute on primary constructors * Formatting * Documentation updates * Add VSMEF007 for duplicated imports * Merge duplicate fields * Adjust whitespace * A few touch-ups * Simplify analyzer verification tests * Code fix simplifies names and formats * Use explicit type name instead of var Matches configured code style and addresses diagnostic. * Test code fixer's addition of parameterless ctor Addresses TODO. Small change in code fix to put this constructor first, and apply formatting. * Fix VSMEF007 when ctor params not attributes Add a bunch more test cases. Also some pattern matching and other clean up. * Better notes in AnalyzerReleases.Unshipped.md * Handle value types correctly in VSMEF006 Don't warn about nullable value types (int?) or AllowDefault on value types, since these have different semantics than nullable reference types. * Allow VSMEF005 to work with structs * Document that contract name is an approximation * Don't set Name on ExportCodeFixProviderAttribute * Fix broken link * Move analyzer docs to new folder (post merge) * Codefix adds `= null!` to property initializer * Handle attributes derived from known MEF attributes * Handle InheritedExportAttribute --------- Co-authored-by: Andrew Arnott <[email protected]>
1 parent f84ae6b commit 4e2c4f8

26 files changed

+4556
-59
lines changed

docfx/analyzers/VSMEF004.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# VSMEF004 Exported type missing importing constructor
2+
3+
A class that exports itself or has exported members must be instantiable by MEF. If the class defines non-default constructors, it must have either:
4+
5+
1. A parameterless constructor, or
6+
2. A constructor annotated with `[ImportingConstructor]`
7+
8+
The following class definition would produce a diagnostic from this rule:
9+
10+
```cs
11+
[Export]
12+
class Foo
13+
{
14+
public Foo(string parameter)
15+
{
16+
// Non-default constructor without [ImportingConstructor]
17+
}
18+
}
19+
```
20+
21+
This class exports itself but only has a non-default constructor that is not annotated with `[ImportingConstructor]`. MEF cannot instantiate this class because it doesn't know how to provide the required `string` parameter.
22+
23+
## Fixing the diagnostic
24+
25+
There are several ways to fix this diagnostic:
26+
27+
### Option 1: Add a parameterless constructor
28+
29+
```cs
30+
[Export]
31+
class Foo
32+
{
33+
public Foo()
34+
{
35+
// Default constructor for MEF
36+
}
37+
38+
public Foo(string parameter)
39+
{
40+
// Non-default constructor for other uses
41+
}
42+
}
43+
```
44+
45+
### Option 2: Annotate a constructor with [ImportingConstructor]
46+
47+
```cs
48+
[Export]
49+
class Foo
50+
{
51+
[ImportingConstructor]
52+
public Foo(string parameter)
53+
{
54+
// MEF will satisfy the 'parameter' import
55+
}
56+
}
57+
```
58+
59+
### Option 3: Use importing constructor with MEF imports
60+
61+
```cs
62+
[Export]
63+
class Foo
64+
{
65+
[ImportingConstructor]
66+
public Foo([Import] IService service)
67+
{
68+
// MEF will inject the required IService
69+
}
70+
}
71+
```
72+
73+
## Examples that trigger this rule
74+
75+
### Class-level export with non-default constructor
76+
77+
```cs
78+
[Export]
79+
class Service
80+
{
81+
public Service(string connectionString) // ❌ Error: missing [ImportingConstructor]
82+
{
83+
}
84+
}
85+
```
86+
87+
### Class with exported members and non-default constructor
88+
89+
```cs
90+
class ServiceProvider
91+
{
92+
[Export]
93+
public IService GetService() => new Service();
94+
95+
public ServiceProvider(string config) // ❌ Error: missing [ImportingConstructor]
96+
{
97+
}
98+
}
99+
```
100+
101+
### Mixed static and instance exports
102+
103+
```cs
104+
class MixedExports
105+
{
106+
[Export]
107+
public static string StaticValue = "test"; // ✅ OK: static export
108+
109+
[Export]
110+
public string InstanceValue { get; set; } // ❌ Requires instantiation
111+
112+
public MixedExports(string parameter) // ❌ Error: missing [ImportingConstructor]
113+
{
114+
}
115+
}
116+
```
117+
118+
## Examples that do NOT trigger this rule
119+
120+
### Class with only static exports
121+
122+
```cs
123+
class StaticOnlyExports
124+
{
125+
[Export]
126+
public static IService Service { get; } = new Service();
127+
128+
public StaticOnlyExports(string parameter) // ✅ OK: no instance exports
129+
{
130+
}
131+
}
132+
```
133+
134+
### Class with default constructor
135+
136+
```cs
137+
[Export]
138+
class Service
139+
{
140+
public Service() // ✅ OK: default constructor
141+
{
142+
}
143+
}
144+
```
145+
146+
### Class with importing constructor
147+
148+
```cs
149+
[Export]
150+
class Service
151+
{
152+
[ImportingConstructor]
153+
public Service(string connectionString) // ✅ OK: has [ImportingConstructor]
154+
{
155+
}
156+
}
157+
```
158+
159+
### Abstract class
160+
161+
```cs
162+
[Export]
163+
abstract class ServiceBase
164+
{
165+
public ServiceBase(string parameter) // ✅ OK: abstract classes are not instantiated
166+
{
167+
}
168+
}
169+
```
170+
171+
## Applies to both MEF versions
172+
173+
This rule applies to both MEF v1 (`System.ComponentModel.Composition`) and MEF v2 (`System.Composition`) attributes:
174+
175+
```cs
176+
// MEF v1
177+
[System.ComponentModel.Composition.Export]
178+
class Service
179+
{
180+
[System.ComponentModel.Composition.ImportingConstructor]
181+
public Service(string parameter) { }
182+
}
183+
184+
// MEF v2
185+
[System.Composition.Export]
186+
class Service
187+
{
188+
[System.Composition.ImportingConstructor]
189+
public Service(string parameter) { }
190+
}
191+
```

docfx/analyzers/VSMEF005.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# VSMEF005 Multiple importing constructors
2+
3+
A class that exports itself or has exported members can only have one constructor marked with `[ImportingConstructor]`. MEF requires exactly one importing constructor per type to know how to instantiate the class.
4+
5+
The following class definition would produce a diagnostic from this rule:
6+
7+
```cs
8+
[Export]
9+
class Foo
10+
{
11+
[ImportingConstructor]
12+
public Foo()
13+
{
14+
}
15+
16+
[ImportingConstructor]
17+
public Foo(string parameter)
18+
{
19+
}
20+
}
21+
```
22+
23+
This class has two constructors both marked with `[ImportingConstructor]`, which creates ambiguity for MEF about which constructor to use for instantiation.
24+
25+
## Fixing the diagnostic
26+
27+
Remove the `[ImportingConstructor]` attribute from all but one constructor. The remaining importing constructor will be used by MEF to create instances of the class.
28+
29+
```cs
30+
[Export]
31+
class Foo
32+
{
33+
public Foo()
34+
{
35+
}
36+
37+
[ImportingConstructor]
38+
public Foo(string parameter)
39+
{
40+
}
41+
}
42+
```
43+
44+
In this corrected version, only one constructor is marked as the importing constructor, and MEF will use it to instantiate the class, providing the required `string` parameter through dependency injection.
45+
46+
## Alternative solutions
47+
48+
If you need multiple constructors for different scenarios:
49+
50+
### Option 1: Use only one importing constructor
51+
52+
Keep only one constructor marked with `[ImportingConstructor]` and use that for MEF instantiation:
53+
54+
```cs
55+
[Export]
56+
class Foo
57+
{
58+
// Regular constructor for manual instantiation
59+
public Foo()
60+
{
61+
}
62+
63+
// MEF importing constructor
64+
[ImportingConstructor]
65+
public Foo(IService service)
66+
{
67+
// Initialize with injected dependencies
68+
}
69+
}
70+
```
71+
72+
### Option 2: Use factory pattern
73+
74+
If you need complex instantiation logic, consider using a factory pattern:
75+
76+
```cs
77+
[Export]
78+
class FooFactory
79+
{
80+
[ImportingConstructor]
81+
public FooFactory(IService service)
82+
{
83+
this.service = service;
84+
}
85+
86+
public Foo CreateFoo() => new Foo();
87+
public Foo CreateFoo(string parameter) => new Foo(parameter, this.service);
88+
}
89+
```

0 commit comments

Comments
 (0)