Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

refactor(jshint): don't assume browser-only globals #14345

Closed
wants to merge 1 commit into from

Conversation

mgol
Copy link
Member

@mgol mgol commented Mar 30, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Feature: We're no longer assuming browser globals in src/**/*.js which will prevent issues like #13442 from happening in the future.

What is the current behavior? (You can also link to an open issue here)

Currently browser globals are assumed. This sometimes breaks tools like jsdom that make them available under window but not as globals; see e.g. #13442.

What is the new behavior (if this is a feature change)?

For the browser these changes shouldn't matter. In other environments browser globals are now taken from the window variable instead of the global.

Does this PR introduce a breaking change?

No.

Please check if the PR fulfills these requirements

Other information:

Fixes #13442

@mgol
Copy link
Member Author

mgol commented Mar 30, 2016

I also updated JSHint.

@mgol
Copy link
Member Author

mgol commented Mar 30, 2016

@petebacondarwin I see JSHint failures in the docs gulp task. Which JSHint config is used to lint build/docs/**/*.js?

@petebacondarwin
Copy link
Contributor

@mgol I guess it is just the top level .jshintrc in the root of the project.

@petebacondarwin
Copy link
Contributor

If you want we could copy a docs specific .jshintrc into the build/docs folder as part of the gulp assets task.

@mgol
Copy link
Member Author

mgol commented Mar 30, 2016

Where are globals like by taken from? It surprised me to see errors like:

'by' is not defined

@gkalpak
Copy link
Member

gkalpak commented Mar 31, 2016

by should be from Protractor

WRT .jshintrc, I believe it's better to take a more granular approach, specifying globals in the directories they are needed. E.g. don't have something in the top level if we only need it in src/ or test/ etc.

@petebacondarwin
Copy link
Contributor

@gkalpak - in that case we need dgeni to create and provide such a file for every example that contains e2e tests.

@gkalpak
Copy link
Member

gkalpak commented Apr 1, 2016

Why is that ? Can't we just put the required globals in a parent directory's .jshintrc ?

@petebacondarwin
Copy link
Contributor

OK, so I forgot that the e2e tests all go in their own folder.
The relevant folders we have are:

docs
 - js
 - examples
 - ptore2e

So we should copy a baseline .jshintrc in the docs folder, then perhaps a specialized one inside the ptoe2e.

@mgol
Copy link
Member Author

mgol commented Apr 1, 2016

So we should copy a baseline .jshintrc in the docs folder

We can extend a common one, no need to copy all the settings. :)

@lgalfaso
Copy link
Contributor

lgalfaso commented Apr 1, 2016

Would it be possible to use $window in the cases that this is possible?

@mgol
Copy link
Member Author

mgol commented Apr 1, 2016

@lgalfaso It would but I'm pretty sure it'd break a lot of tests as people are mocking $window and not providing all necessary fields like setTimeout so I don't think that's something we should attempt in 1.5.x.

Also, we're currently providing globals window & document as parameters to the factory; we'd need to drop document from there and always use $window.document.

We might consider all that for 1.6 but I wanted to land something in 1.5.x and it seems too risky to me. WDYT?

@lgalfaso
Copy link
Contributor

lgalfaso commented Apr 1, 2016

Fair point, let's keep window

On Friday, April 1, 2016, Michał Gołębiowski notifications@github.com
wrote:

@lgalfaso https://github.com/lgalfaso It would but I'm pretty sure it'd
break a lot of tests as people are mocking $window and not providing all
necessary fields like setTimeout so I don't think that's something we
should attempt in 1.5.x.

Also, we're currently providing globals window & document as parameters
to the factory

})(window, document);
;
we'd need to drop document from there and always use $window.document.

We might consider all that for 1.6 but I wanted to land something in 1.5.x
and it seems too risky to me. WDYT?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#14345 (comment)

@gkalpak
Copy link
Member

gkalpak commented Apr 1, 2016

@mgol, now that you mention it, couldn't we just change document to window.document in angular.suffix#L5 and leave it as just document in most other places ?

@mgol
Copy link
Member Author

mgol commented Apr 1, 2016

@gkalpak I've already talked with @petebacondarwin about that. :) I plan to apply this change.

@mgol
Copy link
Member Author

mgol commented Apr 4, 2016

So, I've been wondering about this change. Since I'm removing "browser": true I need to specify each browser global that should be used directly in .jshintrc. But that would mean I need to add document there as well. If we go this route, we might also want to add window.setTimeout & friends to the factory parameters in

})(window, document);
but then they'd need to be added there as well... Perhaps we need one base .jshintrc for all source files that would declare these assumed browser globals? Or maybe we should just prefix all browser globals with window. and not do all that? Thoughts?

@petebacondarwin
Copy link
Contributor

Don't we have that situation right now?
The .jshintrc in the src folder is responsible (really) for the ng folder source files and the top level source files such as Angular.js and jqLite.js. Then each of the modules has its own .jshintrc. Each of these extends the jshintrc-base file.

@mgol
Copy link
Member Author

mgol commented Apr 5, 2016

The .jshintrc-base file is a base for the whole project but I want the few browser globals to only be defined in code run in browsers, not e.g. Node. We could perhaps get a separate .jshintrc-base created under src/ that would extend the main base config & add those globals from the factory and then extend this file?

@mgol
Copy link
Member Author

mgol commented Apr 5, 2016

OTH, as @lgalfaso mentioned, ideally we'd use $window where appropriate but we can't do it that way before 1.6.0. But if we want to do it in the end, keeping anything except window in the factory seems backwards. So we should decide if we want to change it in 1.6.0 and if the answer's yes I'd remove document from the factory & just prefix every non-core JS browser global with window..

@petebacondarwin
Copy link
Contributor

@mgol I think that your suggestion based on moving to using $window everywhere in 1.6 and prefixing all browser globals with window in 1.5 sounds reasonable. Do you want to have a go at that and see if it turns up any difficulties?

@mgol
Copy link
Member Author

mgol commented Apr 6, 2016

@petebacondarwin on it!

@mgol
Copy link
Member Author

mgol commented Apr 6, 2016

PR updated.

@mgol
Copy link
Member Author

mgol commented Apr 6, 2016

All tests have passed this time.

@petebacondarwin
Copy link
Contributor

Sweet!

@@ -88,7 +109,7 @@ gulp.task('assets', ['bower'], function() {
});


gulp.task('doc-gen', ['bower'], function() {
gulp.task('doc-gen', ['bower'], function(cb) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need the callback here

Copy link
Member Author

Choose a reason for hiding this comment

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

Right; I forgot to remove it after backtracking from a previous idea. :)

@@ -3,4 +3,4 @@
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {
(function(window) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that having undefined as a parameter, which is not assigned, is actually a clever way to ensure that we have the "real" undefined inside the closure. So I am for leaving this as a parameter.

Copy link
Member Author

Choose a reason for hiding this comment

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

What would it guard against, though? We've used to have the same parameter in jQuery but we removed it; if someone created a variable undefined that is not equal to undefined, basically everything would be broken.

Copy link
Member Author

Choose a reason for hiding this comment

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

IOW, shadowning undefined with sth else is more or less equivalent to shadowing any other builtin like Object, Array, Object.prototype methods etc. There are lots of ways in which someone can create a broken environment in which Angular breaks. I don't think it buys us much to defend just against shadowing undefined and it's impossible to guard against everything.

Copy link
Contributor

Choose a reason for hiding this comment

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

var old_undefined = undefined;
undefined = {};
function moo(undefined) {
  console.log(undefined === old_undefined);
}
moo();

Copy link
Member Author

Choose a reason for hiding this comment

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

I know what it does. :) What I'm saying is that it doesn't really buy us much; there are thousands other APIs that, if messed with, will break Angular and we don't protect against such changes. Moreover, I think it's more likely that someone will break Object.prototype for us than that they'll shadow undefined. I feel this undefined parameter is only providing a false sense of security.

Copy link
Contributor

Choose a reason for hiding this comment

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

It saves us from using void 0 everywhere, no?
I agree it doesn't save us from the other stuff, but we do have various bits of defensive code in place in the case that people do override parts of the Object.prototype.
What is the benefit in removing it?

Copy link
Member Author

Choose a reason for hiding this comment

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

It saves us from using void 0 everywhere, no?

I'd just rely on the undefined from the outer scope. Note that it's not even possible in ES5 to overwrite undefined, it's immutable even in sloppy mode (the only difference is that in strict mode trying to overwrite undefined throws while in sloppy mode it's just a noop). The only way this could backfire is if Angular was loaded not via a regular script tag but wrapped in a separate closure which shadowed undefined. But that requires modifying the source and if someone does that we've already lost the battle.

I agree it doesn't save us from the other stuff, but we do have various bits of defensive code in place in the case that people do override parts of the Object.prototype.

Since the global undefined is immutable if Angular is loaded normally so that its undefined points to the global one no one is able to modify that; shadowing is also impossible after the fact.

What is the benefit in removing it?

There is no huge benefit, perhaps a tiny size decrease. If you feel strongly about it, I'll restore it. :) I just think it doesn't really solve any problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, you convinced me :-)

@petebacondarwin
Copy link
Contributor

OK, LGTM - please merge

@mgol mgol closed this in ddad264 Apr 6, 2016
@mgol mgol deleted the non-browser branch April 6, 2016 18:36
mgol added a commit that referenced this pull request Apr 6, 2016
@gkalpak
Copy link
Member

gkalpak commented Apr 7, 2016

👍

Regarding the using $window in 1.6.x, won't that break lots of tests relying on a mocked $window ?

@mgol
Copy link
Member Author

mgol commented Apr 7, 2016

@gkalpak #14345 (comment) :P

That's why I didn't want to land it for 1.5. As for 1.6, we can still discuss.

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

Successfully merging this pull request may close these issues.

Uncaught ReferenceError: Node is not defined
5 participants