Skip to content

Releases: nicoespeon/abracadabra

Officially stable 🎩

15 Nov 14:41
Compare
Choose a tag to compare

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 ⚡

13 Nov 21:29
Compare
Choose a tag to compare

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:

  1. 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.
  2. 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:

  1. We now only parse the code once, instead of each refactoring parsing the code again.
  2. 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 🕺

27 Oct 17:44
Compare
Choose a tag to compare

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 🔮

17 Oct 17:13
Compare
Choose a tag to compare

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 💀

23 Sep 00:00
Compare
Choose a tag to compare

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! 🔦

16 Sep 11:12
Compare
Choose a tag to compare

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 🐠

08 Sep 22:06
Compare
Choose a tag to compare

Added

  • [New Refactoring] Bubble up If Statement

Changed

Merge If Statements now handles else-ifs

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 🤙

26 Aug 01:30
Compare
Choose a tag to compare

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:

  1. "Replace all N occurrences". This is the default since it's what we want to do most of the time.
  2. "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 💂‍♀️

18 Aug 13:40
Compare
Choose a tag to compare

Changed

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())
  • Extract Variable on JSX Elements now triggers symbol rename as expected
  • Extract Variable now works on JSXTexts

Y U no use template strings? 😫

10 Aug 14:04
Compare
Choose a tag to compare

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.