Releases: nicoespeon/abracadabra
Officially stable 🎩
Abracadabra is now stable enough to be considered v1 🎉✨🎩
Fixed
- Cursor position after "Move Statements" is now more accurate.
- VS Code now scrolls to the moved statement, so we don't loose track of it!
Refactor Barry, Refactor ⚡
Changed
Improve Quick Fixes performances
We've optimized the way we determine which refactorings can be executed at your current cursor position. This is an Abracabra key feature.
Every time you move your cursor on the code, we propose you the relevant refactorings through the VS Code Quick Fixes (aka, the lightbulb 💡). This process was taking quite some time, which was causing 2 issues:
- On large files (> 1000 LOC), it would take many seconds to propose you anything. This is lon. And let's be honest, legacy code we're dealing with frequently comes as large files we want to refactor.
- Each new refactoring was adding a bit more of computing. Today, that's around 20 refactorings. If we want to add more, we add to improve performances first, so the extension stays usable.
In short, 2 things were improved:
- We now only parse the code once, instead of each refactoring parsing the code again.
- We shortcut the refactoring execution, so we save a bunch of time on the transformation part too.
As for every performance optimization, you need to measure it or it didn't happen! Overall, here's what it looks like:
File size | Execution time before | Execution time after | Gain |
---|---|---|---|
Small (70 LOC) | 200ms | 40ms | -80%, 5 times faster |
Large (2.400 LOC) | 6s | 350ms | -94%, 17 times faster |
Move Statements now handles one-liners more intuitively
Consider following code:
const user = {
firstName: "John",
lastName: "Doe",
age: 24
};
const rights = { admin: false, moderator: true };
Before, if you had the cursor on admin
and you tried to move the statement up, you'd have swapped the parameters:
const user = {
firstName: "John",
lastName: "Doe",
age: 24
};
const rights = { moderator: true, admin: false };
But what you probably intended to do was to swap the two variable declarations. From usage, we think "Move Statements" more as something you'd like to use to move things up or down. If things are at the same line, you certainly don't expect them to move.
The behaviour was surprising, so it was improved. Now, the same operation will generate:
const rights = { admin: false, moderator: true };
const user = {
firstName: "John",
lastName: "Doe",
age: 24
};
However, if your cursor is now on lastName
and you move the statement down, it will still produce the following code since statements are on different lines:
const rights = { admin: false, moderator: true };
const user = {
firstName: "John",
age: 24,
lastName: "Doe"
};
Noticed how it handles the trailing comma? Ok, that was already here. But it's still super neat!
Can't type this 🕺
Changed
Inline Variable now handles Type Aliases
Consider the following TypeScript code:
type Value = "one" | "many" | "none";
interface Something {
value: Value;
}
You can now inline the Value
type just like a regular variable. Inlining it will result in following code:
interface Something {
value: "one" | "many" | "none";
}
Merge else-if is more intuitive
Consider the following nested if statements:
if (isValid) {
doSomething();
} else {
if (shouldDoSomething) {
doSomethingElse();
} else {
if (isCorrect) {
doAnotherThing();
}
}
}
If your cursor is on the nested if (shouldDoSomething)
and you want to execute "✨ Merge else-if" quick fix, then you'd expect this if to be merged with the previous one.
However, because of the nested if (isCorrect)
, the actual output would have been:
if (isValid) {
doSomething();
} else {
if (shouldDoSomething) {
doSomethingElse();
} else if (isCorrect) {
doAnotherThing();
}
}
We improved the UX to be more intuitive. So if your cursor is on if (shouldDoSomething)
, we'll prioritize the parent merge and the result would be:
if (isValid) {
doSomething();
} else if (shouldDoSomething) {
doSomethingElse();
} else {
if (isCorrect) {
doAnotherThing();
}
}
Fixed
- Extract Variable with arrays of different length now matches other occurrences correctly.
Let me guess your name 🔮
Changed
Inline Variable now handles destructured array patterns
Consider the following code:
const [firstName] = names;
console.log(firstName);
If you tried to inline firstName
, it wouldn't work because destructured array patterns were not supported.
Now it would work as expected:
console.log(names[0]);
That means Inline Variable now handles all kind of destructured variables. Making it much more flexible and handy!
Extract Variable infers variable name on String Literals
Consider the following code:
console.log("Hello World!");
If you extracted "Hello World!"
, you would end up with the following code:
const extracted = "Hello World!";
console.log(extracted);
And you'll be renaming the extracted
symbol. Which is a quick and efficient way to extract the variable.
But now, it'll try to do a bit better. Now, you'll end up with:
const helloWorld = "Hello World!";
console.log(helloWorld);
Which would make sense in that case, saving you the trouble of naming it!
Now, predicting the variable name is hard. Thus, you'll still be in "renaming mode", so it doesn't really matter if the guess is wrong. If it's correct though, it will certainly save you some more time in your refactoring, and that's the goal!
One last thing: if the inferred name is too long (> 20 characters), it will default on "extracted"
because it's probably not a good name for your variable.
Fixed
- All refactorings Quick Fixes used to appear on Windows because of EOL. Not anymore!
I see dead code 💀
Added
- [New Refactoring] Remove Dead Code
- [New Refactoring] Convert For-Loop to ForEach
Changed
Inline Variable now handles destructured object patterns
Consider the following code:
const { userId } = session;
messages.map(message => ({ userId }));
If you tried to inline userId
, it wouldn't work because destructured object patterns were not supported.
Now it would work as expected:
messages.map(message => ({ userId: session.userId }));
Thanks to @noway for bringing this one up.
Destructured array patterns (e.g. const [userId] = session
) are still not supported, but we're working on it.
Fixed
- Convert If/Else to Ternary now preserve comments that were inside each branches.
Switch it on! 🔦
Added
- [New Refactoring] Convert If/Else to Switch
- [New Refactoring] Merge With Previous If Statement
Changed
Split Declaration and Initialization now handles nested declarations
Consider the following code:
const getLastName = () => {
const lastName = "Doe";
return lastName;
};
If your cursor is on const lastName
, executing the refactoring would have produced this result before:
let getLastName;
getLastName = () => {
let lastName;
lastName = "Doe";
return lastName;
};
Refactoring would have been applied to both declarations. It's valid, but probably not want you wanted to do.
Now it will produce the following, expected output:
const getLastName = () => {
let lastName;
lastName = "Doe";
return lastName;
};
Fixed
- Flip If/Else now works when both if and else branches have return statements
- Inline Function now preserves comments as much as possible
Bubble it up 🐠
Added
- [New Refactoring] Bubble up If Statement
Changed
Merge If Statements now handles else-if
s
Extract Variable handles Spread Elements better
Consider the following snippet:
console.log({ ...foo.bar });
Before, executing Extract Variable with the cursor of foo
would have produced:
const extracted = { ...foo.bar };
console.log(extracted);
Now, you can extract the Spread Element. The result will be:
const extracted = foo.bar;
console.log({ ...extracted });
If your cursor is on the ...
symbol however, you will still extract the whole thing.
Extract 'em all 🤙
Added
- [New Refactoring] Replace Binary with Assignment
Changed
Extract Variable now handles extraction of multiple occurrences!
If the extracted variable has no other occurrence in scope, it will just perform the extraction as it does today.
But if we can find other occurrences of the variable in the scope, then it will ask you what you want to do:
- "Replace all N occurrences". This is the default since it's what we want to do most of the time.
- "Replace this occurrence only". In case you only want to extract this one.
Fixed
- Extract Variable now works for call expressions in JSX Elements (e.g.
<Button onClick={this.extractMe()} />
) - Extract Variable now works in for statements
Here come the Guards 💂♀️
Changed
- Flip If/Else now works better on guard clause patterns
Consider this guard clause example:
function doSomething(someData) {
if (!isValid(someData)) {
return;
}
// … rest of the code
}
Before, running Flip If/Else would have produced:
function doSomething(someData) {
if (isValid(someData)) {
} else {
return;
}
// … rest of the code
}
Which is valid, but probably not what you had in mind.
Now, it would produce the following result:
function doSomething(someData) {
if (isValid(someData)) {
// … rest of the code
}
}
Fixed
- Inline Function now says it can't inline function with many statements to assigned call expressions
- Inline Function now works on return statements identifiers (e.g.
return inlineMe;
) - Inline Function now works on every call expression that is:
- assigned to a variable (e.g.
const result = isValid ? inlineMe() : "default";
) - inside another call expression (e.g.
console.log(inlineMe())
) - inside an arrow function expression (e.g.
() => inlineMe()
)
- assigned to a variable (e.g.
- Extract Variable on JSX Elements now triggers symbol rename as expected
- Extract Variable now works on
JSXText
s
Y U no use template strings? 😫
Added
- [New refactoring] Convert to Template Literal
- [New refactoring] Split Declaration and Initialization
Fixed
- Don't add unnecessary braces when extracting JSX elements.
- Don't extract arrow function expressions params.
- Remove Redundant Else now handles nested If statements correctly.
- Flip Ternary now handles nested ternaries correctly.
- Flip If/Else now handles nested If statements correctly.