Putout v25

coderaiser
CodeX
Published in
6 min readFeb 20, 2022

Dropped support of node < 16

Among the maxims on Lord Naoshige’s wall there was this one: ”Matters of’ great concern should be treated lightly.”
Master lttei commented, “Matters of small concern should be treated seriously.”

© Yamamoto Tsunetomo “Hagakure”

Hi folks!
The time is came for a new release 🎉 !

The support of node versions < 16 is dropped. “Why is so?” — you ask. The main goal of 🐊Putout is to bring best practices in software development process. And one of best practices is: updating dependencies in time.

Node v16 stable, LTS, and I see no reason to wait for about a year while everybody drop v14.

☝️ If you cannot upgrade just use 🐊Putout v24, and if you have any problems create an issue I'm always here to help :)

☝️Need help with github actions support use: @putout/plugin-github

apply-array-at

Among one’s affairs there should not be more than two or three matters of what one could call great concern.
If these are deliberated upon during ordinary times, they can be understood. Thinking about things previously
and then handling them lightly when the time comes is what this is all about.

© Yamamoto Tsunetomo “Hagakure”

The second reason of dropping older node versions is adapting and popularizing new constructions like array.at(). So from now on @putout/plugin-apply-array-at is bundled, enabled and will help you, when you want to get last element of an array.

❌ Example of incorrect code

const array = [1, 2, 3];
console.log(array[array.length - 1]);

✅ Example of correct code

const array = [1, 2, 3];
console.log(array.at(-1));

apply-array-at is written in 🦎PutoutScript and looks this way:

module.exports.replace = () => ({
'__a[__a.length - __b]': '__a.at(-__b)',
});

So much power in so little code, breathtaking isn’t it 😏?

☝️ Wandering why still in CommonJS and not ESM? Worry not! Wait until eslint/eslint#15394 resolved: we need eslint-plugin-putout working! Anyways convert-commonjs-to-esm is ready 🤺, and when the time is come — conversion unavoidable ✊.

apply-try-catch

With help of apply-try-catch big and ugly try-catch blocks will be converted to small and beautiful calls.

❌ Example of incorrect code

try {
say('hello');
} catch (error) {
log(error);
}

✅ Example of correct code

import tryCatch from 'try-catch';
const [error] = tryCatch(say, 'hello');
if (error)
log(error);

This all possible with help of:

Here is reasoning of using try-catch instead of old syntax construction.

New loaders API

To face an event anew solve it lightly is difficult if you are not resolved beforehand,
and there will always be uncertainty in hitting your mark.
However, if the foundation is laid previously, you can think of the saying,
“Matters of great concern should be treated lightly,” as your own basis for action.

© Yamamoto Tsunetomo “Hagakure”

Loaders is experimentation new feature of node.js. According to documentation it can change any time, but:

☝️Loaders is the only way to override imported module in ESM

🐊Putout and tools based on it supports this experimental technology.

Node v14 has one type of loaders:

And node v16 has other:

That’s not fun at all to support both of them 😅…

☝️ So mock-import, zenload and 🎩EScover also updated to support only latest version of API.

Decreased dependencies count

© Cipactli

These three attributes, Sat, Chit, and Ananda
(Existence, Consciousness, and Bliss),
Do not actually define Brahman.
A poison is poison to others,
But not to itself.

Shininess, hardness, and yellowness,
Together signify gold.
Stickiness, sweetness, and viscosity,
Together signify honey.

© Jnaneshwara “Amṛit’ānubhava”

There is a couple changes that makes installing 🐊Putout faster.

Merged rules into promises

@putout/plugin-promises has next rules as dependencies:

  • apply-top-level-await
  • remove-useless-async
  • remove-useless-await

but now they merged to one npm package.

Merged rules into remove-empty

Same with @putout/plugin-remove-empty it merged remove-empty-pattern.

New rule destruct in remove-useless-variables

remove-useless-variables can do a lit bit more 😏 now:

-function hello(args) {
- const {a, b} = args;
-}
+function hello({a, b}) {
+}

☝️Take a look at draft if you want to see how it works

remove-unused-variables became more powerful

Now it supports ClassProperties (#96), and code:

import thing from ‘./thing.js’;
const pi = Math.PI;
export default class {
static t = thing;
static mathPi = pi;
t = thing;
}

will be linted with no false positives:

1:7  error   "thing" is defined but never used  remove-unused-variables 
3:6 error "pi" is defined but never used remove-unused-variables */

Thanks for reporting to @adamdicarlo!

Compare

“There is nothing outside of yourself that can ever enable you to get better, stronger, richer, quicker, or smarter.
Everything is within. Everything exists. Seek nothing outside of yourself.”
© Miyamoto Musashi

A couple changes in @putout/compare. It used a lot in Replacer, Includer and makes 🦎PutoutScript a live!

Long time ago, for the reason of simplification was made next decision: compare upwards when types of nodes are different.

But 🎩ESCover needs to preserve comparing, so now we have findUp flag (which is turned on by default):

compare(path, 'const __a = __b', {
findUp: false,
});

And code:

const operator = getOperator(ret.argument || ret);

Can be converted to:

const operator = (__c4['🧨'](4, 17), getOperator((__c4['🧨'](4, 29), ret.argument) || (__c4['🧨'](4, 45), ret)));

Instead of:

const operator = (__c4['🧨'](4, 17), getOperator(ret.argument || ret));

While SequenceExpressions like (__c4['🧨'](__d, __e), __f) excluded.

🤷‍♂️ How this changes relates to ♨️Speca?

Well, ♨️Speca is a test generator based on 🐊Putout. Some cases wasn't covered, so I trapped on this.

Here is how it looks like:

What more can I do with 🐊Putout?

You can learn people code with help of transformations!

Let’s suppose you want to learn student to write variable declaration:

const voice = cow.say("moo");

What we doing here is: creating a new constant with a name voice and then assign it to cow.say("moo") call expression. Expression cow.say("moo") consists of member expression callee cow.say and string literal argument "moo" . Let’s look at code and warnings examples.

Code:

const voice = cow.say("moo");
cow.say("mew");
cow.say("moo");
say("moo");

Warnings:

‘✅ Assign “voice” to “caw.say” call with “moo” argument passed”’;
‘❌ Call “cow.say” with argument “moo”’;
‘❌ Assign “cow.say” call to “voice” constant’;
‘❌ “say” should be a property of member expression’;

Here is 🐊Putout implementation(that’s right, you can play with it in 🐊Putout Editor! 😏):

module.exports.rules = {
'-cow-say': {
report: () => '❌ cow say moo',
match: () => ({
'cow.say("moo")': ({__a}, path) => {
return !path.parentPath.isVariableDeclarator();
},
'cow.say(__a)': ({__a}, path) => {
return __a.value !== 'moo';
},
}),
replace: () => ({
'cow.say(__a)': (vars, path) => {
return `'❌ Call "cow.say" with argument "moo"'`;
},
'cow.say("moo")': (vars, path) => {
return `'❌ Assign "cow.say" call to "voice" constant'`;
},
'say("moo")': `'❌ "say" should be a property of member expression'`,
})
},
'+cow-say': {
report: () => '✅ cow say moo',
replace: () => ({
'const voice = cow.say(__a)': `'✅ Assign "voice" to "caw.say" call with "moo" argument passed"'`,
}),
},
};

Implementation consists of couple rules bundled in one. This is how nested rules works.

Good first issue

If you want to help, and don’t know how, take a look at good first issue label :).

That’s all for today 🎈! Have a nice day and take care of your codebases 🦔!

--

--

coderaiser
CodeX
Writer for

Open Source contributor, maintainer and developer. Author of a lot of npm packages.