lizard 1.20.0__py2.py3-none-any.whl → 1.21.0__py2.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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lizard
3
- Version: 1.20.0
3
+ Version: 1.21.0
4
4
  Summary: A code analyzer without caring the C/C++ header files. It works with Java, C/C++, JavaScript, Python, Ruby, Swift, Objective C. Metrics includes cyclomatic complexity number etc.
5
5
  Home-page: http://www.lizard.ws
6
+ Download-URL: https://pypi.python.org/lizard/
6
7
  Author: Terry Yin
7
8
  Author-email: terry@odd-e.com
8
9
  License: MIT
9
- Download-URL: https://pypi.python.org/lizard/
10
10
  Project-URL: Source, https://github.com/terryyin/lizard
11
11
  Platform: any
12
12
  Classifier: Development Status :: 5 - Production/Stable
@@ -351,6 +351,15 @@ behavior of lizard. There are two types of forgiveness comments:
351
351
  ...
352
352
  }
353
353
 
354
+ Selective forgiveness: Use "#lizard forgives(metric1, metric2)" to forgive only specific metrics (e.g. length, cyclomatic_complexity, parameter_count, nloc, token_count).
355
+
356
+ ::
357
+
358
+ int foo() {
359
+ // #lizard forgives(length) // Forgive only length violations
360
+ ...
361
+ }
362
+
354
363
  2. Global code forgiveness: Put "#lizard forgive global" before global code to suppress warnings for all code outside of functions.
355
364
 
356
365
  ::
@@ -1,4 +1,4 @@
1
- lizard.py,sha256=B0g5lEp5me31bQpjfMKVayF0iUfH0PalVecOKeUX3-A,41365
1
+ lizard.py,sha256=L5CBQ47VYvqzUwqrQD5LfzfcwCB5qBuUx4J59-zKOUU,42076
2
2
  lizard_ext/__init__.py,sha256=UQ2oZ4ej1CCekgiY2Qj8sGx8HheoYyxjcOxvrwF70kc,871
3
3
  lizard_ext/auto_open.py,sha256=byD_RbeVhvSUhR2bJMRitvA3zcKEapFwv0-XaDJ6GFo,1096
4
4
  lizard_ext/checkstyleoutput.py,sha256=UzDHg837ErEZepXkR8I8YCoz2r1lkmzGctMA7dpyB-M,1245
@@ -26,25 +26,25 @@ lizard_ext/lizardns.py,sha256=3PYJrXZeFjzFnbotINZBm-MHeS8MQxVNtkQJd_S5fYA,5161
26
26
  lizard_ext/lizardoutside.py,sha256=FGm2tbBZ17-2OCgmQlD-vobUCfQKb0FAygf86eM3xuM,336
27
27
  lizard_ext/lizardstatementcount.py,sha256=xYk6ixSIItSE1BWQXzrWmduFgGhA3VR817SNKLffyVQ,1182
28
28
  lizard_ext/lizardwordcount.py,sha256=2QYXD7-AtkkgAbi9VSidunMbSsGQ7MKYb6IT-bS-cok,7575
29
- lizard_ext/version.py,sha256=-BXaaeyLOLQJRgob-W_0qvVq5YJw_YJGMzo50rFVFAc,181
29
+ lizard_ext/version.py,sha256=sIUtJyuNVeq7CiJtEKzI_OdOiYNcDqIVBKdYY_OOYRg,181
30
30
  lizard_ext/xmloutput.py,sha256=-cbh0he4O_X-wX56gkv9AnSPNN0qvR7FACqlBeezUS4,5609
31
31
  lizard_languages/__init__.py,sha256=DniggHM3SOFlwkP8Tm4csEcisbf7_i1EvfoAkG9Atg0,1611
32
- lizard_languages/clike.py,sha256=msdvIm5WAlczPUEtl8tK7xobWxxwow1gxgxjr3qURPo,13834
33
- lizard_languages/code_reader.py,sha256=a5JxBQ_4HAeXiWHfLb6o6aCGJCCIAESRLjJcl3Zx-SY,7875
32
+ lizard_languages/clike.py,sha256=BMtIrqPuxmRur-KB7LG6imJtUvnOH2-7p4XFeIFLjME,14892
33
+ lizard_languages/code_reader.py,sha256=I2Kc9F0o4RNX4UUsasUMUieX34u4eOaS73ePhQLC9jg,7844
34
34
  lizard_languages/csharp.py,sha256=UDuiG20Ydbb4KMD_mgb82shD37URq3ZvqNLpI77VBPE,3015
35
35
  lizard_languages/erlang.py,sha256=tbVPDqi90jV2X1-luDYS32H1zK_2KHwssbJ-tCCOvzg,4249
36
36
  lizard_languages/fortran.py,sha256=vwKz1VdwyUtWoECURXuR6e8E2kTR71HtpdbOsmDoyDw,8993
37
37
  lizard_languages/gdscript.py,sha256=3mHeCarDq_ilDK_OZ2LOvwfWJqLTzK2lquprLDUnX08,737
38
38
  lizard_languages/go.py,sha256=sntz0jOEuj4klPipoTFd16UDK1fAUQfwK7YX_cLMZAc,1346
39
39
  lizard_languages/golike.py,sha256=vRIfjTVvc0VmJf27lTOLht55ZF1AQ9wn0Fvu-9WabWk,2858
40
- lizard_languages/java.py,sha256=HQBTZjUKbUJwgmtLYIzJrWtPpFP3ZdBP_NJK7YOXZC0,6424
40
+ lizard_languages/java.py,sha256=8t8G6TR-1qxLHGu3m_T5Kuny49WvMfwCQe1eYnvwZmk,6610
41
41
  lizard_languages/javascript.py,sha256=vniCNMW-ea9Jpv6c8qCcjLVDYjT8VztjXigp5XRWt0E,317
42
42
  lizard_languages/js_style_regex_expression.py,sha256=Xgyogch4xElYtCG4EnBKvalHTl3tjRPcIIcIQRRd61I,1970
43
43
  lizard_languages/kotlin.py,sha256=1ao-VOHUrrSluxgGjMcMPIDt_-dqVfT1JOz15PJLJH8,3024
44
44
  lizard_languages/lua.py,sha256=3nqBcunBzJrhv4Iqaf8xvbyqxZy3aSxJ-IiHimHFlac,1573
45
45
  lizard_languages/objc.py,sha256=2a1teLdaXZBtCeFiIZer1j_sVx9LZ1CbF2XfnqlvLmk,2319
46
46
  lizard_languages/perl.py,sha256=lxxdC0KJsr3tmNLMATO7vx3O7yWGgICxHxVTfmu7db8,12111
47
- lizard_languages/php.py,sha256=BIIxdKd-yeaqIjxw3yoq2ZFlhqQ-F1NNK26BOfRbO5U,10093
47
+ lizard_languages/php.py,sha256=ebHkNyKDOPLyzey5eDhKuu1AMsmGflxqGGmx1ZrlrGA,10542
48
48
  lizard_languages/plsql.py,sha256=Wtny6-YW4Jc6G4EhCgoSuw8D86x6JVREprzaUEyKffw,17261
49
49
  lizard_languages/python.py,sha256=-sI7ZzJ1erdmqo1MnCcg5cqn1krUhCHlR_phlhnmd_M,5978
50
50
  lizard_languages/r.py,sha256=NHNPDGnWXcl8hMi1VL8OM6nw8F9pP6xZX7EgUIocOec,12930
@@ -62,9 +62,9 @@ lizard_languages/ttcn.py,sha256=tSkPmtifsuUI5aUKBG-uHvwcmZTWcbtMWoUMF3Cw4cI,2199
62
62
  lizard_languages/typescript.py,sha256=SOef4ja2GE-Idt9fSF7d0gZTW6bwCNl8K2IT67nIMyA,14805
63
63
  lizard_languages/vue.py,sha256=KXUBUo2R1zNF8Pffrz_KsQEN44m5XFRMoGXylxKUeT0,1038
64
64
  lizard_languages/zig.py,sha256=RiDZyjnCn97jqFmcl5EQl7pbuH0G1oI2Qo72EXvdtDU,813
65
- lizard-1.20.0.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
66
- lizard-1.20.0.dist-info/METADATA,sha256=36pI9t3KO-tvapXxKTbrX1Gc465zs1zQqubteXPS4fY,16697
67
- lizard-1.20.0.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
68
- lizard-1.20.0.dist-info/entry_points.txt,sha256=pPMMwoHAltzGHqR2WeJQItLeeyR7pbX5R2S_POC-xoo,40
69
- lizard-1.20.0.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
70
- lizard-1.20.0.dist-info/RECORD,,
65
+ lizard-1.21.0.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
66
+ lizard-1.21.0.dist-info/METADATA,sha256=jLq4TDqRDAQd2RimhMDLBNUlv7ULP0vJ6pqybAtClPg,16978
67
+ lizard-1.21.0.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
68
+ lizard-1.21.0.dist-info/entry_points.txt,sha256=pPMMwoHAltzGHqR2WeJQItLeeyR7pbX5R2S_POC-xoo,40
69
+ lizard-1.21.0.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
70
+ lizard-1.21.0.dist-info/RECORD,,
lizard.py CHANGED
@@ -291,6 +291,7 @@ class FunctionInfo(Nesting): # pylint: disable=R0902
291
291
  self.fan_out = 0
292
292
  self.general_fan_out = 0
293
293
  self.max_nesting_depth = 0 # Initialize max_nesting_depth to 0
294
+ self.forgiven_metrics = set()
294
295
 
295
296
  @property
296
297
  def name_in_space(self):
@@ -509,9 +510,15 @@ def comment_counter(tokens, reader):
509
510
  if comment is not None:
510
511
  for _ in comment.splitlines()[1:]:
511
512
  yield '\n'
512
- if comment.strip().startswith("#lizard forgive global"):
513
+ stripped = comment.strip()
514
+ if stripped.startswith("#lizard forgive global"):
513
515
  reader.context.forgive_global = True
514
- elif comment.strip().startswith("#lizard forgive"):
516
+ elif stripped.startswith("#lizard forgives("):
517
+ match = re.search(r'#lizard forgives?\(([^)]*)\)', stripped)
518
+ if match:
519
+ metrics = {m.strip() for m in match.group(1).split(',') if m.strip()}
520
+ reader.context.current_function.forgiven_metrics.update(metrics)
521
+ elif stripped.startswith("#lizard forgive"):
515
522
  reader.context.forgive = True
516
523
  if "GENERATED CODE" in comment:
517
524
  return
@@ -597,9 +604,16 @@ def warning_filter(option, module_infos):
597
604
  for file_info in module_infos:
598
605
  if file_info:
599
606
  for fun in file_info.function_list:
600
- if any(getattr(fun, attr) > limit for attr, limit in
601
- option.thresholds.items()):
602
- yield fun
607
+ violated_metrics = [
608
+ attr for attr, limit in option.thresholds.items()
609
+ if getattr(fun, attr) > limit
610
+ ]
611
+ if not violated_metrics:
612
+ continue
613
+ forgiven = getattr(fun, 'forgiven_metrics', set())
614
+ if all(metric in forgiven for metric in violated_metrics):
615
+ continue
616
+ yield fun
603
617
 
604
618
 
605
619
  def whitelist_filter(warnings, script=None, whitelist=None):
@@ -1109,7 +1123,8 @@ def main(argv=None):
1109
1123
  sys.stdout = original_stdout
1110
1124
  output_file.close()
1111
1125
  if 0 <= options.number < warning_count:
1112
- sys.exit(1)
1126
+ return 1
1127
+ return 0
1113
1128
 
1114
1129
 
1115
1130
  def print_extension_results(extensions):
@@ -1119,4 +1134,4 @@ def print_extension_results(extensions):
1119
1134
 
1120
1135
 
1121
1136
  if __name__ == "__main__":
1122
- main()
1137
+ sys.exit(main())
lizard_ext/version.py CHANGED
@@ -3,4 +3,4 @@
3
3
  #
4
4
  # pylint: disable=missing-docstring,invalid-name
5
5
 
6
- version = "1.20.0"
6
+ version = "1.21.0"
lizard_languages/clike.py CHANGED
@@ -322,53 +322,77 @@ class CLikeStates(CodeStateMachine):
322
322
  def _state_lambda_check(self, token):
323
323
  """Check if this is a lambda expression or a function attribute."""
324
324
  if token == ']':
325
- # This is a lambda expression [](params) or [capture](params)
326
- # Skip the lambda and continue parsing normally
325
+ # This is an empty capture list [](params)
327
326
  self._state = self._state_lambda_params
328
327
  elif token == '[':
329
328
  # This is a function attribute [[attribute]]
330
329
  self._state = self._state_attribute
331
330
  else:
332
331
  # This is a lambda with capture list [capture](params)
333
- # Skip until we find the closing bracket
334
332
  self._state = self._state_lambda_capture
335
333
 
336
334
  def _state_lambda_params(self, token):
337
335
  """Handle lambda parameters and body."""
338
336
  if token == '(':
339
- # Start of parameter list, skip until closing parenthesis
337
+ # Start of parameter list
338
+ self.bracket_stack.append('(')
340
339
  self._state = self._state_lambda_param_list
341
340
  else:
342
- # No parameters, check for body
341
+ # No parameters, check for body or go back to global
343
342
  self._state = self._state_lambda_body
343
+ self._state(token)
344
344
 
345
345
  def _state_lambda_param_list(self, token):
346
- """Handle lambda parameter list."""
347
- if token == ')':
348
- # End of parameter list, check for body
349
- self._state = self._state_lambda_body
350
- # Otherwise, continue in parameter list
346
+ """Handle lambda parameter list with proper bracket tracking."""
347
+ if token == '(':
348
+ self.bracket_stack.append('(')
349
+ elif token == ')':
350
+ if self.bracket_stack and self.bracket_stack[-1] == '(':
351
+ self.bracket_stack.pop()
352
+ if not self.bracket_stack:
353
+ # End of parameter list, check for body
354
+ self._state = self._state_lambda_body
355
+ elif token in ('<', '['):
356
+ self.bracket_stack.append(token)
357
+ elif token == '>' and self.bracket_stack and self.bracket_stack[-1] == '<':
358
+ self.bracket_stack.pop()
359
+ elif token == ']' and self.bracket_stack and self.bracket_stack[-1] == '[':
360
+ self.bracket_stack.pop()
351
361
 
352
362
  def _state_lambda_body(self, token):
353
- """Handle lambda body."""
363
+ """Handle lambda body and qualifiers."""
354
364
  if token == '{':
355
- # Start of lambda body, skip until closing brace
365
+ # Start of lambda body
366
+ self.bracket_stack.append('{')
356
367
  self._state = self._state_lambda_body_skip
357
- elif token == ';':
358
- # Lambda without body, just a semicolon
368
+ elif token in ('mutable', 'noexcept', 'constexpr', 'consteval'):
369
+ # Lambda qualifiers, stay in this state
370
+ pass
371
+ elif token == '->':
372
+ # Trailing return type, stay in this state until we find '{'
373
+ pass
374
+ elif token in (';', ',', ')'):
375
+ # Lambda declaration ended, return to global
359
376
  self._state = self._state_global
360
- # Otherwise, continue
377
+ self._state(token)
378
+ else:
379
+ # Other tokens (type names, etc.) - stay in state
380
+ pass
361
381
 
362
382
  def _state_lambda_body_skip(self, token):
363
- """Skip lambda body until closing brace."""
364
- if token == '}':
365
- # End of lambda body, continue parsing normally
366
- self._state = self._state_global
367
- # Otherwise, continue skipping
383
+ """Skip lambda body with proper brace tracking."""
384
+ if token == '{':
385
+ self.bracket_stack.append('{')
386
+ elif token == '}':
387
+ if self.bracket_stack and self.bracket_stack[-1] == '{':
388
+ self.bracket_stack.pop()
389
+ if not self.bracket_stack:
390
+ # End of lambda body
391
+ self._state = self._state_global
368
392
 
369
393
  def _state_lambda_capture(self, token):
370
394
  """Handle lambda capture list."""
371
395
  if token == ']':
372
- # End of capture list, continue parsing normally
373
- self._state = self._state_global
396
+ # End of capture list, now expect parameters
397
+ self._state = self._state_lambda_params
374
398
  # Otherwise, continue in capture list
@@ -94,7 +94,7 @@ class CodeReader:
94
94
  ext = []
95
95
  languages = None
96
96
  extra_subclasses = set()
97
-
97
+
98
98
  # Condition categories - separate types that contribute to cyclomatic complexity
99
99
  _control_flow_keywords = {'if', 'for', 'while', 'catch'}
100
100
  _logical_operators = {'&&', '||'}
@@ -104,21 +104,21 @@ class CodeReader:
104
104
  @classmethod
105
105
  def _build_conditions(cls):
106
106
  """Build combined conditions set from separated categories.
107
-
107
+
108
108
  Returns combined set of all condition types for CCN calculation.
109
109
  """
110
- return (cls._control_flow_keywords |
111
- cls._logical_operators |
112
- cls._case_keywords |
110
+ return (cls._control_flow_keywords |
111
+ cls._logical_operators |
112
+ cls._case_keywords |
113
113
  cls._ternary_operators)
114
114
 
115
115
  def __init__(self, context):
116
116
  self.parallel_states = []
117
117
  self.context = context
118
-
118
+
119
119
  # Build combined conditions set from separated categories
120
120
  self.conditions = copy(self.__class__._build_conditions())
121
-
121
+
122
122
  # Expose individual categories for extensions
123
123
  self.control_flow_keywords = copy(self.__class__._control_flow_keywords)
124
124
  self.logical_operators = copy(self.__class__._logical_operators)
lizard_languages/java.py CHANGED
@@ -54,11 +54,15 @@ class JavaStates(CLikeStates): # pylint: disable=R0903
54
54
 
55
55
  def _try_start_a_class(self, token):
56
56
  if token in ("class", "record", "enum"):
57
+ # "record" inside method bodies is a variable name, not a keyword
58
+ if token == "record" and self.in_method_body:
59
+ return False
57
60
  self.class_name = None
58
61
  self.is_record = token == "record"
59
62
  self.in_record_constructor = False
60
63
  self._state = self._state_class_declaration
61
64
  return True
65
+ return False
62
66
 
63
67
  def _state_global(self, token):
64
68
  if token == '@':
lizard_languages/php.py CHANGED
@@ -31,11 +31,15 @@ class PHPLanguageStates(CodeStateMachine):
31
31
  self.match_case_count = 0
32
32
 
33
33
  def _state_global(self, token):
34
- if token == 'class':
34
+ if token == 'use':
35
+ # Enter use statement state
36
+ self._state = self._state_use
37
+ elif token == 'class':
35
38
  self._state = self._class_declaration
36
39
  elif token == 'trait':
37
40
  self._state = self._trait_declaration
38
41
  elif token == 'function':
42
+ # Treat 'function' as function declaration
39
43
  self.is_function_declaration = True
40
44
  self._state = self._function_name
41
45
  elif token == 'fn':
@@ -88,6 +92,13 @@ class PHPLanguageStates(CodeStateMachine):
88
92
  else:
89
93
  self.last_tokens = ''
90
94
 
95
+ def _state_use(self, token):
96
+ """Handle use statements (use function, use const, etc.)"""
97
+ if token == ';':
98
+ # End of use statement, return to global state
99
+ self._state = self._state_global
100
+ # Ignore all other tokens in use statements
101
+
91
102
  def _trait_declaration(self, token):
92
103
  if token and not token.isspace() and token not in ['{', '(']:
93
104
  self.trait_name = token