@coffeelint/cli
Version:
Lint your CoffeeScript
1,550 lines (1,349 loc) • 139 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.coffeelint = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
module.exports={
"name": "@coffeelint/cli",
"description": "Lint your CoffeeScript",
"version": "5.2.11",
"homepage": "https://coffeelint.github.io/",
"keywords": [
"lint",
"coffeescript",
"coffee-script"
],
"author": "Tony Brix <Tony@Brix.ninja> (https://Tony.Brix.ninja)",
"main": "./lib/coffeelint.js",
"engines": {
"node": ">=12.x"
},
"repository": {
"type": "git",
"url": "git://github.com/coffeelint/coffeelint.git"
},
"bin": {
"coffeelint": "./bin/coffeelint"
},
"dependencies": {
"coffeescript": "2.7.0",
"glob": "^8.0.1",
"ignore": "^5.2.0",
"resolve": "^1.21.0",
"strip-json-comments": "^3.1.1",
"yargs": "^17.3.1"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^8.0.2",
"@semantic-release/npm": "^9.0.0",
"@semantic-release/release-notes-generator": "^10.0.3",
"browserify": "^17.0.0",
"coffeeify": "^3.0.1",
"semantic-release": "^19.0.2",
"underscore": "^1.13.2",
"vows": "^0.8.3"
},
"license": "MIT",
"scripts": {
"test": "npm run compile && node ./vowsrunner.js --dot-matrix test/*.coffee test/*.litcoffee",
"testrule": "npm run compile && node ./vowsrunner.js --spec",
"lint": "npm run compile && node ./bin/coffeelint .",
"lint-csv": "npm run compile && node ./bin/coffeelint --reporter csv .",
"lint-jslint": "npm run compile && node ./bin/coffeelint --reporter jslint .",
"compile": "cake compile",
"prepublishOnly": "npm run compile"
}
}
},{}],2:[function(require,module,exports){
var ASTApi, ASTLinter, BaseLinter, hasChildren, node_children,
hasProp = {}.hasOwnProperty;
BaseLinter = require('./base_linter.coffee');
node_children = {
Class: ['variable', 'parent', 'body'],
Code: ['params', 'body'],
For: ['body', 'source', 'guard', 'step'],
If: ['condition', 'body', 'elseBody'],
Obj: ['properties'],
Op: ['first', 'second'],
Switch: ['subject', 'cases', 'otherwise'],
Try: ['attempt', 'recovery', 'ensure'],
Value: ['base', 'properties'],
While: ['condition', 'guard', 'body']
};
hasChildren = function(node, children) {
var ref;
return (node != null ? (ref = node.children) != null ? ref.length : void 0 : void 0) === children.length && (node != null ? node.children.every(function(elem, i) {
return elem === children[i];
}) : void 0);
};
ASTApi = class ASTApi {
constructor(config1) {
this.config = config1;
}
getNodeName(node) {
var children, name, ref;
name = node != null ? (ref = node.constructor) != null ? ref.name : void 0 : void 0;
if (node_children[name]) {
return name;
} else {
for (name in node_children) {
if (!hasProp.call(node_children, name)) continue;
children = node_children[name];
if (hasChildren(node, children)) {
return name;
}
}
}
}
};
// A class that performs static analysis of the abstract
// syntax tree.
module.exports = ASTLinter = class ASTLinter extends BaseLinter {
constructor(source, config, rules, CoffeeScript) {
super(source, config, rules);
this.CoffeeScript = CoffeeScript;
this.astApi = new ASTApi(this.config);
}
// This uses lintAST instead of lintNode because I think it makes it a bit
// more clear that the rule needs to walk the AST on its own.
acceptRule(rule) {
return typeof rule.lintAST === 'function';
}
lint() {
var coffeeError, err, errors, j, len, ref, rule, v;
errors = [];
try {
this.node = this.CoffeeScript.nodes(this.source);
} catch (error) {
coffeeError = error;
// If for some reason you shut off the 'coffeescript_error' rule err
// will be null and should NOT be added to errors
err = this._parseCoffeeScriptError(coffeeError);
if (err != null) {
errors.push(err);
}
return errors;
}
ref = this.rules;
for (j = 0, len = ref.length; j < len; j++) {
rule = ref[j];
this.astApi.createError = (attrs = {}) => {
return this.createError(rule.rule.name, attrs);
};
// HACK: Push the local errors object into the plugin. This is a
// temporary solution until I have a way for it to really return
// multiple errors.
rule.errors = errors;
v = this.normalizeResult(rule, rule.lintAST(this.node, this.astApi));
if (v != null) {
return v;
}
}
return errors;
}
_parseCoffeeScriptError(coffeeError) {
var attrs, columnNumber, lineNumber, match, message, rule;
rule = this.config['coffeescript_error'];
message = coffeeError.toString();
// Parse the line number
lineNumber = -1;
if (coffeeError.location != null) {
lineNumber = coffeeError.location.first_line + 1;
columnNumber = coffeeError.location.first_column + 1;
} else {
match = /line (\d+)/.exec(message);
if ((match != null ? match.length : void 0) > 1) {
lineNumber = parseInt(match[1], 10);
}
columnNumber = 1;
}
attrs = {
message: message,
level: rule.level,
lineNumber: lineNumber,
columnNumber: columnNumber
};
return this.createError('coffeescript_error', attrs);
}
};
},{"./base_linter.coffee":3}],3:[function(require,module,exports){
// Patch the source properties onto the destination.
var BaseLinter, defaults, extend;
extend = function(destination, ...sources) {
var i, k, len, source, v;
for (i = 0, len = sources.length; i < len; i++) {
source = sources[i];
(function() {
var results;
results = [];
for (k in source) {
v = source[k];
results.push(destination[k] = v);
}
return results;
})();
}
return destination;
};
// Patch any missing attributes from defaults to source.
defaults = function(source, defaults) {
return extend({}, defaults, source);
};
module.exports = BaseLinter = class BaseLinter {
constructor(source1, config, rules) {
this.source = source1;
this.config = config;
this.setupRules(rules);
}
isObject(obj) {
return obj === Object(obj);
}
// Create an error object for the given rule with the given
// attributes.
createError(ruleName, attrs = {}) {
var level;
// Level should default to what's in the config, but can be overridden.
if (attrs.level == null) {
attrs.level = this.config[ruleName].level;
}
level = attrs.level;
if (level !== 'ignore' && level !== 'warn' && level !== 'error') {
throw new Error(`unknown level ${level} for rule: ${ruleName}`);
}
if (level === 'error' || level === 'warn') {
attrs.rule = ruleName;
return defaults(attrs, this.config[ruleName]);
} else {
return null;
}
}
acceptRule(rule) {
throw new Error('acceptRule needs to be overridden in the subclass');
}
// Only rules that have a level of error or warn will even get constructed.
setupRules(rules) {
var RuleConstructor, level, name, results, rule;
this.rules = [];
results = [];
for (name in rules) {
RuleConstructor = rules[name];
level = this.config[name].level;
if (level === 'error' || level === 'warn') {
rule = new RuleConstructor(this, this.config);
if (this.acceptRule(rule)) {
results.push(this.rules.push(rule));
} else {
results.push(void 0);
}
} else if (level !== 'ignore') {
throw new Error(`unknown level ${level} for rule: ${name}`);
} else {
results.push(void 0);
}
}
return results;
}
normalizeResult(p, result) {
if (result === true) {
return this.createError(p.rule.name);
}
if (this.isObject(result)) {
return this.createError(p.rule.name, result);
}
}
};
},{}],4:[function(require,module,exports){
/*
CoffeeLint
Copyright (c) 2011 Matthew Perpick.
CoffeeLint is freely distributable under the MIT license.
*/
var ASTLinter, CoffeeScript, ERROR, ErrorReport, IGNORE, LexicalLinter, LineLinter, RULES, WARN, _rules, cache, coffeelint, defaults, difference, extend, getTokens, mergeDefaultConfig, nodeRequire, packageJSON, sameJSON, union,
indexOf = [].indexOf,
slice = [].slice;
// Coffeelint's namespace.
// Browserify wrapps this file in a UMD that will set window.coffeelint to
// exports
coffeelint = exports;
// Hide from browserify
nodeRequire = require;
if (typeof window !== "undefined" && window !== null) {
// If we're in the browser assume CoffeeScript is already loaded.
CoffeeScript = window.CoffeeScript;
}
// By using nodeRequire it prevents browserify from finding this dependency.
// If it isn't hidden there is an error attempting to inline CoffeeScript.
// if browserify uses `-i` to ignore the dependency it creates an empty shim
// which breaks NodeJS
// https://github.com/substack/node-browserify/issues/471
// Atom has a `window`, but not a `window.CoffeeScript`. Calling `nodeRequire`
// here should fix Atom without breaking anything else.
if (CoffeeScript == null) {
CoffeeScript = nodeRequire('coffeescript');
}
if (CoffeeScript == null) {
throw new Error('Unable to find CoffeeScript');
}
// Browserify will inline the file at compile time.
packageJSON = require('./../package.json');
// The current version of Coffeelint.
coffeelint.VERSION = packageJSON.version;
// CoffeeLint error levels.
ERROR = 'error';
WARN = 'warn';
IGNORE = 'ignore';
coffeelint.RULES = RULES = require('./rules.coffee');
// Patch the source properties onto the destination.
extend = function(destination, ...sources) {
var j, k, len, source, v;
for (j = 0, len = sources.length; j < len; j++) {
source = sources[j];
(function() {
var results;
results = [];
for (k in source) {
v = source[k];
results.push(destination[k] = v);
}
return results;
})();
}
return destination;
};
// Patch any missing attributes from defaults to source.
defaults = function(source, defaults) {
return extend({}, defaults, source);
};
// Helper to add rules to disabled list
union = function(a, b) {
var c, j, len, len1, n, results, x;
c = {};
for (j = 0, len = a.length; j < len; j++) {
x = a[j];
c[x] = true;
}
for (n = 0, len1 = b.length; n < len1; n++) {
x = b[n];
c[x] = true;
}
results = [];
for (x in c) {
results.push(x);
}
return results;
};
// Helper to remove rules from disabled list
difference = function(a, b) {
var j, len, results, x;
results = [];
for (j = 0, len = a.length; j < len; j++) {
x = a[j];
if (indexOf.call(b, x) < 0) {
results.push(x);
}
}
return results;
};
LineLinter = require('./line_linter.coffee');
LexicalLinter = require('./lexical_linter.coffee');
ASTLinter = require('./ast_linter.coffee');
// Cache instance, disabled by default
cache = null;
// Merge default and user configuration.
mergeDefaultConfig = function(userConfig) {
var config, rule, ruleConfig, ruleLoader;
try {
// When run from the browser it may not be able to find the ruleLoader.
ruleLoader = nodeRequire('./ruleLoader');
ruleLoader.loadFromConfig(coffeelint, userConfig);
} catch (error) {}
config = {};
if (userConfig.coffeelint) {
config.coffeelint = userConfig.coffeelint;
}
for (rule in RULES) {
ruleConfig = RULES[rule];
config[rule] = defaults(userConfig[rule], ruleConfig);
}
return config;
};
sameJSON = function(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
};
coffeelint.trimConfig = function(userConfig) {
var config, dConfig, dValue, key, newConfig, ref, rule, value;
newConfig = {};
userConfig = mergeDefaultConfig(userConfig);
for (rule in userConfig) {
config = userConfig[rule];
dConfig = RULES[rule];
if (rule === 'coffeelint') {
config.transforms = config._transforms;
delete config._transforms;
config.coffeescript = config._coffeescript;
delete config._coffeescript;
newConfig[rule] = config;
} else if ((config.level === (ref = dConfig.level) && ref === 'ignore')) {
// If the rule is going to be ignored and would be by default it
// doesn't matter what you may have configured
void 0;
} else if (config.level === 'ignore') {
// If the rule is being ignored you don't need the rest of the
// config.
newConfig[rule] = {
level: 'ignore'
};
} else {
config.module = config._module;
delete config._module;
for (key in config) {
value = config[key];
if (key === 'message' || key === 'description' || key === 'name') {
continue;
}
dValue = dConfig[key];
if (value !== dValue && !sameJSON(value, dValue)) {
if (newConfig[rule] == null) {
newConfig[rule] = {};
}
newConfig[rule][key] = value;
}
}
}
}
return newConfig;
};
coffeelint.invertLiterate = function(source) {
var j, len, line, newSource, ref;
source = CoffeeScript.helpers.invertLiterate(source);
// Strip the first 4 spaces or a tab from every line.
// After this the markdown is commented and all of the other code
// should be at their natural location.
newSource = '';
ref = source.split('\n');
for (j = 0, len = ref.length; j < len; j++) {
line = ref[j];
if (line.match(/^#/)) {
// strip trailing space
line = line.replace(/\s*$/, '');
}
// Strip the first 4 spaces or a tab of every line. This is how Markdown
// indicates code, so in the end this pulls everything back to where it
// would be indented if it hadn't been written in literate style.
line = line.replace(/^[ ]{4}|^\t/g, '');
newSource += `${line}\n`;
}
return newSource;
};
_rules = {};
coffeelint.registerRule = function(RuleConstructor, ruleName = void 0) {
var e, name, p, ref, ref1;
p = new RuleConstructor();
name = (p != null ? (ref = p.rule) != null ? ref.name : void 0 : void 0) || '(unknown)';
e = function(msg) {
throw new Error(`Invalid rule: ${name} ${msg}`);
};
if (p.rule == null) {
e('Rules must provide rule attribute with a default configuration.');
}
if (p.rule.name == null) {
e('Rule defaults require a name');
}
if ((ruleName != null) && ruleName !== p.rule.name) {
e(`Mismatched rule name: ${ruleName}`);
}
if (p.rule.message == null) {
e('Rule defaults require a message');
}
if (p.rule.description == null) {
e('Rule defaults require a description');
}
if ((ref1 = p.rule.level) !== 'ignore' && ref1 !== 'warn' && ref1 !== 'error') {
e("Default level must be 'ignore', 'warn', or 'error'");
}
if (typeof p.lintToken === 'function') {
if (!p.tokens) {
e("'tokens' is required for 'lintToken'");
}
} else if (typeof p.lintLine !== 'function' && typeof p.lintAST !== 'function') {
e('Rules must implement lintToken, lintLine, or lintAST');
}
// Capture the default options for the new rule.
RULES[p.rule.name] = p.rule;
return _rules[p.rule.name] = RuleConstructor;
};
coffeelint.getRules = function() {
var j, key, len, output, ref;
output = {};
ref = Object.keys(RULES).sort();
for (j = 0, len = ref.length; j < len; j++) {
key = ref[j];
output[key] = RULES[key];
}
return output;
};
// These all need to be explicitly listed so they get picked up by browserify.
coffeelint.registerRule(require('./rules/arrow_spacing.coffee'));
coffeelint.registerRule(require('./rules/braces_spacing.coffee'));
coffeelint.registerRule(require('./rules/bracket_spacing.coffee'));
coffeelint.registerRule(require('./rules/no_tabs.coffee'));
coffeelint.registerRule(require('./rules/no_spaces.coffee'));
coffeelint.registerRule(require('./rules/no_trailing_whitespace.coffee'));
coffeelint.registerRule(require('./rules/max_line_length.coffee'));
coffeelint.registerRule(require('./rules/line_endings.coffee'));
coffeelint.registerRule(require('./rules/no_trailing_semicolons.coffee'));
coffeelint.registerRule(require('./rules/indentation.coffee'));
coffeelint.registerRule(require('./rules/camel_case_classes.coffee'));
coffeelint.registerRule(require('./rules/colon_assignment_spacing.coffee'));
coffeelint.registerRule(require('./rules/no_implicit_braces.coffee'));
coffeelint.registerRule(require('./rules/no_nested_string_interpolation.coffee'));
coffeelint.registerRule(require('./rules/no_plusplus.coffee'));
coffeelint.registerRule(require('./rules/no_throwing_strings.coffee'));
coffeelint.registerRule(require('./rules/no_backticks.coffee'));
coffeelint.registerRule(require('./rules/no_implicit_parens.coffee'));
coffeelint.registerRule(require('./rules/no_empty_param_list.coffee'));
coffeelint.registerRule(require('./rules/no_stand_alone_at.coffee'));
coffeelint.registerRule(require('./rules/space_operators.coffee'));
coffeelint.registerRule(require('./rules/duplicate_key.coffee'));
coffeelint.registerRule(require('./rules/empty_constructor_needs_parens.coffee'));
coffeelint.registerRule(require('./rules/cyclomatic_complexity.coffee'));
coffeelint.registerRule(require('./rules/newlines_after_classes.coffee'));
coffeelint.registerRule(require('./rules/no_unnecessary_fat_arrows.coffee'));
coffeelint.registerRule(require('./rules/missing_fat_arrows.coffee'));
coffeelint.registerRule(require('./rules/prefer_fat_arrows_in_methods.coffee'));
coffeelint.registerRule(require('./rules/non_empty_constructor_needs_parens.coffee'));
coffeelint.registerRule(require('./rules/no_unnecessary_double_quotes.coffee'));
coffeelint.registerRule(require('./rules/no_debugger.coffee'));
coffeelint.registerRule(require('./rules/no_interpolation_in_single_quotes.coffee'));
coffeelint.registerRule(require('./rules/no_empty_functions.coffee'));
coffeelint.registerRule(require('./rules/prefer_english_operator.coffee'));
coffeelint.registerRule(require('./rules/prefer_logical_operator.coffee'));
coffeelint.registerRule(require('./rules/spacing_after_comma.coffee'));
coffeelint.registerRule(require('./rules/transform_messes_up_line_numbers.coffee'));
coffeelint.registerRule(require('./rules/ensure_comprehensions.coffee'));
coffeelint.registerRule(require('./rules/no_this.coffee'));
coffeelint.registerRule(require('./rules/eol_last.coffee'));
coffeelint.registerRule(require('./rules/no_private_function_fat_arrows.coffee'));
coffeelint.registerRule(require('./rules/missing_parseint_radix.coffee'));
coffeelint.registerRule(require('./rules/object_shorthand.coffee'));
getTokens = function(source) {
try {
// If there are syntax errors this will abort the lexical and line
// linters.
return CoffeeScript.tokens(source);
} catch (error) {}
return null;
};
ErrorReport = require('./error_report.coffee');
coffeelint.getErrorReport = function() {
return new ErrorReport(coffeelint);
};
// Check the source against the given configuration and return an array
// of any errors found. An error is an object with the following
// properties:
// {
// rule : 'Name of the violated rule',
// lineNumber: 'Number of the line that caused the violation',
// level: 'The error level of the violated rule',
// message: 'Information about the violated rule',
// context: 'Optional details about why the rule was violated'
// }
coffeelint.lint = function(source, userConfig = {}, literate = false) {
var allErrors, astErrors, cmd, config, disabled, disabledEntirely, disabledInitially, disabledLine, e, errors, i, inlineConfig, j, l, len, len1, lexErrors, lexicalLinter, lineErrors, lineLinter, m, n, name, nextLine, o, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, regex, rule, ruleLoader, rules, set, sourceLength, tokens, tokensByLine, transform;
errors = [];
if (cache != null) {
cache.setConfig(userConfig);
}
if (cache != null ? cache.has(source) : void 0) {
return cache != null ? cache.get(source) : void 0;
}
config = mergeDefaultConfig(userConfig);
if (literate) {
source = this.invertLiterate(source);
}
if ((userConfig != null ? (ref = userConfig.coffeelint) != null ? ref.transforms : void 0 : void 0) != null) {
sourceLength = source.split('\n').length;
ref2 = userConfig != null ? (ref1 = userConfig.coffeelint) != null ? ref1.transforms : void 0 : void 0;
for (j = 0, len = ref2.length; j < len; j++) {
m = ref2[j];
try {
ruleLoader = nodeRequire('./ruleLoader');
transform = ruleLoader.require(m);
source = transform(source);
} catch (error) {}
}
// NOTE: This can have false negatives. For example if your transformer
// changes one line into two early in the file and later condenses two
// into one you'll end up with the same length and not get the warning
// even though everything in between will be off by one.
if (sourceLength !== source.split('\n').length && config.transform_messes_up_line_numbers.level !== 'ignore') {
errors.push(extend({
lineNumber: 1,
context: `File was transformed from ${sourceLength} lines to ${source.split("\n").length} lines`
}, config.transform_messes_up_line_numbers));
}
}
if ((userConfig != null ? (ref3 = userConfig.coffeelint) != null ? ref3.coffeescript : void 0 : void 0) != null) {
CoffeeScript = ruleLoader.require(userConfig.coffeelint.coffeescript);
}
// coffeescript_error is unique because it's embedded in the ASTLinter. It
// indicates a syntax error and would not work well as a stand alone rule.
// Why can't JSON just support comments?
for (name in userConfig) {
if (name !== 'coffeescript_error' && name !== '_comment') {
if (_rules[name] == null) {
// TODO: Figure out a good way to notify the user that they have
// configured a rule that doesn't exist. throwing an Error was
// definitely a mistake. I probably need a good way to generate lint
// warnings for configuration.
void 0;
}
}
}
// disabledInitially is to prevent the rule from becoming active before
// the actual inlined comment appears
disabledInitially = [];
ref4 = source.split('\n');
// Check ahead for inline enabled rules
for (n = 0, len1 = ref4.length; n < len1; n++) {
l = ref4[n];
ref5 = LineLinter.getDirective(l) || [], [regex, set] = ref5, [rule] = slice.call(ref5, -1);
if ((set === 'enable' || set === 'enable-line') && ((ref6 = config[rule]) != null ? ref6.level : void 0) === 'ignore') {
disabledInitially.push(rule);
config[rule].level = 'error';
}
}
// Do AST linting first so all compile errors are caught.
astErrors = new ASTLinter(source, config, _rules, CoffeeScript).lint();
errors = errors.concat(astErrors);
// only do further checks if the syntax is okay, otherwise they just fail
// with syntax error exceptions
tokens = getTokens(source);
if (tokens) {
// Do lexical linting.
lexicalLinter = new LexicalLinter(source, config, _rules, CoffeeScript, tokens);
lexErrors = lexicalLinter.lint();
errors = errors.concat(lexErrors);
// Do line linting.
tokensByLine = lexicalLinter.tokensByLine;
lineLinter = new LineLinter(source, config, _rules, tokensByLine, literate);
lineErrors = lineLinter.lint();
errors = errors.concat(lineErrors);
inlineConfig = lineLinter.inlineConfig;
} else {
// default this so it knows what to do
inlineConfig = {
enable: {},
disable: {},
'enable-line': {},
'disable-line': {}
};
}
// Sort by line number and return.
errors.sort(function(a, b) {
return a.lineNumber - b.lineNumber;
});
// Create a list of all errors
disabledEntirely = (function() {
var len2, map, o, ref7, result;
result = [];
map = {};
ref7 = errors || [];
for (o = 0, len2 = ref7.length; o < len2; o++) {
({name} = ref7[o]);
if (!map[name]) {
result.push(name);
map[name] = true;
}
}
return result;
})();
// Disable/enable rules for inline blocks
allErrors = errors;
errors = [];
disabled = disabledInitially;
nextLine = 0;
for (i = o = 0, ref7 = source.split('\n').length; (0 <= ref7 ? o < ref7 : o > ref7); i = 0 <= ref7 ? ++o : --o) {
disabledLine = disabled;
for (cmd in inlineConfig) {
rules = inlineConfig[cmd][i];
if (rules != null) {
({
'disable': function() {
if (rules.length) {
disabled = union(disabled, rules);
return disabledLine = union(disabledLine, rules);
} else {
return disabled = disabledLine = disabledEntirely;
}
},
'disable-line': function() {
if (rules.length) {
return disabledLine = union(disabledLine, rules);
} else {
return disabledLine = disabledEntirely;
}
},
'enable': function() {
if (rules.length) {
disabled = difference(disabled, rules);
return disabledLine = difference(disabledLine, rules);
} else {
return disabled = disabledLine = disabledInitially;
}
},
'enable-line': function() {
if (rules.length) {
return disabledLine = difference(disabledLine, rules);
} else {
return disabledLine = disabledInitially;
}
}
})[cmd]();
}
}
// advance line and append relevant messages
while (nextLine === i && allErrors.length > 0) {
nextLine = allErrors[0].lineNumber - 1;
e = allErrors[0];
if (e.lineNumber === i + 1 || (e.lineNumber == null)) {
e = allErrors.shift();
if (ref8 = e.rule, indexOf.call(disabledLine, ref8) < 0) {
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,"./error_report.coffee":5,"./lexical_linter.coffee":6,"./line_linter.coffee":7,"./rules.coffee":8,"./rules/arrow_spacing.coffee":9,"./rules/braces_spacing.coffee":10,"./rules/bracket_spacing.coffee":11,"./rules/camel_case_classes.coffee":12,"./rules/colon_assignment_spacing.coffee":13,"./rules/cyclomatic_complexity.coffee":14,"./rules/duplicate_key.coffee":15,"./rules/empty_constructor_needs_parens.coffee":16,"./rules/ensure_comprehensions.coffee":17,"./rules/eol_last.coffee":18,"./rules/indentation.coffee":19,"./rules/line_endings.coffee":20,"./rules/max_line_length.coffee":21,"./rules/missing_fat_arrows.coffee":22,"./rules/missing_parseint_radix.coffee":23,"./rules/newlines_after_classes.coffee":24,"./rules/no_backticks.coffee":25,"./rules/no_debugger.coffee":26,"./rules/no_empty_functions.coffee":27,"./rules/no_empty_param_list.coffee":28,"./rules/no_implicit_braces.coffee":29,"./rules/no_implicit_parens.coffee":30,"./rules/no_interpolation_in_single_quotes.coffee":31,"./rules/no_nested_string_interpolation.coffee":32,"./rules/no_plusplus.coffee":33,"./rules/no_private_function_fat_arrows.coffee":34,"./rules/no_spaces.coffee":35,"./rules/no_stand_alone_at.coffee":36,"./rules/no_tabs.coffee":37,"./rules/no_this.coffee":38,"./rules/no_throwing_strings.coffee":39,"./rules/no_trailing_semicolons.coffee":40,"./rules/no_trailing_whitespace.coffee":41,"./rules/no_unnecessary_double_quotes.coffee":42,"./rules/no_unnecessary_fat_arrows.coffee":43,"./rules/non_empty_constructor_needs_parens.coffee":44,"./rules/object_shorthand.coffee":45,"./rules/prefer_english_operator.coffee":46,"./rules/prefer_fat_arrows_in_methods.coffee":47,"./rules/prefer_logical_operator.coffee":48,"./rules/space_operators.coffee":49,"./rules/spacing_after_comma.coffee":50,"./rules/transform_messes_up_line_numbers.coffee":51}],5:[function(require,module,exports){
// A summary of errors in a CoffeeLint run.
var ErrorReport;
module.exports = ErrorReport = class ErrorReport {
constructor(coffeelint) {
this.coffeelint = coffeelint;
this.paths = {};
}
lint(filename, source, config = {}, literate = false) {
return this.paths[filename] = this.coffeelint.lint(source, config, literate);
}
getExitCode() {
var path;
for (path in this.paths) {
if (this.pathHasError(path)) {
return 1;
}
}
return 0;
}
getSummary() {
var error, errorCount, errors, i, len, path, pathCount, ref, warningCount;
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, warningCount, pathCount};
}
getErrors(path) {
return this.paths[path];
}
pathHasWarning(path) {
return this._hasLevel(path, 'warn');
}
pathHasError(path) {
return this._hasLevel(path, 'error');
}
hasError() {
var path;
for (path in this.paths) {
if (this.pathHasError(path)) {
return true;
}
}
return false;
}
_hasLevel(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;
}
};
},{}],6:[function(require,module,exports){
var BaseLinter, LexicalLinter, TokenApi,
indexOf = [].indexOf;
TokenApi = (function() {
class TokenApi {
constructor(CoffeeScript, source, config1, tokensByLine, tokens1) {
this.config = config1;
this.tokensByLine = tokensByLine;
this.tokens = tokens1;
if (this.tokens == null) {
this.tokens = CoffeeScript.tokens(source);
}
this.lines = source.split('\n');
this.tokensByLine = {}; // A map of tokens by line.
}
// Return the token n places away from the current token.
peek(n = 1) {
return this.tokens[this.i + n] || null;
}
};
TokenApi.prototype.i = 0; // The index of the current token we're linting.
return TokenApi;
}).call(this);
BaseLinter = require('./base_linter.coffee');
// A class that performs checks on the output of CoffeeScript's lexer.
module.exports = LexicalLinter = class LexicalLinter extends BaseLinter {
constructor(source, config, rules, CoffeeScript, tokens) {
super(source, config, rules);
this.tokenApi = new TokenApi(CoffeeScript, source, this.config, this.tokensByLine, tokens);
// This needs to be available on the LexicalLinter so it can be passed
// to the LineLinter when this finishes running.
this.tokensByLine = this.tokenApi.tokensByLine;
}
acceptRule(rule) {
return typeof rule.lintToken === 'function';
}
// Return a list of errors encountered in the given source.
lint() {
var error, errors, i, j, k, len, len1, ref, ref1, token;
errors = [];
ref = this.tokenApi.tokens;
for (i = j = 0, len = ref.length; j < len; i = ++j) {
token = ref[i];
this.tokenApi.i = i;
ref1 = this.lintToken(token);
for (k = 0, len1 = ref1.length; k < len1; k++) {
error = ref1[k];
errors.push(error);
}
}
return errors;
}
// Return an error if the given token fails a lint check, false otherwise.
lintToken(token) {
var base, errors, j, len, lineNumber, ref, ref1, rule, type, v, value;
[
type,
value,
{
first_line: lineNumber
}
] = token;
if ((base = this.tokensByLine)[lineNumber] == null) {
base[lineNumber] = [];
}
this.tokensByLine[lineNumber].push(token);
// CoffeeScript loses line numbers of interpolations and multi-line
// regexes, so fake it by using the last line number we know.
this.lineNumber = lineNumber || this.lineNumber || 0;
this.tokenApi.lineNumber = this.lineNumber;
// Multiple rules might run against the same token to build context.
// Every rule should run even if something has already produced an
// error for the same token.
errors = [];
ref = this.rules;
for (j = 0, len = ref.length; j < len; j++) {
rule = ref[j];
if (!(ref1 = token[0], indexOf.call(rule.tokens, ref1) >= 0)) {
continue;
}
v = this.normalizeResult(rule, rule.lintToken(token, this.tokenApi));
if (v != null) {
errors.push(v);
}
}
return errors;
}
createError(ruleName, attrs = {}) {
var token;
if (attrs.lineNumber == null) {
attrs.lineNumber = this.lineNumber;
}
attrs.lineNumber += 1;
attrs.line = this.tokenApi.lines[attrs.lineNumber - 1];
if (attrs.token) {
token = attrs.token;
attrs.lineNumber = token[2].first_line + 1;
attrs.columnNumber = token[2].first_column + 1;
if (token[2].last_line) {
attrs.lineNumberEnd = token[2].last_line + 1;
}
if (token[2].last_column) {
attrs.columnNumberEnd = token[2].last_column + 1;
}
}
return super.createError(ruleName, attrs);
}
};
},{"./base_linter.coffee":3}],7:[function(require,module,exports){
var BaseLinter, LineApi, LineLinter, configShortcuts, configStatement;
LineApi = (function() {
class LineApi {
constructor(source, config1, tokensByLine1, literate1) {
this.config = config1;
this.tokensByLine = tokensByLine1;
this.literate = literate1;
this.line = null;
this.lines = source.split('\n');
this.lineCount = this.lines.length;
// maintains some contextual information
// inClass: bool; in class or not
// lastUnemptyLineInClass: null or lineNumber, if the last not-empty
// line was in a class it holds its number
// classIndents: the number of indents within a class
this.context = {
class: {
inClass: false,
lastUnemptyLineInClass: null,
classIndents: null
}
};
}
isLiterate() {
return this.literate;
}
// maintain the contextual information for class-related stuff
maintainClassContext(line) {
if (this.context.class.inClass) {
if (this.lineHasToken('INDENT')) {
this.context.class.classIndents++;
} else if (this.lineHasToken('OUTDENT')) {
this.context.class.classIndents--;
if (this.context.class.classIndents === 0) {
this.context.class.inClass = false;
this.context.class.classIndents = null;
}
}
if (!line.match(/^\s*$/)) {
this.context.class.lastUnemptyLineInClass = this.lineNumber;
}
} else {
if (!line.match(/\\s*/)) {
this.context.class.lastUnemptyLineInClass = null;
}
if (this.lineHasToken('CLASS')) {
this.context.class.inClass = true;
this.context.class.lastUnemptyLineInClass = this.lineNumber;
this.context.class.classIndents = 0;
}
}
return null;
}
isLastLine() {
return this.lineNumber === this.lineCount - 1;
}
// Return true if the given line actually has tokens.
// Optional parameter to check for a specific token type and line number.
lineHasToken(tokenType = null, lineNumber = null) {
var i, len, token, tokens;
lineNumber = lineNumber != null ? lineNumber : this.lineNumber;
if (tokenType == null) {
return this.tokensByLine[lineNumber] != null;
} else {
tokens = this.tokensByLine[lineNumber];
if (tokens == null) {
return null;
}
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token[0] === tokenType) {
return true;
}
}
return false;
}
}
// Return tokens for the given line number.
getLineTokens() {
return this.tokensByLine[this.lineNumber] || [];
}
};
LineApi.prototype.lineNumber = 0;
return LineApi;
}).call(this);
BaseLinter = require('./base_linter.coffee');
// Some repeatedly used regular expressions.
configStatement = /coffeelint:\s*((disable|enable)(-line)?)(?:=([\w\s,]*))?/;
// TODO: make this user (and / or api) configurable
configShortcuts = [[/\#.*noqa/, 'coffeelint: disable-line']];
// A class that performs regex checks on each line of the source.
module.exports = LineLinter = class LineLinter extends BaseLinter {
static getDirective(line) {
var i, len, replacement, shortcut;
for (i = 0, len = configShortcuts.length; i < len; i++) {
[shortcut, replacement] = configShortcuts[i];
if (line.match(shortcut)) {
return configStatement.exec(replacement);
}
}
return configStatement.exec(line);
}
constructor(source, config, rules, tokensByLine, literate = false) {
super(source, config, rules);
this.lineApi = new LineApi(source, config, tokensByLine, literate);
// Store suppressions in the form of { line #: type }
this.inlineConfig = {
enable: {},
disable: {},
'enable-line': {},
'disable-line': {}
};
}
acceptRule(rule) {
return typeof rule.lintLine === 'function';
}
lint() {
var error, errors, i, j, len, len1, line, lineNumber, ref, ref1;
errors = [];
ref = this.lineApi.lines;
for (lineNumber = i = 0, len = ref.length; i < len; lineNumber = ++i) {
line = ref[lineNumber];
this.lineApi.lineNumber = this.lineNumber = lineNumber;
this.lineApi.line = this.lineApi.lines[lineNumber];
this.lineApi.maintainClassContext(line);
this.collectInlineConfig(line);
ref1 = this.lintLine(line);
for (j = 0, len1 = ref1.length; j < len1; j++) {
error = ref1[j];
errors.push(error);
}
}
return errors;
}
// Return an error if the line contained failed a rule, null otherwise.
lintLine(line) {
var errors, i, len, ref, rule, v;
// Multiple rules might run against the same line to build context.
// Every every rule should run even if something has already produced an
// error for the same token.
errors = [];
ref = this.rules;
for (i = 0, len = ref.length; i < len; i++) {
rule = ref[i];
v = this.normalizeResult(rule, rule.lintLine(line, this.lineApi));
if (v != null) {
errors.push(v);
}
}
return errors;
}
collectInlineConfig(line) {
var cmd, i, len, r, ref, result, rules;
// Check for block config statements enable and disable
result = this.constructor.getDirective(line);
if (result != null) {
cmd = result[1];
rules = [];
if (result[4] != null) {
ref = result[4].split(',');
for (i = 0, len = ref.length; i < len; i++) {
r = ref[i];
rules.push(r.replace(/^\s+|\s+$/g, ''));
}
}
this.inlineConfig[cmd][this.lineNumber] = rules;
}
return null;
}
createError(rule, attrs = {}) {
var ref;
if (attrs.lineNumber == null) {
attrs.lineNumber = this.lineNumber + 1; // Lines are indexed by zero.
}
attrs.level = (ref = this.config[rule]) != null ? ref.level : void 0;
return super.createError(rule, attrs);
}
};
},{"./base_linter.coffee":3}],8:[function(require,module,exports){
// CoffeeLint error levels.
var ERROR, IGNORE, WARN;
ERROR = 'error';
WARN = 'warn';
IGNORE = 'ignore';
// CoffeeLint's default rule configuration.
module.exports = {
coffeescript_error: {
level: ERROR,
message: '' // The default coffeescript error is fine.
}
};
},{}],9:[function(require,module,exports){
var ArrowSpacing;
module.exports = ArrowSpacing = (function() {
class ArrowSpacing {
lintToken(token, tokenApi) {
var pp;
// Throw error unless the following happens.
// We will take a look at the previous token to see
// 1. That the token is properly spaced
// 2. Wasn't generated by the CoffeeScript compiler
// 3. That it is just indentation
// 4. If the function declaration has no parameters
// e.g. x(-> 3)
// x( -> 3)
// or a statement is wrapped in parentheses
// e.g. (-> true)()
// we will accept either having a space or not having a space there.
// Also if the -> is the beginning of the file, then simply just return
pp = tokenApi.peek(-1);
if (!pp) {
return;
}
// Ignore empty functions
if (!token.spaced && tokenApi.peek(1)[0] === 'INDENT' && tokenApi.peek(2)[0] === 'OUTDENT') {
return null;
// Throw error unless the previous token...
} else if (!(((token.spaced != null) || (token.newLine != null)) && (((pp.spaced != null) || pp[0] === 'TERMINATOR') || (pp.generated != null) || pp[0] === 'INDENT' || (pp[1] === '(' && (pp.generated == null))))) { //4
return {token};
} else {
return null;
}
}
};
ArrowSpacing.prototype.rule = {
type: 'style',
name: 'arrow_spacing',
level: 'ignore',
message: 'Function arrows (-> and =>) must be spaced properly',
description: `<p>This rule checks to see that there is spacing before and after
the arrow operator that declares a function. This rule is disabled
by default.</p> <p>Note that if arrow_spacing is enabled, and you
pass an empty function as a parameter, arrow_spacing will accept
either a space or no space in-between the arrow operator and the
parenthesis</p>
<pre><code># Both of this will not trigger an error,
# even with arrow_spacing enabled.
x(-> 3)
x( -> 3)
# However, this will trigger an error
x((a,b)-> 3)
</code>
</pre>`
};
ArrowSpacing.prototype.tokens = ['->', '=>'];
return ArrowSpacing;
}).call(this);
},{}],10:[function(require,module,exports){
var BracesSpacing,
indexOf = [].indexOf;
module.exports = BracesSpacing = (function() {
class BracesSpacing {
distanceBetweenTokens(firstToken, secondToken) {
return secondToken[2].first_column - firstToken[2].last_column - 1;
}
findNearestToken(token, tokenApi, difference) {
var nearestToken, totalDifference;
totalDifference = 0;
while (true) {
totalDifference += difference;
nearestToken = tokenApi.peek(totalDifference);
if ((nearestToken != null ? nearestToken[0] : void 0) === 'OUTDENT' || ((nearestToken != null ? nearestToken.generated : void 0) != null)) {
continue;
}
return nearestToken;
}
}
tokensOnSameLine(firstToken, secondToken) {
return firstToken[2].first_line === secondToken[2].first_line;
}
tokenSetsMatch(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
getExpectedSpaces(tokenApi, tokens) {
var config, mono, ref, ref1;
config = tokenApi.config[this.rule.name];
mono = ['IDENTIFIER', ...this.tokens];
tokens = tokens.map(function(token) {
return token != null ? token[0] : void 0;
}).filter(function(token) {
return indexOf.call(mono, token) >= 0;
});
if (this.tokenSetsMatch(tokens.slice(0, 2), this.tokens)) {
return (ref = config.empty_object_spaces) != null ? ref : config.spaces;
} else if (this.tokenSetsMatch(mono, tokens.sort())) {
return (ref1 = config.mono_object_spaces) != null ? ref1 : config.spaces;
} else {
return config.spaces;
}
}
lintToken(token, tokenApi) {
var actual, expected, firstToken, msg, secondToken, tokens;
if (token.generated) {
return null;
}
[firstToken, secondToken] = tokens = token[0] === '{' ? [token, this.findNearestToken(token, tokenApi, 1), this.findNearestToken(token, tokenApi, 2)] : [this.findNearestToken(token, tokenApi, -1), token, this.findNearestToken(token, tokenApi, -2)];
if (!this.tokensOnSameLine(firstToken, secondToken)) {
return null;
}
expected = this.getExpectedSpaces(tokenApi, tokens);
actual = this.distanceBetweenTokens(firstToken, secondToken);
if (actual === expected) {
return null;
} else {
msg = `There should be ${expected} space`;
if (expected !== 1) {
msg += 's';
}
msg += ` inside \"${token[0]}\"`;
return {
token,
context: msg
};
}
}
};
BracesSpacing.prototype.rule = {
type: 'style',
name: 'braces_spacing',
level: 'ignore',
spaces: 0,
empty_object_spaces: 0,
message: 'Curly braces must have the proper spacing',
description: `This rule checks to see that there is the proper spacing inside
curly braces. The spacing amount is specified by "spaces".
The spacing amount for empty objects is specified by
"empty_object_spaces".
The spacing amount for objects containing a single item is
specified by "mono_object_spaces".
<pre><code>
# Spaces is 0
{a: b} # Good
{a: b } # Bad
{ a: b} # Bad
{ a: b } # Bad
# Spaces is 1
{a: b} # Bad
{a: b } # Bad
{ a: b} # Bad
{ a: b } # Good
{ a: b } # Bad
{ a: b } # Bad
{ a: b } # Bad
# Empty Object Spaces is 0
{} # Good
{ } # Bad
# Empty Object Spaces is 1
{} # Bad
{ } # Good
# Mono Object Spaces is 0
{a} # Good
{ a } # Bad
# Mono Object Spaces is 1
{a} # Bad
{ a } # Good
</code></pre>
This rule is disabled by default.`
};
BracesSpacing.prototype.tokens = ['{', '}'];
return BracesSpacing;
}).call(this);
},{}],11:[function(require,module,exports){
var BracketSpacing;
module.exports = BracketSpacing = (function() {
class BracketSpacing {
distanceBetweenTokens(firstToken, secondToken) {
return secondToken[2].first_column - firstToken[2].last_column - 1;
}
findNearestToken(token, tokenApi, difference) {
var nearestToken, totalDifference;
totalDifference = 0;
while (true) {
totalDifference += difference;
nearestToken = tokenApi.peek(totalDifference);
if (nearestToken != null ? nearestToken[0].startsWith('STRING_') : void 0) {
// Render quotes for string interpolation.
nearestToken[1] = '"';
}
if ((nearestToken != null ? nearestToken[0] : void 0) === 'OUTDENT' || ((nearestToken != null ? nearestToken.generated : void 0) != null)) {
continue;
}
return nearestToken;
}
}
tokensOnSameLine(firstToken, secondToken) {
return firstToken[2].first_line === secondToken[2].first_line;
}
escape(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
getExpectedSpaces(tokenApi, tokens) {
var config, except, pattern, ref, ref1;
config = tokenApi.config[this.rule.name];
except = this.escape(config.exceptions.join(''));
pattern = tokens.map(function(token) {
return token != null ? token[1] : void 0;
}).join('');
switch (false) {
case !(except && RegExp(`^\\[[${except}]|[${except}]\\]$`).test(pattern)):
if (config.spaces) {
return 0;
} else {
return 1;
}
break;
case !pattern.includes('[]'):
return (ref = config.empty_array_spaces) != null ? ref : config.spaces;
case !/\[\w+\]/.test(pattern):
return (ref1 = config.mono_array_spaces) != null ? ref1 : config.spaces;
default:
return config.spaces;
}
}
lintToken(token, tokenApi) {
var actual, expected, firstToken, msg, secondToken, tokens;
if (token.generated) {
return null;
}
tokens = token[0] === this.tokens[0] ? (firstToken = token, secondToken = this.findNearestToken(token, tokenApi, 1), [firstToken, secondToken, this.findNearestToken(token, tokenApi, 2)]) : (firstToken = this.findNearestToken(token, tokenApi, -1), secondToken = token, [this.findNearestToken(token, tokenApi, -2), firstToken, secondToken]);
if (!this.tokensOnSameLine(firstToken, secondToken)) {
return null;
}
expected = this.getExpectedSpaces(tokenApi, tokens);
actual = this.distanceBetweenTokens(firstToken, secondToken);
if (actual === expected) {
return null;
} else {
msg = `There should be ${expected} space`;
if (expected !== 1) {
msg += 's';
}
msg += ` inside \"${token[0]}\"`;
return {
token,
context: msg
};
}
}
};
BracketSpacing.prototype.rule = {
type: 'style',
name: 'bracket_spacing',
level: 'ignore',
spaces: 0,
empty_array_spaces: 0,
exceptions: [],
message: 'Square brackets must have the proper spacing',
des