Skip to content

Change "one-var" requirement for es5 (and related rules) #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Krinkle opened this issue Mar 17, 2021 · 8 comments · Fixed by #361
Closed

Change "one-var" requirement for es5 (and related rules) #360

Krinkle opened this issue Mar 17, 2021 · 8 comments · Fixed by #361

Comments

Image for: Comments
Copy link
Member

Krinkle commented Mar 17, 2021

In the ES6 preset, we turned off the one-var rule because the "one big comma-separated statement" style is uncommon in modern JavaScript and needlessly obfuscates code when writing or reviewing code. The reason it is uncommon is that let and const feature a "temporary dead-zone" in JavaScript, which means the caveat that motivated the "one-var" style, is only applicable to var and not other variable types like let and const.

I'm not sure why we kept the rule even for ES5 code, but I'm guessing it's because the underlying concern (variable hoisting) is of course still valid for actual var statements. However, I think this is really a very unlikely edge case to be worth keeping the rule for. The concern is basically to avoid code like this:

function foo() {
 var x = 1;
 bar(y + 1);

 var y = 2;
}

If it weren't for style rules like this, the code would pass ESLint and the code would not fail with an (obvious) exception at run-time either, because the engine hoists var y, declared as undefined, to the start of the function. In ES6, when using let or const this is not a concern as those are not hoisted (leading to the "temporal dead zone", to ensure an error is thrown if you access them earlier).

The way we currently avoid this mistake is through the one-var rule. The intention being to bring the var statements together, so that you write code like this instead:

function foo() {
 var x = 1,
     y = 2;
 bar(y + 1);
}

... or, if you are unable to compute y that early yet, then this will force you to realize there is a bug and deal with that, or to acknowledge that it is okay to use as undefined or some other default value, by declaring explicitly as such at the start:

function foo() {
 var x = 1,
     y = 0;
 bar(y, 1);

 y = 2;
}

... Note that the one-var rule not only requires bringing the var statements together, it also currently enforces writing them as a single akwardly-indented comma-separated statement, and (to my surprise) I could not find anything at https://eslint.org/docs/rules/one-var that accmodates the style of "together, but as distinct var statements".

My guess is, the only reason it is like this historically is because it was easier to write such rule in JSHint, or that someone tried to optimise a few bytes in the output, or that simply nobody has enough recently about this rule to fix it.

Proposal

I think the one-var rule is obsoleted by the no-use-before-define rule and so I'd like us to remove it, and allow developers to start writing code like such:

function foo() {
 var x = 1;
 var y = 0;
 bar(y, 1);

 y = 2;
}

Existing code using the comma is totally fine, and may be preferred e.g. to group together variables that have no assignment:

function foo() {
 var a, b, c;
 var x = 1;
 var y = 0;
 bar(y, 1);

 y = 2;
}

It does mean there's more than one way to write a statement, but I think this small amount of consistency will help authors to choose which style is most readable for the given code at hand, and also to slowly get used to this more modern coding style (if they want to).

My main motivation for this is that ESLint overrides and TypeScript declarations look less obtuse when they are not sandwhiched within a statement. As well as documentation comments more generally.

Compare:

function foo() {
  var
    // Foo
    foo = 1,
    bar = 2;
}

function bar() {
  // Foo
  var foo = 1,
    // bar      <!-- indented, but not actually a logically nested block
    bar = 2;
}

function baz() {
  var /** @type {Object} */ foo = 1, // Help me
    // eslint-next-line-disable some-rule-here
    bar = 2;
}

To:

function foo() {
  // Foo
  var foo = 1;
  var bar = 2;
}

function bar() {
  // Foo
  var foo = 1;
  // bar
  bar = 2;
}

function baz() {
  /** @type {Object} */
  var foo = 1;
  // eslint-next-line-disable some-rule-here
  var bar = 2;
}
Copy link
Member

edg2s commented Mar 17, 2021

I think if no-use-before-define prevents people from writing broken code then everything else is just stylistic. I'm used to it now, but as multiple vars/lets is the style we are moving to with ES6, then either way seems fine.

We should see if any repos have disabled no-use-before-define because of the effect on functions, and ensure it is still enabled for variables.

... to allow

I think this means turning it to "off".

Copy link
Contributor

catrope commented Mar 17, 2021

+1 to what Ed said. I would want this change even so that I could write:

function foo( param ) {
    if ( !param ) {
        return false;
    }
    var a = param.x,
        b = param.getY();
    ...
}

instead of

function foo( param ) {
    var a, b;
    if ( !param ) {
        return false;
    }
    a = param.x;
    b = param.getY();
    ...
}

and generally move the declarations of variables that are only used in small parts of large functions to the top of the block where they're first used.

Copy link
Contributor

catrope commented Mar 17, 2021

We would also have to disable the vars-on-top rule.

jdforrester changed the title Change "one-var" requirement for es5 to allow Mar 17, 2021
Copy link
Member

The proper venue for discussion of changing Wikimedia's JS coding standards is https://www.mediawiki.org/wiki/Manual_talk:Coding_conventions/JavaScript and I think it'd be better to mostly continue this there given the large number of people and teams that changing this would impact. (I don't have a particular view on the proposal as such.)

Copy link
Member Author

Krinkle commented Mar 18, 2021

Copy link
Member

edg2s commented Mar 18, 2021

This code would become possible:

for ( ... ) {
   var x = f( y );
}
output( x );

but could be prevented by enabling block-scoped-var.

edg2s added a commit that referenced this issue Mar 18, 2021
* Disabled one-var and vars-on-top rules
* Enables block-scoped-var rule to prevent confusing usage.
  no-use-before-define should catch other problematic uses of var.

Fixes #360
edg2s added a commit that referenced this issue Mar 25, 2021
* Disabled one-var and vars-on-top rules
* Enables block-scoped-var rule to prevent confusing usage.
  no-use-before-define should catch other problematic uses of var.

Fixes #360
wmfgerrit pushed a commit to wikimedia/mediawiki that referenced this issue Feb 3, 2022
Remove some of the "one-var" style declarations where we hoisted
the variable for no reason other than to satisfy this eslint rule,
which we have sinced removed from out preset, per
<wikimedia/eslint-config-wikimedia#360>.

Change-Id: I356d2c934f02db7a100c835f60b1811b632fff55
Copy link
Member

Should https://www.mediawiki.org/wiki/Manual:Coding_conventions/JavaScript#Declarations be reworded? It currently states "Variables … should be the first statement in any file or function"

Copy link
Member

Should https://www.mediawiki.org/wiki/Manual:Coding_conventions/JavaScript#Declarations be reworded? It currently states "Variables … should be the first statement in any file or function"

Oops, yes, good point. We started the conversation on https://www.mediawiki.org/wiki/Topic:W5dh34nv202fzq7f but didn't resolve it and update. Will do.

Krinkle changed the title Change "one-var" requirement for es5 to allow (and related rules) Apr 6, 2022
wmfgerrit pushed a commit to wikimedia/mediawiki-libs-metrics-platform that referenced this issue Apr 11, 2022
* Adopt ES6-style inlined variables instead of one-var hoisting.
  wikimedia/eslint-config-wikimedia#360

* Adopt assert.true() and assert.false() where possible.

* Run unit tests before the slower lint/compile steps for faster
  feedback when iterating before finalising quality (plus often
  already in IDE for fast feedback on that too).

* Actually enable ESLint's cache.

Change-Id: Iad78d4d0f481f939d628b6ae99ec1842a1f794a7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment