Class: SasLinter::Rules::UnreachableInnerBranchValue
- Inherits:
-
SasLinter::Rule
- Object
- SasLinter::Rule
- SasLinter::Rules::UnreachableInnerBranchValue
- Defined in:
- lib/sas_linter/rules/unreachable_inner_branch_value.rb
Overview
Flag inner branches whose comparison values are excluded by an enclosing ‘if VAR in (…) then do; … end;` guard.
Motivating shape: an outer guard ‘if RANK in (0,1,2,3,4,5,6,8) then do;` omits 7, while an inner `if RANK in (5,6,7,8) then cOut = 2;` lists 7. Value 7 falls through the outer guard, so the inner branch can never fire for it and cOut silently stays missing.
Detection: outer guard pushes a allowed_set frame; inner ‘if VAR in (…)`, `if VAR = N`, or `if VAR eq N` references inside the same DO block are checked against that set. Values absent from the outer set produce a finding.
Constant Summary collapse
- TT =
SasLexer::Lexer::TokenType
Instance Attribute Summary
Attributes inherited from SasLinter::Rule
Instance Method Summary collapse
-
#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object
Outer guard pattern: KW_IF IDENT KW_IN LPAREN <lits…> RPAREN KW_THEN KW_DO SEMI Inner check patterns: KW_IF IDENT(V) KW_IN LPAREN <lits…> RPAREN KW_IF IDENT(V) KW_EQ <lit> KW_IF IDENT(V) ASSIGN <lit> (SAS uses ‘=` as comparison in IF).
Methods inherited from SasLinter::Rule
all, #autofix?, description, fetch, from_config, inherited, #initialize, register, registry, rule_id, severity, supports_autofix?
Constructor Details
This class inherits a constructor from SasLinter::Rule
Instance Method Details
#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object
Outer guard pattern: KW_IF IDENT KW_IN LPAREN <lits…> RPAREN KW_THEN KW_DO SEMI Inner check patterns:
KW_IF IDENT(V) KW_IN LPAREN <lits...> RPAREN
KW_IF IDENT(V) KW_EQ <lit>
KW_IF IDENT(V) ASSIGN <lit> (SAS uses `=` as comparison in IF)
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/sas_linter/rules/unreachable_inner_branch_value.rb', line 35 def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument findings = [] guard_stack = [] # array of {var:, allowed:, depth:} do_depth = 0 i = 0 while i < tokens.length tok = tokens[i] if tok[:type] == TT::KW_IF consumed, frame, inner_findings = analyze_if(tokens, i, do_depth, guard_stack, path) findings.concat(inner_findings) if frame guard_stack.push(frame) do_depth += 1 end i += consumed next end if tok[:type] == TT::KW_DO # bare `do;` (no IF prefix), or `do i = 1 to N;` — both increment depth do_depth += 1 i += 1 next end if tok[:type] == TT::KW_END do_depth -= 1 if do_depth > 0 guard_stack.pop while guard_stack.last && guard_stack.last[:depth] > do_depth i += 1 next end i += 1 end findings end |