Skip to content

Instantly share code, notes, and snippets.

@modernserf
Created October 15, 2013 12:58
Show Gist options
  • Save modernserf/6991211 to your computer and use it in GitHub Desktop.
Save modernserf/6991211 to your computer and use it in GitHub Desktop.
Basic SASS linter
fs = require 'fs'
file_path = __INSERT_FILE_PATH_HERE__
fs.readFile file_path, {encoding: 'utf-8'}, (err, data)->
# global document object
doc = {
tab_size: 2
lines: data.split("\n")
}
options = [
'deep_selectors'
'multi_selectors'
'magic_numbers'
]
parsed = doc.lines.map parse_line
parsed.forEach (p, index)->
errors = (warn(p, doc) for key, warn of warnings when (key in options))
.filter (x)-> x?
warn_at_line(index, doc) if errors.length
warn_at_line = (index, doc)->
console.warn "#{index}:", doc.lines[index], "\n"
null
warnings = {
deep_selectors: (line, doc)->
# checks for selectors more than 3 levels deep which may be too specific
return unless line.kind is "selector"
depth = line.indent / doc.tab_size + Math.max.apply null, line.args.map (g)-> g.length
if depth > 3
console.warn "Warning: selector #{depth} levels deep"
return {deep_selectors: depth}
multi_selectors: (line, doc)->
# checks for multiple selectors on a line
return unless line.kind is "selector"
selector_count = line.args.length
if selector_count > 1
console.warn "Warning: #{selector_count} selectors on single line"
return {multi_selectors: selector_count}
magic_numbers: (line, doc)->
# checks for suspicious "magic numbers", i.e. pixel/color/percent values
# that are not attached to a variable
return unless line.kind is "attribute"
# ignore var definition lines
return if line.args[0][0] is '$'
# px, percent, or color values
blacklist = /(px|%|#|rgb)/
# exceptions
whitelist = /^(1px|100%|50%|25%|20%)/
errors = line.args
.map((arg)-> arg if blacklist.test(arg))
.filter((arg)-> arg? && !whitelist.test(arg))
if errors.length
console.warn "Warning: Magic Numbers ", errors.join(', ')
return {magic_numbers: errors}
}
# per line
parse_line = (line)->
return null unless line?
# indent: # of preceding spaces
# kind: mixin, attribute, selector
# args: array of values or selectors
trim = line.trimLeft()
kind = if (line.indexOf('@') > 0)
"mixin"
else if (line.indexOf(': ') > 0)
"attribute"
else
"selector"
{
indent: line.length - trim.length
kind: kind
args: do ()->
if kind is "attribute"
# split on spaces, colons
trim.split(/\s|:\s/)
else if kind is "selector"
# split on commas
trim.split(/\s*,\s*/)
.filter( (group)-> group? ) # remove empty items
.map (group)->
group.split(/\s+/) # split on spaces
else
null
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment