@coffeelint/cli
Version:
Lint your CoffeeScript
162 lines (140 loc) • 5.05 kB
JavaScript
(function() {
var MissingFatArrows, any, containsButIsnt,
indexOf = [].indexOf;
any = function(arr, test) {
return arr.reduce((function(res, elt) {
return res || test(elt);
}), false);
};
containsButIsnt = function(node, nIsThis, nIsClass) {
var target;
target = void 0;
node.traverseChildren(false, function(n) {
if (nIsClass(n)) {
return false;
}
if (nIsThis(n)) {
target = n;
return false;
}
});
return target;
};
module.exports = MissingFatArrows = (function() {
class MissingFatArrows {
constructor() {
this.isCode = this.isCode.bind(this);
this.isClass = this.isClass.bind(this);
this.isValue = this.isValue.bind(this);
this.isObject = this.isObject.bind(this);
this.isThis = this.isThis.bind(this);
this.isFatArrowCode = this.isFatArrowCode.bind(this);
}
lintAST(node, astApi) {
this.astApi = astApi;
this.lintNode(node);
return void 0;
}
lintNode(node, methods = []) {
var error, isStrict, ref;
isStrict = (ref = this.astApi.config[this.rule.name]) != null ? ref.is_strict : void 0;
if (this.isPrototype(node)) {
return;
}
if (this.isConstructor(node)) {
return;
}
// Ignore any nodes we know to be methods
if ((!this.isFatArrowCode(node)) && (isStrict ? true : indexOf.call(methods, node) < 0) && (this.needsFatArrow(node))) {
error = this.astApi.createError({
lineNumber: node.locationData.first_line + 1,
columnNumber: node.locationData.first_column + 1
});
this.errors.push(error);
}
return node.eachChild((child) => {
return this.lintNode(child, (function() {
switch (false) {
case !this.isClass(node):
return this.methodsOfClass(node);
// Once we've hit a function, we know we can't be in the top
// level of a method anymore, so we can safely reset the methods
// to empty to save work.
case !this.isCode(node):
return [];
default:
return methods;
}
}).call(this));
});
}
isCode(node) {
return this.astApi.getNodeName(node) === 'Code';
}
isClass(node) {
return this.astApi.getNodeName(node) === 'Class';
}
isValue(node) {
return this.astApi.getNodeName(node) === 'Value';
}
isObject(node) {
return this.astApi.getNodeName(node) === 'Obj';
}
isPrototype(node) {
var i, ident, len, props, ref, ref1;
props = (node != null ? (ref = node.variable) != null ? ref.properties : void 0 : void 0) || [];
for (i = 0, len = props.length; i < len; i++) {
ident = props[i];
if (((ref1 = ident.name) != null ? ref1.value : void 0) === 'prototype') {
return true;
}
}
return false;
}
isThis(node) {
return this.isValue(node) && node.base.value === 'this';
}
isFatArrowCode(node) {
return this.isCode(node) && node.bound;
}
isConstructor(node) {
var ref, ref1;
return ((ref = node.variable) != null ? (ref1 = ref.base) != null ? ref1.value : void 0 : void 0) === 'constructor';
}
needsFatArrow(node) {
return this.isCode(node) && (any(node.params, (param) => {
return param.contains(this.isThis) != null;
}) || containsButIsnt(node.body, this.isThis, this.isClass));
}
methodsOfClass(classNode) {
var bodyNodes, returnNode;
bodyNodes = classNode.body.expressions;
returnNode = bodyNodes[bodyNodes.length - 1];
if ((returnNode != null) && this.isValue(returnNode) && this.isObject(returnNode.base)) {
return returnNode.base.properties.map(function(assignNode) {
return assignNode.value;
}).filter(this.isCode);
} else {
return [];
}
}
};
MissingFatArrows.prototype.rule = {
type: 'problem',
name: 'missing_fat_arrows',
level: 'ignore',
is_strict: false,
message: 'Used `this` in a function without a fat arrow',
description: `Warns when you use \`this\` inside a function that wasn't defined
with a fat arrow. This rule does not apply to methods defined in a
class, since they have \`this\` bound to the class instance (or the
class itself, for class methods). The option \`is_strict\` is
available for checking bindings of class methods.
It is impossible to statically determine whether a function using
\`this\` will be bound with the correct \`this\` value due to language
features like \`Function.prototype.call\` and
\`Function.prototype.bind\`, so this rule may produce false positives.`
};
return MissingFatArrows;
}).call(this);
}).call(this);