Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

Several enhancements, including CJSX fixes #632

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cakefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ task 'compile:commandline', 'Compiles commandline.js', ->
coffeeSync 'src/cache.coffee', 'lib/cache.js'
coffeeSync 'src/ruleLoader.coffee', 'lib/ruleLoader.js'
fs.mkdirSync 'lib/reporters' unless fs.existsSync 'lib/reporters'
fs.mkdirSync 'lib/rules' unless fs.existsSync 'lib/rules'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed so we can export the default rules for use in eslint.

for src in glob.sync('reporters/*.coffee', { cwd: 'src' })
# Slice the "coffee" extension of the end and replace with js
dest = src[...-6] + 'js'
coffeeSync "src/#{src}", "lib/#{dest}"

for src in glob.sync('rules/*.coffee', { cwd: 'src' })
# Slice the "coffee" extension of the end and replace with js
dest = src[...-6] + 'js'
coffeeSync "src/#{src}", "lib/#{dest}"

task 'compile:browserify', 'Uses browserify to compile coffeelint', ->
opts =
standalone: 'coffeelint'
Expand Down
3 changes: 3 additions & 0 deletions coffeelint.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"space_operators": {
"level": "error"
},
"max_line_length": {
"value": "100"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

80 is just wayyy too low, it was causing issues in the test files. Easier to just up the limit than to adhere to a dogmatic 80.

},
"cyclomatic_complexity": {
"value": 11,
"level": "warn"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"strip-json-comments": "^1.0.2"
},
"devDependencies": {
"vows": ">=0.8.1",
"underscore": ">=1.4.4"
"underscore": ">=1.4.4",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm automatically alphabeticalizes now.

"vows": ">=0.8.1"
},
"license": "MIT",
"scripts": {
Expand Down
3 changes: 3 additions & 0 deletions src/ast_linter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,15 @@ module.exports = class ASTLinter extends BaseLinter
lineNumber = -1
if coffeeError.location?
lineNumber = coffeeError.location.first_line + 1
columnNumber = coffeeError.location.first_column + 1
else
match = /line (\d+)/.exec message
lineNumber = parseInt match[1], 10 if match?.length > 1
columnNumber = 1
attrs = {
message: message
level: rule.level
lineNumber: lineNumber
columnNumber: columnNumber
}
return @createError 'coffeescript_error', attrs
12 changes: 6 additions & 6 deletions src/coffeelint.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,12 @@ coffeelint.registerRule require './rules/no_this.coffee'
coffeelint.registerRule require './rules/eol_last.coffee'
coffeelint.registerRule require './rules/no_private_function_fat_arrows.coffee'

hasSyntaxError = (source) ->
getTokens = (source) ->
try
# If there are syntax errors this will abort the lexical and line
# linters.
CoffeeScript.tokens(source)
return false
return true
return CoffeeScript.tokens(source)
return null

ErrorReport = require('./error_report.coffee')
coffeelint.getErrorReport = ->
Expand Down Expand Up @@ -310,9 +309,10 @@ coffeelint.lint = (source, userConfig = {}, literate = false) ->

# only do further checks if the syntax is okay, otherwise they just fail
# with syntax error exceptions
unless hasSyntaxError(source)
tokens = getTokens(source)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we were passing the source to Coffeescript before twice, which basically means we're doing twice as much work as needed.

if tokens
# Do lexical linting.
lexicalLinter = new LexicalLinter(source, config, _rules, CoffeeScript)
lexicalLinter = new LexicalLinter(source, config, _rules, CoffeeScript, tokens)
lexErrors = lexicalLinter.lint()
errors = errors.concat(lexErrors)

Expand Down
26 changes: 19 additions & 7 deletions src/configfinder.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ findFile = (name, dir) ->
# Possibly find CoffeeLint configuration within a package.json file.
loadNpmConfig = (dir) ->
fp = findFile('package.json', dir)
loadJSON(fp)?.coffeelintConfig if fp
loadJSON(fp, 'coffeelintConfig') if fp

# Parse a JSON file gracefully.
loadJSON = (filename) ->
loadJSON = (filename, attr = null) ->
try
JSON.parse(stripComments(fs.readFileSync(filename).toString()))
config = JSON.parse(stripComments(fs.readFileSync(filename).toString()))
if attr
return null if not config[attr]?
config = config[attr]

config.__location__ = filename
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so we can remember where the config file is loaded from, so we can resolve the "extends" property relative to the location of the configfile (as opposed to the cwd)

return config
catch e
process.stderr.write "Could not load JSON file '#{filename}': #{e}"
null
Expand All @@ -48,7 +54,7 @@ getConfig = (dir) ->
return loadJSON(process.env.COFFEELINT_CONFIG)

npmConfig = loadNpmConfig(dir)
return npmConfig if npmConfig
return npmConfig if npmConfig
projConfig = findFile('coffeelint.json', dir)
return loadJSON(projConfig) if projConfig

Expand Down Expand Up @@ -86,11 +92,16 @@ expandModuleNames = (dir, config) ->

config

extendConfig = (config) ->
extendConfig = (dir, config) ->
unless config.extends
return config
try
parentConfig = require resolve config.extends,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we cant resolve the path relative to our present directory, then we can look in node_modules

basedir: dir
extensions: ['.js', '.coffee', '.json']
catch
parentConfig = require config.extends

parentConfig = require config.extends
extendedConfig = {}

for ruleName, rule of config
Expand All @@ -109,8 +120,9 @@ exports.getConfig = (filename = null) ->

config = getConfig(dir)

dir = path.dirname(config.__location__)
if config
config = extendConfig(config)
config = extendConfig(dir, config)
config = expandModuleNames(dir, config)

config
17 changes: 13 additions & 4 deletions src/lexical_linter.coffee
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class TokenApi

constructor: (CoffeeScript, source, @config, @tokensByLine) ->
@tokens = CoffeeScript.tokens(source)
constructor: (CoffeeScript, source, @config, @tokensByLine, @tokens) ->
@tokens ?= CoffeeScript.tokens(source)
@lines = source.split('\n')
@tokensByLine = {} # A map of tokens by line.

Expand All @@ -18,10 +18,10 @@ BaseLinter = require './base_linter.coffee'
#
module.exports = class LexicalLinter extends BaseLinter

constructor: (source, config, rules, CoffeeScript) ->
constructor: (source, config, rules, CoffeeScript, tokens) ->
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so we can pass in the tokens from coffeelint.coffee (so we're not running CoffeeScript.tokens(source) twice)

super source, config, rules

@tokenApi = new TokenApi CoffeeScript, source, @config, @tokensByLine
@tokenApi = new TokenApi CoffeeScript, source, @config, @tokensByLine, tokens
# This needs to be available on the LexicalLinter so it can be passed
# to the LineLinter when this finishes running.
@tokensByLine = @tokenApi.tokensByLine
Expand Down Expand Up @@ -64,4 +64,13 @@ module.exports = class LexicalLinter extends BaseLinter
attrs.lineNumber ?= @lineNumber
attrs.lineNumber += 1
attrs.line = @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

super ruleName, attrs
2 changes: 1 addition & 1 deletion src/line_linter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,6 @@ module.exports = class LineLinter extends BaseLinter


createError: (rule, attrs = {}) ->
attrs.lineNumber = @lineNumber + 1 # Lines are indexed by zero.
attrs.lineNumber ?= @lineNumber + 1 # Lines are indexed by zero.
attrs.level = @config[rule]?.level
super rule, attrs
2 changes: 1 addition & 1 deletion src/rules/arrow_spacing.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ module.exports = class ArrowSpacing
pp.generated? or #2
pp[0] is 'INDENT' or #3
(pp[1] is '(' and not pp.generated?)) #4
true
{ token }
else
null
2 changes: 1 addition & 1 deletion src/rules/braces_spacing.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ module.exports = class BracesSpacing
msg = "There should be #{expected} space"
msg += 's' unless expected is 1
msg += " inside \"#{token[0]}\""
context: msg
{ token, context: msg }
2 changes: 1 addition & 1 deletion src/rules/camel_case_classes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ module.exports = class CamelCaseClasses

# Now check for the error.
if not regexes.camelCase.test(className)
return { context: "class name: #{className}" }
return { token, context: "class name: #{className}" }
8 changes: 6 additions & 2 deletions src/rules/colon_assignment_spacing.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ module.exports = class ColonAssignmentSpacing
when 'left'
token[2].first_column - previousToken[2].last_column - 1
when 'right'
nextToken[2].first_column - token[2].first_column - 1
# csx tags 'column' resolves to the beginning of the tag definition, rather
# than the '<'
offset = if nextToken[0] != 'CSX_TAG' then -1 else -2
nextToken[2].first_column - token[2].first_column + offset

checkSpacing = (direction) ->
spacing = getSpaceFromToken direction
Expand All @@ -57,7 +60,8 @@ module.exports = class ColonAssignmentSpacing
[isLeftSpaced, leftSpacing] = checkSpacing 'left'
[isRightSpaced, rightSpacing] = checkSpacing 'right'

if isLeftSpaced and isRightSpaced
if token.csxColon or isLeftSpaced and isRightSpaced
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have no control over spacing of "csxColons" (these are autogenerated by the compiler)

null
else
token: token
context: "Incorrect spacing around column #{token[2].first_column}"
2 changes: 2 additions & 0 deletions src/rules/cyclomatic_complexity.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ module.exports = class CyclomaticComplexity
context: complexity + 1
lineNumber: node.locationData.first_line + 1
lineNumberEnd: node.locationData.last_line + 1
columnNumber: node.locationData.first_column + 1
columnNumberEnd: node.locationData.last_column + 1
}
@errors.push error if error

Expand Down
2 changes: 1 addition & 1 deletion src/rules/duplicate_key.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports = class DuplicateKey
# Added a prefix to not interfere with things like "constructor".
key = "identifier-#{key}"
if @currentScope[key]
return true
return { token }
else
@currentScope[key] = token
null
Expand Down
6 changes: 3 additions & 3 deletions src/rules/empty_constructor_needs_parens.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ module.exports = class EmptyConstructorNeedsParens
# line with implicit parens, and if your parameters start on the
# next line, but is missing if there are no params and no parens.
if isIdent and nextToken?
return @handleExpectedCallStart(nextToken)
return @handleExpectedCallStart(nextToken, tokenApi)

handleExpectedCallStart: (isCallStart) ->
handleExpectedCallStart: (isCallStart, tokenApi) ->
if isCallStart[0] isnt 'CALL_START'
return true
return { token: tokenApi.peek(isCallStart, 1) }
4 changes: 2 additions & 2 deletions src/rules/ensure_comprehensions.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module.exports = class EnsureComprehensions
break

if prevToken[0] is '=' and numParenEnds is numParenStarts
atEqual = true
atEqual = { token }

peeker--

Expand All @@ -62,7 +62,7 @@ module.exports = class EnsureComprehensions
# amount of CALL_START/CALL_END tokens. An unequal number means the list
# comprehension is inside of a function call
if atEqual and numCallStarts is numCallEnds
return { context: '' }
return { token, context: '' }

findIdents: (tokenApi) ->
peeker = 1
Expand Down
3 changes: 2 additions & 1 deletion src/rules/indentation.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports = class Indentation

if dotIndent % expected isnt 0
return {
token,
context: "Expected #{expected} got #{got}"
}

Expand Down Expand Up @@ -97,7 +98,7 @@ module.exports = class Indentation

# Now check the indentation.
if not ignoreIndent and not (expected in numIndents)
return { context: "Expected #{expected} got #{numIndents[0]}" }
return { token, context: "Expected #{expected} got #{numIndents[0]}" }

# Return true if the current token is inside of an array.
inArray: () ->
Expand Down
2 changes: 1 addition & 1 deletion src/rules/line_endings.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ module.exports = class LineEndings
else
throw new Error("unknown line ending type: #{ending}")
if not valid
return { context: "Expected #{ending}" }
return { columnNumber: line.length, context: "Expected #{ending}" }
else
return null
1 change: 1 addition & 0 deletions src/rules/max_line_length.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ module.exports = class MaxLineLength
return

return {
columnNumber: max
context: "Length is #{lineLength}, max is #{max}"
}
1 change: 1 addition & 0 deletions src/rules/missing_fat_arrows.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module.exports = class MissingFatArrows
(@needsFatArrow node)
error = @astApi.createError
lineNumber: node.locationData.first_line + 1
columnNumber: node.locationData.first_column + 1
@errors.push error

node.eachChild (child) => @lintNode child,
Expand Down
3 changes: 2 additions & 1 deletion src/rules/no_backticks.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ module.exports = class NoBackticks
tokens: ['JS']

lintToken: (token, tokenApi) ->
return not token.comments?
if not token.comments?
{ token }
4 changes: 2 additions & 2 deletions src/rules/no_debugger.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ module.exports = class NoDebugger

lintToken: (token, tokenApi) ->
if token[0] in ['DEBUGGER', 'STATEMENT'] and token[1] is 'debugger'
return { context: "found '#{token[0]}'" }
return { token, context: "found '#{token[0]}'" }

if tokenApi.config[@rule.name]?.console
if token[1] is 'console' and tokenApi.peek(1)?[0] is '.'
method = tokenApi.peek(2)
return { context: "found 'console.#{method[1]}'" }
return { token, context: "found 'console.#{method[1]}'" }
1 change: 1 addition & 0 deletions src/rules/no_empty_functions.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ module.exports = class NoEmptyFunctions
if isEmptyCode node, astApi
error = astApi.createError
lineNumber: node.locationData.first_line + 1
columnNumber: node.locationData.first_column + 1
@errors.push error
node.eachChild (child) => @lintNode child, astApi
3 changes: 2 additions & 1 deletion src/rules/no_empty_param_list.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ module.exports = class NoEmptyParamList

lintToken: (token, tokenApi) ->
nextType = tokenApi.peek()[0]
return nextType is 'PARAM_END'
if nextType is 'PARAM_END'
return { token }
2 changes: 1 addition & 1 deletion src/rules/no_implicit_braces.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ module.exports = class NoImplicitBraces
if peekIdent is @className
return

return true
return { token: tokenApi.peek(c + 1) }

trackClass: (token, tokenApi) ->

Expand Down
4 changes: 2 additions & 2 deletions src/rules/no_implicit_parens.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = class NoImplicitParens
lintToken: (token, tokenApi) ->
if token.generated
unless tokenApi.config[@rule.name].strict is false
return true
return { token }
else
# If strict mode is turned off it allows implicit parens when
# the expression is spread over multiple lines.
Expand All @@ -36,7 +36,7 @@ module.exports = class NoImplicitParens
genCallStart = t[0] is 'CALL_START' and t.generated

if not t? or genCallStart and sameLine
return true
return { token: t or token }

# If we have not found a CALL_START token that is generated,
# and we've moved into a new line, this is fine and should
Expand Down
3 changes: 2 additions & 1 deletion src/rules/no_interpolation_in_single_quotes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ module.exports = class NoInterpolationInSingleQuotes
lintToken: (token, tokenApi) ->
tokenValue = token[1]
hasInterpolation = tokenValue.match(/^\'.*#\{[^}]+\}.*\'$/)
return hasInterpolation
if hasInterpolation
return { token }
Loading