Mert Özen Further With Every Line
Developer Tools IDEs — #04

Refactoring Tools and Code Style: Quick Actions, Alt+Enter, .editorconfig and Code Cleanup

Mert Özen Nov 8, 2026 10 min 5 views
Refactoring Tools and Code Style: Quick Actions, Alt+Enter, .editorconfig and Code Cleanup

Enforcing code style in the build needs severities by rule ID in .editorconfig and EnforceCodeStyleInBuild; in a real run the build reported IDE0028 set with a :warning suffix while dotnet format missed it. Safe refactoring shortcuts in three IDEs, Ctrl+. and Alt+Enter, dotnet format and Code Cleanup, Rider inspections, rename in Angular templates, and what rename misses in JSON, reflection and strings.

Version note: The code in this post was built and run with .NET 10 SDK (Software Development Kit) 10.0.402, Angular 22.2 and Node.js 24. Visual Studio 2026 (18.11) and JetBrains Rider 2026.2 were not run on this machine (macOS); menu names and shortcuts for these two IDEs (Integrated Development Environment) come from Microsoft Learn and the JetBrains documentation, and the Visual Studio Code (VS Code) side from its official C# documentation.

Guess First

Bookstore's inventory module has an old-style class: a block-scoped namespace, a classic constructor that assigns a field, new List<string>() and an unused using.

using System.Collections.Generic;

namespace Bookstore.Inventory
{
    public class StockService
    {
        private readonly IStockRepository _repository;

        public StockService(IStockRepository repository)
        {
            _repository = repository;
        }

        public List<string> FindLowStock(IEnumerable<string> isbns, int threshold)
        {
            List<string> result = new List<string>();
            // ...
        }
    }
}

The team writes these styles into .editorconfig as :warning and enforces them in the build. The build reports four warnings. Then someone runs dotnet format style and says "fixed them all". Do you think it did? The answer is in the real output below; hint: one rule shows up in the build and never shows up in dotnet format.

Safe Refactorings: What the Three IDEs Offer

A "safe" refactoring changes structure without changing behavior, and does it using the compiler's symbol information: rename updates every reference, extract method turns the variables the selected block reads and writes into parameters and a return value, and change signature updates every caller. What to refactor and why is the subject of the Refactoring posts in the Software Design series; here we only talk about the tools.

RefactoringVisual Studio 2026Rider (Windows / macOS)VS Code (C# Dev Kit)
Rename (including file name)Ctrl+R, Ctrl+RShift+F6 / ⇧F6F2
Extract methodCtrl+R, Ctrl+MCtrl+Alt+M / ⌥⌘MNot in the C# refactoring list
Introduce variable / parameterCtrl+. menuCtrl+Alt+V, Ctrl+Alt+P / ⌥⌘V, ⌥⌘PIntroduce parameter (Ctrl+.)
InlineCtrl+. menuCtrl+Alt+N / ⌥⌘NInline method, inline temporary variable
Change signatureCtrl+. menu, Ctrl+R, Ctrl+O (reorder)Ctrl+F6 / ⌘F6Not in the C# refactoring list
Move type to file / namespaceCtrl+. menuF6 (Move)Move type to matching file
All refactoringsCtrl+.Ctrl+Alt+Shift+T / ⌃T (Refactor This)Ctrl+Shift+R / ⌃⇧R (Refactor)

The table is compiled from the Visual Studio shortcut list on Microsoft Learn, Rider's default (Windows) and IntelliJ macOS keymaps, and VS Code's "C# Quick Actions and Refactorings" page. The gaps on the VS Code side show an important fact: VS Code's C# refactorings are Roslyn's code actions; Visual Studio's dialog-based Change Signature and Rider's solution-wide refactorings such as Move and Safe Delete are not there at the same depth.

Ctrl+. and Alt+Enter: Analyzers, Code Fixes and Refactorings

The light bulb (Ctrl+. in Visual Studio and VS Code, ⌘. in VS Code on macOS; Alt+Enter in Rider, ⌥⏎ on macOS) shows three different things in the same menu. A code fix repairs a problem an analyzer found: IDExxxx rules are code style (for example IDE0161, file-scoped namespace), CAxxxx rules are quality and reliability analysis (for example CA2016, not forwarding the CancellationToken). A refactoring is a transformation offered even when nothing is wrong, such as turning an if into a switch. In Rider the same menu fills up with quick-fixes and context actions from ReSharper inspections; intention previews, new in 2026.2, show the diff a quick-fix will produce before you apply it.

The Visual Studio docs give Ctrl+. for Quick Actions; if you come from Rider and want to keep your Alt+Enter habit, check which command that key is bound to under Tools > Options > Environment > Keyboard. Which analyzer rules to turn on and why, and the static analysis policy on the CI (Continuous Integration) side, are the subject of the upcoming Static Analysis series in the Testing and Quality category.

Severity in .editorconfig and Enforcing It in the Build

If code style rules stay suggestions in the IDE, they are left to each person's light bulb. To write a rule into the repo and enforce it in the build you need two things: a severity in .editorconfig and EnforceCodeStyleInBuild in the project.

root = true

[*.cs]
indent_style = space
indent_size = 4

# file-scoped namespace (IDE0161)
csharp_style_namespace_declarations = file_scoped:warning

# primary constructor (IDE0290)
csharp_style_prefer_primary_constructors = true:warning

# collection expression (IDE0028, IDE0300-IDE0305)
dotnet_style_prefer_collection_expression = when_types_loosely_match:warning

# unnecessary using (IDE0005)
dotnet_diagnostic.IDE0005.severity = warning
<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

The first build gave three style warnings and a surprise (we shortened the docs link and project path at the end of each line):

./Bookstore.Inventory/StockService.cs(14,16): warning IDE0290: Use primary constructor
./Bookstore.Inventory/StockService.cs(21,35): warning IDE0028: Collection initialization can be simplified
./Bookstore.Inventory/StockService.cs(3,11): warning IDE0161: Convert to file-scoped namespace
CSC : warning EnableGenerateDocumentationFile: Set MSBuild property 'GenerateDocumentationFile' to 'true' in project file to enable IDE0005 (Remove unnecessary usings/imports) on build

IDE0005 (unnecessary using) shows up in the IDE, but in the build it only runs when an XML (Extensible Markup Language) documentation file is generated; the compiler says so itself. We added <GenerateDocumentationFile>true</GenerateDocumentationFile> to the project, plus <NoWarn>$(NoWarn);CS1591</NoWarn> to silence the requirement for comments on public members, and the fourth warning arrived: StockService.cs(1,1): warning IDE0005: Using directive is unnecessary.

dotnet format and Code Cleanup

The CLI (Command Line Interface) counterpart of the IDE's "fix in whole file" command is dotnet format. On the first try we made a parameter mistake:

$ dotnet format style Bookstore.Inventory --severity warning --verify-no-changes
Argument 'warning' not recognized. Must be one of:
	'info'
	'warn'
	'error'
	'hidden'

.editorconfig says warning, dotnet format says warn. With the right value:

$ dotnet format style Bookstore.Inventory --severity warn --verify-no-changes
./Bookstore.Inventory/StockService.cs(3,11): warning IDE0161: Convert to file-scoped namespace
./Bookstore.Inventory/StockService.cs(1,1): warning IDE0005: Using directive is unnecessary.
./Bookstore.Inventory/StockService.cs(14,16): warning IDE0290: Use primary constructor
$ echo $?
2

Here is the answer to Guess First: the build reported four warnings, dotnet format saw three. IDE0028 is missing. When we fixed with dotnet format style --severity warn and rebuilt, three warnings went away and IDE0028 stayed. The cause is that the severity was written at the end of the option line (:warning); the build applied that syntax for this rule, dotnet format did not. Giving an explicit severity by rule ID closed the gap:

dotnet_diagnostic.IDE0028.severity = warning
$ dotnet format style Bookstore.Inventory --severity warn --verify-no-changes
./Bookstore.Inventory/StockService.cs(14,31): warning IDE0028: Collection initialization can be simplified
$ dotnet format style Bookstore.Inventory --severity warn
$ dotnet build Bookstore.Inventory
Build succeeded.

Lesson: for every rule you enforce in the build, also write a dotnet_diagnostic.<ID>.severity line; in CI, run both the build and dotnet format --verify-no-changes. The resulting code:

namespace Bookstore.Inventory;

public class StockService(IStockRepository repository)
{
    private readonly IStockRepository _repository = repository;

    public List<string> FindLowStock(IEnumerable<string> isbns, int threshold)
    {
        List<string> result = [];
        // ...
    }
}

The primary constructor conversion kept the _repository field and initialized it from the parameter; that is the safe side, because the field stays readonly. Removing the field entirely and using the parameter directly (the parameter is mutable) is a separate decision.

On the IDE side you can have this done on save. Visual Studio's Code Cleanup keeps two profiles (the broom icon at the bottom of the editor); if you turn on Run Code Cleanup profile on save under Tools > Options > Text Editor > Code Cleanup, the selected fixes are applied on every save. In Rider you choose reformat and cleanup under Settings > Tools > Actions on Save; the manual Code Cleanup shortcut in the default keymap is Ctrl+R, C. VS Code has the editor.formatOnSave and editor.codeActionsOnSave settings. The price of auto-fixing on save is that lines you didn't touch end up in the diff too; in an old codebase, first format the whole repo once in a separate commit, then turn on fix-on-save.

Rider: Inspections and Solution-Wide Analysis

The core of Rider's analysis is ReSharper inspections: hundreds of rules, each with a severity (Error, Warning, Suggestion, Hint) and most with a quick-fix. Rider also runs Roslyn analyzers and reads .editorconfig; next to .NET's standard code style options you can also write ReSharper-specific options into .editorconfig. For its own settings there is a layered system: personal settings, the team-shared .DotSettings file in the solution folder, and a personal solution layer. An inspection severity that is a team decision belongs in the team-shared layer, a personal preference in the personal layer.

Solution-Wide Error Analysis (SWEA) continuously analyzes errors in files that aren't open; when you add a member to an interface, every class that doesn't implement it is listed immediately in the Problems window (Alt+6). In large solutions it costs memory and CPU (Central Processing Unit), which is why Rider makes it easy to toggle from the status bar.

The Angular Side: ESLint, Prettier and Rename Across Templates

On the TypeScript side, the counterpart of code style is ESLint (angular-eslint rules for Angular) and Prettier for formatting; both run in CI through scripts in package.json and show up in the editor in all three IDEs through extensions or built-in support. In the Bookstore web project, Prettier 3.8 is already a devDependency.

The critical Angular-specific point is templates. If a component's property is used in the HTML (HyperText Markup Language) template, rename has to update the template too; the Angular Language Service does that (the Angular team's extension in VS Code, the built-in WebStorm support in Rider, and an extension distributed by Microsoft in Visual Studio). When we changed title to name only in the TypeScript file, as an editor without the Language Service would, this happened:

$ npx ng build --configuration development
Application bundle generation failed. [0.389 seconds]

✘ [ERROR] TS2339: Property 'title' does not exist on type 'Book'. [plugin angular-compiler]

    src/app/app.html:1:14:
      1 │ <h1>{{ book().title }}</h1>
        ╵               ~~~~~

  Error occurs in the template of component App.

The error was caught in the build because the Angular compiler's template type-checking validates the reference in the template against the type. That is a safety net, not a rename tool: dynamic access (book()[key]), form control names built from strings and JSON (JavaScript Object Notation) field names coming from the backend are places the compiler can't see.

Where Rename Can't Reach

The C# side has the same limit. A small program that serializes a record to JSON and reads a property once through reflection and once through nameof:

Book book = new("978-0135957059", "The Pragmatic Programmer", 49.90m);

// 1) the JSON contract follows the property name
Console.WriteLine(JsonSerializer.Serialize(book));

// 2) reflection with a string literal: rename can't see this
PropertyInfo? byString = typeof(Book).GetProperty("Title");
Console.WriteLine($"GetProperty(\"Title\"): {byString?.Name ?? "null"}");

// 3) nameof is a symbol reference: rename updates it
PropertyInfo? byNameof = typeof(Book).GetProperty(nameof(Book.Title));
Console.WriteLine($"GetProperty(nameof(Book.Title)): {byNameof?.Name ?? "null"}");

public sealed record Book(string Isbn, string Title, decimal Price);
{"Isbn":"978-0135957059","Title":"The Pragmatic Programmer","Price":49.90}
GetProperty("Title"): Title
GetProperty(nameof(Book.Title)): Title

Now we rename Title to Name the way the IDE would: only the symbol references change, that is, the record parameter and nameof(Book.Title). Strings stay as they are:

{"Isbn":"978-0135957059","Name":"The Pragmatic Programmer","Price":49.90}
GetProperty("Title"): null
GetProperty(nameof(Book.Title)): Name

The code compiled and the tests that rely on the compiler passed, but two things broke: the API's (Application Programming Interface) JSON contract changed from Title to Name, so book.title on the Angular side is now undefined, and the string-based reflection returned null. The line using nameof worked correctly. To tie the contract to the code you have to write the property's JSON name explicitly:

public sealed record Book(string Isbn, [property: JsonPropertyName("Title")] string Name, decimal Price);
{"Isbn":"978-0135957059","Title":"The Pragmatic Programmer","Price":49.90}

Visual Studio's rename window has "Include strings" and "Include comments", and Rider's rename dialog has options to search occurrences in comments and strings; both are text matches and show them to you for confirmation. Razor files are included in rename in Visual Studio and Rider, but action and controller names passed as strings, as in @Html.ActionLink("Details", "Book"), are not.

In Production

Protect the public contract from rename. For API DTOs (Data Transfer Object), message contracts and Entity Framework Core (EF Core) entities that go to the database, pin the JSON name or the column name with an attribute or configuration, so a rename on the C# side doesn't change the contract. In EF Core a property rename can show up in a migration as dropping and re-adding the column; always read the migration (see the migration posts in the EF Core series).

Enforce code style gradually. If you make every rule a warning at once in an existing codebase and turn on TreatWarningsAsErrors, the first build gives thousands of errors. First suggestion, then a one-off dotnet format commit, then warning.

In a large solution, Rider's SWEA and Visual Studio's full solution analysis need memory. If things get slow, check the analysis scope first (open files only or the entire solution).

Common Pitfalls

Thinking rename is text replacement, or the other way round. Semantic rename doesn't see strings, reflection, JSON names or dynamic template access; Find and Replace also changes unrelated symbols.

Giving severity only through the :warning suffix. Real measurement: the build reported IDE0028, dotnet format didn't see it. A dotnet_diagnostic.<ID>.severity line closed that gap.

Writing --severity warning. dotnet format expects warn and exits with an error on the wrong value; check the exit code in your CI script so this error doesn't hide behind a green step.

Not understanding why IDE0005 doesn't show up in the build. With GenerateDocumentationFile off, the build doesn't run this rule; the compiler says so in a warning.

Turning on cleanup-on-save in an old file. The pull request for a small fix arrives with hundreds of lines of formatting changes and becomes impossible to review.

A Small Experiment

★☆☆ Turn on EnforceCodeStyleInBuild in your own project and make only IDE0161 a warning. How many files warn? Fix just this rule with dotnet format style --diagnostics IDE0161 --severity warn.

★★☆ Rename a property in an API DTO with the IDE and see whether your integration tests or the Angular side catch it. If they don't, write a test that protects the contract: compare the field names in the serialized JSON with a fixed list.

★★★ Add two steps to your CI pipeline: dotnet build -warnaserror for style rules only (hint: a list of IDs in WarningsAsErrors) and dotnet format --verify-no-changes. Do the two report the same rules? If not, find out why.

We have learned to change code; next comes running and debugging the code we changed. In the next post we debug a .NET API and Angular together with one key, using launchSettings.json, VS Code's launch.json and compound configurations.