@coffeelint/cli
Version:
Lint your CoffeeScript
115 lines (104 loc) • 3.66 kB
JavaScript
(function() {
var EnsureComprehensions,
indexOf = [].indexOf;
module.exports = EnsureComprehensions = (function() {
class EnsureComprehensions {
lintToken(token, tokenApi) {
var atEqual, idents, numCallEnds, numCallStarts, numParenEnds, numParenStarts, peeker, prevIdents, prevToken, ref, ref1;
// Rules
// Ignore if normal for-loop with a block
// If LHS of operation contains either the key or value variable of
// the loop, assume that it is not a comprehension.
// Find all identifiers (including lhs values and parts of for loop)
idents = this.findIdents(tokenApi);
// if it looks like a for block, don't bother checking
if (this.forBlock) {
this.forBlock = false;
return;
}
peeker = -1;
atEqual = false;
numCallEnds = 0;
numCallStarts = 0;
numParenStarts = 0;
numParenEnds = 0;
prevIdents = [];
while ((prevToken = tokenApi.peek(peeker))) {
if (prevToken[0] === 'CALL_END') {
numCallEnds++;
}
if (prevToken[0] === 'CALL_START') {
numCallStarts++;
}
if (prevToken[0] === '(') {
numParenStarts++;
}
if (prevToken[0] === ')') {
numParenEnds++;
}
if (prevToken[0] === 'IDENTIFIER') {
if (!atEqual) {
prevIdents.push(prevToken[1]);
} else if (ref = prevToken[1], indexOf.call(idents, ref) >= 0) {
return;
}
}
if (((ref1 = prevToken[0]) === '(' || ref1 === '->' || ref1 === 'TERMINATOR') || (prevToken.newLine != null)) {
break;
}
if (prevToken[0] === '=' && numParenEnds === numParenStarts) {
atEqual = {token};
}
peeker--;
}
// If we hit a terminal node (TERMINATOR token or w/ property newLine)
// or if we hit the top of the file and we've seen an '=' sign without
// any identifiers that are part of the for-loop, and there is an equal
// amount of CALL_START/CALL_END tokens. An unequal number means the list
// comprehension is inside of a function call
if (atEqual && numCallStarts === numCallEnds) {
return {
token,
context: ''
};
}
}
findIdents(tokenApi) {
var idents, nextToken, peeker, ref;
peeker = 1;
idents = [];
while ((nextToken = tokenApi.peek(peeker))) {
if (nextToken[0] === 'IDENTIFIER') {
idents.push(nextToken[1]);
}
if ((ref = nextToken[0]) === 'FORIN' || ref === 'FOROF') {
break;
}
peeker++;
}
// now search ahead to see if this becomes a FOR block
while ((nextToken = tokenApi.peek(peeker))) {
if (nextToken[0] === 'TERMINATOR') {
break;
}
if (nextToken[0] === 'INDENT') {
this.forBlock = true;
break;
}
peeker++;
}
return idents;
}
};
EnsureComprehensions.prototype.rule = {
type: 'style',
name: 'ensure_comprehensions',
level: 'warn',
message: 'Comprehensions must have parentheses around them',
description: `This rule makes sure that parentheses are around comprehensions.`
};
EnsureComprehensions.prototype.tokens = ['FOR'];
EnsureComprehensions.prototype.forBlock = false;
return EnsureComprehensions;
}).call(this);
}).call(this);