Is it safe to apply autofix from ESLint? - javascript

I'd like to format several files but I'm worried about breaking code.
When I'm saying if it is safe for not, I'm saying that the execution of the code before and after is exactly the same.
For instance, there are rules that look pretty safe to be applied (like indenting). I suppose that changing the number of spaces won't affect the execution of the code.
Then, there are rules that don't look so safe. For instance, changing var to let or const could cause a different execution as var is not exactly the same as let.
So, I'm wondering if there are any auto-fix rule from ESLint that can change the code so the execution is different when applied the --fix thing.

Yes, it's safe, because the --fix flag does not fix all your JS issues [1].
So you have to fix some eslint warnings/errors yourself.
[1] https://eslint.org/docs/user-guide/command-line-interface#--fix

Mostly yes, but I recently came across an issue when using the autofix option. The codebase I work with uses Mithril v.0.2, which initializes the component's controller using the new keyword. The new keyword only works on constructors, but the eslint --fix command (specifically the prefer-arrow-callback replaced some of my anonymous functions with arrow functions. Which caused some errors, since arrow functions are not constructors.
So my case was like this:
const component = {};
// My method in this object used to be a regular function like the one below
component.normalFunction = function () {
return {
test: true
};
}
// Eslint autofix changed it to be an arrow function
component.arrowFunction = () => {
return {
test: true
};
}
try {
new component.normalFunction();
new component.arrowFunction();
} catch (err) {
document.body.prepend(err);
}
So far that was the only issue I have found with the autofix rule. In my case I ran eslint autofix excluding that rule.

Related

ESLint prefer-arrow-functions not flagging correctly

Currently working on an .eslintrc file and I have implemented this so far
"prefer-arrow/prefer-arrow-functions": [
"warn",
{
"disallowPrototype": false,
"singleReturnOnly": true,
"classPropertiesAllowed": false,
"allowStandaloneDeclarations": true
}
],
const a = activeABTests.reduce(function(accumulator: number, abtest: ABTestFunnelType): number {
I want this function above ^ to flag. It is not warning me in the Github check. It should flag and ask to be converted to an arrow function that looks like this:
const a = activeABTests.reduce((accumulator: number, abtest: ABTestFunnelType) => number {
I don't want to flag functions like this. I think the allowStandaloneDeclarations was the fix, but I am not sure.
I am getting two types of warnings:
Warning: 105:36 warning Prefer using arrow functions over plain functions prefer-arrow/prefer-arrow-functions
Warning: 31:5 warning Use const or class constructors instead of named functions prefer-arrow/prefer-arrow-functions
What can I do to make sure that the right functions are being flagged? I'm currently getting a ton of warnings but want to eliminate them/make sure they are the right ones.

OnChange function not defined when I reference my script as a module? [duplicate]

I have my HTML setup like this:
<script type="module" src="main.js"></script>
and all the ES6 modules work fine. The only problem is I now can't refer to anything from within DevTools (like using the Console and typing in a variable to see it's value or using a function manually).
How do I import modules whilst being able to use the DevTools? Thanks!
One way to make a variable accessable within DevTools is to create it on the window object:
// Variable in your module
var importantNumber = 1;
window.importantNumber = importantNumber;
This method works fine if you just have a couple of variables, but if you need to have access to a lot more variables within DevTools, I would recommend you go to the sources-tab in DevTools, search for your module and adding a breakpoint. When the execution pauses, you have access to all the variables within that module on the DevTools console.
If you want to be able to refer to variables created within the module from the console's global scope, you'll have to deliberately expose each such variable that you want to be visible from the console. Either assign each variable to window (probably not a good idea - the whole point of modules is to make things more modular, without global pollution), or perhaps assign a single object to window, to which you assign module variables. For example:
// in the console:
setTimeout(() => {
window.myModule.foo();
console.log(window.myModule.bar);
});
<script type="module">
function foo() {
console.log('doing foo');
}
const bar = 'a string bar';
const thisModule = { foo, bar };
window.myModule = thisModule;
// If you ever reassign variables, you'll have to reassign them on thisModule too
// or, only reference and reassign properties of thisModule, rather than create independent variables
</script>
For anyone else interested, if you're comfortable with it, use a bundler like Webpack. I don't believe (at least at this point) that the browser will by itself be able to use the DevTools on modules (the other solutions are quite janky, and aren't fantastic to work with).
Hopefully in the future, when all major browsers will be able to support ES6 modules without a bundler, we'll be able to use DevTools.
Using a Helper
I personally use a little helper function in development that allows me to expose a bunch a variables in a single expression. For example, it makes the following two blocks equivalent:
window.playerOne = playerOne;
window.someClass = someClass;
window.localData = localData;
globalize({playerOne, someClass, localData});
The helper looks like this:
const globalize = function(variables) {
Object.entries(variables).forEach(([name, value]) => window[name] = value);
};

Retrieve the current test's name within a Mocha test using Typescript

This question is very similar to this one, but has one very significant difference: typescript is used.
I'm trying to get current test title from within the mocha test, but since typescript is used this code does not work:
import 'mocha';
describe("top", () => {
console.log(this.title);
console.log(this.fullTitle());
it("test", () => {
console.log(this.test.title);
console.log(this.test.fullTitle());
});
});
Typescripts obscures this and access to native JavaScript's this is not possible anymore.
Has anyone faced this situation before? Is there any workaround for it?
Your problem isn't that you use TypeScript, it's that you use arrow functions.
Arrow functions automatically bind this to the this where the function is defined.
Since they all use arrow functions, your this is the this found at the global level, which is either global outside of strict mode, or undefined in strict mode. (Since you're using ES modules, per-spec you are automatically in strict mode)
import 'mocha';
describe("top", function() {
console.log(this.title);
console.log(this.fullTitle());
it("test", function() {
console.log(this.test.title);
console.log(this.test.fullTitle());
});
});

Angular controllerAs and babel

I'm creating an Angular app using ES6 features, so I'm using Babel to transpile JS. I decided not to use ES6 modules, so instead of regular es2015 preset I'm using es2015-script.
The problem is with the way Babel compiles arrow functions. I'm using Angular's controllerAs notation, like this
angular.module('appControllers')
.controller('StoryController', ['StoryProvider', (StoryProvider)=>{
this.plaintext='test plain';
}]);
this code is transpiled to
var _this = this;
angular.module('myControllers')
.controller('StoryController', ['StoryProvider', function (StoryProvider)
{
_this.plaintext = 'test plain';
}]);
so this is in completely wrong scope and binding is not working. When I manually changed compiled code to
angular.module('myControllers')
.controller('StoryController', ['StoryProvider', function (StoryProvider)
{
var _this = this;
_this.plaintext = 'test plain';
}]);
it all works fine.
Why is Babel setting the intermediate _this variable this way? is there another Babel preset to handle this properly? Should I go with ES6 modules? I had the same problem as described in this issue angular 1.5 component and babel in the first place, that's why I used the es2015-script preset.
Ok so there are several approaches here
1) use ES6 class
class StoryController{
constructor(StoryProvider){
this.plaintext='test plain';
this.StoryProvider=StoryProvider;
}
}
this adds about 1k of helper functions, plus as you can see, we need to save injected dependencies in the constructor.
2) get rid of arrow function in the main controller definition and use it only in methods. something like
function StoryController(StoryProvider){
this.plaintext='test plain';
this.asyncTest=()=>{
setTimeout(()=>{
console.log(this.plaintext);
}, 100);
};
}
this transpiles to a nice
function StoryController(StoryProvider) {
var _this = this;
this.plaintext = 'test plain';
this.asyncTest = function () {
setTimeout(function () {
console.log(_this.plaintext);
}, 100);
};
}
which properly scopes the _this variable.
The alternate solutions in your answer appear valid, but I'd like to provide an answer to the question:
Why is Babel setting the intermediate _this variable this way?
First, some background information. According to MDN:
Until arrow functions, every new function defined its own this value (a new object in case of a constructor, undefined in strict mode function calls, the context object if the function is called as an "object method", etc.). This proved to be annoying with an object-oriented style of programming.
The thing to understand about fat-arrow functions is that they aren't just a shorter syntax for creating a function - they create a function without it's own lexical scope. This means that when you use this.plaintext inside a fat-arrow function (in non-strict mode,) this isn't being set on the controller as you expect, but on the global object.
Now about Babel.
Babel is doing it's best to make your transpiled code behave exactly like the source (that's the point, right?) So when it has to rewrite your fat-arrow function using an ES5-compatible function declaration, it has to pull in the reference to the global object from somewhere. So, on the preceding line, it grabs a reference to it as _this and uses that inside your fat-arrow function, because it thinks that's what you wanted.
As you showed in your question, the simplest solution is to replace the fat-arrow function with a standard function declaration in the first place, so that you'll have a lexical scope to hang things on.

Are there any dangers associated with using JavaScript namespaces?

Are there any dangers/caveats one should be aware of when creating JavaScript namespaces?
Our project is fairly expansive and we are running a lot of JavaScript files (20+, expecting more). It is impossible to have any code maintainability without using namespaces, so we are implementing them like so:
var namespace1 = {
doSomething: function() {
...
},
doSomethingElse: function() {
...
}
}
And then to create hierarchies, we link them like so:
var globalNamespace = {
functions1: namespace1,
functions2: namespace2,
...
}
This works fine, but it is essentially a "trick" to make JS behave as if it did have namespaces. Although this method gets used a lot, most literature on this seems to focus on how to do it, and not whether there are any possible drawbacks. As we write more JS code, this is quickly becoming an integral part of the way our system works. So it's important that it works seamlessly.
Were there any situations in which this "induced" namespace system caused you errors, or otherwise needed special attention? Can we safely expect identical behaviour across all browsers?
The way you define namespaces in your example it appears to create globals out of each namespace so you end up with
window.namespace1
window.namespace2
window.globalNamespace
window.globalNamespace.namespace1
window.globalNamespace.namespace2
So if you have anything that clobbers window.namespace1 it will also clobber window.globalNamespace.namespace1
edit:
Here's how we got around this problem:
namespacing = {
init: function(namespace) {
var spaces = [];
namespace.split('.').each(function(space) {
var curSpace = window,
i;
spaces.push(space);
for (i = 0; i < spaces.length; i++) {
if (typeof curSpace[spaces[i]] === 'undefined') {
curSpace[spaces[i]] = {};
}
curSpace = curSpace[spaces[i]];
}
});
}
};
Then you use it like this:
namespacing.init('globalNamespace.namespace1');
globalNamespace.namespace1.doSomething = function() { ... };
This way you don't have to introduce new global variables and you can confidently add to an existing namespace without clobbering other objects in it.
Since you are basically adding functions to objects and those objects into other objects, I would expect each browser to handle this the same way.
But if you want modularity, why not use a (relatively) simple framework like require.js? That will allow you and your team to write code in a modular fashion and allows the team to 'import' these modules where needed:
require(["helper/util"], function() {
//This function is called when scripts/helper/util.js is loaded.
});
Require.js will take care of dependencies, and it will also prevent polluting the global namespace.
We use a similar system at work and it does the job just fine. I don't see any drawbacks there could be; it's just objects and properties. For that same reason, cross browser compatibility should be good. You can end up having to write some long names to resolve to a particular function, like Foo.Bar.Test.Namespace2.Function, but even then that can be solved by assigning it to a variable before hand.
This is how I'd recommend doing it, so you stay out of the global scope entirely except for your "base" namespace. We do something similar where I work. Let's say you work for Acme co, and want ACME to be your base namespace.
At the top of every file, you'd include:
if (!window.ACME) { window.ACME = {} }
Then you just go and define whatever you want in terms of that.
ACME.Foo = {
bar: function () { console.log("baz"); }
}
If you want a deeper level of namespace, you just do the same thing for each level.
if (!window.ACME) { window.ACME = {} }
if (!ACME.Foo) { ACME.Foo = {} }
This way each file can be tested independently and they'll set up the namespace infrastructure automatically, but when you compile them together or if you test multiple files simultaneously, they won't keep overwriting things that are already defined.

Categories