RefactorMCP catalog

Join Declaration and Assignment

Joins a local declaration that has no initializer with the first assignment to the local: T x; followed by x = e; becomes T x = e;. The reverse of Split Declaration and Assignment.

Target

The local, by a caret on its declaration or on any use of it. In a later step of a composite, the containing member's symbol with arguments.local naming the local.

Precondition

  • The local is declared by a local declaration statement in a block or switch section, without an initializer, and the statement declares only that local.
  • The first statement after the declaration that mentions the local is a plain assignment to it (x = e;) in the same block, and e does not read the local. An assignment inside a nested block, a compound assignment, or a use as an out or ref argument does not qualify.

Transformation

  • The declaration moves down to the assignment and takes its value. The type is kept as written.
  • Statements between the declaration and the assignment stay where they were; none of them mentions the local.

Preserved

  • Behaviour: the value is assigned at the same point, and the local is not used before it.
  • Comments above the declaration move with it, below the assignment's own leading blank lines and comments. A comment at the end of the assignment's line stays; a comment at the end of the declaration's line is kept when the assignment has none.
  • A blank line before the declaration stays before the statement that now follows its old place.

Limitations

  • The local's type is not changed to var.
  • Locals declared by for or using statements are not covered.

Error codes

CodeMeaning
has-initializerthe declaration already has a value
first-use-not-assignmentthe first use of the local is not a plain assignment in the same block
never-assignednothing after the declaration uses the local
multiple-declaratorsthe statement declares several locals
not-a-localthe target is not a local variable

Cases

·

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

#adjacent-assignment

Joins a declaration and the assignment that follows it into one declaration

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 public class Sample
22 {
33 public int Sum(int a, int b)
44 {
5− int /*^*/total;
6− total = a + b;
5+ int total = a + b;
76 return total * 2;
87 }
98 }

#first-use-not-assignment-rejected

Refuses when the local is first assigned inside a nested block, where a declaration would narrow its scope

refusal
TargetSample.cs, the /*^*/ caret
Refusesfirst-use-not-assignment; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Pick(bool first)
4 {
5 int /*^*/choice;
6 if (first)
7 {
8 choice = 1;
9 }
10 else
11 {
12 choice = 2;
13 }
14
15 return choice;
16 }
17 }

#has-initializer-rejected

Refuses a declaration that already has a value

refusal
TargetSample.cs, the /*^*/ caret
Refuseshas-initializer; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Sum(int a, int b)
4 {
5 int /*^*/total = 0;
6 total = a + b;
7 return total;
8 }
9 }

#moves-declaration-to-assignment

The declaration moves down to the first assignment, bringing its comment, past statements that do not use the local

success
TargetSample.cs, the /*^*/ caret
Sample.csmodified
11 using System;
22
33 public class Sample
44 {
55 public void Run(int count)
66 {
7− // How many were written.
8− int /*^*/written;
97 Console.WriteLine("Starting");
108
11− written = Write(count); // after writing
9+ // How many were written.
10+ int written = Write(count); // after writing
1211 Console.WriteLine(written);
1312 }
1413
1514 private int Write(int count) => count;
1615 }

#multiple-declarators-rejected

Refuses a declaration of several locals

refusal
TargetSample.cs, the /*^*/ caret
Refusesmultiple-declarators; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Sum()
4 {
5 int /*^*/a, b;
6 a = 1;
7 b = 2;
8 return a + b;
9 }
10 }

#never-assigned-rejected

Refuses a local that is never assigned

refusal
TargetSample.cs, the /*^*/ caret
Refusesnever-assigned; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Zero()
4 {
5 int /*^*/unused;
6 return 0;
7 }
8 }

#nullable-context

The declared type, with its nullable annotation, is kept as written

success
TargetSample.cs, the /*^*/ caret
Projectnullable enable
Sample.csmodified
11 public class Sample
22 {
33 public string Describe(int id)
44 {
5− string? /*^*/name;
6− name = Find(id);
5+ string? name = Find(id);
76 return name ?? "unknown";
87 }
98
109 private string? Find(int id) => id > 0 ? "found" : null;
1110 }

#out-argument-rejected

Refuses when the local is first written as an out argument rather than assigned

refusal
TargetSample.cs, the /*^*/ caret
Refusesfirst-use-not-assignment; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Parse(string text)
4 {
5 int /*^*/value;
6 int.TryParse(text, out value);
7 return value;
8 }
9 }

#parameter-rejected

Refuses a parameter, which is not a local

refusal
TargetSample.cs, the /*^*/ caret
Refusesnot-a-local; every file is left unchanged
Sample.csinput
1 public class Sample
2 {
3 public int Twice(int /*^*/value)
4 {
5 return value * 2;
6 }
7 }