Class: SasLinter::Rules::MalformedIfCondition
- Inherits:
-
SasLinter::Rule
- Object
- SasLinter::Rule
- SasLinter::Rules::MalformedIfCondition
- Defined in:
- lib/sas_linter/rules/malformed_if_condition.rb
Overview
Validate that ‘if … then` conditions form well-shaped boolean expressions. Catches authoring mistakes the lexer cheerfully accepts but that won’t run, e.g.
if A1 = 1 A2 = 2 then ... * missing `and`/`or`
if A1 = 1 and then ... * trailing operator
if = 1 then ... * leading operator, no left operand
if then ... * empty condition
A1 = 1 then ... * missing `if`
if (a = 1 and b = 2 then ... * unbalanced parens
Strategy: at each ‘KW_IF`, walk forward to the matching top-level `KW_THEN` (or `;` for a subsetting `if`) running a tiny operand/operator state machine. Top-level only — anything inside parens is treated as a single sub-expression so function calls and `in (…)` lists don’t trigger false positives.
An orphan ‘KW_THEN` (one not consumed by an enclosing `if`) is reported as a likely missing `if`.
Constant Summary collapse
- TT =
SasLexer::Lexer::TokenType
- COMPARISON_OPS =
[ TT::ASSIGN, TT::KW_EQ, TT::KW_NE, TT::NE, TT::KW_LT, TT::LT, TT::KW_LE, TT::LE, TT::KW_GT, TT::GT, TT::KW_GE, TT::GE, TT::KW_IN, TT::SOUNDS_LIKE, TT::GTLT, TT::LTGT, TT::KW_EQT, TT::KW_GTT, TT::KW_LTT, TT::KW_GET, TT::KW_LET, TT::KW_NET ].freeze
- LOGICAL_OPS =
[TT::KW_AND, TT::KW_OR, TT::AMP, TT::PIPE, TT::PIPE2].freeze
- ARITHMETIC_OPS =
[TT::PLUS, TT::MINUS, TT::STAR, TT::FSLASH, TT::STAR2, TT::EXCL, TT::EXCL2, TT::BPIPE, TT::BPIPE2].freeze
- BINOPS =
(COMPARISON_OPS + LOGICAL_OPS + ARITHMETIC_OPS).to_set.freeze
- UNARY_PREFIXES =
‘+`/`-` are also binary; the state machine disambiguates by checking whether we currently expect an operand.
[TT::KW_NOT, TT::NOT, TT::MINUS, TT::PLUS].to_set.freeze
- OPERAND_TOKENS =
[ TT::IDENTIFIER, TT::INTEGER_LITERAL, TT::FLOAT_LITERAL, TT::FLOAT_EXPONENT_LITERAL, TT::STRING_LITERAL, TT::HEX_STRING_LITERAL, TT::BIT_TESTING_LITERAL, TT::DATE_LITERAL, TT::DATE_TIME_LITERAL, TT::TIME_LITERAL, TT::NAME_LITERAL, TT::MACRO_VAR_RESOLVE, TT::MACRO_IDENTIFIER, TT::MACRO_STRING, TT::STRING_EXPR_START ].to_set.freeze
Instance Attribute Summary
Attributes inherited from SasLinter::Rule
Instance Method Summary collapse
-
#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
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
rubocop:disable Lint/UnusedMethodArgument
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/sas_linter/rules/malformed_if_condition.rb', line 62 def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument findings = [] consumed_thens = {} i = 0 while i < tokens.length tok = tokens[i] if tok[:type] == TT::KW_IF new_i, sub_findings = analyze_if(tokens, i, path, consumed_thens) findings.concat(sub_findings) i = new_i next end if tok[:type] == TT::KW_THEN && !consumed_thens[i] findings << finding( line: tok[:start_line], column: tok[:start_column] + 1, message: "`then` without a preceding `if` condition — likely missing `if`.", path: path ) end i += 1 end findings end |