Skip to content
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

Use standard JavaScript Style #4909

Merged
merged 48 commits into from Apr 1, 2016
Merged

Use standard JavaScript Style #4909

merged 48 commits into from Apr 1, 2016

Conversation

zeke
Copy link
Contributor

@zeke zeke commented Mar 24, 2016

This pull request migrates all of electron's JavaScript code to standard style.

Why use standard on a project like electron? The standard README summarizes it well:

Adopting standard style means ranking the importance of code clarity and community conventions higher than personal style. This might not make sense for 100% of projects and development cultures, however open source can be a hostile place for newbies. Setting up clear, automated contributor expectations makes a project healthier.

The Rules

  • 2 spaces – for indentation
  • Single quotes for strings – except to avoid escaping
  • No unused variables – this one catches tons of bugs!
  • No semicolonsIt's fine. Really!
  • Never start a line with ( or [
    • This is the only gotcha with omitting semicolons – automatically checked for you!
    • More details
  • Space after keywords if (condition) { ... }
  • Space after function name function name (arg) { ... }
  • Always use === instead of == – but obj == null is allowed to check null || undefined.
  • Always handle the node.js err function parameter
  • Always prefix browser globals with window – except document and navigator are okay
    • Prevents accidental use of poorly-named browser globals like open, length,
      event, and name.

For more details, see https://github.com/feross/standard/blob/master/RULES.md

The Refactor

I kicked off the refactor using standard-format to automatically convert much of the code. This got me a long way, but the tool has its share of issues. Sometimes it chokes on files containing valid syntax, and sometimes it overlooks files that contain invalid syntax. ¯\_(ツ)_/¯

After squeezing everything I could out of standard-format, I removed it from the dependencies because we probably won't need automated code refactoring once this refactor rolls out.

Next I installed snazzy, a wrapper around standard that produces glamorous and colorful output. This was nice because it made the process of manual code cleanup much more... pleasant. As I made changes, I committed them often to ensure the test suite continued to pass.

Once I got all the code cleaned up, I uninstalled snazzy in favor of vanilla standard for a few reasons:

  1. standard is more actively maintained than snazzy
  2. standard is a more recognizable name for the dependency
  3. snazzy's output is a little buggy sometimes

The Most Common Fixes

Here's a sorted list of the most common errors, generated by standard-summary:

5966   Extra semicolon
1615   Missing space before function parentheses
380    'it' is not defined
318    Strings must use singlequote
206    'describe' is not defined
55     Return statement should not contain assignment
52     '$' is not defined
38     Expected { after 'if' condition
26     'afterEach' is not defined
24     Expected '===' and instead saw '=='
23     Missing '()' invoking a constructor
17     Block must not be padded by blank lines
16     Unexpected trailing comma
13     Multiple spaces found before '+='
12     The '__proto__' property is deprecated
10     Expected space(s) after "catch"
9      Multiple spaces found before '='
8      Expected '!==' and instead saw '!='
8      Gratuitous parentheses around expression
7      More than 1 blank line not allowed
7      Expected { after 'else'
6      Unexpected use of comma operator
5      Use path.join() or path.resolve() instead of + to create paths
5      Do not use 'new' for side effects
3      Infix operators must be spaced
...

Globals

The test suite contains a number of global variables, mostly from mocha: after, afterEach, before, beforeEach, Blob, btoa, describe, DevToolsAPI, Event, fetch, HTMLElement, HTMLObjectElement, InspectorFrontendHost, it, location, MutationObserver, Response, self, SharedWorker, URL, WebInspector, WebSocket, WebView, Worker, xdescribe, xit.

These globals are whitelisted in a few different ways:

  1. a standard.globals array in package.json is used for the most common repeat offenders, like mocha and jquery.

  2. a /* globals foo, bar */ comment at the top of the offending file, for less commonly used globals.

    For more info, see the standard docs about globals

{
  "standard": {
    "globals": ["$", "after", "afterEach", "before", "beforeEach", "describe", "it", "location"]
  }
}

Ignoring Stuff

In package.json, there's an ignore array that accepts glob patterns for files and directories:

{
  "standard": {
    "ignore": ["/vendor"],
  }
}

To ignore a few lines of code, wrap them in these comments:

/* eslint-disable */
// wild and crazy javascript goes in here!
/* eslint-enable */

To ignore a single line of code, use a // eslint-disable-line comment:

 let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))) // eslint-disable-line

CoffeeScript Legacy

Back in January, the electron codebase was converted from Coffeescript to JavaScript. This automated refactor left behind a number of code artifacts, some of which have been cleaned up here.

All implicit returns that were doing assignment have been removed (e.g. return foo = 1). There are undoubtedly still lots of unneeded (yet syntactically valid) implicit returns left over.

There's still plenty of leftover cruft from the coffeescript conversion, but most of it is harmless. Any time you see ref = in a javascript file, it's pretty safe to assume that it's old coffee code that could use some cleanup.

Getting and Setting Prototype

// old setter
thing.__proto__ = EventEmitter.prototype

// new setter
Object.setPrototypeOf(thing, EventEmitter.prototype)

// old getter
thing.__proto__

// new getter
Object.getPrototypeOf(thing)

Invoking Constructors

It works to leave off the parens, but don't do it:

// no
new Thing

// yes
new Thing()

Merging this PR

As @paulcbetts recently observed:

On a more important tip, for older projects, try to get as many PRs as possible merged before you make the conversion, because the rest of the PRs will get rekt by merge conflicts

As of this writing, there are 15 open pull requests. We should decide which of those we want to land before this refactor, and which we should close or rebase post-standard.

Given the haphazard and compulsively granular commit history in this branch, I'd be happy to squash it all into a single commit if that's preferred by the team.

@ungoldman
Copy link
Contributor

Woo cool!

@zeke zeke changed the title [WIP] Standard Use standard JavaScript Style Mar 29, 2016
@zeke
Copy link
Contributor Author

zeke commented Mar 29, 2016

This is ready for review!

@jlord @kevinsawicki @zcbenz @simurai

@anaisbetts
Copy link
Contributor

@zeke You're doin' the Lord's work, 👍

@kevinsawicki
Copy link
Contributor

The test suite contains a number of global variables, mostly from mocha

Is it possible to configure the mocha globals as only available inside the spec/ folder? So that using it in a spec doesn't throw a standard error, but doing so in lib/ would.

@@ -4,16 +4,32 @@
"devDependencies": {
"asar": "^0.10.0",
"eslint": "^2.1.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this now be removed?

@anaisbetts
Copy link
Contributor

Is it possible to configure the mocha globals as only available inside the spec/ folder? So that using it in a spec doesn't throw a standard error, but doing so in lib/ would.

Just include a .eslintrc in that folder with mocha: true in the env

@kevinsawicki
Copy link
Contributor

To ignore a few lines of code, wrap them in these comments:

@zeke do you think it is possible to avoid these completely in this PR? It would seem ideal to not have any pollution of standard/eslint comments like previously.

@zeke
Copy link
Contributor Author

zeke commented Mar 30, 2016

Just include a .eslintrc in that folder with mocha: true in the env

@paulcbetts are you sure that works with standard? I just created an rc file per the eslint.org docs, but it doesn't seem to be respected. Am I doing this right?

❯ cat spec/.eslintrc.js 
module.exports = {
  env: {
    mocha: true
  }
}

~/electron/electron standard*
❯ standard | head -5 

spec/api-app-spec.js:9:1: 'describe' is not defined.
spec/api-app-spec.js:10:3: 'it' is not defined.
spec/api-app-spec.js:14:3: 'it' is not defined.
spec/api-app-spec.js:28:1: 'describe' is not defined.
spec/api-app-spec.js:29:3: 'describe' is not defined.

It would seem ideal to not have any pollution of standard/eslint comments

@kevinsawicki I agree. There were just a handful of spots in the code where I couldn't easily figure out how to refactor it without breaking the test suite. Perhaps we can pair tomorrow and try to knock them out?

@kevinsawicki
Copy link
Contributor

Perhaps we can pair tomorrow and try to knock them out?

Sounds great 👍 ✖️ 💯

@kevinsawicki
Copy link
Contributor

are you sure that works with standard?

Yeah, I wasn't clear if it did either.

Since there is a spec/package.json, perhaps the globals config could go directly in there?

@anaisbetts
Copy link
Contributor

spec/.eslintrc.js

Edit: Hm, I have no idea if it works with standard, just copy-paste their eslintrc if it doesn't

@zeke
Copy link
Contributor Author

zeke commented Mar 30, 2016

@paulcbetts:

It doesn't end with .js, just .eslintrc

I actually tried that format first, then tried the javascript format described in the eslint.org docs:

JavaScript - use .eslintrc.js and export an object containing your configuration.

@kevinsawicki:

Since there is a spec/package.json, perhaps the globals config could go directly in there?

Good idea, but it didn't work. Perhaps @feross can give us some pointers?

@feross
Copy link
Contributor

feross commented Mar 30, 2016

There's currently no way use .eslintrc files in subfolders to override the default rule set. This was an intentional design decision. I'm sorry that it's causing trouble for you in this situtation 😢

If setting mocha: true at the top-level is not acceptable, here's an alternate solution:

Add your globals to spec/package.json, change the top-level package.json script to ignore spec/ and run standard separately for the spec/ folder, like this:

{
  "lint": "standard && standard spec && python ./script/cpplint.py",
}

@zeke
Copy link
Contributor Author

zeke commented Mar 30, 2016

I tested the standard && standard spec approach and it works. It's implemented in a separate branch. Here's the commit: 525f3cc

My personal preference would be to keep all the config in the top-level package.json for the sake of discoverability/simplicity, and for the composability of running one command to lint all the javascript, instead of two. See https://github.com/atom/electron/blob/e342d65abb9d4df85fd508b55669b9eaf2c77681/package.json#L13-L24

The difference are subtle though. Either approach works for me.

@zeke
Copy link
Contributor Author

zeke commented Mar 30, 2016

@kevinsawicki and I paired on this for a bit today. Here's what we got done:

  • Removed all but one eslint style exception. (eval)
  • Split out the standard configuration into /package.json and /spec/package.json
  • Removed all inline /* globals ... */ comments in favor of standard.env declarations in /spec/package.json
  • Got the CI builds passing! (Except for Windows 64, which timed out; Kevin says it's flaky)

Kevin said he'll review the outstanding pull requests and merge any that seem like they need to land first. Barring any other feedback or concerns from the rest of the team, this is ready to 🐑!

})();
var _isVisible = true
var _isMinimized = false
;(function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this leading ; required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'tis. We could name the function, then invoke it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could name the function, then invoke it.

I'm in favor of doing this ☝️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 8a159de

@kevinsawicki
Copy link
Contributor

Kevin said he'll review the outstanding pull requests and merge any that seem like they need to land first.

I landed one and rebased this branch, no plans to merge any others before this lands.

@zcbenz
Copy link
Member

zcbenz commented Apr 1, 2016

Let's merge this so we can merge other PRs.

@zcbenz zcbenz merged commit 7023528 into master Apr 1, 2016
@zcbenz zcbenz deleted the standard branch April 1, 2016 02:13
@ichpuchtli
Copy link

RIP git blame functionality

@zcbenz
Copy link
Member

zcbenz commented Apr 1, 2016

We have lost him long time ago when converting coffee to js.

@bbondy
Copy link
Contributor

bbondy commented Apr 1, 2016

@ichpuchtli you can git blame more than one level deep.
@zeke nice work!

@joeytwiddle
Copy link

I was curious what @bbondy meant regard multi-level git blame.

So far I found git log -L, git gui, the recursive-blame package, and git log -S.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants