# Public: Check the manifest tokens for correct indent levels and # record a warning for each instance found. PuppetLint.new_check(:strict_indent) do def match(tokens) opening_token = { RBRACE: :LBRACE, RBRACK: :LBRACK, RPAREN: :LPAREN, HEREDOC: :HEREDOC_OPEN, HEREDOC_POST: :HEREDOC_OPEN, } open = { LBRACE: [], LBRACK: [], LPAREN: [], HEREDOC_OPEN: [], } matches = {} tokens.each do |token| if %i[LBRACE LBRACK LPAREN HEREDOC_OPEN].include?(token.type) open[token.type] << token elsif %i[RBRACE RBRACK RPAREN HEREDOC HEREDOC_POST].include?(token.type) match = open[opening_token[token.type]].pop unless match.nil? matches[token] = match matches[match] = token end end end matches end def check chars_per_indent = PuppetLint.configuration.chars_per_indent || 2 indent = 0 colon_indent = nil matches = match(tokens) tokens.select do |token| token.type == :NEWLINE end.reject do |token| # ignore newline at end of code token.next_token.nil? end.each do |token| temp_indent = 0 # indent for open groups in the previous line open_groups = 0 prev_token = token.prev_token while !prev_token.nil? and prev_token.type != :NEWLINE temp_indent += 1 if prev_token.type == :HEREDOC_OPEN if %i[LBRACE LBRACK LPAREN].include?(prev_token.type) && (matches[prev_token].nil? or matches[prev_token].line > prev_token.line) # left braces not matched in the same line increase indent open_groups += 1 end prev_token = prev_token.prev_token end indent += open_groups # reset prev_token to last non-whitespace token on previous line prev_token = token.prev_token while !prev_token.nil? and (prev_token.type == :WHITESPACE or prev_token.type == :COMMENT) prev_token = prev_token.prev_token end # get type if available prev_type = prev_token.nil? ? nil : prev_token.type # handle change in indent based on last token case prev_type when :COLON if open_groups == 0 if colon_indent.nil? # only indent for a colon when you haven't indented yet colon_indent = prev_token.line indent += 1 else # you probably missed a semicolon two lines ago end end when :SEMIC unless colon_indent.nil? # only unindent for a semicolon when we've indented for a colon colon_indent = nil indent -= 1 end when :EQUALS, :FARROW temp_indent += 1 end # unindent for closing brackets in the current line next_token = token.next_token while !next_token.nil? and next_token.type != :NEWLINE if %i[RBRACE RBRACK RPAREN].include?(next_token.type) if !matches[next_token].nil? and matches[next_token].line < next_token.line # right braces matched in a previous line decrease indent indent -= 1 end if next_token.type == :RBRACE and !colon_indent.nil? && (!matches[next_token].nil? and matches[next_token].line < colon_indent) # unindent at the end of resources if needed indent -= 1 colon_indent = nil end end next_token = next_token.next_token end # obviously we have a problem if indent < 0 notify :error, { message: 'Error calculating indent. Please file an issue at https://github.com/relud/puppet-lint-indent-check/issues', line: token.next_token.line, column: token.next_token.column, } # stop parsing indent break end # get actual indent actual = 0 actual = if token.next_token.type == :INDENT token.next_token.value.length elsif !token.prev_token.nil? and token.prev_token.type == :HEREDOC token.prev_token.value.split("\n").last.length elsif !token.prev_token.nil? and token.prev_token.type == :HEREDOC_OPEN next_token.prev_token.value.split("\n").last.length else 0 end # expected indent expected = (indent + temp_indent) * chars_per_indent # oh no! incorrect indent! next unless actual != expected # no one cares if blank lines and comments are indented correctly next if %i[COMMENT NEWLINE].include?(token.next_token.type) notify :warning, { message: "indent should be #{expected} chars and is #{actual}", line: token.next_token.line, column: token.next_token.column, token: token.next_token, indent: expected, } end end def fix(problem) char_for_indent = ' ' if %i[INDENT WHITESPACE].include?(problem[:token].type) problem[:token].value = char_for_indent * problem[:indent] else tokens.insert( tokens.find_index(problem[:token]), PuppetLint::Lexer::Token.new(:INDENT, char_for_indent * problem[:indent], problem[:line], problem[:column]), ) end end end