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

Commit

Permalink
Merge pull request #552 from za-creature/noqa
Browse files Browse the repository at this point in the history
Better directive handling
  • Loading branch information
AsaAyers committed Mar 6, 2016
2 parents 247177a + 0e68c70 commit a8e7653
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 19 deletions.
59 changes: 47 additions & 12 deletions src/coffeelint.coffee
Expand Up @@ -52,14 +52,19 @@ extend = (destination, sources...) ->
defaults = (source, defaults) ->
extend({}, defaults, source)

# Helper to add rules to disabled list
union = (a, b) ->
c = {}
for x in a
c[x] = true
for x in b
c[x] = true

x for x of c

# Helper to remove rules from disabled list
difference = (a, b) ->
j = 0
while j < a.length
if a[j] in b
a.splice(j, 1)
else
j++
x for x in a when x not in b

LineLinter = require './line_linter.coffee'
LexicalLinter = require './lexical_linter.coffee'
Expand Down Expand Up @@ -293,8 +298,8 @@ coffeelint.lint = (source, userConfig = {}, literate = false) ->
disabledInitially = []
# Check ahead for inline enabled rules
for l in source.split('\n')
[ regex, set, rule ] = LineLinter.configStatement.exec(l) or []
if set is 'enable' and config[rule]?.level is 'ignore'
[ regex, set, ..., rule ] = LineLinter.getDirective(l) or []
if set in ['enable', 'enable-line'] and config[rule]?.level is 'ignore'
disabledInitially.push rule
config[rule].level = 'error'

Expand Down Expand Up @@ -322,32 +327,62 @@ coffeelint.lint = (source, userConfig = {}, literate = false) ->
inlineConfig =
enable: {}
disable: {}
'enable-line': {}
'disable-line': {}

# Sort by line number and return.
errors.sort((a, b) -> a.lineNumber - b.lineNumber)

# Create a list of all errors
disabledEntirely = do ->
result = []
map = {}
for { name } in errors or []
if not map[name]
result.push(name)
map[name] = true
result

# Disable/enable rules for inline blocks
allErrors = errors
errors = []
disabled = disabledInitially
nextLine = 0
for i in [0...source.split('\n').length]
disabledLine = disabled
for cmd of inlineConfig
rules = inlineConfig[cmd][i]
{
'disable': ->
disabled = disabled.concat(rules)
if rules.length
disabled = union(disabled, rules)
disabledLine = union(disabledLine, rules)
else
disabled = disabledLine = disabledEntirely
'disable-line': ->
if rules.length
disabledLine = union(disabledLine, rules)
else
disabledLine = disabledEntirely
'enable': ->
difference(disabled, rules)
disabled = disabledInitially if rules.length is 0
if rules.length
disabled = difference(disabled, rules)
disabledLine = difference(disabledLine, rules)
else
disabled = disabledLine = disabledInitially
'enable-line': ->
if rules.length
disabledLine = difference(disabledLine, rules)
else
disabledLine = disabledInitially
}[cmd]() if rules?
# advance line and append relevant messages
while nextLine is i and allErrors.length > 0
nextLine = allErrors[0].lineNumber - 1
e = allErrors[0]
if e.lineNumber is i + 1 or not e.lineNumber?
e = allErrors.shift()
errors.push e unless e.rule in disabled
errors.push e unless e.rule in disabledLine

cache?.set source, errors

Expand Down
21 changes: 15 additions & 6 deletions src/line_linter.coffee
Expand Up @@ -69,15 +69,22 @@ class LineApi
BaseLinter = require './base_linter.coffee'

# Some repeatedly used regular expressions.
configStatement = /coffeelint:\s*(disable|enable)(?:=([\w\s,]*))?/
configStatement = /coffeelint:\s*((disable|enable)(-line)?)(?:=([\w\s,]*))?/
configShortcuts = [
# TODO: make this user (and / or api) configurable
[/\#.*noqa/, 'coffeelint: disable-line']
]

#
# A class that performs regex checks on each line of the source.
#
module.exports = class LineLinter extends BaseLinter

# This is exposed here so coffeelint.coffee can reuse it
@configStatement: configStatement
@getDirective: (line) ->
for [shortcut, replacement] in configShortcuts
if line.match(shortcut)
return configStatement.exec(replacement)
return configStatement.exec(line)

constructor: (source, config, rules, tokensByLine, literate = false) ->
super source, config, rules
Expand All @@ -88,6 +95,8 @@ module.exports = class LineLinter extends BaseLinter
@inlineConfig =
enable: {}
disable: {}
'enable-line': {}
'disable-line': {}

acceptRule: (rule) ->
return typeof rule.lintLine is 'function'
Expand Down Expand Up @@ -117,12 +126,12 @@ module.exports = class LineLinter extends BaseLinter

collectInlineConfig: (line) ->
# Check for block config statements enable and disable
result = configStatement.exec(line)
result = @constructor.getDirective(line)
if result?
cmd = result[1]
rules = []
if result[2]?
for r in result[2].split(',')
if result[4]?
for r in result[4].split(',')
rules.push r.replace(/^\s+|\s+$/g, '')
@inlineConfig[cmd][@lineNumber] = rules
return null
Expand Down
94 changes: 93 additions & 1 deletion test/test_comment_config.coffee
Expand Up @@ -25,6 +25,82 @@ vows.describe('comment_config').addBatch({
assert.equal(errors[0].lineNumber, 5)
assert.ok(errors[0].message)

'Disable all statements':
topic: () ->
'''
# coffeelint: disable
a 'you get a semi-colon';
b 'you get a semi-colon';
# coffeelint: enable
c 'everybody gets a semi-colon';
'''

'can disable rules in your config': (source) ->
config =
no_trailing_semicolons: level: 'error'
errors = coffeelint.lint(source, config)
assert.equal(errors.length, 1)
assert.equal(errors[0].rule, 'no_trailing_semicolons')
assert.equal(errors[0].level, 'error')
assert.equal(errors[0].lineNumber, 5)
assert.ok(errors[0].message)

'Disable statements per line':
topic: () ->
'''
a 'foo'; # coffeelint: disable-line=no_trailing_semicolons
b 'bar';
'''

'can disable rules in your config': (source) ->
config =
no_trailing_semicolons: level: 'error'
errors = coffeelint.lint(source, config)
assert.equal(errors.length, 1)
assert.equal(errors[0].rule, 'no_trailing_semicolons')
assert.equal(errors[0].level, 'error')
assert.equal(errors[0].lineNumber, 2)
assert.ok(errors[0].message)

'Expand shortcuts':
topic: () ->
'''
a 'foo'; # noqa
b 'bar';
'''

'will expand and honor directive shortcuts': (source) ->
config =
no_trailing_semicolons: level: 'error'
errors = coffeelint.lint(source, config)
assert.equal(errors.length, 1)
assert.equal(errors[0].rule, 'no_trailing_semicolons')
assert.equal(errors[0].level, 'error')
assert.equal(errors[0].lineNumber, 2)
assert.ok(errors[0].message)

'Disable all statements per line':
topic: () ->
'''
a 'foo'; # coffeelint: disable-line
b 'bar';
'''

'can disable rules in your config': (source) ->
config =
no_trailing_semicolons: level: 'error'
no_implicit_parens: level: 'error'
errors = coffeelint.lint(source, config)
assert.equal(errors.length, 2)
assert.equal(errors[0].rule, 'no_implicit_parens')
assert.equal(errors[0].level, 'error')
assert.equal(errors[0].lineNumber, 2)
assert.ok(errors[0].message)
assert.equal(errors[1].rule, 'no_trailing_semicolons')
assert.equal(errors[1].level, 'error')
assert.equal(errors[1].lineNumber, 2)
assert.ok(errors[1].message)

'Enable statements':
topic: () ->
'''
Expand All @@ -49,7 +125,23 @@ vows.describe('comment_config').addBatch({
assert.equal(errors[1].lineNumber, 3)
assert.ok(errors[1].message)

'Enable all statements':
'Enable statements per line':
topic: () ->
'''
a 'foo'
b 'bar' # coffeelint: enable-line=no_implicit_parens
c 'baz'
'''

'can enable rules not in your config': (source) ->
errors = coffeelint.lint(source)
assert.equal(errors.length, 1)
assert.equal(errors[0].rule, 'no_implicit_parens')
assert.equal(errors[0].level, 'error')
assert.equal(errors[0].lineNumber, 2)
assert.ok(errors[0].message)

'Revert to post-config state':
topic: () ->
'''
# coffeelint: disable=no_trailing_semicolons,no_implicit_parens
Expand Down

0 comments on commit a8e7653

Please sign in to comment.