Class: SasLinter::Rules::SourceHeaders

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

Overview

Restore the standard 90-char ‘;` header convention to broken SAS source files. Detects header lines that look like `**`-comments but produce DEFAULT-channel tokens, and re-wraps them as proper `** … **;` rows.

Working sources use a uniform 90-char-wide header where each line is its own self-contained ‘*` comment statement:

****************************************************************************************;
**  PROGRAM:          ...                                                            **;
**  BY:               ...                                                            **;

Broken sources have lines that look like comments (start with ‘**`) but produce DEFAULT-channel tokens. Two flavors:

A. Missing trailing `;` on every header line — the whole
   header is one giant unterminated `*` comment until the
   first inline `;` ends it, leaking the rest of that
   physical line and following lines onto DEFAULT.

B. Trailing `**;` is present but an inline `;` (e.g. a
   semicolon-separated list like `First Reviewer; Second
   Reviewer`) terminates the comment in the middle of the
   line — what follows the inline `;` ends up on DEFAULT
   even though the line *looks* terminated.

Some files also have header continuation lines (text that should be inside a ‘**` comment) that lost their `**` prefix during a text-conversion step. Those are detected only inside the file’s leading header block — before the first KW_DATA / KW_PROC token the lexer reports — so legitimate body code sandwiched between ‘**` marker comments is left alone.

Recognized config options:

autofix: true | false   (default: false)

Constant Summary collapse

TARGET_WIDTH =
90
PAD_TO =

leave 3 chars for trailing ‘**;`

TARGET_WIDTH - 3
DEFAULT_CHANNEL =
SasLexer::Lexer::TokenChannel::DEFAULT
KW_DATA =
SasLexer::Lexer::TokenType::KW_DATA
KW_PROC =
SasLexer::Lexer::TokenType.const_get(:KW_PROC)
C_STYLE_COMMENT =
SasLexer::Lexer::TokenType::C_STYLE_COMMENT
IDENTIFIER =
SasLexer::Lexer::TokenType::IDENTIFIER
SEMI =
SasLexer::Lexer::TokenType::SEMI
ASSIGN =
SasLexer::Lexer::TokenType::ASSIGN

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from SasLinter::Rule

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

Constructor Details

This class inherits a constructor from SasLinter::Rule

Class Method Details

.supports_autofix?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/sas_linter/rules/source_headers.rb', line 60

def self.supports_autofix?
  true
end

Instance Method Details

#autofix(source) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sas_linter/rules/source_headers.rb', line 77

def autofix(source)
  # Step 0: expand any tab characters to 4 spaces. Tabs in
  # SAS source headers often come from Word docs, and
  # break the column-alignment of the header box. Doing this
  # first means every downstream check sees consistent column
  # offsets.
  text = source.gsub("\t", "    ")
  10.times do
    tokens = tokenize(text)
    skip   = c_comment_lines(tokens)
    bad    = broken_lines_for(text, tokens, skip) |
             asterisk_rows_missing_semi_for(text, skip)
    break if bad.empty?

    text = rewrite(text, bad)
  end
  text
end

#broken_header_lines(source) ⇒ Object

0-indexed line numbers the lexer thinks are broken header text in ‘source`. Public so the rule’s ‘check` can produce findings without re-tokenizing on its own.



99
100
101
102
# File 'lib/sas_linter/rules/source_headers.rb', line 99

def broken_header_lines(source)
  tokens = tokenize(source)
  broken_lines_for(source, tokens, c_comment_lines(tokens))
end

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

rubocop:disable Lint/UnusedMethodArgument



64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/sas_linter/rules/source_headers.rb', line 64

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

  broken_header_lines(source).map do |line_idx|
    finding(
      line: line_idx + 1,
      column: 1,
      message: "broken header line#{autofix? ? ' (autofixed)' : ''}",
      path: path
    )
  end
end