troubadix 25.7.1__py3-none-any.whl → 25.7.3__py3-none-any.whl

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.
troubadix/__version__.py CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  # THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH!
4
4
 
5
- __version__ = "25.7.1"
5
+ __version__ = "25.7.3"
@@ -4,6 +4,7 @@
4
4
  """Helper for parsing if blocks and single-expression if statements in NASL files."""
5
5
 
6
6
  from dataclasses import dataclass
7
+ from enum import Enum
7
8
 
8
9
  from troubadix.helper.text_utils import (
9
10
  StringState,
@@ -12,7 +13,7 @@ from troubadix.helper.text_utils import (
12
13
 
13
14
  # Brace pairings
14
15
  CONDITION_BRACES = ("(", ")")
15
- BLOCK_BRACES = ("{", "}")
16
+ BODY_BRACES = ("{", "}")
16
17
 
17
18
 
18
19
  @dataclass
@@ -21,10 +22,35 @@ class IfStatement:
21
22
  if_end: int
22
23
  condition_start: int
23
24
  condition_end: int
24
- statement_start: int
25
- statement_end: int
25
+ outcome_start: int
26
+ outcome_end: int
26
27
  condition: str
27
- statement: str
28
+ outcome: str
29
+
30
+
31
+ class IfErrorType(Enum):
32
+ UNCLOSED_CONDITION = "Unclosed parenthesis in if condition at line {line}"
33
+ UNCLOSED_BODY = "Unclosed brace in if body at line {line}"
34
+ MISSING_OUTCOME = (
35
+ "Missing statement or body after if condition at line {line}"
36
+ )
37
+ TERMINATED_AFTER_CONDITION = (
38
+ "Semicolon after if condition at line {line} causes if to terminate early. "
39
+ "Following block will always execute."
40
+ )
41
+ MISSING_STATEMENT = "Missing expression after if condition at line {line}"
42
+
43
+
44
+ @dataclass
45
+ class IfParseError:
46
+ line: int
47
+ error_type: IfErrorType
48
+
49
+
50
+ @dataclass
51
+ class IfParseResult:
52
+ statements: list[IfStatement]
53
+ errors: list[IfParseError]
28
54
 
29
55
 
30
56
  class IfParser:
@@ -33,44 +59,76 @@ class IfParser:
33
59
  def __init__(self, file_content: str):
34
60
  self.file_content = file_content
35
61
 
36
- def find_if_statements(self) -> list[IfStatement]:
37
- """Parse the file to find all if statements (blocks and single expressions)."""
62
+ def find_if_statements(self) -> IfParseResult:
63
+ """
64
+ Parse the file to find all if statements (blocks and single expressions), collecting errors.
65
+
66
+ Example NASL if statements:
67
+ if (x > 0) {
68
+ foo();
69
+ }
70
+ if (y < 5)
71
+ bar();
72
+ """
38
73
  results: list[IfStatement] = []
74
+ errors: list[IfParseError] = []
75
+ # Step 1: Find all 'if' condition starts
39
76
  starts = self._find_condition_starts()
40
77
  if not starts:
41
- return results
78
+ return IfParseResult(results, errors)
42
79
 
43
80
  for if_start, opening_brace in starts:
44
81
  line, _ = index_to_linecol(self.file_content, if_start)
45
82
 
46
- condition_end = self._find_closing_brace(
47
- opening_brace, CONDITION_BRACES, line
83
+ # Step 2: Find the end of the condition (the closing parenthesis)
84
+ condition_end, condition_error = self._find_closing_brace(
85
+ opening_brace, CONDITION_BRACES
48
86
  )
87
+ if condition_error:
88
+ errors.append(
89
+ IfParseError(line=line, error_type=condition_error)
90
+ )
91
+ continue
49
92
  condition = self.file_content[
50
93
  opening_brace + 1 : condition_end
51
94
  ].strip()
52
- statement_pos = self._find_statement_start(condition_end, line)
53
95
 
54
- if self.file_content[statement_pos] == "{":
55
- # Block statement
56
- block_end = self._find_closing_brace(
57
- statement_pos, BLOCK_BRACES, line
96
+ # Step 3: Find the start of the outcome (first non-whitespace after condition)
97
+ outcome_start, outcome_error = self._find_outcome_start(
98
+ condition_end
99
+ )
100
+ if outcome_error:
101
+ errors.append(IfParseError(line=line, error_type=outcome_error))
102
+ continue
103
+
104
+ # Step 4: Determine if this is a body or single-expression statement
105
+ if self.file_content[outcome_start] == "{":
106
+ # Body: find closing brace for body '}'
107
+ body_end, body_error = self._find_closing_brace(
108
+ outcome_start, BODY_BRACES
58
109
  )
59
- if_end = block_end + 1
60
- statement_start = statement_pos + 1
61
- statement_end = block_end
62
- statement = self.file_content[
63
- statement_pos + 1 : block_end
64
- ].strip()
110
+ if body_error:
111
+ errors.append(
112
+ IfParseError(line=line, error_type=body_error)
113
+ )
114
+ continue
115
+ if_end = body_end + 1
116
+ outcome_start = outcome_start + 1 # exclude opening brace
117
+ outcome_end = body_end
65
118
  else:
66
- # Single expression
67
- expression_end = self._find_expression_end(statement_pos, line)
68
- if_end = expression_end + 1
69
- statement_start = statement_pos
70
- statement_end = expression_end
71
- statement = self.file_content[
72
- statement_pos:expression_end
73
- ].strip()
119
+ # Single statement: find end of statement ';'
120
+ statement_end, statement_error = self._find_statement_end(
121
+ outcome_start
122
+ )
123
+ if statement_error:
124
+ errors.append(
125
+ IfParseError(line=line, error_type=statement_error)
126
+ )
127
+ continue
128
+ if_end = statement_end + 1
129
+ outcome_end = statement_end
130
+
131
+ outcome = self.file_content[outcome_start:outcome_end].strip()
74
132
 
75
133
  results.append(
76
134
  IfStatement(
@@ -78,21 +136,20 @@ class IfParser:
78
136
  if_end=if_end,
79
137
  condition_start=opening_brace + 1,
80
138
  condition_end=condition_end,
81
- statement_start=statement_start,
82
- statement_end=statement_end,
139
+ outcome_start=outcome_start,
140
+ outcome_end=outcome_end,
83
141
  condition=condition,
84
- statement=statement,
142
+ outcome=outcome,
85
143
  )
86
144
  )
87
145
 
88
- return results
146
+ return IfParseResult(results, errors)
89
147
 
90
148
  def _find_closing_brace(
91
149
  self,
92
150
  start_pos: int,
93
151
  brace_pair: tuple[str, str],
94
- line: int,
95
- ) -> int:
152
+ ) -> tuple[int | None, IfErrorType | None]:
96
153
  """Find the matching closing brace, with proper error reporting."""
97
154
  opening_brace, closing_brace = brace_pair
98
155
  open_count = 1
@@ -111,19 +168,13 @@ class IfParser:
111
168
  elif char == closing_brace:
112
169
  open_count -= 1
113
170
  if open_count == 0:
114
- return i
171
+ return i, None
115
172
 
116
- # Generate appropriate error message based on brace type
173
+ # Error: unclosed brace
117
174
  if opening_brace == "(":
118
- raise ValueError(
119
- f"Unclosed parenthesis in if statement at line {line}"
120
- )
121
- elif opening_brace == "{":
122
- raise ValueError(f"Unclosed brace in if statement at line {line}")
175
+ return None, IfErrorType.UNCLOSED_CONDITION
123
176
  else:
124
- raise ValueError(
125
- f"Unclosed {opening_brace} in if statement at line {line}"
126
- )
177
+ return None, IfErrorType.UNCLOSED_BODY
127
178
 
128
179
  def _find_condition_starts(self) -> list[tuple[int, int]]:
129
180
  """
@@ -159,42 +210,41 @@ class IfParser:
159
210
 
160
211
  return starts
161
212
 
162
- def _find_statement_start(self, condition_end: int, line: int) -> int:
163
- """Find the start of the statement after the condition (next non-whitespace character)."""
213
+ def _find_outcome_start(
214
+ self, condition_end: int
215
+ ) -> tuple[int | None, IfErrorType | None]:
216
+ """
217
+ Find the start of the outcome/then part after the condition (next non-whitespace character).
218
+ """
164
219
  pos = condition_end + 1
165
220
  while pos < len(self.file_content) and self.file_content[pos].isspace():
166
221
  pos += 1
167
222
 
168
223
  if pos >= len(self.file_content):
169
- raise ValueError(
170
- f"Missing statement after if condition at line {line}"
171
- )
224
+ return None, IfErrorType.MISSING_OUTCOME
172
225
 
173
226
  if self.file_content[pos] == ";":
174
- raise ValueError(
175
- f"Semicolon after if condition at line {line} makes following block "
176
- f"always execute. Remove semicolon to fix."
177
- )
227
+ return None, IfErrorType.TERMINATED_AFTER_CONDITION
178
228
 
179
- return pos
229
+ return pos, None
180
230
 
181
- def _find_expression_end(self, expression_start: int, line: int) -> int:
182
- """Find the end of a single expression (semicolon outside of strings)."""
231
+ def _find_statement_end(
232
+ self, statement_start: int
233
+ ) -> tuple[int | None, IfErrorType | None]:
234
+ """Find the end of a single statement (semicolon outside of strings)."""
183
235
  string_state = StringState()
184
236
 
185
- for i in range(expression_start, len(self.file_content)):
237
+ for i in range(statement_start, len(self.file_content)):
186
238
  char = self.file_content[i]
187
239
  string_state.process_next_char(char)
188
240
  if not string_state.in_string and char == ";":
189
- return i
241
+ return i, None
190
242
 
191
- raise ValueError(
192
- f"Missing expression after if condition at line {line}"
193
- )
243
+ return None, IfErrorType.MISSING_STATEMENT
194
244
 
195
245
 
196
246
  # Wrapper function to maintain backward compatibility
197
- def find_if_statements(file_content: str) -> list[IfStatement]:
198
- """Parse a file to find all if statements (blocks and single expressions)."""
247
+ def find_if_statements(file_content: str) -> IfParseResult:
248
+ """Parse a file to find all if statements."""
199
249
  parser = IfParser(file_content)
200
250
  return parser.find_if_statements()
@@ -39,6 +39,7 @@ from .forking_nasl_functions import CheckForkingNaslFunctions
39
39
  from .get_kb_on_services import CheckGetKBOnServices
40
40
  from .grammar import CheckGrammar
41
41
  from .http_links_in_tags import CheckHttpLinksInTags
42
+ from .if_statement_syntax import CheckIfStatementSyntax
42
43
  from .illegal_characters import CheckIllegalCharacters
43
44
  from .log_messages import CheckLogMessages
44
45
  from .malformed_dependencies import CheckMalformedDependencies
@@ -149,6 +150,7 @@ _FILE_PLUGINS = [
149
150
  CheckVTPlacement,
150
151
  CheckWrongSetGetKBCalls,
151
152
  CheckSpacesBeforeDots,
153
+ CheckIfStatementSyntax,
152
154
  ]
153
155
 
154
156
  # plugins checking all files
@@ -0,0 +1,41 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ # SPDX-FileCopyrightText: 2025 Greenbone AG
3
+
4
+
5
+ from pathlib import Path
6
+ from typing import Iterator
7
+
8
+ from troubadix.helper.if_block_parser import find_if_statements
9
+ from troubadix.helper.remove_comments import remove_comments
10
+ from troubadix.plugin import FileContentPlugin, LinterError, LinterResult
11
+
12
+
13
+ class CheckIfStatementSyntax(FileContentPlugin):
14
+ """Check for syntax errors in if statements.
15
+
16
+ This plugin uses the if statement parser to detect common syntax errors such as:
17
+ - Unclosed parentheses in if conditions
18
+ - Unclosed braces in if blocks
19
+ - Missing statements after if conditions
20
+ - Semicolons after if conditions (which make following blocks always execute)
21
+ - Missing semicolons in single-line expressions
22
+ """
23
+
24
+ name = "check_if_statement_syntax"
25
+
26
+ def check_content(
27
+ self,
28
+ nasl_file: Path,
29
+ file_content: str,
30
+ ) -> Iterator[LinterResult]:
31
+ """Check the file content for if statement syntax errors."""
32
+ # Remove comments to avoid false positives from commented code
33
+ comment_free_content = remove_comments(file_content)
34
+
35
+ result = find_if_statements(comment_free_content)
36
+ for error in result.errors:
37
+ yield LinterError(
38
+ error.error_type.value.format(line=error.line),
39
+ file=nasl_file,
40
+ plugin=self.name,
41
+ )
@@ -38,22 +38,15 @@ class CheckUsingDisplay(FileContentPlugin):
38
38
  return
39
39
 
40
40
  comment_free_content = remove_comments(file_content)
41
- try:
42
- if_statements = find_if_statements(comment_free_content)
43
- except ValueError as e:
44
- yield LinterError(
45
- str(e),
46
- file=nasl_file,
47
- plugin=self.name,
48
- )
41
+
42
+ display_matches = list(DISPLAY_PATTERN.finditer(comment_free_content))
43
+
44
+ if not display_matches:
49
45
  return
50
46
 
51
- # Find all display() calls in the entire file
52
- all_display_matches = list(
53
- DISPLAY_PATTERN.finditer(comment_free_content)
54
- )
47
+ if_statements = find_if_statements(comment_free_content).statements
55
48
 
56
- for display_match in all_display_matches:
49
+ for display_match in display_matches:
57
50
  display_pos = display_match.start()
58
51
 
59
52
  # Skip if this match is inside a string literal
@@ -184,13 +184,13 @@ class CheckValidOID(FileContentPlugin):
184
184
  return
185
185
 
186
186
  amazon_sa_match = re.search(
187
- r"^1\.3\.6\.1\.4\.1\.25623\.1\.1\.5\.20[0-4][0-9]\.[0-9]+$",
187
+ r"^1\.3\.6\.1\.4\.1\.25623\.1\.1\.5\.([1-3]\.)?20[0-4][0-9]\.[0-9]+$",
188
188
  oid,
189
189
  )
190
190
  if amazon_sa_match is None:
191
191
  yield LinterError(
192
192
  f"script_oid() {invalid_oid} '{str(oid)}' (Amazon "
193
- "pattern: 1.3.6.1.4.1.25623.1.1.5.[ADVISORY_YEAR]"
193
+ "pattern: 1.3.6.1.4.1.25623.1.1.5.[OPTIONAL PRODUCT PREFIX.][ADVISORY_YEAR]"
194
194
  ".[ADVISORY_ID])",
195
195
  file=nasl_file,
196
196
  plugin=self.name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: troubadix
3
- Version: 25.7.1
3
+ Version: 25.7.3
4
4
  Summary: A linting and QA check tool for NASL files
5
5
  License: GPL-3.0-or-later
6
6
  Author: Greenbone
@@ -1,5 +1,5 @@
1
1
  troubadix/__init__.py,sha256=K7sIXXDrC7YRb7BvIpdQ6ZfG_QkT0qUH_wAlHROVRfM,716
2
- troubadix/__version__.py,sha256=s9wUZaxMg1UcS_yFk7qOs9hPH3B74hRj6Rm9Ownkqz0,103
2
+ troubadix/__version__.py,sha256=u9FnDhh-YcJ5SxwN459lv4y5J4xuIuXl2bH00srEsSE,103
3
3
  troubadix/argparser.py,sha256=-H07Jhqh68_M4Mbjq9qJjTr3MShy_N2pxl2qHA6cfRU,7481
4
4
  troubadix/codespell/codespell.additions,sha256=E62zoHJavUhcbMoyBqO2rOUe3OqIXikh7uAiGmNs5uw,524
5
5
  troubadix/codespell/codespell.exclude,sha256=8blBv8KRrKU4KQhneHOPth1tSCYdrEoOCI5kYRfhOEA,147451
@@ -7,13 +7,13 @@ troubadix/codespell/codespell.ignore,sha256=2CP8u6O2VENcDpt2FfEDNmfa1Eh3D80yeYHT
7
7
  troubadix/helper/__init__.py,sha256=tp2fPLzwGEA_2eiJbvuePiY6rjYSFxx7VUsCV4fSwvw,1110
8
8
  troubadix/helper/date_format.py,sha256=2m8EWZPmTQ1kxgv4i5hrPoPlAA8usjz28aRff352zNU,2488
9
9
  troubadix/helper/helper.py,sha256=GXapYLii2rLKwkX2ok31YoAdUSizBnyPjWz-aPP6HM8,3105
10
- troubadix/helper/if_block_parser.py,sha256=mPcx5XYca4Rr3F1jTo53DRMQ2DZlgY2I7ydymMjeLB0,6813
10
+ troubadix/helper/if_block_parser.py,sha256=_51UlsYrELt4GwWzJIU7qc7HIZPdpmExLG8WM8xtjTg,8294
11
11
  troubadix/helper/linguistic_exception_handler.py,sha256=Bq7ULjDdWTKUpFNTUX6XMPdD4s4v8eIjZPyqBe8VLws,6811
12
12
  troubadix/helper/patterns.py,sha256=qhiAtNQs89Hk6-yRGrjjRO4DdVIMhYLd8IEQ48REpu4,9752
13
13
  troubadix/helper/remove_comments.py,sha256=zjhXPQXgKaEOparAdu4aBXcJlEul8LbNaP3uLCYGwHw,1244
14
14
  troubadix/helper/text_utils.py,sha256=gBX0box9VtgA0CMfE0u2Vvi1IuOopxUfCQFYn_1wVjE,2543
15
15
  troubadix/plugin.py,sha256=3fQPj3Qe_hgwHerlYE4hbdzYMzRU557NxJ-UwtE9mOI,3525
16
- troubadix/plugins/__init__.py,sha256=w5ItmwNALfW4tIHuPuxqi2PMExA6eKbLiXCmviY9CSE,8661
16
+ troubadix/plugins/__init__.py,sha256=7NqTzddmptQZJ_s1yxpamVo0Y8zohAWlEQU2lf103_Y,8745
17
17
  troubadix/plugins/badwords.py,sha256=k1A1d2pdXzie87FGGXrykP2BgdZbY5QtmQItupHtNyw,4701
18
18
  troubadix/plugins/copyright_text.py,sha256=jYsLWmTbT_A78XQQxQFK-5kMMHkh3xdvlh7mEF2dZGU,3583
19
19
  troubadix/plugins/copyright_year.py,sha256=XzM9MHVzOXwNLwHpfuaWj8PUOmswr56SBVOLBdvxjd4,5478
@@ -32,6 +32,7 @@ troubadix/plugins/forking_nasl_functions.py,sha256=IUtCrTK_sGDx79jAPS8lF_aN8zSG2
32
32
  troubadix/plugins/get_kb_on_services.py,sha256=oFmcjiF7ZD3x5tEbJEoZNn80y1qUzNgeSZNsogSqaZ0,3401
33
33
  troubadix/plugins/grammar.py,sha256=9VPdf4nO-UOE2P8fiAKgh_8GwjGbLztPEixb2AQyeFU,9642
34
34
  troubadix/plugins/http_links_in_tags.py,sha256=yKT5SgLo__TJKAfudfIHkoMF0g9VtOP4VI77dANj014,7221
35
+ troubadix/plugins/if_statement_syntax.py,sha256=5BRJwCCghvZn1AfvYzmk8l9S7aRqbVaLHhSKod_Q9zw,1429
35
36
  troubadix/plugins/illegal_characters.py,sha256=B6q_RU85AxCjLry56Oc-RhMSpnJU8mTrxclRzi1FVFU,4406
36
37
  troubadix/plugins/log_messages.py,sha256=COrnp3bXMG8PRIAD2x5Ka9hk-jI16We9ifXj6JBZI0c,2960
37
38
  troubadix/plugins/malformed_dependencies.py,sha256=96W5RJX8CbMkprA2M601qVLgBV-ibJ96bAmtyoSrgFw,3000
@@ -70,8 +71,8 @@ troubadix/plugins/spelling.py,sha256=3AW5sNtLL67OthKLaCH_y2HCVJ5YH_eyF9xjTJMIDG4
70
71
  troubadix/plugins/tabs.py,sha256=7zXaTZe4cZoZvrLyqntVfTeNN_W3D8dfQl67QevXxtc,1319
71
72
  troubadix/plugins/todo_tbd.py,sha256=MN5fFwBhPmt3JDQ2Hx20B8yUy1vz7LIZC3rDIOzfW9M,1758
72
73
  troubadix/plugins/trailing_spaces_tabs.py,sha256=nMly8ZsmGprxHvwCDclKBDRB0eq6JEkjERYKvtStkY4,1873
73
- troubadix/plugins/using_display.py,sha256=BBqjMzRhlVZbbeOAAktdhqysuYoHNaMmqO6-6l_-Hy4,3983
74
- troubadix/plugins/valid_oid.py,sha256=20E2BW1TSoEuT2j8dJcYedqdUwXzEGrLbRdQpgAPoms,17788
74
+ troubadix/plugins/using_display.py,sha256=jCl55MXujaO4VlwN_yex269CKnH-oHiMeq8WqvDmGRI,3760
75
+ troubadix/plugins/valid_oid.py,sha256=zoSDyHdePitXJwCtBk4WExtCN36Ql5NATgoQh90oKpI,17824
75
76
  troubadix/plugins/valid_script_tag_names.py,sha256=6uMJsBdV-Zx-k1F2_MWmQPHXNo1u0ifuosbftbg-27E,3447
76
77
  troubadix/plugins/variable_assigned_in_if.py,sha256=NNz8iuzyQ4rSM6My4WYC1s5TABQqgs7us15PkDA-VV0,3285
77
78
  troubadix/plugins/variable_redefinition_in_foreach.py,sha256=SfaA70TkpD9dsvNbhwJEA3eLAHWvj4YwksN-qeBMowg,2470
@@ -107,8 +108,8 @@ troubadix/standalone_plugins/no_solution.py,sha256=p_-az9Igl4GH6HnhLLYbYlWIiEP64
107
108
  troubadix/standalone_plugins/util.py,sha256=JTXGmi-_BJouTNe6QzEosLlXUt9jKW-3fz4db05RJJw,696
108
109
  troubadix/standalone_plugins/version_updated.py,sha256=6YHF0OjL5NWszQdsSh7XzlSji1e6Uaqwu_Y6m3R0mvI,4203
109
110
  troubadix/troubadix.py,sha256=5__Jz3bYSrya4aG6RCBWxqnsDepXfwXZ3v0bjCzEFi0,6039
110
- troubadix-25.7.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
111
- troubadix-25.7.1.dist-info/METADATA,sha256=25a8sYPpn4ZehTgpyyCLlTVWk2gmspMJ44vsn2_5qFw,4462
112
- troubadix-25.7.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
113
- troubadix-25.7.1.dist-info/entry_points.txt,sha256=ETEPBi4fKv3o7hzkzceX4838G6g5_5wRdEddYot8N6A,920
114
- troubadix-25.7.1.dist-info/RECORD,,
111
+ troubadix-25.7.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
112
+ troubadix-25.7.3.dist-info/METADATA,sha256=Gom5h4veT9nQrAy-44KjDlz5TunMgOFJpu13dLmzkAo,4462
113
+ troubadix-25.7.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
114
+ troubadix-25.7.3.dist-info/entry_points.txt,sha256=ETEPBi4fKv3o7hzkzceX4838G6g5_5wRdEddYot8N6A,920
115
+ troubadix-25.7.3.dist-info/RECORD,,