lizard 1.18.0__py2.py3-none-any.whl → 1.20.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.
Files changed (40) hide show
  1. {lizard-1.18.0.dist-info → lizard-1.20.0.dist-info}/METADATA +30 -20
  2. lizard-1.20.0.dist-info/RECORD +70 -0
  3. lizard_ext/__init__.py +0 -1
  4. lizard_ext/htmloutput.py +86 -17
  5. lizard_ext/lizardcomplextags.py +6 -0
  6. lizard_ext/lizardmccabe.py +6 -1
  7. lizard_ext/lizardmodified.py +7 -2
  8. lizard_ext/lizardnd.py +64 -1
  9. lizard_ext/lizardnonstrict.py +2 -1
  10. lizard_ext/lizardns.py +31 -8
  11. lizard_ext/version.py +1 -1
  12. lizard_languages/__init__.py +2 -0
  13. lizard_languages/clike.py +6 -3
  14. lizard_languages/code_reader.py +27 -3
  15. lizard_languages/csharp.py +21 -2
  16. lizard_languages/erlang.py +9 -1
  17. lizard_languages/fortran.py +5 -6
  18. lizard_languages/gdscript.py +6 -2
  19. lizard_languages/kotlin.py +6 -3
  20. lizard_languages/perl.py +8 -4
  21. lizard_languages/php.py +6 -2
  22. lizard_languages/plsql.py +422 -0
  23. lizard_languages/python.py +6 -4
  24. lizard_languages/r.py +9 -6
  25. lizard_languages/rubylike.py +6 -3
  26. lizard_languages/rust.py +7 -2
  27. lizard_languages/scala.py +6 -2
  28. lizard_languages/solidity.py +6 -1
  29. lizard_languages/st.py +9 -5
  30. lizard_languages/swift.py +6 -2
  31. lizard_languages/tnsdl.py +6 -1
  32. lizard_languages/tsx.py +2 -2
  33. lizard_languages/ttcn.py +5 -3
  34. lizard_languages/typescript.py +58 -4
  35. lizard_languages/zig.py +7 -1
  36. lizard-1.18.0.dist-info/RECORD +0 -69
  37. {lizard-1.18.0.dist-info → lizard-1.20.0.dist-info}/LICENSE.txt +0 -0
  38. {lizard-1.18.0.dist-info → lizard-1.20.0.dist-info}/WHEEL +0 -0
  39. {lizard-1.18.0.dist-info → lizard-1.20.0.dist-info}/entry_points.txt +0 -0
  40. {lizard-1.18.0.dist-info → lizard-1.20.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lizard
3
- Version: 1.18.0
3
+ Version: 1.20.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/
7
6
  Author: Terry Yin
8
7
  Author-email: terry@odd-e.com
9
8
  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
@@ -50,31 +50,32 @@ code analysis.
50
50
 
51
51
  A list of supported languages:
52
52
 
53
+ - C# (C Sharp)
53
54
  - C/C++ (works with C++14)
55
+ - Erlang
56
+ - Fortran
57
+ - GDScript
58
+ - Golang
54
59
  - Java
55
- - C# (C Sharp)
56
60
  - JavaScript (With ES6 and JSX)
57
- - TypeScript (With TSX)
58
- - VueJS
61
+ - Kotlin
62
+ - Lua
59
63
  - Objective-C
60
- - Swift
64
+ - Perl
65
+ - PHP
66
+ - PL/SQL
61
67
  - Python
68
+ - R
62
69
  - Ruby
63
- - TTCN-3
64
- - PHP
65
- - Scala
66
- - GDScript
67
- - Golang
68
- - Lua
69
70
  - Rust
70
- - Fortran
71
- - Kotlin
71
+ - Scala
72
72
  - Solidity
73
- - Erlang
74
- - Zig
75
- - Perl
76
73
  - Structured Text (St)
77
- - R
74
+ - Swift
75
+ - TTCN-3
76
+ - TypeScript (With TSX)
77
+ - VueJS
78
+ - Zig
78
79
 
79
80
  By default lizard will search for any source code that it knows and mix
80
81
  all the results together. This might not be what you want. You can use
@@ -162,7 +163,7 @@ Options
162
163
  search for all languages it knows. `lizard -l cpp -l java`searches for
163
164
  C++ and Java code. The available languages are: cpp, java, csharp,
164
165
  javascript, python, objectivec, ttcn, ruby, php, swift, scala, GDScript,
165
- go, lua, rust, typescript
166
+ go, lua, rust, typescript, plsql
166
167
  -V, --verbose Output in verbose mode (long function name)
167
168
  -C CCN, --CCN CCN Threshold for cyclomatic complexity number warning. The default value is
168
169
  15. Functions with CCN bigger than it will generate warning
@@ -197,7 +198,7 @@ Options
197
198
  -X, --xml Generate XML in cppncss style instead of the tabular output. Useful to
198
199
  generate report in Jenkins server
199
200
  --csv Generate CSV output as a transform of the default output
200
- -H, --html Output HTML report
201
+ -H, --html Output HTML report with interactive DataTables (sortable, searchable, filterable)
201
202
  --checkstyle Generate Checkstyle XML output for integration with Jenkins and other tools
202
203
  -m, --modified Calculate modified cyclomatic complexity number , which count a
203
204
  switch/case with multiple cases as one CCN.
@@ -415,5 +416,14 @@ Lizard is also used as a plugin for fastlane to help check code complexity and s
415
416
  - `European research project FASTEN (Fine-grained Analysis of SofTware Ecosystems as Networks, <http://fasten-project.eu/)>`_
416
417
  - `for a quality analyzer <https://github.com/fasten-project/quality-analyzer>`_
417
418
 
419
+ How To Contribute
420
+ -----------------
421
+
422
+ Contributions are welcome. Please refer to the rules and development workflow in:
423
+
424
+ - https://github.com/terryyin/lizard/tree/master/.cursor/rules
425
+
426
+ These guidelines are usable by both AI assistants and human contributors — what works for AI works for "I" as well — to keep changes cohesive, simple, and well-tested.
427
+
418
428
 
419
429
 
@@ -0,0 +1,70 @@
1
+ lizard.py,sha256=B0g5lEp5me31bQpjfMKVayF0iUfH0PalVecOKeUX3-A,41365
2
+ lizard_ext/__init__.py,sha256=UQ2oZ4ej1CCekgiY2Qj8sGx8HheoYyxjcOxvrwF70kc,871
3
+ lizard_ext/auto_open.py,sha256=byD_RbeVhvSUhR2bJMRitvA3zcKEapFwv0-XaDJ6GFo,1096
4
+ lizard_ext/checkstyleoutput.py,sha256=UzDHg837ErEZepXkR8I8YCoz2r1lkmzGctMA7dpyB-M,1245
5
+ lizard_ext/csvoutput.py,sha256=43fhmo8kB85qcdujCwySGNuTC4FkKUPLqIApPeljPnA,2663
6
+ lizard_ext/default_ordered_dict.py,sha256=YbVz6nPlQ6DjWc_EOFBz6AJN2XLo9dpnUdeyejQvUDE,831
7
+ lizard_ext/extension_base.py,sha256=rnjUL2mqSGToUVYydju7fa8ZwynLPY8S1F17gIJP55I,346
8
+ lizard_ext/htmloutput.py,sha256=G7MdLD7AwuR0LLCAkEpyHx4N_ssCcFQdXd7Xg5jZWX0,6537
9
+ lizard_ext/keywords.py,sha256=VxsxoATtKV-8egMKd7I8sd2qbZMtEFEpsszk__6rmjQ,893
10
+ lizard_ext/lizardboolcount.py,sha256=abmMA9X3VFRO5mziicUxWKmHldHNC0jBEe7NKAKA5fs,1062
11
+ lizard_ext/lizardcomplextags.py,sha256=z4Jsz8HNxbZXhDRuUeVnNfgt5Zv4FYEW2kRPO4G3F3E,1027
12
+ lizard_ext/lizardcpre.py,sha256=bVrMXffGUZlydv_zwIVtp-Ij9XyXMFEcap2R4DmLXPU,1277
13
+ lizard_ext/lizarddependencycount.py,sha256=6Nt2z69cyG8P3pceTUCTU6yh4h73lezL2awRFh7K77Y,2423
14
+ lizard_ext/lizarddumpcomments.py,sha256=-c46U3CP_beDfBuB9QhObpiB6wK7xq1u9jeRjgAyU90,683
15
+ lizard_ext/lizardduplicate.py,sha256=tlU-UTB_d_qjekiO4MSaXPBT1FSX8uTaOcfeXnjBzU8,9762
16
+ lizard_ext/lizardduplicated_param_list.py,sha256=_ApeSbjA-NU_rmICsivyfGEEg0-O94wWgWpeeE8F3Zc,1811
17
+ lizard_ext/lizardexitcount.py,sha256=ziAVFi5RgjxJOwAQy6LzdZZflkvvxTiXOIp0FAL0wKQ,692
18
+ lizard_ext/lizardgotocount.py,sha256=w2GWWwqVh4j7Fum41Wvg172b70JvtCm5BCzZUmmTlcM,514
19
+ lizard_ext/lizardignoreassert.py,sha256=sqLwcnJQ06SYqIk901ib4NQ8ECwjIe_qL4T6z1wLXAk,644
20
+ lizard_ext/lizardio.py,sha256=xQN-AgLGLKJarJkgfaqX_TKyupbb7GTcwPxrL2B1J1w,3357
21
+ lizard_ext/lizardmccabe.py,sha256=nLwfYjnAWn1DSoDc-k3iPvltiSX_YmAEdAfM3rOdLSQ,1538
22
+ lizard_ext/lizardmodified.py,sha256=51EY-HM840k_-jFiJugp_fOPBKvovX2Wu0s4ghLM3us,1002
23
+ lizard_ext/lizardnd.py,sha256=h4MaSsFAY5rK8kOSr61LkWntwJWV1KnhaEOWZgp3-c0,7496
24
+ lizard_ext/lizardnonstrict.py,sha256=DWdE2kTn39Gf8UkaRwIO9ogWTDuqA4lXNlQIBT3uLRM,457
25
+ lizard_ext/lizardns.py,sha256=3PYJrXZeFjzFnbotINZBm-MHeS8MQxVNtkQJd_S5fYA,5161
26
+ lizard_ext/lizardoutside.py,sha256=FGm2tbBZ17-2OCgmQlD-vobUCfQKb0FAygf86eM3xuM,336
27
+ lizard_ext/lizardstatementcount.py,sha256=xYk6ixSIItSE1BWQXzrWmduFgGhA3VR817SNKLffyVQ,1182
28
+ lizard_ext/lizardwordcount.py,sha256=2QYXD7-AtkkgAbi9VSidunMbSsGQ7MKYb6IT-bS-cok,7575
29
+ lizard_ext/version.py,sha256=-BXaaeyLOLQJRgob-W_0qvVq5YJw_YJGMzo50rFVFAc,181
30
+ lizard_ext/xmloutput.py,sha256=-cbh0he4O_X-wX56gkv9AnSPNN0qvR7FACqlBeezUS4,5609
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
34
+ lizard_languages/csharp.py,sha256=UDuiG20Ydbb4KMD_mgb82shD37URq3ZvqNLpI77VBPE,3015
35
+ lizard_languages/erlang.py,sha256=tbVPDqi90jV2X1-luDYS32H1zK_2KHwssbJ-tCCOvzg,4249
36
+ lizard_languages/fortran.py,sha256=vwKz1VdwyUtWoECURXuR6e8E2kTR71HtpdbOsmDoyDw,8993
37
+ lizard_languages/gdscript.py,sha256=3mHeCarDq_ilDK_OZ2LOvwfWJqLTzK2lquprLDUnX08,737
38
+ lizard_languages/go.py,sha256=sntz0jOEuj4klPipoTFd16UDK1fAUQfwK7YX_cLMZAc,1346
39
+ lizard_languages/golike.py,sha256=vRIfjTVvc0VmJf27lTOLht55ZF1AQ9wn0Fvu-9WabWk,2858
40
+ lizard_languages/java.py,sha256=HQBTZjUKbUJwgmtLYIzJrWtPpFP3ZdBP_NJK7YOXZC0,6424
41
+ lizard_languages/javascript.py,sha256=vniCNMW-ea9Jpv6c8qCcjLVDYjT8VztjXigp5XRWt0E,317
42
+ lizard_languages/js_style_regex_expression.py,sha256=Xgyogch4xElYtCG4EnBKvalHTl3tjRPcIIcIQRRd61I,1970
43
+ lizard_languages/kotlin.py,sha256=1ao-VOHUrrSluxgGjMcMPIDt_-dqVfT1JOz15PJLJH8,3024
44
+ lizard_languages/lua.py,sha256=3nqBcunBzJrhv4Iqaf8xvbyqxZy3aSxJ-IiHimHFlac,1573
45
+ lizard_languages/objc.py,sha256=2a1teLdaXZBtCeFiIZer1j_sVx9LZ1CbF2XfnqlvLmk,2319
46
+ lizard_languages/perl.py,sha256=lxxdC0KJsr3tmNLMATO7vx3O7yWGgICxHxVTfmu7db8,12111
47
+ lizard_languages/php.py,sha256=BIIxdKd-yeaqIjxw3yoq2ZFlhqQ-F1NNK26BOfRbO5U,10093
48
+ lizard_languages/plsql.py,sha256=Wtny6-YW4Jc6G4EhCgoSuw8D86x6JVREprzaUEyKffw,17261
49
+ lizard_languages/python.py,sha256=-sI7ZzJ1erdmqo1MnCcg5cqn1krUhCHlR_phlhnmd_M,5978
50
+ lizard_languages/r.py,sha256=NHNPDGnWXcl8hMi1VL8OM6nw8F9pP6xZX7EgUIocOec,12930
51
+ lizard_languages/ruby.py,sha256=HL1ZckeuUUJU3QSVAOPsG_Zsl0C6X2PX5_VaWqclzkM,2277
52
+ lizard_languages/rubylike.py,sha256=vnfY2lSeIYEgfjJWrfNL6fg1ISHGiwo32H7bf7Stwos,3604
53
+ lizard_languages/rust.py,sha256=7ioyo-kpy5zSQ6Fwua6GcYopkLUdfyExg6kLP0Br3p0,1027
54
+ lizard_languages/scala.py,sha256=onEuS8IsT7aCB-HV0arVDjmgpezXWndk1RCOgoySUes,1436
55
+ lizard_languages/script_language.py,sha256=SKe45AbO6Z-axbN8KW_g7jf9g7YTXZ6dWzJj4ubDsM8,1172
56
+ lizard_languages/solidity.py,sha256=eg7HQ7cuV1d0Awwj4lTySu9f63F1vrK8czsPt_W5Kqw,680
57
+ lizard_languages/st.py,sha256=QiiGNYE8CzmfmZAF_wLQKovXk1Fyqa8QFGTXM1meNNk,4385
58
+ lizard_languages/swift.py,sha256=JcHmBV-p916K2o2XrTKf98U6Bt9-jAEOS9JqA1W5tkA,2570
59
+ lizard_languages/tnsdl.py,sha256=y2dcZI_PmriNd7UTP1Db9QEpWBzKeoH8odRzkiN-P1k,3009
60
+ lizard_languages/tsx.py,sha256=fY9qzPcSvhHSs-62ZFh0mMGyKVaSCEfsYZxBS_uHzww,17161
61
+ lizard_languages/ttcn.py,sha256=tSkPmtifsuUI5aUKBG-uHvwcmZTWcbtMWoUMF3Cw4cI,2199
62
+ lizard_languages/typescript.py,sha256=SOef4ja2GE-Idt9fSF7d0gZTW6bwCNl8K2IT67nIMyA,14805
63
+ lizard_languages/vue.py,sha256=KXUBUo2R1zNF8Pffrz_KsQEN44m5XFRMoGXylxKUeT0,1038
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,,
lizard_ext/__init__.py CHANGED
@@ -21,7 +21,6 @@ def print_csv(results, options, _, total_factory):
21
21
 
22
22
  def print_checkstyle(results, options, _, total_factory, file=None):
23
23
  import sys
24
- print("DEBUG: print_checkstyle called", file=sys.stderr)
25
24
  output = checkstyle_output(total_factory(list(results)), options.verbose)
26
25
  if file is None:
27
26
  file = sys.stdout
lizard_ext/htmloutput.py CHANGED
@@ -48,6 +48,12 @@ TEMPLATE = '''<!DOCTYPE HTML PUBLIC
48
48
  <head>
49
49
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
50
50
  <title>Code complexity report</title>
51
+ <!-- DataTables CSS -->
52
+ <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
53
+ <!-- jQuery -->
54
+ <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
55
+ <!-- DataTables JS -->
56
+ <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
51
57
  <style>
52
58
  h2
53
59
  {
@@ -88,6 +94,42 @@ TEMPLATE = '''<!DOCTYPE HTML PUBLIC
88
94
  font-family: sans-serif;
89
95
  white-space: nowrap;
90
96
  }
97
+ td.file-header
98
+ {
99
+ background-color: LightBlue;
100
+ font-weight: bold;
101
+ }
102
+ td.function-name
103
+ {
104
+ background-color: LightSteelBlue;
105
+ }
106
+ /* DataTables wrapper styling */
107
+ .dataTables_wrapper {
108
+ margin: 0 auto;
109
+ width: 95%;
110
+ }
111
+ /* Fallback styling if DataTables CSS doesn't load */
112
+ table#complexityTable {
113
+ border-collapse: collapse;
114
+ width: 95%;
115
+ margin: 0 auto;
116
+ }
117
+ table#complexityTable th {
118
+ background-color: #4CAF50;
119
+ color: white;
120
+ padding: 10px;
121
+ text-align: left;
122
+ border: 1px solid #ddd;
123
+ }
124
+ table#complexityTable td {
125
+ border: 1px solid #ddd;
126
+ }
127
+ table#complexityTable tr:nth-child(even) {
128
+ background-color: #f2f2f2;
129
+ }
130
+ table#complexityTable tr:hover {
131
+ background-color: #ddd;
132
+ }
91
133
  </style>
92
134
  </head>
93
135
  <body>
@@ -95,26 +137,29 @@ TEMPLATE = '''<!DOCTYPE HTML PUBLIC
95
137
 
96
138
  <center>
97
139
 
98
- <table>
140
+ <table id="complexityTable" class="display" style="width:100%">
141
+ <thead>
142
+ <tr>
143
+ <th>File</th>
144
+ <th>Function name</th>
145
+ <th>Cyclomatic complexity ({{ thresholds["cyclomatic_complexity"] }})</th>
146
+ <th>LOC ({{ thresholds["nloc"] }})</th>
147
+ <th>
148
+ {% if thresholds["token_count"] %}
149
+ Token count ({{ thresholds["token_count"] }})
150
+ {% else %}
151
+ Token count
152
+ {% endif %}
153
+ </th>
154
+ <th>Parameter count ({{ thresholds["parameter_count"] }})</th>
155
+ </tr>
156
+ </thead>
157
+ <tbody>
99
158
  {% for file in files %}
100
- <tr><td colspan="7" style="background-color:LightBlue;">
101
- Source file: <b>{{ file.filename }}</b></td></tr>
102
- {% if file.functions|length > 0 %}
103
- <tr><th>Function name</th><th></th><th>
104
- Cyclomatic complexity
105
- ({{ thresholds["cyclomatic_complexity"] }})
106
- </th><th>LOC ({{ thresholds["nloc"] }})</th><th>
107
- {% if thresholds["token_count"] %}
108
- Token count ({{ thresholds["token_count"] }})
109
- {% else %}
110
- Token count
111
- {% endif %}
112
- </th><th>Parameter count ({{ thresholds["parameter_count"] }})</th></tr>
113
- {% endif %}
114
159
  {% for func in file.functions %}
115
160
  <tr>
116
- <td style="background-color:LightSteelBlue">{{ func.name }}</td>
117
- <td></td>
161
+ <td>{{ file.filename }}</td>
162
+ <td class="function-name">{{ func.name }}</td>
118
163
  {% if func.cyclomatic_complexity > thresholds["cyclomatic_complexity"] %}
119
164
  <td class="greater-value">{{ func.cyclomatic_complexity }}</td>
120
165
  {% else %}
@@ -145,6 +190,7 @@ Cyclomatic complexity
145
190
  </tr>
146
191
  {% endfor %}
147
192
  {% endfor %}
193
+ </tbody>
148
194
  </table>
149
195
  <center>
150
196
 
@@ -154,6 +200,29 @@ Cyclomatic complexity
154
200
  <a href="http://www.lizard.ws/">Lizard</a> on {{ date }}
155
201
  </td></tr>
156
202
  </table>
203
+
204
+ <script>
205
+ // Gracefully degrade if CDN is unavailable
206
+ if (typeof jQuery !== 'undefined' && typeof jQuery.fn.dataTable !== 'undefined') {
207
+ $(document).ready(function() {
208
+ try {
209
+ $('#complexityTable').DataTable({
210
+ "pageLength": 25,
211
+ "order": [[2, "desc"]], // Sort by cyclomatic complexity descending by default
212
+ "columnDefs": [
213
+ { "type": "num", "targets": [2, 3, 4, 5] } // Ensure numeric sorting for metric columns
214
+ ]
215
+ });
216
+ } catch (e) {
217
+ console.warn('DataTables initialization failed. Displaying static table.', e);
218
+ }
219
+ });
220
+ } else {
221
+ // Fallback: Table will display as static HTML
222
+ console.info('DataTables not available. Displaying static HTML table.');
223
+ }
224
+ </script>
225
+
157
226
  </body>
158
227
  </html>
159
228
 
@@ -5,10 +5,16 @@ that adding the complexity and the line numbers of the keywords appear.
5
5
 
6
6
 
7
7
  class LizardExtension(object): # pylint: disable=R0903
8
+ """
9
+ Complex tags extension: records all complexity-adding keywords and their line numbers.
10
+ Uses reader.conditions (combined set of all condition types) to track all
11
+ complexity contributors: control flow, logical operators, case labels, and ternary.
12
+ """
8
13
 
9
14
  # pylint: disable=W0221
10
15
  def __call__(self, tokens, reader):
11
16
  context = reader.context
17
+ # Use combined conditions set - intentionally includes all types
12
18
  conditions = reader.conditions
13
19
  for token in tokens:
14
20
  yield token
@@ -14,9 +14,14 @@ from .extension_base import ExtensionBase
14
14
 
15
15
 
16
16
  class LizardExtension(ExtensionBase): # pylint: disable=R0903
17
+ """
18
+ McCabe extension: only counts the first 'case' in a switch statement.
19
+ Consecutive cases without code between them don't add to complexity.
20
+ Works by detecting case tokens (conceptually from reader.case_keywords).
21
+ """
17
22
 
18
23
  def _state_global(self, token):
19
- if token == "case":
24
+ if token == "case": # Detect case keywords
20
25
  self._state = self._in_case
21
26
 
22
27
  def _in_case(self, token):
@@ -6,14 +6,19 @@ where the whole switch/case will be counted as 1.
6
6
 
7
7
 
8
8
  class LizardExtension(object): # pylint: disable=R0903
9
+ """
10
+ Modified CCN extension: counts entire switch/case as 1 complexity.
11
+ Adds +1 for 'switch', subtracts -1 for each 'case'.
12
+ Works with switch/case keywords (conceptually from reader.case_keywords).
13
+ """
9
14
 
10
15
  def __call__(self, tokens, reader):
11
16
  for token in tokens:
12
- if token == 'switch':
17
+ if token == 'switch': # Add complexity for switch statement
13
18
  reader.context.add_condition()
14
19
  if hasattr(reader.context, "add_nd_condition"):
15
20
  reader.context.add_nd_condition()
16
- elif token == 'case':
21
+ elif token == 'case': # Subtract complexity for each case
17
22
  reader.context.add_condition(-1)
18
23
  if hasattr(reader.context, "add_nd_condition"):
19
24
  reader.context.add_nd_condition(-1)
lizard_ext/lizardnd.py CHANGED
@@ -45,11 +45,43 @@ class LizardExtension(object): # pylint: disable=R0903
45
45
  else:
46
46
  indent_indicator = ';'
47
47
  for token in tokens:
48
- if token in loops:
48
+ # Handle opening parenthesis - start of condition
49
+ if token == '(':
50
+ reader.context.set_in_condition(True)
51
+ reader.context.increment_condition_depth()
52
+ reader.context.set_logical_operator_added(False) # Reset for new condition
53
+ # Handle closing parenthesis - end of condition
54
+ elif token == ')':
55
+ reader.context.decrement_condition_depth()
56
+ if reader.context.get_condition_depth() == 0:
57
+ reader.context.set_in_condition(False)
58
+ reader.context.set_logical_operator_added(False)
59
+
60
+ # Handle logical operators && and || within conditions
61
+ elif token in ('&&', '||'):
62
+ if reader.context.get_in_condition():
63
+ # Only add nesting depth for the first logical operator in a condition
64
+ # Subsequent && or || operators in the same condition don't add depth
65
+ if not reader.context.get_logical_operator_added():
66
+ l_depth = reader.context.add_nd_condition()
67
+ if not reader.context.get_loop_status():
68
+ reader.context.add_hidden_bracket_condition()
69
+ reader.context.loop_bracket_status()
70
+ reader.context.set_logical_operator_added(True)
71
+ else:
72
+ # Not in a condition, treat as regular nesting
73
+ l_depth = reader.context.add_nd_condition()
74
+ if not reader.context.get_loop_status():
75
+ reader.context.add_hidden_bracket_condition()
76
+ reader.context.loop_bracket_status()
77
+
78
+ # Handle other loop keywords (if, for, while, etc.)
79
+ elif token in loops and token not in ('&&', '||'):
49
80
  l_depth = reader.context.add_nd_condition()
50
81
  if not reader.context.get_loop_status():
51
82
  reader.context.add_hidden_bracket_condition()
52
83
  reader.context.loop_bracket_status()
84
+
53
85
  if token == loop_indicator:
54
86
  reader.context.loop_bracket_status()
55
87
  if token == bracket:
@@ -82,6 +114,7 @@ class NDFileInfoAddition(FileInfoBuilder):
82
114
  self.current_function.nesting_depth = 0
83
115
  self.current_function.hidden_bracket = 0
84
116
  self.current_function.bracket_loop = False
117
+ self.reset_condition_tracking()
85
118
 
86
119
  def add_hidden_bracket_condition(self, inc=1):
87
120
  self.current_function.hidden_bracket += inc
@@ -96,6 +129,33 @@ class NDFileInfoAddition(FileInfoBuilder):
96
129
  def get_loop_status(self):
97
130
  return self.current_function.bracket_loop
98
131
 
132
+ def set_in_condition(self, in_condition):
133
+ self.current_function.in_condition = in_condition
134
+
135
+ def get_in_condition(self):
136
+ return self.current_function.in_condition
137
+
138
+ def increment_condition_depth(self):
139
+ self.current_function.condition_depth += 1
140
+
141
+ def decrement_condition_depth(self):
142
+ if self.current_function.condition_depth > 0:
143
+ self.current_function.condition_depth -= 1
144
+
145
+ def get_condition_depth(self):
146
+ return self.current_function.condition_depth
147
+
148
+ def reset_condition_tracking(self):
149
+ self.current_function.in_condition = False
150
+ self.current_function.condition_depth = 0
151
+ self.current_function.logical_operator_added = False
152
+
153
+ def set_logical_operator_added(self, added):
154
+ self.current_function.logical_operator_added = added
155
+
156
+ def get_logical_operator_added(self):
157
+ return self.current_function.logical_operator_added
158
+
99
159
 
100
160
  def get_method(cls, name):
101
161
  """ python3 doesn't need the __func__ to get the func of the
@@ -127,6 +187,9 @@ def _init_nesting_depth_data(self, *_):
127
187
  self.max_nesting_depth = 0
128
188
  self.hidden_bracket = 0
129
189
  self.bracket_loop = False
190
+ self.in_condition = False # Track if we're inside a condition
191
+ self.condition_depth = 0 # Track nesting depth within conditions
192
+ self.logical_operator_added = False # Track if we've added nesting for logical operators in current condition
130
193
 
131
194
 
132
195
  patch(NDFileInfoAddition, FileInfoBuilder)
@@ -9,5 +9,6 @@ class LizardExtension(object): # pylint: disable=R0903
9
9
 
10
10
  # pylint: disable=W0221
11
11
  def __call__(self, tokens, reader):
12
- reader.conditions -= set(['&&', '||', 'and', 'or'])
12
+ # Remove logical operators from conditions (non-strict mode)
13
+ reader.conditions -= reader.logical_operators
13
14
  return tokens
lizard_ext/lizardns.py CHANGED
@@ -67,10 +67,35 @@ class LizardExtension(ExtensionBase): # pylint: disable=R0903
67
67
 
68
68
  def __init__(self):
69
69
  super(LizardExtension, self).__init__(None)
70
- self.structure_piles = [0]
70
+ self.structure_piles = [0] # Invariant: must always have at least one element
71
71
 
72
- def pile_up_within_block(self):
72
+ def _push_scope(self):
73
+ """Push a new scope level. Safe to call anytime."""
74
+ self.structure_piles.append(0)
75
+
76
+ def _pop_scope(self):
77
+ """Pop a scope level. Maintains invariant of at least one element."""
78
+ if len(self.structure_piles) > 1:
79
+ self.structure_piles.pop()
80
+
81
+ def _increment_current_scope(self):
82
+ """Increment structure count in current scope. Safe even if piles corrupted."""
83
+ if not self.structure_piles:
84
+ self.structure_piles = [0] # Restore invariant
73
85
  self.structure_piles[-1] += 1
86
+
87
+ def _reset_or_decrement_current_scope(self, decrement=False):
88
+ """Reset or decrement current scope counter. Safe even if piles corrupted."""
89
+ if not self.structure_piles:
90
+ self.structure_piles = [0] # Restore invariant
91
+ return
92
+ if decrement:
93
+ self.structure_piles[-1] -= 1
94
+ else:
95
+ self.structure_piles[-1] = 0
96
+
97
+ def pile_up_within_block(self):
98
+ self._increment_current_scope()
74
99
  cur_level = sum(self.structure_piles)
75
100
  # Is there a path around _state_global?
76
101
  if not hasattr(self.context.current_function, "max_nested_structures"):
@@ -83,10 +108,10 @@ class LizardExtension(ExtensionBase): # pylint: disable=R0903
83
108
  if not hasattr(self.context.current_function, "max_nested_structures"):
84
109
  self.context.current_function.max_nested_structures = 0
85
110
  if token == '{':
86
- self.structure_piles.append(0)
111
+ self._push_scope()
87
112
  elif token in ';}':
88
113
  if token == '}':
89
- self.structure_piles.pop()
114
+ self._pop_scope()
90
115
  self._state = self._block_ending
91
116
  elif token in self.structures:
92
117
  self._state = self._in_structure_head
@@ -98,10 +123,8 @@ class LizardExtension(ExtensionBase): # pylint: disable=R0903
98
123
  self._state(token)
99
124
 
100
125
  def _block_ending(self, token):
101
- if token in self.matching_structures:
102
- self.structure_piles[-1] -= 1
103
- else:
104
- self.structure_piles[-1] = 0
126
+ decrement = token in self.matching_structures
127
+ self._reset_or_decrement_current_scope(decrement=decrement)
105
128
  self._state = self._state_global
106
129
  self._state(token)
107
130
 
lizard_ext/version.py CHANGED
@@ -3,4 +3,4 @@
3
3
  #
4
4
  # pylint: disable=missing-docstring,invalid-name
5
5
 
6
- version = "1.18.0"
6
+ version = "1.20.0"
@@ -26,6 +26,7 @@ from .vue import VueReader
26
26
  from .perl import PerlReader
27
27
  from .st import StReader
28
28
  from .r import RReader
29
+ from .plsql import PLSQLReader
29
30
 
30
31
 
31
32
  def languages():
@@ -56,6 +57,7 @@ def languages():
56
57
  PerlReader,
57
58
  StReader,
58
59
  RReader,
60
+ PLSQLReader,
59
61
  ]
60
62
 
61
63
 
lizard_languages/clike.py CHANGED
@@ -32,8 +32,11 @@ class CLikeReader(CodeReader, CCppCommentsMixin):
32
32
 
33
33
  @staticmethod
34
34
  def generate_tokens(source_code, addition='', token_class=None):
35
- # Add pattern for floating point literals to the token generation
36
- addition = r"|(?:\d*\.\d+(?:[eE][-+]?\d+)?)" + \
35
+ # Add pattern for C++ raw string literals R"delimiter(content)delimiter"
36
+ # The delimiter can be empty or up to 16 chars (excluding parentheses, backslash, whitespace)
37
+ # Using a simplified pattern that handles most cases
38
+ addition = r"|R\"[^(\\]*\((?:[^)]|\)[^\"])*\)[^(\\]*\"" + \
39
+ r"|(?:\d*\.\d+(?:[eE][-+]?\d+)?)" + \
37
40
  r"|(?:\d+\.(?:\d+)?(?:[eE][-+]?\d+)?)" + \
38
41
  addition
39
42
  return CodeReader.generate_tokens(source_code, addition, token_class)
@@ -54,7 +57,7 @@ class CLikeReader(CodeReader, CCppCommentsMixin):
54
57
  elif macro.group(1) == 'include':
55
58
  yield "#include"
56
59
  yield macro.group(2) or "\"\""
57
- for _ in macro.group(2).splitlines()[1:]:
60
+ for _ in macro.group(2).split('\n')[1:]:
58
61
  yield '\n'
59
62
  else:
60
63
  yield token
@@ -94,12 +94,36 @@ class CodeReader:
94
94
  ext = []
95
95
  languages = None
96
96
  extra_subclasses = set()
97
- _conditions = {'if', 'for', 'while', '&&', '||', '?', 'catch', 'case'}
97
+
98
+ # Condition categories - separate types that contribute to cyclomatic complexity
99
+ _control_flow_keywords = {'if', 'for', 'while', 'catch'}
100
+ _logical_operators = {'&&', '||'}
101
+ _case_keywords = {'case'}
102
+ _ternary_operators = {'?'}
103
+
104
+ @classmethod
105
+ def _build_conditions(cls):
106
+ """Build combined conditions set from separated categories.
107
+
108
+ Returns combined set of all condition types for CCN calculation.
109
+ """
110
+ return (cls._control_flow_keywords |
111
+ cls._logical_operators |
112
+ cls._case_keywords |
113
+ cls._ternary_operators)
98
114
 
99
115
  def __init__(self, context):
100
116
  self.parallel_states = []
101
117
  self.context = context
102
- self.conditions = copy(self._conditions)
118
+
119
+ # Build combined conditions set from separated categories
120
+ self.conditions = copy(self.__class__._build_conditions())
121
+
122
+ # Expose individual categories for extensions
123
+ self.control_flow_keywords = copy(self.__class__._control_flow_keywords)
124
+ self.logical_operators = copy(self.__class__._logical_operators)
125
+ self.case_keywords = copy(self.__class__._case_keywords)
126
+ self.ternary_operators = copy(self.__class__._ternary_operators)
103
127
 
104
128
  @classmethod
105
129
  def match_filename(cls, filename):
@@ -138,7 +162,7 @@ class CodeReader:
138
162
  r"|\/\/" + _until_end +
139
163
  r"|\#" +
140
164
  r"|:=|::|\*\*" +
141
- r"|\<\s*\?(?:\s*extends\s+\w+)?\s*\>" +
165
+ r"|\<(?=(?:[^<>]*\?)+[^<>]*\>)(?:[\w\s,.?]|(?:extends))+\>" +
142
166
  r"|" + r"|".join(re.escape(s) for s in combined_symbols) +
143
167
  r"|\\\n" +
144
168
  r"|\n" +