lizard 1.18.0__py2.py3-none-any.whl → 1.19.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.
- {lizard-1.18.0.dist-info → lizard-1.19.0.dist-info}/METADATA +29 -19
- {lizard-1.18.0.dist-info → lizard-1.19.0.dist-info}/RECORD +14 -13
- lizard_ext/htmloutput.py +86 -17
- lizard_ext/lizardnd.py +64 -1
- lizard_ext/version.py +1 -1
- lizard_languages/__init__.py +2 -0
- lizard_languages/clike.py +1 -1
- lizard_languages/code_reader.py +1 -1
- lizard_languages/plsql.py +419 -0
- lizard_languages/typescript.py +52 -2
- {lizard-1.18.0.dist-info → lizard-1.19.0.dist-info}/LICENSE.txt +0 -0
- {lizard-1.18.0.dist-info → lizard-1.19.0.dist-info}/WHEEL +0 -0
- {lizard-1.18.0.dist-info → lizard-1.19.0.dist-info}/entry_points.txt +0 -0
- {lizard-1.18.0.dist-info → lizard-1.19.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lizard
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.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
6
|
Download-URL: https://pypi.python.org/lizard/
|
|
@@ -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
|
-
-
|
|
58
|
-
-
|
|
61
|
+
- Kotlin
|
|
62
|
+
- Lua
|
|
59
63
|
- Objective-C
|
|
60
|
-
-
|
|
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
|
-
-
|
|
71
|
-
- Kotlin
|
|
71
|
+
- Scala
|
|
72
72
|
- Solidity
|
|
73
|
-
- Erlang
|
|
74
|
-
- Zig
|
|
75
|
-
- Perl
|
|
76
73
|
- Structured Text (St)
|
|
77
|
-
-
|
|
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
|
|
|
@@ -5,7 +5,7 @@ lizard_ext/checkstyleoutput.py,sha256=UzDHg837ErEZepXkR8I8YCoz2r1lkmzGctMA7dpyB-
|
|
|
5
5
|
lizard_ext/csvoutput.py,sha256=43fhmo8kB85qcdujCwySGNuTC4FkKUPLqIApPeljPnA,2663
|
|
6
6
|
lizard_ext/default_ordered_dict.py,sha256=YbVz6nPlQ6DjWc_EOFBz6AJN2XLo9dpnUdeyejQvUDE,831
|
|
7
7
|
lizard_ext/extension_base.py,sha256=rnjUL2mqSGToUVYydju7fa8ZwynLPY8S1F17gIJP55I,346
|
|
8
|
-
lizard_ext/htmloutput.py,sha256=
|
|
8
|
+
lizard_ext/htmloutput.py,sha256=G7MdLD7AwuR0LLCAkEpyHx4N_ssCcFQdXd7Xg5jZWX0,6537
|
|
9
9
|
lizard_ext/keywords.py,sha256=VxsxoATtKV-8egMKd7I8sd2qbZMtEFEpsszk__6rmjQ,893
|
|
10
10
|
lizard_ext/lizardboolcount.py,sha256=abmMA9X3VFRO5mziicUxWKmHldHNC0jBEe7NKAKA5fs,1062
|
|
11
11
|
lizard_ext/lizardcomplextags.py,sha256=flrwYg24P5DoDsBO3gdcK9SxkugX_brhfjuu8zgPnOc,681
|
|
@@ -20,17 +20,17 @@ lizard_ext/lizardignoreassert.py,sha256=sqLwcnJQ06SYqIk901ib4NQ8ECwjIe_qL4T6z1wL
|
|
|
20
20
|
lizard_ext/lizardio.py,sha256=xQN-AgLGLKJarJkgfaqX_TKyupbb7GTcwPxrL2B1J1w,3357
|
|
21
21
|
lizard_ext/lizardmccabe.py,sha256=RiO8ASmQUah4udOH8SbE2OOMxwShIPByW93TlFxXlQU,1274
|
|
22
22
|
lizard_ext/lizardmodified.py,sha256=4Ld7yy1D2m2biMtx-g0DtjXwLa-9mG2togS2IRDAF3k,705
|
|
23
|
-
lizard_ext/lizardnd.py,sha256=
|
|
23
|
+
lizard_ext/lizardnd.py,sha256=h4MaSsFAY5rK8kOSr61LkWntwJWV1KnhaEOWZgp3-c0,7496
|
|
24
24
|
lizard_ext/lizardnonstrict.py,sha256=pPG22up2uh9rEkdRFtTWdiuOaiBNe0ZUjaZQpSTX5LE,394
|
|
25
25
|
lizard_ext/lizardns.py,sha256=8pztUoRS_UWN24MawwxeHEJgYh49id5PWODUBb6O72U,4184
|
|
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=
|
|
29
|
+
lizard_ext/version.py,sha256=gScBFBQFiyhdj77Gb-x1hY6wABdKwZlb_aaNdiiADQ0,181
|
|
30
30
|
lizard_ext/xmloutput.py,sha256=-cbh0he4O_X-wX56gkv9AnSPNN0qvR7FACqlBeezUS4,5609
|
|
31
|
-
lizard_languages/__init__.py,sha256=
|
|
32
|
-
lizard_languages/clike.py,sha256=
|
|
33
|
-
lizard_languages/code_reader.py,sha256=
|
|
31
|
+
lizard_languages/__init__.py,sha256=DniggHM3SOFlwkP8Tm4csEcisbf7_i1EvfoAkG9Atg0,1611
|
|
32
|
+
lizard_languages/clike.py,sha256=_oDUbA1hEIPujKw3wyAtC1uFPvjmg4TBgKbHLa-S1wg,13597
|
|
33
|
+
lizard_languages/code_reader.py,sha256=O0nZkI4xEHIWGgFBDwczj8-ay7aKllPnzRsblQtL3Gc,6874
|
|
34
34
|
lizard_languages/csharp.py,sha256=EfFAIOIcJXUUhXTlZApXGSlzG34NZvHM9OSe6m7hpv0,2141
|
|
35
35
|
lizard_languages/erlang.py,sha256=7YJS2cMyXDKEV_kpH8DzBARxFCFcjKuTOPSQ3K52auU,3860
|
|
36
36
|
lizard_languages/fortran.py,sha256=KATDsnfjob5W3579A_VxbwrbTkK7Rx3p0eXdBgjx25I,8973
|
|
@@ -45,6 +45,7 @@ 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=136w620eECe_t-kmlRUGrsZSxQNo2JQ_PZTSQfCSmHY,11987
|
|
47
47
|
lizard_languages/php.py,sha256=UV40p8WzNC64NQ5qElPKzcFTjVt5kenLMz-eKYlcnMY,9940
|
|
48
|
+
lizard_languages/plsql.py,sha256=17zf0AkDLFW7mA3ngUNrU7z6U6DkmA96CKOcEhYNC5Q,17137
|
|
48
49
|
lizard_languages/python.py,sha256=AsL0SmQ73zhNS1iGi4Z8VtuUE0VjqBzo9W8W0mjqL0E,5790
|
|
49
50
|
lizard_languages/r.py,sha256=IoyMhmFtUmTji6rm6-fqss_j_kvIHu3JjABRh6RNys0,12583
|
|
50
51
|
lizard_languages/ruby.py,sha256=HL1ZckeuUUJU3QSVAOPsG_Zsl0C6X2PX5_VaWqclzkM,2277
|
|
@@ -58,12 +59,12 @@ lizard_languages/swift.py,sha256=p8S2OAkQOx9YQ02yhoVXFkr7pMqUH1Nb3RVXPHRU_9M,245
|
|
|
58
59
|
lizard_languages/tnsdl.py,sha256=pGcalA_lHY362v2wwPS86seYBOOBBjvmU6vd4Yy3A9g,2803
|
|
59
60
|
lizard_languages/tsx.py,sha256=1oOVCcz5yHkmYLYGhSarCMSXfGVasweklAqqapkuNR4,17160
|
|
60
61
|
lizard_languages/ttcn.py,sha256=ygjw_raBmPF-4mgoM8m6CAdyEMpTI-n1kZJK1RL4Vxo,2131
|
|
61
|
-
lizard_languages/typescript.py,sha256=
|
|
62
|
+
lizard_languages/typescript.py,sha256=unCDj040dY9fTOw9iIykqjt2j5tZWJ2Bm9fHYjOWY5I,14706
|
|
62
63
|
lizard_languages/vue.py,sha256=KXUBUo2R1zNF8Pffrz_KsQEN44m5XFRMoGXylxKUeT0,1038
|
|
63
64
|
lizard_languages/zig.py,sha256=NX1iyBstBuJFeAGBOAIaRfrmeBREne2HX6Pt4fXZZTQ,586
|
|
64
|
-
lizard-1.
|
|
65
|
-
lizard-1.
|
|
66
|
-
lizard-1.
|
|
67
|
-
lizard-1.
|
|
68
|
-
lizard-1.
|
|
69
|
-
lizard-1.
|
|
65
|
+
lizard-1.19.0.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
|
|
66
|
+
lizard-1.19.0.dist-info/METADATA,sha256=DNAVbhrtdVrI5tn2rWZoSBwCfZ5ArhMQ0XNmHvOqbh4,16697
|
|
67
|
+
lizard-1.19.0.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
|
|
68
|
+
lizard-1.19.0.dist-info/entry_points.txt,sha256=pPMMwoHAltzGHqR2WeJQItLeeyR7pbX5R2S_POC-xoo,40
|
|
69
|
+
lizard-1.19.0.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
|
|
70
|
+
lizard-1.19.0.dist-info/RECORD,,
|
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
|
|
117
|
-
<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
|
|
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
|
-
|
|
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)
|
lizard_ext/version.py
CHANGED
lizard_languages/__init__.py
CHANGED
|
@@ -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
|
@@ -54,7 +54,7 @@ class CLikeReader(CodeReader, CCppCommentsMixin):
|
|
|
54
54
|
elif macro.group(1) == 'include':
|
|
55
55
|
yield "#include"
|
|
56
56
|
yield macro.group(2) or "\"\""
|
|
57
|
-
for _ in macro.group(2).
|
|
57
|
+
for _ in macro.group(2).split('\n')[1:]:
|
|
58
58
|
yield '\n'
|
|
59
59
|
else:
|
|
60
60
|
yield token
|
lizard_languages/code_reader.py
CHANGED
|
@@ -138,7 +138,7 @@ class CodeReader:
|
|
|
138
138
|
r"|\/\/" + _until_end +
|
|
139
139
|
r"|\#" +
|
|
140
140
|
r"|:=|::|\*\*" +
|
|
141
|
-
r"
|
|
141
|
+
r"|\<(?=(?:[^<>]*\?)+[^<>]*\>)(?:[\w\s,.?]|(?:extends))+\>" +
|
|
142
142
|
r"|" + r"|".join(re.escape(s) for s in combined_symbols) +
|
|
143
143
|
r"|\\\n" +
|
|
144
144
|
r"|\n" +
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Language parser for PL/SQL (Oracle's Procedural Language extension to SQL)
|
|
3
|
+
|
|
4
|
+
This module implements complexity analysis for PL/SQL code, supporting:
|
|
5
|
+
- Procedures, Functions, and Triggers
|
|
6
|
+
- Package Bodies (not specifications - they only contain signatures)
|
|
7
|
+
- Nested procedures and functions
|
|
8
|
+
- Anonymous blocks with nested functions (blocks themselves aren't counted)
|
|
9
|
+
- Control structures: IF/ELSIF/ELSE, CASE/WHEN, LOOP/WHILE/FOR
|
|
10
|
+
- Exception handlers
|
|
11
|
+
- Cursor declarations and cursor FOR loops
|
|
12
|
+
|
|
13
|
+
Design Decisions:
|
|
14
|
+
- EXIT WHEN: The WHEN keyword is filtered out by the preprocessor because
|
|
15
|
+
"EXIT WHEN condition" is not a branching construct - it's a conditional
|
|
16
|
+
exit that doesn't create alternate execution paths. The LOOP itself adds
|
|
17
|
+
complexity.
|
|
18
|
+
|
|
19
|
+
- CONTINUE WHEN: Similar to EXIT WHEN, the WHEN is counted as it does create
|
|
20
|
+
a branch in the loop execution.
|
|
21
|
+
|
|
22
|
+
- GOTO: Does not add to cyclomatic complexity as it's just an unconditional
|
|
23
|
+
jump, not a decision point.
|
|
24
|
+
|
|
25
|
+
- Standalone LOOP: Adds +1 complexity as it creates a repeating path.
|
|
26
|
+
|
|
27
|
+
- FOR/WHILE LOOP: The FOR/WHILE keyword adds complexity; the following LOOP
|
|
28
|
+
keyword is part of the same construct and doesn't add additional complexity.
|
|
29
|
+
|
|
30
|
+
- Parameter Counting: Currently counts all non-whitespace tokens and commas
|
|
31
|
+
in parameter lists. This approach works but differs from some other language
|
|
32
|
+
implementations.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from .code_reader import CodeReader, CodeStateMachine
|
|
36
|
+
from .clike import CCppCommentsMixin
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PLSQLReader(CodeReader, CCppCommentsMixin):
|
|
40
|
+
"""
|
|
41
|
+
Reader for PL/SQL language supporting procedures, functions, packages,
|
|
42
|
+
and core control structures.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
ext = ["sql", "pks", "pkb", "pls", "plb", "pck"]
|
|
46
|
+
language_names = ["plsql", "pl/sql"]
|
|
47
|
+
|
|
48
|
+
# PL/SQL conditions for cyclomatic complexity
|
|
49
|
+
# Note: 'loop' is NOT in this set because LOOP has special handling:
|
|
50
|
+
# - standalone LOOP adds +1
|
|
51
|
+
# - LOOP after WHILE/FOR should not add (it's part of the compound statement)
|
|
52
|
+
_conditions = {"if", "elsif", "when", "while", "for", "and", "or"}
|
|
53
|
+
|
|
54
|
+
def __init__(self, context):
|
|
55
|
+
super(PLSQLReader, self).__init__(context)
|
|
56
|
+
self.parallel_states = [PLSQLStates(context)]
|
|
57
|
+
# PL/SQL is case-insensitive, so add both lowercase and uppercase versions
|
|
58
|
+
# of keywords to the conditions set for the automatic condition counter
|
|
59
|
+
self.conditions = self.conditions | {c.upper() for c in self.conditions}
|
|
60
|
+
|
|
61
|
+
def preprocess(self, tokens):
|
|
62
|
+
"""
|
|
63
|
+
Preprocess tokens to handle PL/SQL-specific constructs.
|
|
64
|
+
Merge compound keywords to prevent the condition counter from double-counting:
|
|
65
|
+
- "END IF", "END LOOP", "END CASE", "END WHILE", "END FOR" -> single tokens
|
|
66
|
+
- "EXIT WHEN" -> remove the WHEN keyword (EXIT doesn't create a branch)
|
|
67
|
+
"""
|
|
68
|
+
last_nonwhitespace_token = None
|
|
69
|
+
pending_tokens = []
|
|
70
|
+
|
|
71
|
+
for token in tokens:
|
|
72
|
+
if not token.isspace() or token == "\n":
|
|
73
|
+
token_upper = token.upper()
|
|
74
|
+
|
|
75
|
+
# Handle "END IF", "END LOOP", etc.
|
|
76
|
+
if (
|
|
77
|
+
last_nonwhitespace_token
|
|
78
|
+
and last_nonwhitespace_token.upper() == "END"
|
|
79
|
+
):
|
|
80
|
+
if token_upper in ("IF", "LOOP", "CASE", "WHILE", "FOR"):
|
|
81
|
+
# Merge into "END_IF", "END_LOOP", etc.
|
|
82
|
+
yield "END_" + token_upper
|
|
83
|
+
last_nonwhitespace_token = None
|
|
84
|
+
pending_tokens = []
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
# Handle "EXIT WHEN" - skip the WHEN keyword
|
|
88
|
+
if (
|
|
89
|
+
last_nonwhitespace_token
|
|
90
|
+
and last_nonwhitespace_token.upper() == "EXIT"
|
|
91
|
+
and token_upper == "WHEN"
|
|
92
|
+
):
|
|
93
|
+
# Skip this WHEN keyword
|
|
94
|
+
pending_tokens = []
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Yield any pending tokens
|
|
98
|
+
if last_nonwhitespace_token:
|
|
99
|
+
yield last_nonwhitespace_token
|
|
100
|
+
for pending in pending_tokens:
|
|
101
|
+
yield pending
|
|
102
|
+
pending_tokens = []
|
|
103
|
+
|
|
104
|
+
# Update tracking
|
|
105
|
+
last_nonwhitespace_token = token
|
|
106
|
+
else:
|
|
107
|
+
# Accumulate whitespace
|
|
108
|
+
pending_tokens.append(token)
|
|
109
|
+
|
|
110
|
+
# Don't forget the last tokens
|
|
111
|
+
if last_nonwhitespace_token:
|
|
112
|
+
yield last_nonwhitespace_token
|
|
113
|
+
for pending in pending_tokens:
|
|
114
|
+
yield pending
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def generate_tokens(source_code, addition="", token_class=None):
|
|
118
|
+
"""
|
|
119
|
+
Generate tokens for PL/SQL code.
|
|
120
|
+
PL/SQL uses:
|
|
121
|
+
- Single-line comments: --
|
|
122
|
+
- Multi-line comments: /* */
|
|
123
|
+
- String literals: 'text' (with '' for escaping)
|
|
124
|
+
- Assignment operator: :=
|
|
125
|
+
"""
|
|
126
|
+
# Add PL/SQL-specific patterns
|
|
127
|
+
addition = r"|--[^\n]*" + addition # Single-line comment starting with --
|
|
128
|
+
return CodeReader.generate_tokens(source_code, addition, token_class)
|
|
129
|
+
|
|
130
|
+
def get_comment_from_token(self, token):
|
|
131
|
+
"""
|
|
132
|
+
Override to recognize PL/SQL's -- line comments in addition to /* */ block comments.
|
|
133
|
+
PL/SQL uses -- for single-line comments (like SQL standard).
|
|
134
|
+
|
|
135
|
+
Note: This method correctly identifies -- comments, but due to a limitation in
|
|
136
|
+
the NLOC calculation, these comments may still be counted in NLOC.
|
|
137
|
+
"""
|
|
138
|
+
if token.startswith("--"):
|
|
139
|
+
return token # Return full comment token (like Lua does)
|
|
140
|
+
# Delegate to parent for /* */ and // comments
|
|
141
|
+
return super().get_comment_from_token(token)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class PLSQLStates(CodeStateMachine):
|
|
145
|
+
"""
|
|
146
|
+
State machine for parsing PL/SQL code structure.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, context):
|
|
150
|
+
super(PLSQLStates, self).__init__(context)
|
|
151
|
+
self.in_parameter_list = False
|
|
152
|
+
self.last_control_keyword = None # Track FOR/WHILE to avoid counting their LOOP
|
|
153
|
+
self.declaring_nested_function = (
|
|
154
|
+
False # Track if we're declaring a nested function
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def _state_global(self, token):
|
|
158
|
+
"""Global state - looking for function/procedure/trigger declarations."""
|
|
159
|
+
token_lower = token.lower()
|
|
160
|
+
|
|
161
|
+
if token_lower == "procedure":
|
|
162
|
+
self.next(self._procedure_name)
|
|
163
|
+
elif token_lower == "function":
|
|
164
|
+
self.next(self._function_name)
|
|
165
|
+
elif token_lower == "trigger":
|
|
166
|
+
self.next(self._trigger_name)
|
|
167
|
+
|
|
168
|
+
def _procedure_name(self, token):
|
|
169
|
+
"""Read procedure name."""
|
|
170
|
+
if token.isspace() or token == "\n":
|
|
171
|
+
return
|
|
172
|
+
if token == "(":
|
|
173
|
+
self.in_parameter_list = True
|
|
174
|
+
self.next(self._parameters, "(")
|
|
175
|
+
elif token.lower() in ("is", "as"):
|
|
176
|
+
self.context.confirm_new_function()
|
|
177
|
+
self.next(self._state_before_begin)
|
|
178
|
+
else:
|
|
179
|
+
# Check if this is a nested function
|
|
180
|
+
if self.declaring_nested_function:
|
|
181
|
+
self.context.push_new_function(token)
|
|
182
|
+
self.declaring_nested_function = False
|
|
183
|
+
else:
|
|
184
|
+
self.context.try_new_function(token)
|
|
185
|
+
self.next(self._procedure_after_name)
|
|
186
|
+
|
|
187
|
+
def _procedure_after_name(self, token):
|
|
188
|
+
"""After procedure name, look for parameters or IS/AS."""
|
|
189
|
+
if token == ".":
|
|
190
|
+
# Schema-qualified name: the previous token was the schema,
|
|
191
|
+
# next non-whitespace token will be the actual procedure name
|
|
192
|
+
self.next(self._procedure_name_after_dot)
|
|
193
|
+
elif token == "(":
|
|
194
|
+
self.in_parameter_list = True
|
|
195
|
+
self.next(self._parameters, "(")
|
|
196
|
+
elif token.lower() in ("is", "as"):
|
|
197
|
+
self.context.confirm_new_function()
|
|
198
|
+
self.next(self._state_before_begin)
|
|
199
|
+
# Skip whitespace and other tokens
|
|
200
|
+
|
|
201
|
+
def _procedure_name_after_dot(self, token):
|
|
202
|
+
"""Read the actual procedure name after schema.dot."""
|
|
203
|
+
if token.isspace() or token == "\n":
|
|
204
|
+
return
|
|
205
|
+
# Replace the previous (schema) name with the actual procedure name
|
|
206
|
+
self.context.current_function.name = token
|
|
207
|
+
self.next(self._procedure_after_name)
|
|
208
|
+
|
|
209
|
+
def _function_name(self, token):
|
|
210
|
+
"""Read function name."""
|
|
211
|
+
if token.isspace() or token == "\n":
|
|
212
|
+
return
|
|
213
|
+
if token == "(":
|
|
214
|
+
self.in_parameter_list = True
|
|
215
|
+
self.next(self._parameters, "(")
|
|
216
|
+
elif token.lower() == "return":
|
|
217
|
+
self.next(self._return_type)
|
|
218
|
+
elif token.lower() in ("is", "as"):
|
|
219
|
+
self.context.confirm_new_function()
|
|
220
|
+
self.next(self._state_before_begin)
|
|
221
|
+
else:
|
|
222
|
+
# Check if this is a nested function
|
|
223
|
+
if self.declaring_nested_function:
|
|
224
|
+
self.context.push_new_function(token)
|
|
225
|
+
self.declaring_nested_function = False
|
|
226
|
+
else:
|
|
227
|
+
self.context.try_new_function(token)
|
|
228
|
+
self.next(self._function_after_name)
|
|
229
|
+
|
|
230
|
+
def _function_after_name(self, token):
|
|
231
|
+
"""After function name, look for parameters, RETURN, or IS/AS."""
|
|
232
|
+
if token == ".":
|
|
233
|
+
# Schema-qualified name: the previous token was the schema,
|
|
234
|
+
# next non-whitespace token will be the actual function name
|
|
235
|
+
self.next(self._function_name_after_dot)
|
|
236
|
+
elif token == "(":
|
|
237
|
+
self.in_parameter_list = True
|
|
238
|
+
self.next(self._parameters, "(")
|
|
239
|
+
elif token.lower() == "return":
|
|
240
|
+
self.next(self._return_type)
|
|
241
|
+
elif token.lower() in ("is", "as"):
|
|
242
|
+
self.context.confirm_new_function()
|
|
243
|
+
self.next(self._state_before_begin)
|
|
244
|
+
# Skip whitespace and other tokens
|
|
245
|
+
|
|
246
|
+
def _function_name_after_dot(self, token):
|
|
247
|
+
"""Read the actual function name after schema.dot."""
|
|
248
|
+
if token.isspace() or token == "\n":
|
|
249
|
+
return
|
|
250
|
+
# Replace the previous (schema) name with the actual function name
|
|
251
|
+
self.context.current_function.name = token
|
|
252
|
+
self.next(self._function_after_name)
|
|
253
|
+
|
|
254
|
+
def _return_type(self, token):
|
|
255
|
+
"""Skip return type declaration."""
|
|
256
|
+
if token.lower() in ("is", "as"):
|
|
257
|
+
self.context.confirm_new_function()
|
|
258
|
+
self.next(self._state_before_begin)
|
|
259
|
+
# Skip everything else (return type tokens)
|
|
260
|
+
|
|
261
|
+
def _parameters(self, token):
|
|
262
|
+
"""Read parameters."""
|
|
263
|
+
if token == ")":
|
|
264
|
+
self.in_parameter_list = False
|
|
265
|
+
self.next(self._after_parameters)
|
|
266
|
+
elif token == ",":
|
|
267
|
+
# Each comma separates parameters
|
|
268
|
+
self.context.parameter(token)
|
|
269
|
+
elif not token.isspace() and token != "\n":
|
|
270
|
+
# Track non-whitespace tokens as potential parameters
|
|
271
|
+
self.context.parameter(token)
|
|
272
|
+
|
|
273
|
+
def _after_parameters(self, token):
|
|
274
|
+
"""After parameters, look for IS/AS or RETURN."""
|
|
275
|
+
if token.lower() == "return":
|
|
276
|
+
self.next(self._return_type)
|
|
277
|
+
elif token.lower() in ("is", "as"):
|
|
278
|
+
self.context.confirm_new_function()
|
|
279
|
+
self.next(self._state_before_begin)
|
|
280
|
+
# Skip whitespace and other tokens
|
|
281
|
+
|
|
282
|
+
def _trigger_name(self, token):
|
|
283
|
+
"""Read trigger name."""
|
|
284
|
+
if token.isspace() or token == "\n":
|
|
285
|
+
return
|
|
286
|
+
# Trigger name found
|
|
287
|
+
self.context.try_new_function(token)
|
|
288
|
+
self.seen_trigger_name_token = False # Track if we've seen non-whitespace after name
|
|
289
|
+
self.next(self._trigger_after_name)
|
|
290
|
+
|
|
291
|
+
def _trigger_after_name(self, token):
|
|
292
|
+
"""After trigger name, skip until DECLARE or BEGIN."""
|
|
293
|
+
token_lower = token.lower()
|
|
294
|
+
|
|
295
|
+
# Only check for dot immediately after trigger name (before any other tokens)
|
|
296
|
+
if token == "." and not self.seen_trigger_name_token:
|
|
297
|
+
# Schema-qualified name: the previous token was the schema,
|
|
298
|
+
# next non-whitespace token will be the actual trigger name
|
|
299
|
+
self.next(self._trigger_name_after_dot)
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
# Mark that we've seen a non-whitespace token after the trigger name
|
|
303
|
+
if not token.isspace() and token != "\n":
|
|
304
|
+
self.seen_trigger_name_token = True
|
|
305
|
+
|
|
306
|
+
if token_lower == "declare":
|
|
307
|
+
self.context.confirm_new_function()
|
|
308
|
+
self.next(self._state_before_begin)
|
|
309
|
+
elif token_lower == "begin":
|
|
310
|
+
self.context.confirm_new_function()
|
|
311
|
+
self.br_count = 1
|
|
312
|
+
self.next(self._state_body)
|
|
313
|
+
# Skip everything else (BEFORE/AFTER, INSERT/UPDATE/DELETE, ON table_name, FOR EACH ROW, etc.)
|
|
314
|
+
|
|
315
|
+
def _trigger_name_after_dot(self, token):
|
|
316
|
+
"""Read the actual trigger name after schema.dot."""
|
|
317
|
+
if token.isspace() or token == "\n":
|
|
318
|
+
return
|
|
319
|
+
# Replace the previous (schema) name with the actual trigger name
|
|
320
|
+
self.context.current_function.name = token
|
|
321
|
+
self.seen_trigger_name_token = False # Reset for the real trigger name
|
|
322
|
+
self.next(self._trigger_after_name)
|
|
323
|
+
|
|
324
|
+
def _state_before_begin(self, token):
|
|
325
|
+
"""
|
|
326
|
+
State between IS/AS and BEGIN - this is the declaration section.
|
|
327
|
+
Watch for nested procedures/functions and the BEGIN keyword.
|
|
328
|
+
"""
|
|
329
|
+
token_lower = token.lower()
|
|
330
|
+
|
|
331
|
+
# Check for nested procedure/function declarations
|
|
332
|
+
if token_lower == "procedure":
|
|
333
|
+
self.declaring_nested_function = True
|
|
334
|
+
# Store current br_count level to know when nested function ends
|
|
335
|
+
if not hasattr(self, "nested_br_level"):
|
|
336
|
+
self.nested_br_level = 0
|
|
337
|
+
self.next(self._procedure_name)
|
|
338
|
+
return
|
|
339
|
+
elif token_lower == "function":
|
|
340
|
+
self.declaring_nested_function = True
|
|
341
|
+
# Store current br_count level to know when nested function ends
|
|
342
|
+
if not hasattr(self, "nested_br_level"):
|
|
343
|
+
self.nested_br_level = 0
|
|
344
|
+
self.next(self._function_name)
|
|
345
|
+
return
|
|
346
|
+
elif token_lower == "begin":
|
|
347
|
+
# Start of the implementation body
|
|
348
|
+
# Check if we had nested functions and need to reset br_count tracking
|
|
349
|
+
if hasattr(self, "nested_br_level"):
|
|
350
|
+
self.br_count = self.nested_br_level + 1
|
|
351
|
+
delattr(self, "nested_br_level")
|
|
352
|
+
else:
|
|
353
|
+
self.br_count = 1 # Initialize counter for the first BEGIN
|
|
354
|
+
self.next(self._state_body)
|
|
355
|
+
|
|
356
|
+
def _state_body(self, token):
|
|
357
|
+
"""
|
|
358
|
+
Process function/procedure body.
|
|
359
|
+
Track control structures for cyclomatic complexity.
|
|
360
|
+
Manually track BEGIN/END blocks.
|
|
361
|
+
"""
|
|
362
|
+
token_lower = token.lower()
|
|
363
|
+
token_upper = token.upper()
|
|
364
|
+
|
|
365
|
+
# Check for merged compound keywords like "END_IF", "END_LOOP", etc.
|
|
366
|
+
# These are created by the preprocessor
|
|
367
|
+
if token_lower.startswith("end_"):
|
|
368
|
+
# This is a compound END keyword, reset tracking
|
|
369
|
+
self.last_control_keyword = None
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
# Handle nested procedure/function declarations
|
|
373
|
+
if token_lower == "procedure":
|
|
374
|
+
self.next(self._procedure_name)
|
|
375
|
+
return
|
|
376
|
+
elif token_lower == "function":
|
|
377
|
+
self.next(self._function_name)
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
# Track FOR and WHILE to know when LOOP follows them
|
|
381
|
+
if token_upper in ("FOR", "WHILE"):
|
|
382
|
+
self.last_control_keyword = token_upper
|
|
383
|
+
|
|
384
|
+
# Handle LOOP keyword manually
|
|
385
|
+
# - Standalone LOOP adds +1 complexity
|
|
386
|
+
# - LOOP after FOR/WHILE does not add complexity (already counted for FOR/WHILE)
|
|
387
|
+
elif token_upper == "LOOP":
|
|
388
|
+
if self.last_control_keyword not in ("FOR", "WHILE"):
|
|
389
|
+
# This is a standalone LOOP, add complexity
|
|
390
|
+
self.context.add_condition()
|
|
391
|
+
# Reset tracking after processing LOOP
|
|
392
|
+
self.last_control_keyword = None
|
|
393
|
+
|
|
394
|
+
# PL/SQL uses BEGIN/END instead of {}
|
|
395
|
+
if token_lower == "begin":
|
|
396
|
+
self.br_count += 1
|
|
397
|
+
self.context.add_bare_nesting()
|
|
398
|
+
elif token_lower == "end":
|
|
399
|
+
# This is a standalone END (for BEGIN/END block)
|
|
400
|
+
self.br_count -= 1
|
|
401
|
+
if self.br_count == 0:
|
|
402
|
+
# This END closes the function/procedure
|
|
403
|
+
# Check if we have a parent function BEFORE ending (stack gets popped)
|
|
404
|
+
has_parent = len(self.context.stacked_functions) > 0
|
|
405
|
+
self.context.end_of_function()
|
|
406
|
+
# Return to appropriate state based on whether this was nested
|
|
407
|
+
if has_parent:
|
|
408
|
+
# Return to parent function's declaration section
|
|
409
|
+
self.next(self._state_before_begin)
|
|
410
|
+
else:
|
|
411
|
+
# No parent function, return to global
|
|
412
|
+
self.next(self._state_global)
|
|
413
|
+
return
|
|
414
|
+
else:
|
|
415
|
+
self.context.pop_nesting()
|
|
416
|
+
|
|
417
|
+
# Note: Basic conditions (if, elsif, when, while, for, and, or)
|
|
418
|
+
# are automatically counted by the condition_counter processor
|
|
419
|
+
# based on the _conditions set in the Reader class.
|
lizard_languages/typescript.py
CHANGED
|
@@ -128,6 +128,9 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
128
128
|
self._getter_setter_prefix = None
|
|
129
129
|
self.arrow_function_pending = False
|
|
130
130
|
self._ts_declare = False # Track if 'declare' was seen
|
|
131
|
+
self._static_seen = False # Track if 'static' was seen
|
|
132
|
+
self._async_seen = False # Track if 'async' was seen
|
|
133
|
+
self._prev_token = '' # Track previous token to detect method calls
|
|
131
134
|
|
|
132
135
|
def statemachine_before_return(self):
|
|
133
136
|
# Ensure the main function is closed at the end
|
|
@@ -152,6 +155,20 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
152
155
|
return
|
|
153
156
|
self._ts_declare = False
|
|
154
157
|
|
|
158
|
+
# Track static and async modifiers
|
|
159
|
+
if token == 'static':
|
|
160
|
+
self._static_seen = True
|
|
161
|
+
self._prev_token = token
|
|
162
|
+
return
|
|
163
|
+
if token == 'async':
|
|
164
|
+
self._async_seen = True
|
|
165
|
+
self._prev_token = token
|
|
166
|
+
return
|
|
167
|
+
if token == 'new':
|
|
168
|
+
# Track 'new' keyword to avoid treating constructors as functions
|
|
169
|
+
self._prev_token = token
|
|
170
|
+
return
|
|
171
|
+
|
|
155
172
|
if self.as_object:
|
|
156
173
|
# Support for getter/setter: look for 'get' or 'set' before method name
|
|
157
174
|
if token in ('get', 'set'):
|
|
@@ -169,15 +186,26 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
169
186
|
self.function_name = self.last_tokens
|
|
170
187
|
return
|
|
171
188
|
elif token == '(':
|
|
189
|
+
# Check if this is a method call (previous token was . or this/identifier)
|
|
190
|
+
if self._prev_token == '.' or self._prev_token == 'new':
|
|
191
|
+
# This is a method call, not a function definition
|
|
192
|
+
self._prev_token = token
|
|
193
|
+
return
|
|
172
194
|
if not self.started_function:
|
|
173
195
|
self.arrow_function_pending = True
|
|
174
196
|
self._function(self.last_tokens)
|
|
175
197
|
self.next(self._function, token)
|
|
176
198
|
return
|
|
199
|
+
# If we've seen async/static and this is an identifier, it's likely a method name
|
|
200
|
+
elif (self._async_seen or self._static_seen) and token not in ('*', 'function'):
|
|
201
|
+
# This is a method name after async/static
|
|
202
|
+
self.last_tokens = token
|
|
203
|
+
return
|
|
177
204
|
|
|
178
205
|
if token in '.':
|
|
179
206
|
self._state = self._field
|
|
180
207
|
self.last_tokens += token
|
|
208
|
+
self._prev_token = token
|
|
181
209
|
return
|
|
182
210
|
if token == 'function':
|
|
183
211
|
self._state = self._function
|
|
@@ -191,8 +219,14 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
191
219
|
elif token == '=':
|
|
192
220
|
self.function_name = self.last_tokens
|
|
193
221
|
elif token == "(":
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
# Check if this is a method call or constructor
|
|
223
|
+
if self._prev_token == '.' or self._prev_token == 'new':
|
|
224
|
+
# This is a method call or constructor, not a function definition
|
|
225
|
+
self.sub_state(
|
|
226
|
+
self.__class__(self.context))
|
|
227
|
+
else:
|
|
228
|
+
self.sub_state(
|
|
229
|
+
self.__class__(self.context))
|
|
196
230
|
elif token in '{':
|
|
197
231
|
if self.started_function:
|
|
198
232
|
self.sub_state(
|
|
@@ -205,14 +239,21 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
205
239
|
elif self.context.newline or token == ';':
|
|
206
240
|
self.function_name = ''
|
|
207
241
|
self._pop_function_from_stack()
|
|
242
|
+
# Reset modifiers on newline/semicolon
|
|
243
|
+
self._static_seen = False
|
|
244
|
+
self._async_seen = False
|
|
208
245
|
|
|
209
246
|
if token == '`':
|
|
210
247
|
self.next(self._state_template_literal)
|
|
211
248
|
if not self.as_object:
|
|
212
249
|
if token == ':':
|
|
213
250
|
self._consume_type_annotation()
|
|
251
|
+
self._prev_token = token
|
|
214
252
|
return
|
|
215
253
|
self.last_tokens = token
|
|
254
|
+
# Don't overwrite _prev_token if it's 'new' or '.' (preserve for next token)
|
|
255
|
+
if self._prev_token not in ('new', '.'):
|
|
256
|
+
self._prev_token = token
|
|
216
257
|
|
|
217
258
|
def read_object(self):
|
|
218
259
|
def callback():
|
|
@@ -220,7 +261,13 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
220
261
|
|
|
221
262
|
object_reader = self.__class__(self.context)
|
|
222
263
|
object_reader.as_object = True
|
|
264
|
+
# Pass along the modifier flags
|
|
265
|
+
object_reader._static_seen = self._static_seen
|
|
266
|
+
object_reader._async_seen = self._async_seen
|
|
223
267
|
self.sub_state(object_reader, callback)
|
|
268
|
+
# Reset modifiers after entering object
|
|
269
|
+
self._static_seen = False
|
|
270
|
+
self._async_seen = False
|
|
224
271
|
|
|
225
272
|
def _expecting_condition_and_statement_block(self, token):
|
|
226
273
|
def callback():
|
|
@@ -269,6 +316,9 @@ class TypeScriptStates(CodeStateMachine):
|
|
|
269
316
|
return
|
|
270
317
|
if token != '(':
|
|
271
318
|
self.function_name = token
|
|
319
|
+
# Reset modifiers after setting function name
|
|
320
|
+
self._static_seen = False
|
|
321
|
+
self._async_seen = False
|
|
272
322
|
else:
|
|
273
323
|
if not self.started_function:
|
|
274
324
|
self._push_function_to_stack()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|