Class: SasLinter::Rules::TabExpansion

Inherits:
SasLinter::Rule show all
Defined in:
lib/sas_linter/rules/tab_expansion.rb

Overview

Flag literal TAB (\t) characters in source. SAS authoring conventions strongly prefer spaces — tabs render at different widths in different editors and break the column alignment SAS sources often rely on for readability.

When autofix is true, each tab is replaced with the number of spaces needed to reach the next column-aligned tab stop (i.e., the standard expand(1) semantics with the configured width). A tab in column N expands to width - (N % width) spaces, so leading whitespace, mid-line alignment, and pre- token padding all stay column-aligned post-fix.

Recognized config options:

width:   integer (default 8)
autofix: true | false (default false)

Constant Summary collapse

DEFAULT_WIDTH =
8

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from SasLinter::Rule

all, #autofix?, description, fetch, inherited, register, registry, rule_id, severity

Constructor Details

#initialize(width: DEFAULT_WIDTH, autofix: false) ⇒ TabExpansion

Returns a new instance of TabExpansion.

Raises:

  • (ArgumentError)


43
44
45
46
47
48
# File 'lib/sas_linter/rules/tab_expansion.rb', line 43

def initialize(width: DEFAULT_WIDTH, autofix: false)
  super(autofix: autofix)
  raise ArgumentError, "width must be positive (got #{width})" if width.to_i < 1

  @width = Integer(width)
end

Instance Attribute Details

#width ⇒ Object (readonly)

Returns the value of attribute width.



41
42
43
# File 'lib/sas_linter/rules/tab_expansion.rb', line 41

def width
  @width
end

Class Method Details

.from_config(opts = {}) ⇒ Object



33
34
35
36
37
38
39
# File 'lib/sas_linter/rules/tab_expansion.rb', line 33

def self.from_config(opts = {})
  opts = opts.transform_keys(&:to_s)
  new(
    width: Integer(opts.fetch("width", DEFAULT_WIDTH)),
    autofix: opts["autofix"] ? true : false
  )
end

.supports_autofix? ⇒ Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/sas_linter/rules/tab_expansion.rb', line 29

def self.supports_autofix?
  true
end

Instance Method Details

#autofix(source) ⇒ Object

Replace every tab with width - (col % width) spaces, where col is the post-expansion column of the tab. Re-counts per line so the line terminator resets the column.



75
76
77
# File 'lib/sas_linter/rules/tab_expansion.rb', line 75

def autofix(source)
  source.each_line.map { |line| expand_line(line) }.join
end

#check(_tokens, path:, all_tokens: nil, source: nil) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/sas_linter/rules/tab_expansion.rb', line 50

def check(_tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument
  return [] unless source

  findings = []
  source.each_line.with_index do |line, idx|
    chomped = line.sub(/\r?\n\z/, "")
    next unless chomped.include?("\t")

    chomped.each_char.with_index do |ch, col|
      next unless ch == "\t"

      findings << finding(
        line: idx + 1,
        column: col + 1,
        message: "tab character#{autofix? ? " (expanded to #{@width}-space tab stop)" : ''}",
        path: path
      )
    end
  end
  findings
end