sas-linter 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b10cf15e62fe7e0a81d8f9d2e2c9eb61af36ee00d10f32e9ce8354a7ebe3e40f
4
- data.tar.gz: 9abe8249ab9d968fdc8aefe558dcdad321ba2879abe4bf08374b9ec998eecb5f
3
+ metadata.gz: b12c05da17de3409a5eb163f3e3325cb7e2e24770d3de42e01efb786fc796d60
4
+ data.tar.gz: b14b81be912177959a039484c70ea7bf71f209910cf995822d8083c8d6ea2897
5
5
  SHA512:
6
- metadata.gz: 38e745b6f3ec4c8a7cc2ce3c23883569bcd931d88d6f9dc0a6c2288ed7f0b39f199e066340e780e8556d0ceeaa7bfeb149185ef2d458f58574281c54dbbf60d3
7
- data.tar.gz: 8e6b5f2751eb5b3b9e50b80f0476f953519763a321fba4dd17ce219d891a84a3f0fe3c1ea1fbc3c18f4a698bc0655ef794ff2ef71d1d859aa23195a9119d41bd
6
+ metadata.gz: 5fa28c6588e96bdd973197577e0e3fb1aa689fd641455ed10225f0354e0243e41fdb1e682b20d0de02d2a443b30ae9d263f419a31d42ea12763d91320bef7078
7
+ data.tar.gz: aa8eb2ffe598911984fd57acff5ea350136865ccc64da7d7003b23033840013ac5c544a9bfd56d7f57bcb67c1f01de5af74c5b32f59d87300eac3525ae9e9bfe
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sas-linter
2
2
 
3
- A configurable lint engine for SAS source files. Built on the [`sas-lexer`](https://github.com/mes-amis/sas-lexer-rb) gem (a Ruby FFI binding to Misha Perlov's Rust [`sas-lexer`](https://github.com/mishamsk/sas-lexer)) and ships with fourteen pluggable rules covering structural defects, cosmetic issues, and source-header conventions.
3
+ A configurable lint engine for SAS source files. Built on the [`sas-lexer`](https://github.com/mes-amis/sas-lexer-rb) gem (a Ruby FFI binding to Misha Perlov's Rust [`sas-lexer`](https://github.com/mishamsk/sas-lexer)) and ships with fifteen pluggable rules covering structural defects, cosmetic issues, and source-header conventions.
4
4
 
5
5
  ## Installation
6
6
 
@@ -63,6 +63,10 @@ rules:
63
63
  enabled: true
64
64
  autofix: false # rule supports autofix; off by default
65
65
 
66
+ unterminated_comment:
67
+ enabled: true
68
+ autofix: false # append `;` to `**…**` lines whose missing terminator eats the next line
69
+
66
70
  inconsistent_variable_case:
67
71
  enabled: true
68
72
  autofix: false # rewrite every minority casing to the most-common form
@@ -141,6 +145,7 @@ findings = linter.lint_file("path/to/source.sas")
141
145
  | `encoding_issues` | Smart-quote / em-dash / Win-1252 byte sequences that confuse downstream tooling. |
142
146
  | `malformed_if_condition` | Empty conditions, missing operators, orphan `then`, unbalanced parens, etc. |
143
147
  | `missing_assignment_semicolon` | Assignment statements followed by an inline `**` comment but no terminating `;`. |
148
+ | `unterminated_comment` | A standalone `** … **` comment whose missing `;` lets the SAS lexer extend the comment into the next line of real code, silently swallowing it. Autofix appends the `;`. |
144
149
  | `variable_value_out_of_known_range` | `if VAR = N` / `if VAR in (...)` literals fall outside the variable's documented acceptable values. Loads the catalog from one or more CSVs with configurable column names and column separator (`,`, `;`, tab). |
145
150
  | `inconsistent_variable_case` | Identifier appears with more than one casing in the same file (`myVar` vs `MyVar`). SAS treats both as the same variable; autofix rewrites every minority spelling to the most-common form. Skips proc-format definitions and `format.` / `lib.member` references. |
146
151
  | `format_for_unknown_variable` | `format` / `informat` / `attrib` statement assigns a format to a variable that's referenced nowhere else in the file — almost always a typo. Skipped on files that pull in columns via `set` / `merge` / `update` / `infile` / `input`. |
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../sas_linter"
4
+ require "sas_lexer"
5
+
6
+ class SasLinter
7
+ module Rules
8
+ # Flag a `** ... **` comment line whose missing `;` causes the
9
+ # SAS lexer to extend the comment statement into the following
10
+ # line(s) of real code, silently swallowing it.
11
+ #
12
+ # SAS `*` / `**` comment statements are terminated by the next
13
+ # `;` — the closing `**` is just prose. So
14
+ #
15
+ # ** SOME COMMENT **
16
+ # y = x + 1;
17
+ #
18
+ # lexes as a single comment token covering both lines, and the
19
+ # `y = x + 1;` assignment never executes.
20
+ #
21
+ # Detection: a COMMENT-channel `PREDICTED_COMMENT_STAT` token
22
+ # whose `start_line != end_line` AND whose first source line,
23
+ # rstripped, ends with `**`. That shape is the boxed-comment
24
+ # closer the user clearly intended — they only forgot the `;`.
25
+ # Legitimate multi-line `*...;` prose ends its first line with
26
+ # plain text, not `**`, so it's left alone.
27
+ #
28
+ # Autofix: append `;` to the end of each flagged first line.
29
+ class UnterminatedComment < Rule
30
+ rule_id :unterminated_comment
31
+ description "`**` comment missing its terminating `;` — consumes following code."
32
+ severity :warning
33
+
34
+ TT = SasLexer::Lexer::TokenType
35
+ COMMENT_CHANNEL = SasLexer::Lexer::TokenChannel::COMMENT
36
+
37
+ def self.supports_autofix?
38
+ true
39
+ end
40
+
41
+ def check(_tokens, path:, all_tokens: nil, source: nil)
42
+ return [] unless all_tokens && source
43
+
44
+ lines = source.split("\n", -1)
45
+ unterminated_comment_lines(all_tokens, lines).map do |i|
46
+ finding_for_line(lines[i], i, path)
47
+ end
48
+ end
49
+
50
+ def autofix(source)
51
+ return source if source.nil? || source.empty?
52
+
53
+ lines = source.split("\n", -1)
54
+ bad = unterminated_comment_lines(tokenize(source), lines)
55
+ return source if bad.empty?
56
+
57
+ bad.each do |i|
58
+ lines[i] = "#{lines[i].rstrip};"
59
+ end
60
+ lines.join("\n")
61
+ end
62
+
63
+ private
64
+
65
+ def finding_for_line(line, idx, path)
66
+ finding(
67
+ line: idx + 1,
68
+ column: line.length - line.lstrip.length + 1,
69
+ message: "`**` comment missing `;` — consumes the next line of code as comment text.",
70
+ path: path
71
+ )
72
+ end
73
+
74
+ def tokenize(source)
75
+ lexer = SasLexer::Lexer.new
76
+ begin
77
+ lexer.tokenize(source)
78
+ ensure
79
+ lexer.free
80
+ end
81
+ end
82
+
83
+ # 0-indexed source lines that hold a `** ... **` comment whose
84
+ # missing `;` made the lexer extend it into the next line.
85
+ def unterminated_comment_lines(all_tokens, lines)
86
+ bad = []
87
+ all_tokens.each do |t|
88
+ next unless t[:channel] == COMMENT_CHANNEL
89
+ next unless t[:type] == TT::PREDICTED_COMMENT_STAT
90
+ next unless t[:start_line] < t[:end_line]
91
+
92
+ first = lines[t[:start_line] - 1] or next
93
+ next unless first.rstrip.end_with?("**")
94
+
95
+ bad << (t[:start_line] - 1)
96
+ end
97
+ bad
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SasLinter
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/sas_linter.rb CHANGED
@@ -311,3 +311,4 @@ require_relative "sas_linter/rules/variable_value_out_of_known_range"
311
311
  require_relative "sas_linter/rules/invalid_numeric_literal"
312
312
  require_relative "sas_linter/rules/inconsistent_variable_case"
313
313
  require_relative "sas_linter/rules/format_for_unknown_variable"
314
+ require_relative "sas_linter/rules/unterminated_comment"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sas-linter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig McNamara
@@ -74,6 +74,7 @@ files:
74
74
  - lib/sas_linter/rules/tab_expansion.rb
75
75
  - lib/sas_linter/rules/trailing_whitespace.rb
76
76
  - lib/sas_linter/rules/unreachable_inner_branch_value.rb
77
+ - lib/sas_linter/rules/unterminated_comment.rb
77
78
  - lib/sas_linter/rules/variable_value_out_of_known_range.rb
78
79
  - lib/sas_linter/version.rb
79
80
  homepage: https://github.com/mes-amis/sas-linter