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 +4 -4
- data/README.md +6 -1
- data/lib/sas_linter/rules/unterminated_comment.rb +101 -0
- data/lib/sas_linter/version.rb +1 -1
- data/lib/sas_linter.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b12c05da17de3409a5eb163f3e3325cb7e2e24770d3de42e01efb786fc796d60
|
|
4
|
+
data.tar.gz: b14b81be912177959a039484c70ea7bf71f209910cf995822d8083c8d6ea2897
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
data/lib/sas_linter/version.rb
CHANGED
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
|
+
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
|