RefactorMCP catalog

Introduce Type Alias

Declares a using alias for a type written in a file, such as using StockIndex = System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<int>>;, and writes the alias wherever the file names that type.

Precondition

  • The caret is on the name of a class, struct, interface, enum or delegate, constructed or not.
  • The type is not built from a type parameter, which an alias cannot name.
  • The alias name is a C# identifier and means nothing where the type is used: no type, namespace, member, local or alias the file can see has that name.

Transformation

  • The alias is added after the file's other using directives. In a file without any, it goes at the top, below any header comment, followed by a blank line.
  • The alias names the type fully qualified, since the file's other using directives do not apply to it, with type arguments written as C# keywords where there is one.
  • Every name of the type in the file, in declarations, object creations and static member accesses, is replaced with the alias, whether it was written short or qualified. Other constructions of the same generic type, such as Dictionary<string, int> beside Dictionary<string, List<int>>, are left alone.

Preserved

  • What every name in the file refers to.
  • A nullable annotation on a use, which stays on the use: Dictionary<string, string>? becomes Lookup?.
  • Every other file, even where it names the same type: an alias is local to its file.

Limitations

  • Nullable annotations inside the type arguments are not carried into the alias; a use whose type arguments differ only in annotations is replaced too.
  • Only named types can be aliased. Arrays, tuples and pointers, which C# 12 allows as alias targets, are not offered.
  • The alias is added to the compilation unit even when the file declares its other usings inside a namespace.

Error codes

CodeMeaning
not-a-typethe caret is not on the name of a type
uses-type-parameterthe type is built from a type parameter
name-conflictthe alias name already means something where the type is used
invalid-namethe alias name is not a C# identifier

Cases

·

Diffs show each file from before to after. /*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.

#file-without-usings

Adds the alias below the header comment of a file without using directives, replacing qualified names in types and expressions

success
TargetClock.cs, the /*^*/ caret
Arguments
name"Stamp"
Clock.csmodified
11 // Clock helpers.
2+using Stamp = System.DateTimeOffset;
3+
24 namespace Shop
35 {
46 public class Clock
57 {
6− public System./*^*/DateTimeOffset Now() => System.DateTimeOffset.UtcNow;
8+ public Stamp Now() => Stamp.UtcNow;
79
8− public bool IsPast(System.DateTimeOffset when) => when < Now();
10+ public bool IsPast(Stamp when) => when < Now();
911 }
1012 }

#generic-type-in-file

Declares an alias for the constructed type under the caret and uses it everywhere the file names that type

success
TargetInventory.cs, the /*^*/ caret
Arguments
name"StockIndex"
Inventory.csmodified
11 using System.Collections.Generic;
2+using StockIndex = System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<int>>;
23
34 namespace Shop
45 {
56 public class Inventory
67 {
7− private readonly /*^*/Dictionary<string, List<int>> _stock = new Dictionary<string, List<int>>();
8+ private readonly StockIndex _stock = new StockIndex();
89
9− public Dictionary<string, List<int>> Snapshot() => new Dictionary<string, List<int>>(_stock);
10+ public StockIndex Snapshot() => new StockIndex(_stock);
1011
1112 public List<int> Levels(string sku) => _stock[sku];
1213
1314 public Dictionary<string, int> Totals() => new Dictionary<string, int>();
1415 }
1516 }
Report.csunchanged
11 using System.Collections.Generic;
22
33 namespace Shop
44 {
55 public class Report
66 {
77 public int Count(Dictionary<string, List<int>> stock) => stock.Count;
88 }
99 }

#invalid-name-refused

Refuses an alias name that is not an identifier

refusal
TargetOrder.cs, the /*^*/ caret
Arguments
name"2Lines"
Refusesinvalid-name; every file is left unchanged
Order.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Order
6 {
7 public /*^*/List<string> Lines { get; } = new List<string>();
8 }
9 }

#name-conflict-refused

Refuses an alias name that a type the file can see already has

refusal
TargetOrder.cs, the /*^*/ caret
Arguments
name"Customer"
Refusesname-conflict; every file is left unchanged
Order.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Customer
6 {
7 }
8
9 public class Order
10 {
11 public /*^*/List<string> Lines { get; } = new List<string>();
12 }
13 }

#not-a-type-refused

Refuses when the caret is not on the name of a type

refusal
TargetOrder.cs, the /*^*/ caret
Arguments
name"Lines"
Refusesnot-a-type; every file is left unchanged
Order.csinput
1 namespace Shop
2 {
3 public class Order
4 {
5 public int Count(int /*^*/quantity) => quantity;
6 }
7 }

#nullable-use

Keeps a nullable annotation on the use rather than in the alias

success
TargetCache.cs, the /*^*/ caret
Arguments
name"Lookup"
Projectnullable enable
Cache.csmodified
11 using System.Collections.Generic;
2+using Lookup = System.Collections.Generic.Dictionary<string, string>;
23
34 namespace Shop
45 {
56 public class Cache
67 {
7− private Dictionary<string, string>? _entries;
8+ private Lookup? _entries;
89
9− public /*^*/Dictionary<string, string> Entries => _entries ??= new Dictionary<string, string>();
10+ public Lookup Entries => _entries ??= new Lookup();
1011 }
1112 }

#type-parameter-refused

Refuses a type built from a type parameter, which an alias cannot name

refusal
TargetPager.cs, the /*^*/ caret
Arguments
name"Page"
Refusesuses-type-parameter; every file is left unchanged
Pager.csinput
1 using System.Collections.Generic;
2
3 namespace Shop
4 {
5 public class Pager<T>
6 {
7 public /*^*/List<T> First(IEnumerable<T> items) => new List<T>(items);
8 }
9 }