UNPKG

coffeelint

Version:
169 lines (134 loc) 6.3 kB
module.exports = class Indentation rule: name: 'indentation' value : 2 level : 'error' message : 'Line contains inconsistent indentation' description: """ This rule imposes a standard number of spaces to be used for indentation. Since whitespace is significant in CoffeeScript, it's critical that a project chooses a standard indentation format and stays consistent. Other roads lead to darkness. <pre> <code># Enabling this option will prevent this ugly # but otherwise valid CoffeeScript. twoSpaces = () -> fourSpaces = () -> eightSpaces = () -> 'this is valid CoffeeScript' </code> </pre> Two space indentation is enabled by default. """ tokens: ['INDENT', '[', ']', '.'] constructor: -> @arrayTokens = [] # A stack tracking the array token pairs. # Return an error if the given indentation token is not correct. lintToken: (token, tokenApi) -> [type, numIndents, { first_line: lineNumber }] = token { lines, lineNumber } = tokenApi expected = tokenApi.config[@rule.name].value # See: 'Indented chained invocations with bad indents' # This actually checks the chained call to see if its properly indented if type is '.' # Keep this if statement separately, since we still need to let # the linting pass if the '.' token is not at the beginning of # the line currentLine = lines[lineNumber] if currentLine.match(/\S/i)?[0] is '.' return @handleChain(tokenApi, expected) return undefined if type in ['[', ']'] @lintArray(token) return undefined return null if token.generated? # HACK: CoffeeScript's lexer insert indentation in string # interpolations that start with spaces e.g. "#{ 123 }" # so ignore such cases. Are there other times an indentation # could possibly follow a '+'? previous = tokenApi.peek(-2) isInterpIndent = previous and previous[0] is '+' # Ignore the indentation inside of an array, so that # we can allow things like: # x = ["foo", # "bar"] previous = tokenApi.peek(-1) isArrayIndent = @inArray() and previous?.newLine # Ignore indents used to for formatting on multi-line expressions, so # we can allow things like: # a = b = # c = d previousSymbol = tokenApi.peek(-1)?[0] isMultiline = previousSymbol in ['=', ','] # Summarize the indentation conditions we'd like to ignore ignoreIndent = isInterpIndent or isArrayIndent or isMultiline # Correct CoffeeScript's incorrect INDENT token value when functions # get chained. See https://github.com/jashkenas/coffeescript/issues/3137 # Also see CoffeeLint Issues: #4, #88, #128, and many more. numIndents = @getCorrectIndent(tokenApi) # Now check the indentation. if not ignoreIndent and numIndents isnt expected return { context: "Expected #{expected} got #{numIndents}" } # Return true if the current token is inside of an array. inArray: () -> return @arrayTokens.length > 0 # Lint the given array token. lintArray: (token) -> # Track the array token pairs if token[0] is '[' @arrayTokens.push(token) else if token[0] is ']' @arrayTokens.pop() # Return null, since we're not really linting # anything here. null handleChain: (tokenApi, expected) -> lastCheck = 1 callStart = 1 prevNum = 1 { lineNumber, lines } = tokenApi currentLine = lines[lineNumber] # Traverse up the token list until we see a CALL_START token. # Don't scan above this line findCallStart = tokenApi.peek(-callStart) while (findCallStart and findCallStart[0] isnt 'TERMINATOR') { first_line: lastCheck } = findCallStart[2] callStart += 1 findCallStart = tokenApi.peek(-callStart) # Keep going back until we are not at a comment or a blank lines # and set a new "previousLine" while (lineNumber - prevNum > lastCheck) and not /^\s*\./.test(lines[lineNumber - prevNum]) prevNum += 1 checkNum = lineNumber - prevNum if checkNum >= 0 prevLine = lines[checkNum] # If this is just a one-chain function, or the "corrected" # previous line begins with a '.', check for correct # indentation if prevLine.match(/\S/i)[0] is '.' or checkNum is lastCheck currentSpaces = currentLine.match(/\S/i)?.index prevSpaces = prevLine.match(/\S/i)?.index numIndents = currentSpaces - prevSpaces # If both prev and current lines have uneven spacing, # assume the current line could be lined by default # indent spacing, and set numIndents to current # number of spaces prevIsIndent = prevSpaces % expected isnt 0 currIsIndent = currentSpaces % expected isnt 0 if prevIsIndent and currIsIndent numIndents = currentSpaces if numIndents % expected isnt 0 return { context: "Expected #{expected} got #{numIndents}" } # Returns a corrected INDENT value if the current line is part of # a chained call. Otherwise returns original INDENT value. getCorrectIndent: (tokenApi) -> { lineNumber, lines, tokens, i } = tokenApi curIndent = lines[lineNumber].match(/\S/)?.index prevNum = 1 prevNum += 1 while (/^\s*(#|$)/.test(lines[lineNumber - prevNum])) prevLine = lines[lineNumber - prevNum] prevIndent = prevLine.match(/^(\s*)\./)?[1].length if prevIndent > 0 return curIndent - prevLine.match(/\S/)?.index else return tokens[i][1]