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

#widthObject (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