coffeelint/lib/coffeelint.js in coffeelint-0.3.0 vs coffeelint/lib/coffeelint.js in coffeelint-0.4.0

- old
+ new

@@ -1,19 +1,20 @@ !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.coffeelint=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ module.exports={ "name": "coffeelint", "description": "Lint your CoffeeScript", - "version": "1.4.1", + "version": "1.6.1", "homepage": "http://www.coffeelint.org", "keywords": [ "lint", "coffeescript", "coffee-script" ], "author": "Matthew Perpick <clutchski@gmail.com>", "main": "./lib/coffeelint.js", "engines": { + "npm": ">=1.3.7", "node": ">=0.8.0" }, "repository": { "type": "git", "url": "git://github.com/clutchski/coffeelint.git" @@ -23,12 +24,13 @@ }, "dependencies": { "browserify": "~3.37", "coffee-script": "~1.7", "coffeeify": "~0.6.0", - "glob": ">=3.1.9", - "optimist": ">=0.2.8", + "glob": "^4.0.0", + "ignore": "^2.2.15", + "optimist": "^0.6.1", "resolve": "^0.6.3" }, "devDependencies": { "vows": ">=0.6.0", "underscore": ">=1.4.4" @@ -39,18 +41,18 @@ "url": "http://github.com/clutchski/coffeelint/raw/master/LICENSE" } ], "scripts": { "pretest": "cake compile", - "test": "coffee vowsrunner.coffee --spec test/*.coffee test/*.litcoffee", + "test": "./vowsrunner.js --spec test/*.coffee test/*.litcoffee", "posttest": "npm run lint", "prepublish": "cake prepublish", "publish": "cake publish", "install": "cake install", - "lint": "cake compile && ./bin/coffeelint -f coffeelint.json src/*.coffee test/*.coffee test/*.litcoffee", - "lint-csv": "cake compile && ./bin/coffeelint --csv -f coffeelint.json src/*.coffee test/*.coffee", - "lint-jslint": "cake compile && ./bin/coffeelint --jslint -f coffeelint.json src/*.coffee test/*.coffee", + "lint": "cake compile && ./bin/coffeelint .", + "lint-csv": "cake compile && ./bin/coffeelint --csv .", + "lint-jslint": "cake compile && ./bin/coffeelint --jslint .", "compile": "cake compile" } } },{}],2:[function(_dereq_,module,exports){ @@ -276,21 +278,22 @@ CoffeeLint Copyright (c) 2011 Matthew Perpick. CoffeeLint is freely distributable under the MIT license. */ -var ASTLinter, CoffeeScript, ERROR, IGNORE, LexicalLinter, LineLinter, RULES, WARN, coffeelint, cs, defaults, difference, extend, hasSyntaxError, mergeDefaultConfig, packageJSON, _rules, +var ASTLinter, CoffeeScript, ERROR, ErrorReport, IGNORE, LexicalLinter, LineLinter, RULES, WARN, cache, coffeelint, defaults, difference, extend, hasSyntaxError, mergeDefaultConfig, nodeRequire, packageJSON, _rules, __slice = [].slice, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; coffeelint = exports; +nodeRequire = _dereq_; + if (typeof window !== "undefined" && window !== null) { CoffeeScript = window.CoffeeScript; } else { - cs = 'coffee-script'; - CoffeeScript = _dereq_(cs); + CoffeeScript = nodeRequire('coffee-script'); } packageJSON = _dereq_('./../package.json'); coffeelint.VERSION = packageJSON.version; @@ -338,10 +341,12 @@ LexicalLinter = _dereq_('./lexical_linter.coffee'); ASTLinter = _dereq_('./ast_linter.coffee'); +cache = null; + mergeDefaultConfig = function(userConfig) { var config, rule, ruleConfig; config = {}; for (rule in RULES) { ruleConfig = RULES[rule]; @@ -405,10 +410,21 @@ } RULES[p.rule.name] = p.rule; return _rules[p.rule.name] = RuleConstructor; }; +coffeelint.getRules = function() { + var key, output, _i, _len, _ref; + output = {}; + _ref = Object.keys(RULES).sort(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + output[key] = RULES[key]; + } + return output; +}; + coffeelint.registerRule(_dereq_('./rules/arrow_spacing.coffee')); coffeelint.registerRule(_dereq_('./rules/no_tabs.coffee')); coffeelint.registerRule(_dereq_('./rules/no_trailing_whitespace.coffee')); @@ -461,26 +477,44 @@ coffeelint.registerRule(_dereq_('./rules/no_interpolation_in_single_quotes.coffee')); coffeelint.registerRule(_dereq_('./rules/no_empty_functions.coffee')); +coffeelint.registerRule(_dereq_('./rules/prefer_english_operator.coffee')); + hasSyntaxError = function(source) { try { CoffeeScript.tokens(source); return false; } catch (_error) {} return true; }; +ErrorReport = _dereq_('./error_report.coffee'); + +coffeelint.getErrorReport = function() { + return new ErrorReport(coffeelint); +}; + coffeelint.lint = function(source, userConfig, literate) { - var all_errors, astErrors, block_config, cmd, config, disabled, disabled_initially, e, errors, i, l, lexErrors, lexicalLinter, lineErrors, lineLinter, name, next_line, r, rules, s, tokensByLine, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4; + var all_errors, astErrors, block_config, cmd, config, disabled, disabled_initially, e, errors, i, l, lexErrors, lexicalLinter, lineErrors, lineLinter, name, next_line, r, ruleLoader, rules, s, tokensByLine, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4; if (userConfig == null) { userConfig = {}; } if (literate == null) { literate = false; } + try { + ruleLoader = nodeRequire('./ruleLoader'); + ruleLoader.loadFromConfig(this, userConfig); + } catch (_error) {} + if (cache != null) { + cache.setConfig(userConfig); + } + if (cache != null ? cache.has(source) : void 0) { + return cache != null ? cache.get(source) : void 0; + } if (literate) { source = this.invertLiterate(source); } for (name in userConfig) { if (name !== 'coffeescript_error' && name !== '_comment') { @@ -560,15 +594,114 @@ errors.push(e); } } } } + if (cache != null) { + cache.set(source, errors); + } return errors; }; +coffeelint.setCache = function(obj) { + return cache = obj; +}; -},{"./../package.json":1,"./ast_linter.coffee":2,"./lexical_linter.coffee":5,"./line_linter.coffee":6,"./rules.coffee":7,"./rules/arrow_spacing.coffee":8,"./rules/camel_case_classes.coffee":9,"./rules/colon_assignment_spacing.coffee":10,"./rules/cyclomatic_complexity.coffee":11,"./rules/duplicate_key.coffee":12,"./rules/empty_constructor_needs_parens.coffee":13,"./rules/indentation.coffee":14,"./rules/line_endings.coffee":15,"./rules/max_line_length.coffee":16,"./rules/missing_fat_arrows.coffee":17,"./rules/newlines_after_classes.coffee":18,"./rules/no_backticks.coffee":19,"./rules/no_debugger.coffee":20,"./rules/no_empty_functions.coffee":21,"./rules/no_empty_param_list.coffee":22,"./rules/no_implicit_braces.coffee":23,"./rules/no_implicit_parens.coffee":24,"./rules/no_interpolation_in_single_quotes.coffee":25,"./rules/no_plusplus.coffee":26,"./rules/no_stand_alone_at.coffee":27,"./rules/no_tabs.coffee":28,"./rules/no_throwing_strings.coffee":29,"./rules/no_trailing_semicolons.coffee":30,"./rules/no_trailing_whitespace.coffee":31,"./rules/no_unnecessary_double_quotes.coffee":32,"./rules/no_unnecessary_fat_arrows.coffee":33,"./rules/non_empty_constructor_needs_parens.coffee":34,"./rules/space_operators.coffee":35}],5:[function(_dereq_,module,exports){ + +},{"./../package.json":1,"./ast_linter.coffee":2,"./error_report.coffee":5,"./lexical_linter.coffee":6,"./line_linter.coffee":7,"./rules.coffee":8,"./rules/arrow_spacing.coffee":9,"./rules/camel_case_classes.coffee":10,"./rules/colon_assignment_spacing.coffee":11,"./rules/cyclomatic_complexity.coffee":12,"./rules/duplicate_key.coffee":13,"./rules/empty_constructor_needs_parens.coffee":14,"./rules/indentation.coffee":15,"./rules/line_endings.coffee":16,"./rules/max_line_length.coffee":17,"./rules/missing_fat_arrows.coffee":18,"./rules/newlines_after_classes.coffee":19,"./rules/no_backticks.coffee":20,"./rules/no_debugger.coffee":21,"./rules/no_empty_functions.coffee":22,"./rules/no_empty_param_list.coffee":23,"./rules/no_implicit_braces.coffee":24,"./rules/no_implicit_parens.coffee":25,"./rules/no_interpolation_in_single_quotes.coffee":26,"./rules/no_plusplus.coffee":27,"./rules/no_stand_alone_at.coffee":28,"./rules/no_tabs.coffee":29,"./rules/no_throwing_strings.coffee":30,"./rules/no_trailing_semicolons.coffee":31,"./rules/no_trailing_whitespace.coffee":32,"./rules/no_unnecessary_double_quotes.coffee":33,"./rules/no_unnecessary_fat_arrows.coffee":34,"./rules/non_empty_constructor_needs_parens.coffee":35,"./rules/prefer_english_operator.coffee":36,"./rules/space_operators.coffee":37}],5:[function(_dereq_,module,exports){ +var ErrorReport; + +module.exports = ErrorReport = (function() { + function ErrorReport(coffeelint) { + this.coffeelint = coffeelint; + this.paths = {}; + } + + ErrorReport.prototype.lint = function(filename, source, config, literate) { + if (config == null) { + config = {}; + } + if (literate == null) { + literate = false; + } + return this.paths[filename] = this.coffeelint.lint(source, config, literate); + }; + + ErrorReport.prototype.getExitCode = function() { + var path; + for (path in this.paths) { + if (this.pathHasError(path)) { + return 1; + } + } + return 0; + }; + + ErrorReport.prototype.getSummary = function() { + var error, errorCount, errors, path, pathCount, warningCount, _i, _len, _ref; + pathCount = errorCount = warningCount = 0; + _ref = this.paths; + for (path in _ref) { + errors = _ref[path]; + pathCount++; + for (_i = 0, _len = errors.length; _i < _len; _i++) { + error = errors[_i]; + if (error.level === 'error') { + errorCount++; + } + if (error.level === 'warn') { + warningCount++; + } + } + } + return { + errorCount: errorCount, + warningCount: warningCount, + pathCount: pathCount + }; + }; + + ErrorReport.prototype.getErrors = function(path) { + return this.paths[path]; + }; + + ErrorReport.prototype.pathHasWarning = function(path) { + return this._hasLevel(path, 'warn'); + }; + + ErrorReport.prototype.pathHasError = function(path) { + return this._hasLevel(path, 'error'); + }; + + ErrorReport.prototype.hasError = function() { + var path; + for (path in this.paths) { + if (this.pathHasError(path)) { + return true; + } + } + return false; + }; + + ErrorReport.prototype._hasLevel = function(path, level) { + var error, _i, _len, _ref; + _ref = this.paths[path]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + error = _ref[_i]; + if (error.level === level) { + return true; + } + } + return false; + }; + + return ErrorReport; + +})(); + + +},{}],6:[function(_dereq_,module,exports){ var BaseLinter, LexicalLinter, TokenApi, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -668,11 +801,11 @@ return LexicalLinter; })(BaseLinter); -},{"./base_linter.coffee":3}],6:[function(_dereq_,module,exports){ +},{"./base_linter.coffee":3}],7:[function(_dereq_,module,exports){ var BaseLinter, LineApi, LineLinter, configStatement, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; LineApi = (function() { @@ -851,11 +984,11 @@ return LineLinter; })(BaseLinter); -},{"./base_linter.coffee":3}],7:[function(_dereq_,module,exports){ +},{"./base_linter.coffee":3}],8:[function(_dereq_,module,exports){ var ERROR, IGNORE, WARN; ERROR = 'error'; WARN = 'warn'; @@ -868,11 +1001,11 @@ message: '' } }; -},{}],8:[function(_dereq_,module,exports){ +},{}],9:[function(_dereq_,module,exports){ var ArrowSpacing; module.exports = ArrowSpacing = (function() { function ArrowSpacing() {} @@ -886,11 +1019,13 @@ ArrowSpacing.prototype.tokens = ['->']; ArrowSpacing.prototype.lintToken = function(token, tokenApi) { var pp; pp = tokenApi.peek(-1); - if (!(((token.spaced != null) || (token.newLine != null) || this.atEof(tokenApi)) && (((pp.spaced != null) || pp[0] === 'TERMINATOR') || (pp.generated != null) || pp[0] === "INDENT" || (pp[1] === "(" && (pp.generated == null))))) { + if (!token.spaced && (pp[1] === "(" && (pp.generated == null)) && tokenApi.peek(1)[0] === 'INDENT' && tokenApi.peek(2)[0] === 'OUTDENT') { + return null; + } else if (!(((token.spaced != null) || (token.newLine != null) || this.atEof(tokenApi)) && (((pp.spaced != null) || pp[0] === 'TERMINATOR') || (pp.generated != null) || pp[0] === "INDENT" || (pp[1] === "(" && (pp.generated == null))))) { return true; } else { return null; } }; @@ -911,11 +1046,11 @@ return ArrowSpacing; })(); -},{}],9:[function(_dereq_,module,exports){ +},{}],10:[function(_dereq_,module,exports){ var CamelCaseClasses, regexes; regexes = { camelCase: /^[A-Z][a-zA-Z\d]*$/ }; @@ -958,11 +1093,11 @@ return CamelCaseClasses; })(); -},{}],10:[function(_dereq_,module,exports){ +},{}],11:[function(_dereq_,module,exports){ var ColonAssignmentSpacing; module.exports = ColonAssignmentSpacing = (function() { function ColonAssignmentSpacing() {} @@ -978,12 +1113,12 @@ }; ColonAssignmentSpacing.prototype.tokens = [':']; ColonAssignmentSpacing.prototype.lintToken = function(token, tokenApi) { - var checkSpacing, getSpaceFromToken, isLeftSpaced, isRightSpaced, leftSpacing, nextToken, previousToken, rightSpacing, spacingAllowances, _ref, _ref1; - spacingAllowances = tokenApi.config[this.rule.name].spacing; + var checkSpacing, getSpaceFromToken, isLeftSpaced, isRightSpaced, leftSpacing, nextToken, previousToken, rightSpacing, spaceRules, _ref, _ref1; + spaceRules = tokenApi.config[this.rule.name].spacing; previousToken = tokenApi.peek(-1); nextToken = tokenApi.peek(1); getSpaceFromToken = function(direction) { switch (direction) { case 'left': @@ -993,30 +1128,30 @@ } }; checkSpacing = function(direction) { var isSpaced, spacing; spacing = getSpaceFromToken(direction); - isSpaced = spacing < 0 ? true : spacing === parseInt(spacingAllowances[direction]); + isSpaced = spacing < 0 ? true : spacing === parseInt(spaceRules[direction]); return [isSpaced, spacing]; }; _ref = checkSpacing('left'), isLeftSpaced = _ref[0], leftSpacing = _ref[1]; _ref1 = checkSpacing('right'), isRightSpaced = _ref1[0], rightSpacing = _ref1[1]; if (isLeftSpaced && isRightSpaced) { return null; } else { return { - context: "Incorrect spacing around column " + token[2].first_column + ".\nExpected left: " + spacingAllowances.left + ", right: " + spacingAllowances.right + ".\nGot left: " + leftSpacing + ", right: " + rightSpacing + "." + context: "Incorrect spacing around column " + token[2].first_column + ".\nExpected left: " + spaceRules.left + ", right: " + spaceRules.right + ".\nGot left: " + leftSpacing + ", right: " + rightSpacing + "." }; } }; return ColonAssignmentSpacing; })(); -},{}],11:[function(_dereq_,module,exports){ +},{}],12:[function(_dereq_,module,exports){ var NoTabs; module.exports = NoTabs = (function() { function NoTabs() {} @@ -1071,35 +1206,35 @@ return NoTabs; })(); -},{}],12:[function(_dereq_,module,exports){ +},{}],13:[function(_dereq_,module,exports){ var DuplicateKey; module.exports = DuplicateKey = (function() { DuplicateKey.prototype.rule = { name: 'duplicate_key', level: 'error', message: 'Duplicate key defined in object or class', description: "Prevents defining duplicate keys in object literals and classes" }; - DuplicateKey.prototype.tokens = ['IDENTIFIER', "{", "}"]; + DuplicateKey.prototype.tokens = ['IDENTIFIER', '{', '}']; function DuplicateKey() { this.braceScopes = []; } DuplicateKey.prototype.lintToken = function(_arg, tokenApi) { var type; type = _arg[0]; - if (type === "{" || type === "}") { + if (type === '{' || type === '}') { this.lintBrace.apply(this, arguments); return void 0; } - if (type === "IDENTIFIER") { + if (type === 'IDENTIFIER') { return this.lintIdentifier.apply(this, arguments); } }; DuplicateKey.prototype.lintIdentifier = function(token, tokenApi) { @@ -1140,11 +1275,11 @@ return DuplicateKey; })(); -},{}],13:[function(_dereq_,module,exports){ +},{}],14:[function(_dereq_,module,exports){ var EmptyConstructorNeedsParens; module.exports = EmptyConstructorNeedsParens = (function() { function EmptyConstructorNeedsParens() {} @@ -1187,11 +1322,11 @@ return EmptyConstructorNeedsParens; })(); -},{}],14:[function(_dereq_,module,exports){ +},{}],15:[function(_dereq_,module,exports){ var Indentation; module.exports = Indentation = (function() { Indentation.prototype.rule = { name: 'indentation', @@ -1199,46 +1334,43 @@ level: 'error', message: 'Line contains inconsistent indentation', description: "This rule imposes a standard number of spaces to be used for\nindentation. Since whitespace is significant in CoffeeScript, it's\ncritical that a project chooses a standard indentation format and\nstays consistent. Other roads lead to darkness. <pre> <code>#\nEnabling this option will prevent this ugly\n# but otherwise valid CoffeeScript.\ntwoSpaces = () ->\n fourSpaces = () ->\n eightSpaces = () ->\n 'this is valid CoffeeScript'\n\n</code>\n</pre>\nTwo space indentation is enabled by default." }; - Indentation.prototype.tokens = ['INDENT', "[", "]"]; + Indentation.prototype.tokens = ['INDENT', '[', ']', '.']; function Indentation() { this.arrayTokens = []; } Indentation.prototype.lintToken = function(token, tokenApi) { - var currentLine, expected, ignoreIndent, isArrayIndent, isInterpIndent, isMultiline, lineNumber, lines, numIndents, prevNum, previous, previousIndentation, previousLine, previousSymbol, type, _ref; - type = token[0], numIndents = token[1], lineNumber = token[2]; - if (type === "[" || type === "]") { + var currentLine, expected, ignoreIndent, isArrayIndent, isInterpIndent, isMultiline, lineNumber, lines, numIndents, previous, previousSymbol, type, _ref, _ref1, _ref2; + type = token[0], numIndents = token[1], (_ref = token[2], lineNumber = _ref.first_line); + lines = tokenApi.lines, lineNumber = tokenApi.lineNumber; + expected = tokenApi.config[this.rule.name].value; + if (type === '.') { + currentLine = lines[lineNumber]; + if (((_ref1 = currentLine.match(/\S/i)) != null ? _ref1[0] : void 0) === '.') { + return this.handleChain(tokenApi, expected); + } + return void 0; + } + if (type === '[' || type === ']') { this.lintArray(token); return void 0; } if (token.generated != null) { return null; } previous = tokenApi.peek(-2); isInterpIndent = previous && previous[0] === '+'; previous = tokenApi.peek(-1); isArrayIndent = this.inArray() && (previous != null ? previous.newLine : void 0); - previousSymbol = (_ref = tokenApi.peek(-1)) != null ? _ref[0] : void 0; + previousSymbol = (_ref2 = tokenApi.peek(-1)) != null ? _ref2[0] : void 0; isMultiline = previousSymbol === '=' || previousSymbol === ','; ignoreIndent = isInterpIndent || isArrayIndent || isMultiline; - if (this.isChainedCall(tokenApi)) { - lines = tokenApi.lines, lineNumber = tokenApi.lineNumber; - currentLine = lines[lineNumber]; - prevNum = 1; - while (/^\s*(#|$)/.test(lines[lineNumber - prevNum])) { - prevNum += 1; - } - previousLine = lines[lineNumber - prevNum]; - previousIndentation = previousLine.match(/^(\s*)/)[1].length; - numIndents = currentLine.match(/^(\s*)/)[1].length; - numIndents -= previousIndentation; - } - expected = tokenApi.config[this.rule.name].value; + numIndents = this.getCorrectIndent(tokenApi); if (!ignoreIndent && numIndents !== expected) { return { context: "Expected " + expected + " got " + numIndents }; } @@ -1255,49 +1387,70 @@ this.arrayTokens.pop(); } return null; }; - Indentation.prototype.isChainedCall = function(tokenApi) { - var i, lastNewLineIndex, lines, t, token, tokens; - tokens = tokenApi.tokens, i = tokenApi.i; - lines = (function() { - var _i, _len, _ref, _results; - _ref = tokens.slice(0, +i + 1 || 9e9); - _results = []; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - token = _ref[i]; - if (token.newLine != null) { - _results.push(i); - } - } - return _results; - })(); - lastNewLineIndex = lines ? lines[lines.length - 2] : null; - if (lastNewLineIndex == null) { - return false; + Indentation.prototype.handleChain = function(tokenApi, expected) { + var callStart, checkNum, currIsIndent, currentLine, currentSpaces, findCallStart, lastCheck, lineNumber, lines, numIndents, prevIsIndent, prevLine, prevNum, prevSpaces, _ref, _ref1; + lastCheck = 1; + callStart = 1; + prevNum = 1; + lineNumber = tokenApi.lineNumber, lines = tokenApi.lines; + currentLine = lines[lineNumber]; + findCallStart = tokenApi.peek(-callStart); + while (findCallStart && findCallStart[0] !== 'TERMINATOR') { + lastCheck = findCallStart[2].first_line; + callStart += 1; + findCallStart = tokenApi.peek(-callStart); } - tokens = [tokens[lastNewLineIndex], tokens[lastNewLineIndex + 1]]; - return !!((function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = tokens.length; _i < _len; _i++) { - t = tokens[_i]; - if (t && t[0] === '.') { - _results.push(t); + while ((lineNumber - prevNum > lastCheck) && !/^\s*\./.test(lines[lineNumber - prevNum])) { + prevNum += 1; + } + checkNum = lineNumber - prevNum; + if (checkNum >= 0) { + prevLine = lines[checkNum]; + if (prevLine.match(/\S/i)[0] === '.' || checkNum === lastCheck) { + currentSpaces = (_ref = currentLine.match(/\S/i)) != null ? _ref.index : void 0; + prevSpaces = (_ref1 = prevLine.match(/\S/i)) != null ? _ref1.index : void 0; + numIndents = currentSpaces - prevSpaces; + prevIsIndent = prevSpaces % expected !== 0; + currIsIndent = currentSpaces % expected !== 0; + if (prevIsIndent && currIsIndent) { + numIndents = currentSpaces; } + if (numIndents % expected !== 0) { + return { + context: "Expected " + expected + " got " + numIndents + }; + } } - return _results; - })()).length; + } }; + Indentation.prototype.getCorrectIndent = function(tokenApi) { + var curIndent, i, lineNumber, lines, prevIndent, prevLine, prevNum, tokens, _ref, _ref1, _ref2; + lineNumber = tokenApi.lineNumber, lines = tokenApi.lines, tokens = tokenApi.tokens, i = tokenApi.i; + curIndent = (_ref = lines[lineNumber].match(/\S/)) != null ? _ref.index : void 0; + prevNum = 1; + while (/^\s*(#|$)/.test(lines[lineNumber - prevNum])) { + prevNum += 1; + } + prevLine = lines[lineNumber - prevNum]; + prevIndent = (_ref1 = prevLine.match(/^(\s*)\./)) != null ? _ref1[1].length : void 0; + if (prevIndent > 0) { + return curIndent - ((_ref2 = prevLine.match(/\S/)) != null ? _ref2.index : void 0); + } else { + return tokens[i][1]; + } + }; + return Indentation; })(); -},{}],15:[function(_dereq_,module,exports){ +},{}],16:[function(_dereq_,module,exports){ var LineEndings; module.exports = LineEndings = (function() { function LineEndings() {} @@ -1337,11 +1490,11 @@ return LineEndings; })(); -},{}],16:[function(_dereq_,module,exports){ +},{}],17:[function(_dereq_,module,exports){ var MaxLineLength, regexes; regexes = { literateComment: /^\#\s/, longUrlComment: /^\s*\#\s*http[^\s]+$/ @@ -1382,21 +1535,36 @@ return MaxLineLength; })(); -},{}],17:[function(_dereq_,module,exports){ -var MissingFatArrows, any, +},{}],18:[function(_dereq_,module,exports){ +var MissingFatArrows, any, containsButIsnt, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 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() { function MissingFatArrows() { this.isFatArrowCode = __bind(this.isFatArrowCode, this); this.isThis = __bind(this.isThis, this); this.isObject = __bind(this.isObject, this); @@ -1472,11 +1640,11 @@ MissingFatArrows.prototype.needsFatArrow = function(node) { return this.isCode(node) && (any(node.params, (function(_this) { return function(param) { return param.contains(_this.isThis) != null; }; - })(this)) || (node.body.contains(this.isThis) != null)); + })(this)) || containsButIsnt(node.body, this.isThis, this.isClass)); }; MissingFatArrows.prototype.methodsOfClass = function(classNode) { var bodyNodes, returnNode; bodyNodes = classNode.body.expressions; @@ -1493,11 +1661,11 @@ return MissingFatArrows; })(); -},{}],18:[function(_dereq_,module,exports){ +},{}],19:[function(_dereq_,module,exports){ var NewlinesAfterClasses; module.exports = NewlinesAfterClasses = (function() { function NewlinesAfterClasses() {} @@ -1528,11 +1696,11 @@ return NewlinesAfterClasses; })(); -},{}],19:[function(_dereq_,module,exports){ +},{}],20:[function(_dereq_,module,exports){ var NoBackticks; module.exports = NoBackticks = (function() { function NoBackticks() {} @@ -1552,11 +1720,11 @@ return NoBackticks; })(); -},{}],20:[function(_dereq_,module,exports){ +},{}],21:[function(_dereq_,module,exports){ var NoDebugger; module.exports = NoDebugger = (function() { function NoDebugger() {} @@ -1578,11 +1746,11 @@ return NoDebugger; })(); -},{}],21:[function(_dereq_,module,exports){ +},{}],22:[function(_dereq_,module,exports){ var NoEmptyFunctions, isEmptyCode; isEmptyCode = function(node, astApi) { var nodeName; nodeName = astApi.getNodeName(node); @@ -1622,11 +1790,11 @@ return NoEmptyFunctions; })(); -},{}],22:[function(_dereq_,module,exports){ +},{}],23:[function(_dereq_,module,exports){ var NoEmptyParamList; module.exports = NoEmptyParamList = (function() { function NoEmptyParamList() {} @@ -1648,11 +1816,11 @@ return NoEmptyParamList; })(); -},{}],23:[function(_dereq_,module,exports){ +},{}],24:[function(_dereq_,module,exports){ var NoImplicitBraces; module.exports = NoImplicitBraces = (function() { function NoImplicitBraces() {} @@ -1697,11 +1865,11 @@ return NoImplicitBraces; })(); -},{}],24:[function(_dereq_,module,exports){ +},{}],25:[function(_dereq_,module,exports){ var NoImplicitParens; module.exports = NoImplicitParens = (function() { function NoImplicitParens() {} @@ -1711,25 +1879,25 @@ level: 'ignore', message: 'Implicit parens are forbidden', description: "This rule prohibits implicit parens on function calls.\n<pre>\n<code># Some folks don't like this style of coding.\nmyFunction a, b, c\n\n# And would rather it always be written like this:\nmyFunction(a, b, c)\n</code>\n</pre>\nImplicit parens are permitted by default, since their use is\nidiomatic CoffeeScript." }; - NoImplicitParens.prototype.tokens = ["CALL_END"]; + NoImplicitParens.prototype.tokens = ['CALL_END']; NoImplicitParens.prototype.lintToken = function(token, tokenApi) { var i, t; if (token.generated) { if (tokenApi.config[this.rule.name].strict !== false) { return true; } else { i = -1; while (true) { t = tokenApi.peek(i); - if ((t == null) || t[0] === 'CALL_START') { + if ((t == null) || (t[0] === 'CALL_START' && t.generated)) { return true; } - if (t.newLine) { + if (t[2].first_line !== token[2].first_line) { return null; } i -= 1; } } @@ -1739,21 +1907,21 @@ return NoImplicitParens; })(); -},{}],25:[function(_dereq_,module,exports){ +},{}],26:[function(_dereq_,module,exports){ var NoInterpolationInSingleQuotes; module.exports = NoInterpolationInSingleQuotes = (function() { function NoInterpolationInSingleQuotes() {} NoInterpolationInSingleQuotes.prototype.rule = { name: 'no_interpolation_in_single_quotes', level: 'ignore', message: 'Interpolation in single quoted strings is forbidden', - description: 'This rule prohibits string interpolation in a single quoted string.\n<pre>\n<code># String interpolation in single quotes is not allowed:\nfoo = \'#{bar}\'\n\n# Double quotes is OK of course\nfoo = "#{bar}"\n</code>\n</pre>\nString interpolation in single quoted strings is permitted by \ndefault.' + description: 'This rule prohibits string interpolation in a single quoted string.\n<pre>\n<code># String interpolation in single quotes is not allowed:\nfoo = \'#{bar}\'\n\n# Double quotes is OK of course\nfoo = "#{bar}"\n</code>\n</pre>\nString interpolation in single quoted strings is permitted by\ndefault.' }; NoInterpolationInSingleQuotes.prototype.tokens = ['STRING']; NoInterpolationInSingleQuotes.prototype.lintToken = function(token, tokenApi) { @@ -1766,11 +1934,11 @@ return NoInterpolationInSingleQuotes; })(); -},{}],26:[function(_dereq_,module,exports){ +},{}],27:[function(_dereq_,module,exports){ var NoPlusPlus; module.exports = NoPlusPlus = (function() { function NoPlusPlus() {} @@ -1792,11 +1960,11 @@ return NoPlusPlus; })(); -},{}],27:[function(_dereq_,module,exports){ +},{}],28:[function(_dereq_,module,exports){ var NoStandAloneAt; module.exports = NoStandAloneAt = (function() { function NoStandAloneAt() {} @@ -1805,11 +1973,11 @@ level: 'ignore', message: '@ must not be used stand alone', description: "This rule checks that no stand alone @ are in use, they are\ndiscouraged. Further information in CoffeScript issue <a\nhref=\"https://github.com/jashkenas/coffee-script/issues/1601\">\n#1601</a>" }; - NoStandAloneAt.prototype.tokens = ["@"]; + NoStandAloneAt.prototype.tokens = ['@']; NoStandAloneAt.prototype.lintToken = function(token, tokenApi) { var isDot, isIdentifier, isIndexStart, isValidProtoProperty, nextToken, protoProperty, spaced; nextToken = tokenApi.peek(); spaced = token.spaced; @@ -1828,11 +1996,11 @@ return NoStandAloneAt; })(); -},{}],28:[function(_dereq_,module,exports){ +},{}],29:[function(_dereq_,module,exports){ var NoTabs, indentationRegex, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; indentationRegex = /\S/; @@ -1859,11 +2027,11 @@ return NoTabs; })(); -},{}],29:[function(_dereq_,module,exports){ +},{}],30:[function(_dereq_,module,exports){ var NoThrowingStrings; module.exports = NoThrowingStrings = (function() { function NoThrowingStrings() {} @@ -1872,11 +2040,11 @@ level: 'error', message: 'Throwing strings is forbidden', description: "This rule forbids throwing string literals or interpolations. While\nJavaScript (and CoffeeScript by extension) allow any expression to\nbe thrown, it is best to only throw <a\nhref=\"https://developer.mozilla.org\n/en/JavaScript/Reference/Global_Objects/Error\"> Error</a> objects,\nbecause they contain valuable debugging information like the stack\ntrace. Because of JavaScript's dynamic nature, CoffeeLint cannot\nensure you are always throwing instances of <tt>Error</tt>. It will\nonly catch the simple but real case of throwing literal strings.\n<pre>\n<code># CoffeeLint will catch this:\nthrow \"i made a boo boo\"\n\n# ... but not this:\nthrow getSomeString()\n</code>\n</pre>\nThis rule is enabled by default." }; - NoThrowingStrings.prototype.tokens = ["THROW"]; + NoThrowingStrings.prototype.tokens = ['THROW']; NoThrowingStrings.prototype.lintToken = function(token, tokenApi) { var n1, n2, nextIsString, _ref; _ref = [tokenApi.peek(), tokenApi.peek(2)], n1 = _ref[0], n2 = _ref[1]; nextIsString = n1[0] === 'STRING' || (n1[0] === '(' && n2[0] === 'STRING'); @@ -1886,12 +2054,13 @@ return NoThrowingStrings; })(); -},{}],30:[function(_dereq_,module,exports){ +},{}],31:[function(_dereq_,module,exports){ var NoTrailingSemicolons, regexes, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; regexes = { trailingSemicolon: /;\r?$/ }; @@ -1905,41 +2074,43 @@ message: 'Line contains a trailing semicolon', description: "This rule prohibits trailing semicolons, since they are needless\ncruft in CoffeeScript.\n<pre>\n<code># This semicolon is meaningful.\nx = '1234'; console.log(x)\n\n# This semicolon is redundant.\nalert('end of line');\n</code>\n</pre>\nTrailing semicolons are forbidden by default." }; NoTrailingSemicolons.prototype.lintLine = function(line, lineApi) { - var endPos, first, hasNewLine, hasSemicolon, last, lineTokens, newLine, startCounter, startPos, _i, _ref; + var endPos, first, hasNewLine, hasSemicolon, last, lineTokens, newLine, startCounter, startPos, stopTokens, tokenLen, _i, _ref, _ref1; lineTokens = lineApi.getLineTokens(); - if (lineTokens.length === 1 && ((_ref = lineTokens[0][0]) === 'TERMINATOR' || _ref === 'HERECOMMENT')) { + tokenLen = lineTokens.length; + stopTokens = ['TERMINATOR', 'HERECOMMENT']; + if (tokenLen === 1 && (_ref = lineTokens[0][0], __indexOf.call(stopTokens, _ref) >= 0)) { return; } newLine = line; - if (lineTokens.length > 1 && lineTokens[lineTokens.length - 1][0] === 'TERMINATOR') { - startPos = lineTokens[lineTokens.length - 2][2].last_column + 1; - endPos = lineTokens[lineTokens.length - 1][2].first_column; + if (tokenLen > 1 && lineTokens[tokenLen - 1][0] === 'TERMINATOR') { + startPos = lineTokens[tokenLen - 2][2].last_column + 1; + endPos = lineTokens[tokenLen - 1][2].first_column; if (startPos !== endPos) { startCounter = startPos; - while (line[startCounter] !== "#" && startCounter < line.length) { + while (line[startCounter] !== '#' && startCounter < line.length) { startCounter++; } newLine = line.substring(0, startCounter).replace(/\s*$/, ''); } } hasSemicolon = regexes.trailingSemicolon.test(newLine); first = 2 <= lineTokens.length ? __slice.call(lineTokens, 0, _i = lineTokens.length - 1) : (_i = 0, []), last = lineTokens[_i++]; hasNewLine = last && (last.newLine != null); - if (hasSemicolon && !hasNewLine && lineApi.lineHasToken() && last[0] !== 'STRING') { + if (hasSemicolon && !hasNewLine && lineApi.lineHasToken() && !((_ref1 = last[0]) === 'STRING' || _ref1 === 'IDENTIFIER' || _ref1 === 'CALL_END')) { return true; } }; return NoTrailingSemicolons; })(); -},{}],31:[function(_dereq_,module,exports){ +},{}],32:[function(_dereq_,module,exports){ var NoTrailingWhitespace, regexes; regexes = { trailingWhitespace: /[^\s]+[\t ]+\r?$/, onlySpaces: /^[\t ]+\r?$/, @@ -1998,47 +2169,99 @@ return NoTrailingWhitespace; })(); -},{}],32:[function(_dereq_,module,exports){ +},{}],33:[function(_dereq_,module,exports){ var NoUnnecessaryDoubleQuotes; module.exports = NoUnnecessaryDoubleQuotes = (function() { - function NoUnnecessaryDoubleQuotes() {} - NoUnnecessaryDoubleQuotes.prototype.rule = { name: 'no_unnecessary_double_quotes', level: 'ignore', message: 'Unnecessary double quotes are forbidden', - description: 'This rule prohibits double quotes unless string interpolation is \nused or the string contains single quotes.\n<pre>\n<code># Double quotes are discouraged:\nfoo = "bar"\n\n# Unless string interpolation is used:\nfoo = "#{bar}baz"\n\n# Or they prevent cumbersome escaping:\nfoo = "I\'m just following the \'rules\'"\n</code>\n</pre>\nDouble quotes are permitted by default.' + description: 'This rule prohibits double quotes unless string interpolation is\nused or the string contains single quotes.\n<pre>\n<code># Double quotes are discouraged:\nfoo = "bar"\n\n# Unless string interpolation is used:\nfoo = "#{bar}baz"\n\n# Or they prevent cumbersome escaping:\nfoo = "I\'m just following the \'rules\'"\n</code>\n</pre>\nDouble quotes are permitted by default.' }; + function NoUnnecessaryDoubleQuotes() { + this.regexps = []; + } + NoUnnecessaryDoubleQuotes.prototype.tokens = ['STRING']; NoUnnecessaryDoubleQuotes.prototype.lintToken = function(token, tokenApi) { - var hasLegalConstructs, stringValue, tokenValue; + var e, hasLegalConstructs, i, notInBlock, s, stringValue, tokenValue; tokenValue = token[1]; + i = tokenApi.i; stringValue = tokenValue.match(/^\"(.*)\"$/); - if (!stringValue) { + if (this.regexps.length === 0) { + this.regexps = this.getBlockRegExps(tokenApi); + } + notInBlock = ((function() { + var _i, _len, _ref, _ref1, _results; + _ref = this.regexps; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + _ref1 = _ref[_i], s = _ref1[0], e = _ref1[1]; + if ((s < i && i < e)) { + _results.push(1); + } + } + return _results; + }).call(this)).length === 0; + if (!(stringValue && notInBlock)) { return false; } hasLegalConstructs = this.isInterpolated(tokenApi) || this.containsSingleQuote(tokenValue); return !hasLegalConstructs; }; + NoUnnecessaryDoubleQuotes.prototype.getBlockRegExps = function(tokenApi) { + var callEnds, col, curTok, i, idx, ii, lin, lines, regexps, t, tokens, _i, _j, _len, _len1, _ref; + lines = tokenApi.lines, tokens = tokenApi.tokens; + regexps = []; + for (i = _i = 0, _len = tokens.length; _i < _len; i = ++_i) { + t = tokens[i]; + if (!(t[0] === 'IDENTIFIER' && t[1] === 'RegExp')) { + continue; + } + _ref = t[2], lin = _ref.first_line, col = _ref.first_column; + if (lines[lin].slice(col, +(col + 2) + 1 || 9e9) === "///") { + regexps.push([i, 0]); + } + } + for (idx = _j = 0, _len1 = regexps.length; _j < _len1; idx = ++_j) { + i = regexps[idx][0]; + ii = 2; + callEnds = 1; + while (callEnds > 0 && (curTok = tokens[i + ii][0])) { + if (curTok === 'CALL_END') { + callEnds--; + } + if (curTok === 'CALL_START') { + callEnds++; + } + ii++; + } + regexps[idx][1] = i + ii - 1; + } + return regexps; + }; + NoUnnecessaryDoubleQuotes.prototype.isInterpolated = function(tokenApi) { - var currentIndex, i, isInterpolated, lineTokens, token, tokenName, _i, _ref; - currentIndex = tokenApi.i; + var i, idx, isInterpolated, token, tokenName, _i, _ref; + idx = tokenApi.i; isInterpolated = false; - lineTokens = tokenApi.tokensByLine[tokenApi.lineNumber]; - for (i = _i = 1; 1 <= currentIndex ? _i <= currentIndex : _i >= currentIndex; i = 1 <= currentIndex ? ++_i : --_i) { + for (i = _i = 1; 1 <= idx ? _i <= idx : _i >= idx; i = 1 <= idx ? ++_i : --_i) { token = tokenApi.peek(-i); + if (token == null) { + break; + } tokenName = token[0]; if (tokenName === ')' && token.stringEnd) { break; - } else if (tokenName === '(' && ((_ref = token.origin) != null ? _ref[1] : void 0) === "string interpolation") { + } else if (tokenName === '(' && ((_ref = token.origin) != null ? _ref[1] : void 0) === 'string interpolation') { isInterpolated = true; break; } } return isInterpolated; @@ -2051,11 +2274,11 @@ return NoUnnecessaryDoubleQuotes; })(); -},{}],33:[function(_dereq_,module,exports){ +},{}],34:[function(_dereq_,module,exports){ var NoUnnecessaryFatArrows, any, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; any = function(arr, test) { return arr.reduce((function(res, elt) { @@ -2118,21 +2341,25 @@ return function(param) { return param.contains(_this.isThis) != null; }; })(this)) || (node.body.contains(this.isThis) != null) || (node.body.contains((function(_this) { return function(child) { - return _this.isFatArrowCode(child) && _this.needsFatArrow(child); + if (!_this.astApi.getNodeName(child)) { + return (child.isSuper != null) && child.isSuper; + } else { + return _this.isFatArrowCode(child) && _this.needsFatArrow(child); + } }; })(this)) != null)); }; return NoUnnecessaryFatArrows; })(); -},{}],34:[function(_dereq_,module,exports){ +},{}],35:[function(_dereq_,module,exports){ var NonEmptyConstructorNeedsParens, ParentClass, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; ParentClass = _dereq_('./empty_constructor_needs_parens.coffee'); @@ -2160,11 +2387,72 @@ return NonEmptyConstructorNeedsParens; })(ParentClass); -},{"./empty_constructor_needs_parens.coffee":13}],35:[function(_dereq_,module,exports){ +},{"./empty_constructor_needs_parens.coffee":14}],36:[function(_dereq_,module,exports){ +var RuleProcessor; + +module.exports = RuleProcessor = (function() { + function RuleProcessor() {} + + RuleProcessor.prototype.rule = { + name: 'prefer_english_operator', + description: 'This rule prohibits &&, ||, ==, != and !.\nUse and, or, is, isnt, and not instead.\n!! for converting to a boolean is ignored.', + level: 'ignore', + doubleNotLevel: 'ignore', + message: 'Don\'t use &&, ||, ==, !=, or !' + }; + + RuleProcessor.prototype.tokens = ['COMPARE', 'UNARY_MATH', 'LOGIC']; + + RuleProcessor.prototype.lintToken = function(token, tokenApi) { + var actual_token, config, context, first_column, last_column, level, line, _ref; + config = tokenApi.config[this.rule.name]; + level = config.level; + _ref = token[2], first_column = _ref.first_column, last_column = _ref.last_column; + line = tokenApi.lines[tokenApi.lineNumber]; + actual_token = line.slice(first_column, +last_column + 1 || 9e9); + context = (function() { + var _ref1, _ref2; + switch (actual_token) { + case '==': + return 'Replace "==" with "is"'; + case '!=': + return 'Replace "!=" with "isnt"'; + case '||': + return 'Replace "||" with "or"'; + case '&&': + return 'Replace "&&" with "and"'; + case '!': + if (((_ref1 = tokenApi.peek(1)) != null ? _ref1[0] : void 0) === 'UNARY_MATH') { + level = config.doubleNotLevel; + return '"?" is usually better than "!!"'; + } else if (((_ref2 = tokenApi.peek(-1)) != null ? _ref2[0] : void 0) === 'UNARY_MATH') { + return void 0; + } else { + return 'Replace "!" with "not"'; + } + break; + default: + return void 0; + } + })(); + if (context != null) { + return { + level: level, + context: context + }; + } + }; + + return RuleProcessor; + +})(); + + +},{}],37:[function(_dereq_,module,exports){ var SpaceOperators, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; module.exports = SpaceOperators = (function() { SpaceOperators.prototype.rule = { @@ -2172,29 +2460,29 @@ level: 'ignore', message: 'Operators must be spaced properly', description: "This rule enforces that operators have space around them." }; - SpaceOperators.prototype.tokens = ["+", "-", "=", "**", "MATH", "COMPARE", "LOGIC", "COMPOUND_ASSIGN", "(", ")", "CALL_START", "CALL_END"]; + SpaceOperators.prototype.tokens = ['+', '-', '=', '**', 'MATH', 'COMPARE', 'LOGIC', 'COMPOUND_ASSIGN', '(', ')', 'CALL_START', 'CALL_END']; function SpaceOperators() { this.callTokens = []; this.parenTokens = []; } SpaceOperators.prototype.lintToken = function(_arg, tokenApi) { var type; type = _arg[0]; - if (type === "CALL_START" || type === "CALL_END") { + if (type === 'CALL_START' || type === 'CALL_END') { this.lintCall.apply(this, arguments); return void 0; } - if (type === "(" || type === ")") { + if (type === '(' || type === ')') { this.lintParens.apply(this, arguments); return void 0; } - if (type === "+" || type === "-") { + if (type === '+' || type === '-') { return this.lintPlus.apply(this, arguments); } else { return this.lintMath.apply(this, arguments); } }; @@ -2205,20 +2493,22 @@ return null; } p = tokenApi.peek(-1); unaries = ['TERMINATOR', '(', '=', '-', '+', ',', 'CALL_START', 'INDEX_START', '..', '...', 'COMPARE', 'IF', 'THROW', 'LOGIC', 'POST_IF', ':', '[', 'INDENT', 'COMPOUND_ASSIGN', 'RETURN', 'MATH', 'BY', 'LEADING_WHEN']; isUnary = !p ? false : (_ref = p[0], __indexOf.call(unaries, _ref) >= 0); - if ((isUnary && token.spaced) || (!isUnary && !token.spaced && !token.newLine)) { + if ((isUnary && token.spaced) || (!isUnary && !token.newLine && (!token.spaced || (p && !p.spaced)))) { return { context: token[1] }; } else { return null; } }; SpaceOperators.prototype.lintMath = function(token, tokenApi) { - if (!token.spaced && !token.newLine) { + var p; + p = tokenApi.peek(-1); + if (!token.newLine && (!token.spaced || (p && !p.spaced))) { return { context: token[1] }; } else { return null; \ No newline at end of file