Quick question. Regarding no-jquery/no-global-selector, how are we are supposed to find elements if we can't use $( '#elementIWantToFind' )
? Aren't we at least going to need one of these somewhere so that we can find the correct skin element to before/after/append/prepend newly created elements to?
Manual talk:Coding conventions/JavaScript
@Novem Linguae: I think the idea is to use hooks that pass in a jQuery object that's of narrower scope. For example, wikipage.content hook handlers get a $content object. That way, there's more control over the extensibility of the front end, rather than basically treating the whole output HTML as the API for gadgets to use (and then breaking things because it's not obvious where people are extending what).
The reason we have rules like this is not that it would be forbidden to have such code. You can mark exceptions as such, if needed. We just want people to think about it and consider alternatives, as Sam explained above.
The same applies to some of the "forbidden" functions in PHP, for example. Exceptions are allowed.
In the next release we will be enabling the prefer-arrow-callback
rule by default: https://github.com/wikimedia/eslint-config-wikimedia/issues/572
Is it possible to enforce this only when doing so would not also break other eslint rules? I often find myself having to craft a one-liner that satisfies both prefer-arrow-callback
and max-len
, for example.
prefer-arrow-callback
shouldn't affect how many lines you can use in a callback. Can you give an example?
Sure. Consider:
// eslint-disable-next-line arrow-body-style
provide: ( stateField ) => {
// eslint-disable-next-line arrow-body-style
return showPanel.from( stateField, ( on ) => {
return on ? () => this.panel : null;
} );
}
And if prefer-arrow-callback
had its way:
// eslint-disable-next-line max-len
provide: ( stateField ) => showPanel.from( stateField, ( on ) => on ? () => this.panel : null )
Clearly the first example is more legible. Worse, the second is considered acceptable given max-len
is only a warning. Or is there a third variant I'm missing here?
I think prefer-arrow-callback
is great practice, but ideally it would only apply if what would follow =>
does not then cause it to break max-len
. Hopefully that makes sense.
Rather than max-len disables, why not just write it out with an indent like normal, i.e.:
provide: ( stateField ) => showPanel.from(
stateField, ( on ) => on ? () => this.panel : null
)
As James shows, long expressions can usually be split elsewhere.
Is it possible to enforce this only when doing so would not also break other eslint rules?
To answer your original question, I don't think it is. Rules run independently.
Should we add a section about how to configure for ESLINT with the .js format? The .json format got removed in version 9, but I"m not comfortable making a decision to recommend version 9.
Related: phab:T364065
Currently required:
/* eslint "operator-linebreak": ["error", "after" ] */
var x = isItGreat ?
Example.might("be") :
Example.or("not");
Proposed:
/* eslint "operator-linebreak": ["error", "before" ] */
var x = isItGreat
? Example.might("be") :
: Example.or("not");
In summary:
- Consistency with Standard JS, ESLint default, and JSLint; which nowadays all enforce the "before" style.
- Consistency with MediaWiki PHP.
- Improved readability.
- The ESLint rule to enforce this "after" style came from JSHint, which inherited it from JSLint. It existed in JSLint to support "bugs in old parsers". During the time this concern was theoretically relevant (2009-2016), we actually had it disabled and used the before style instead as we favour readability (and consistency with PHP). In 2016, the auto-fixed transition from JSCS/JSHint to ESLint, accidentally flipped this rule and started enforcing the legacy "after" JSLint style.
- Even JSLint never used this style for ternaries, only for conditionals and concatenation. The reason we enforce it on ternaries is that ESLint changed the rule at some point (with a default opt-out), and we forgot to sync our config. Another auto-fix.
- In 2017, JSLint removed their rule completely, and now enforces the same "before" style that I propose here.
History:
Between 2009-2016, the proposed style was our style in MediaWiki JS code with the operator at the start of the line (and thus line break "before" the operator), same as in PHP.
This seemingly changed by accident in 2016 the JSCS Team helped us migrate from JSHint+JSCS to ESLint. From what I can tell, they misread one of our JSCS preset keys and added this style. This appears to be based on a legacy recommendation by Crockford (author of JSLint) to avoid bugs in "old parsers" that did not implement the ECMAScript spec correctly and for some reason had trouble with line breaks before certain operators. jshint issue #60 (2011):
[Line breaks before operators] may cause problems like semi colon insertion and old javascript parsers breaking.
When we adopted JSHint for MediaWiki in 2012, we had the same convention in both PHP and JavaScript. I asked upstream at the time, how to make our coding style pass under JSHint. jshint issue #557:
You should use laxbreak. Default behavior is (historically) what Crockford recommended.
And so we did! https://gerrit.wikimedia.org/r/c/mediawiki/core/+/14017:
- "[mediawiki/core] jshint: add .jshintrc"
"laxbreak": true
The last version of wikimedia JSCS preset in 2014 contains "disallowOperatorBeforeLineBreak": ["."]
, which requires line breaks before, not after, and only for the .
operator.https://github.com/jscs-dev/node-jscs/blob/cf60723df5/presets/wikimedia.json
"operator-linebreak": ["error", "after"],
// Correct
var x = foo
.bar()
.baz();
// Incorrect
var x = foo.
bar().
baz();
The JSCS project merges into ESLint in 2016. The wikimedia preset used to be maintained within the JSCS project, and Oleg made a separate one for us at eslint-config-wikimedia v0.1.0 which set the operator-linebreak rule to enforce line breaks after operators. We deployed this a few weeks later and auto-fixed our code base:
- "[mediawiki/core] build: Replace jscs+jshint with eslint." https://gerrit.wikimedia.org/r/321732 (2016)
Now, at first, the ESLint operator-linebreak rule was mostly for operators in multi-line values (e.g. +
concatenation) and multi-line conditionals (e.g. &&
).
In 2015, someone requested a feature in upstream ESLint to be able to enforce the legacy JSLint style on ternary operators. This despite not even JSLint itself doing this for ternary operators (I guess the "old parsers" didn't have an issue with ternary operators?). ESLint lead Nicholas Zachas confirms:
I think this might have been intentionally omitted because people tend to put the
?
and:
at the front of lines even when everything else is at the end.
The next ESLint release added support for enforcing this odd style on ternaries, but, because it is believed to be unusual and to avoid regressions, upstream included a "default override". eslint issue #4294
https://eslint.org/docs/latest/rules/operator-linebreak
The default configuration is
"after", { "overrides": { "?": "before", ":": "before" } }
Unfortunately, because projects tend to hardcode the defaults, this meant anyone that configured ["error", "after"]
would see their behaviour change during an upgrade, because setting shadows any "default override". For example the JavaScript Standard Style quickly applied this opt-out to avoid a regression after upgrading ESLint. eslint-config-standard@a78c4bb, [email protected]
fix regression with ternary operator handling
- "operator-linebreak": ["error", "after"], + "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }],
Ironically, Crockford himself has long abandoned this peculiar "after" style in 2017. JSLint (still around!) now embraces the "before" style, same as MediaWiki PHP and Standard JS. jslint-org/jslint@c08484f
[jslint] break left
Proposal:
/* eslint "operator-linebreak": ["error", "before" ] */
var x = isItGreat
? Example.might("be")
: Example.or("not");
if (
mw.foo.hasBar()
&& mw.foo.getThis() === 'that'
&& !mw.foo.getThatFrom( 'this' )
) {}
Pull request: https://github.com/wikimedia/eslint-config-wikimedia/issues/591
Highly agree with "faster comprehension" 👍
Conditionals
if (
mw.foo.hasBar()
&& mw.foo.getThis() === 'that'
&& !mw.foo.getThatFrom( 'this' )
) {}
I don't think this is enforced in phpcs, because 1) I see examples of the opposite of this in a coding conventions example (Manual:Coding conventions#Line width, code block four) and 2) I see examples of both styles in a random file I spot checked (EditPage.php, example 1, example 2)
The closest thing I could find in the coding conventions doc was Manual:Coding conventions#Line width, The operator separating the two lines should be placed consistently (always at the end or always at the start of the line), so it appears to not have an opinion on it.
Ternaries
var x = isItGreat
? Example.might("be")
: Example.or("not");
Same as above. I am finding examples of both in php (EditPage.php, example 1, example 2), suggesting that phpcs does not have a rule about this. Additionally, I tested eslint 8 with the settings
{
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}
and I was not able to get it to error for either type of ternary, suggesting to me that this may not be an eslint recommended rule.
Conclusion
Perhaps we should remove the eslint rule operator-linebreak
from eslint-config-wikimedia completely, so that phpcs, eslint, and coding conventions are in alignment, that alignment being not requiring either style.
@Novem Linguae Do you prefer to place operators in JavaScript in different places at different times?
I'd like to understand what people think they need, and why. I understand you think we don't have a convention for PHP code, but I don't think we should decide based on that. We should decide based on whether there's value in it. Generally speaking, code comprehension is helped by reducing ways to express the same thing.
There are of course aspects of code authoring where it is useful to express things multiple ways to convey different subtle meanings. Such as where and whether to add a blank line between statements, or whether to use a if-else conditional or a ternary expression. Search for declined Codesniffer tasks to find examples such as T179768, T200629, T253914.
Manual:Coding conventions is a shared page about both PHP and JS. The first code block at Manual:Coding conventions#Line width contains PHP code, and uses the "before" style that is conventional in MediaWiki PHP. The fourth code block there contains JavaScript code, and uses the "after" style currently enforced by ESLint.
On the page about PHP coding conventions, it says Manual:Coding conventions/PHP#Ternary operator:
[…] the question mark and colon should go at the beginning of the second and third lines and not the end of the first and second
There is also the matter of having experience with the unfortunately enforced style in JavaScript affecting our muscle memory, with no enforcement on the PHP side. This makes checking existing code biased. Hence I'm asking for input on people's subjective experiences separate from what "exists".
To my knowledge, there is no interest or need for placing ternary symbols in different places at different times. The lack of enforced consistency in PHP is not because we don't want to enforce it, but we because we can't. PHPCS lacks built-in support for enforcing either way. T253187, PHP_CodeSniffer issue 3377, and T116561.
I'm in favour of switching to "operator-linebreak": ["error", "before" ]
, per Krinkle's comments above.
Unfortunately the discussion is a little mood because the ternary operator should rarely be used when it spans more than a single line. I mean, I like the operator and regularly use it. But only when the two branches are trivial (e.g. a single method call) and easy to grasp.
While I, personally, like to place ?
and :
at the beginning of the line I ultimately don't mind much – as long as I'm allowed to use the same style in PHP and JS.
What I care much more about is the placement of ||
and &&
in an if
. I always put these at the end of the line for several reasons. I wouldn't be happy when some sniff would start to enforce the opposite.
"I always put these at the end of the line for several reasons"
Can you elaborate on these?
something == null ? fallback : something
is prohibited by the equality section and something ?? fallback
is prohibited due to being too new. The next best way would be (typeof something === "undefined" || something === null) ? fallback : something
, which is quite too much of a mouthful. Is there some shorter way I'm not aware of, or is this use case just too niche for the MediaWiki codebase?
If you know the expected type you could do e.g. !something && something !== ''
.
Ooh, that is smart. It stunned my brain for a minute but it is short and nice. THanks.
Hi all, I'd like to discuss documentation comment conventions for classes as part of the migration to JSDoc. In JSDoc, specifically for classes:
- The main description in the documentation comment (or the
@description
tag) is interpreted as the description of the constructor - The
@classdesc
tag is interpreted as the description of the class[1]
These two description fields appear in the documentation site as:
# mw.MyClass
Class description
## Constructor
### new mw.MyClass()
Constructor description
There are two ways this can be formatted in the comment:
1. Constructor description first: Simpler but in reverse order from how the information appears on the documentation site
/**
* Constructor description first line.
*
* Constructor description continued...
*
* @classdesc Class description first line.
*
* Class description continued...
*
* @param string myParam
*/
2. Class description first: More verbose but reflects the same order as the documentation site
/**
* @classdesc Class description first line.
*
* Class description continued...
*
* @description Constructor description first line.
*
* Constructor description continued...
*
* @param string myParam
*/
The empty lines aren't required by JSDoc, but I've included them for readability and consistency with Manual:Coding conventions/Documentation.
Which option should we standardize on? Ideally, we'd be able to use @constructor
instead of @description
, but JSDoc doesn't support that. Of the two options, I think option 2 is the least ambiguous and easiest to reason about.
I think developers have a tendency to, whatever comes first in that block, is where we put high-level explainers, detailed descriptions, and usage examples for the class as a whole.
Having that be tucked away in the constructor description would be unfortunate and make the information less discoverable and thus waste investments in it somewhat.
For that reason, and for parity with PHP conventions wherever possible, I'd suggest option 2.
@APaskulin (WMF) I believe your code example is for the lower-level ("ES5-style") class, where the class and constructor are defined together as function FooBar(…) {…}
.
When ES6 syntax sugar is used (which makes it look like the class and constructor are separate things), there is usually two separate doc blocks:
class FooBar { constructor() {…} }
Can you add an example for this as well? Does it require the same tags? I'm hoping @description
would no longer be needed in that case, and would @class
stay in the first block?
That's a great point. This is much cleaner and works correctly:
/**
* Class description.
*/
class myClass {
/**
* Constructor description.
*
* @param ...
*/
constructor() {...}
};
Adding a @class
tag to the ES6 example (in either comment) actually messes it up and causes JSDoc to duplicate the class.
In the ES5 example, the @class
tag isn't necessary in most cases, but it doesn't break anything. I included it because we have a lot of @class
tags in MediaWiki core, but I'll update that post to remove it since it's not required.
With the advent of the frontend stable interface policy, there's a need to mark things as stable. This is especially true in the near-term as most frontend code doesn't have any indication of stability, so visibly seeing @stable
tells the developer that method/class is there to stay.
JSDoc/JSDuck does not currently support @stable
but a similar feature has been proposed. While our generated docs won't do anything special when they see this tag, it'd be nice to be able to use it without eslint yelling.
Thus, I'm proposing we allowlist @stable
through the use of the definedTags option for the check-tag-names
rule.
Later, once the new frontend stable interface policy has matured, we may wish to be more granular like we do for PHP and tag things as "@stable to call", "@stable to extend", etc., but for now just being able to use "@stable" at all (without having to add an ignore rule) I think is a good start.
I have been thinking and researching about the topic of multiple vs. single var
statements recently and have changed my ways entirely. I switched from using var
do declare bunches of variables at the same time to using one var
per line. Here are some reasons, going so far that I would completely discourage from using a single var
for anything more complex than var a, b, c;
- Multiple
var
are better readable in diffs. Each line ends with a ";" and is completely self-contained. E.g.:
var whatever = 0,
whatever2 = 2;
vs. var whatever = 0;
var whatever2 = 2;
Two lines changed One line changed - The multiple
var
version will therefore keep the git blame for the first line in tact, which is a huge advantage. Changing a line of code as a side effect of syntactical sugar or preferences is bad.
- Huge difference when debugging. If you have a list of five variables using one
var
and you want to see the value of the second one before creating the third one, then this is not really possible in any debugger known to me since all five variables are created in one step. - Whenever tabs are displayed with 8 spaces, the whole one var thing looks incredibly ugly:
var foo = 123, bar = 321;
vs. var foo = 123; var bar = 321;
- When assigning initial values does not fit into one line, one is left with the question how to spread the assignment over multiple lines and I have seen various models, none of which makes me feel comfortable when looking at. Some examples (there are many more variations, you get the idea I hope):
// Quick skimming over the code could give you // the expression "xxx" is a variable as well. var foo = foo( xxx + "someverylongstring..." ), anothervar = false;
// less misleading but still kind of ugly var foo = foo( xxx + "someverylongstring..." ), anothervar = false;
var foo = { field: 1 }, bar = { field: 2 };
vs. var foo = { field: 1 }; var bar = { field: 2 };
or, adding tabs for
formatting the first
var as soon as the
second gets added, sucks
a lot when diffing again:var foo = {
■■■■ field: 1
■■■■};,
bar = {
field: 2
};
vs. var foo = {
field: 1
};
var bar = {
field: 2
};
- Multiple
var
offer higher aesthetics for comments on top of variables without even thinking about it:
// some comment about foo var foo = true, // some come comment about bar bar = false;
or (better, but haven't seen this one so often)
// some comment about foo var foo = true, // some come comment about bar bar = false;
vs. // some comment about foo var foo = true; // some come comment about bar var bar = false;
Compared to the first multi
var
version this gives more space for comments and does not look odd or like the one comment is more important than the other one.
Also, the first singlevar
version would look quite horrible in a diff when removing the first variable declaration.
Most of this has also been covered in a more extensive article by Ben Alman.
I am not saying we should change all var statements now, but add this to the coding guidelines and have an eye for it during code reviews. Currently the convention does even discourage from using multiple var
, I highly disagree with this.
Check eslint-config-wikimedia #360 on GitHub.
This landed last year; documentation updated.
What is the convention for quotes - double or single?
It has little meaning, but would be nice for consistency.
jQuery suggests double.
If I remember correctly it is single.
Single quotes indeed.
For JavaScript files quotation is purely stylistic as string literals in JavaScript are specified as either wrapped in double quotes or single quotes. There is no magic functionality or different escaping (e.g. no need for double quotes to use \n
or something).
In PHP we use both because of magic quotes, though in PHP we also prefer single quotes when magic functionality is not intended.