Refactor
VS Code’s refactoring features lean on the language server to rename, extract, inline, and reorganize code safely — cross-file and across language boundaries. Knowing the shortcuts turns repetitive surgery into a single keystroke.
Rename, extract, source actions
EXAMPLE
// 1) Rename symbol — F2
// • Cursor on identifier → F2 → type new name → Enter
// • Updates every reference in the project, across files
// • TS / Python / Rust / Go: respects scopes, doesn't touch unrelated names
// • Works in JSX, .md headings (if symbol-aware), Tailwind class refs (with extension)
// 2) Quick Fix — Ctrl+. (Cmd+. on macOS)
// • Opens the lightbulb menu on the current line
// • Convert require to import, add missing import, fill type annotation,
// remove unused variable, sort imports, generate getter/setter
// • Quick Fix is the entry point to most refactorings
// 3) Extract function / variable / constant
// • Select an expression → Ctrl+. → 'Extract to function/method'
// • Pulls the selection out, infers parameters, picks a sensible scope
//
// Before:
function priceFor(order: Order) {
const tax = order.subtotal * (order.region === 'AU' ? 0.10 : 0.05);
return order.subtotal + tax;
}
//
// Select 'order.region === "AU" ? 0.10 : 0.05' → Extract to variable
function priceFor(order: Order) {
const taxRate = order.region === 'AU' ? 0.10 : 0.05;
return order.subtotal + order.subtotal * taxRate;
}
// 4) Inline variable / inline function
// • Inverse of extract
// • Useful when an extracted helper turns out to be too thin
// 5) Move to file
// • Cursor on a top-level declaration → Ctrl+. → 'Move to a new file'
// • Names the new file from the symbol
// • Updates imports in every consumer
// 6) Convert async patterns
// • Select a .then() chain → 'Convert to async function'
// • Or 'Convert to Optional Chain' on x && x.y && x.y.z
// 7) Source actions on save (settings.json)
{
'editor.codeActionsOnSave': {
'source.organizeImports': 'explicit',
'source.fixAll.eslint': 'explicit',
'source.addMissingImports': 'explicit',
},
'editor.formatOnSave': true,
'typescript.preferences.importModuleSpecifier': 'shortest',
}
//
// Every save: sorts imports, fixes auto-fixable lint, adds missing imports.
// 8) Multi-cursor refactoring (NOT a true refactor — but the practical workhorse)
// • Ctrl+D — select next occurrence of the word under cursor
// • Ctrl+Shift+L — select ALL occurrences in the file
// • Alt+Click — drop additional cursors anywhere
// • Ctrl+Alt+Down / Up — add cursor below / above
//
// Use Find & Replace with regex (Alt+R inside the search box) for project-wide edits.
// 9) Search-replace with capture groups
// • Ctrl+Shift+H → enable regex (.* button)
// • Find: `useState<(\\w+)>\\(` → Replace: `useState<$1 | null>(`
// • Use 'files to include' / 'files to exclude' to scope the change
// 10) Symbol navigation — required for refactors that span files
// • F12 — go to definition
// • Shift+F12 — find all references
// • Alt+F12 — peek definition (preview popover)
// • Ctrl+T — symbol search across the whole workspace
// • Ctrl+Shift+O — symbols in current file
// • Ctrl+G — go to line number
// 11) Refactoring via the language server
// • TS: typescript-language-server
// • Python: Pylance (proprietary) or Pyright + Python extension
// • Rust: rust-analyzer — best refactor support outside JetBrains
// • Go: gopls
// • Java: Red Hat Java
//
// The richer the language server, the better refactors are.
// If F2 'rename' is missing, the language server probably isn't running.
// 12) Workspace settings for safer refactors
{
'files.exclude': { '**/.next': true, '**/dist': true }, // hide generated
'search.exclude': { '**/dist': true, '**/coverage': true }, // don't include in replacements
'editor.suggest.preview': true,
'editor.linkedEditing': true, // rename matching JSX tag pair
}
// 13) Snippets — alternative to repetitive refactor work
// • File → Preferences → Configure User Snippets
// • Define `console.log('X →', X)` triggers, custom test scaffolds, etc.
// 14) Common bugs and pitfalls
// • F2 renamed only the visible scope — language server not indexed full workspace
// • Refactor crossed a project boundary you didn't intend — use 'Move to file' instead of cut/paste
// • Auto-import picked a deep internal path — set 'importModuleSpecifier': 'shortest' or 'project-relative'
// • organizeImports removed something you needed — annotate with /** @public */ or update tsconfig isolatedModules
Why it matters
Lean on F2, Ctrl+., and codeActionsOnSave — they preserve correctness across files in a way that hand-edits don’t. When a refactor option is missing, check that the language server is actually running for that file type; most of VS Code’s refactoring power lives there, not in the editor itself.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// F2 rename symbol everywhere // Ctrl + . quick fix / refactor menu (extract function/variable, sort imports, …)Try it Yourself »
Discussion
Loading…