lizard 1.17.31__py2.py3-none-any.whl → 1.18.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.17.31.dist-info → lizard-1.18.0.dist-info}/METADATA +5 -1
- {lizard-1.17.31.dist-info → lizard-1.18.0.dist-info}/RECORD +17 -17
- {lizard-1.17.31.dist-info → lizard-1.18.0.dist-info}/entry_points.txt +1 -0
- lizard.py +7 -7
- lizard_ext/checkstyleoutput.py +4 -2
- lizard_ext/version.py +1 -1
- lizard_languages/__init__.py +4 -2
- lizard_languages/clike.py +60 -1
- lizard_languages/python.py +36 -0
- lizard_languages/r.py +290 -0
- lizard_languages/rust.py +5 -0
- lizard_languages/st.py +139 -0
- lizard_languages/tsx.py +445 -11
- lizard_languages/typescript.py +214 -14
- lizard_languages/js_style_language_states.py +0 -185
- lizard_languages/jsx.py +0 -337
- {lizard-1.17.31.dist-info → lizard-1.18.0.dist-info}/LICENSE.txt +0 -0
- {lizard-1.17.31.dist-info → lizard-1.18.0.dist-info}/WHEEL +0 -0
- {lizard-1.17.31.dist-info → lizard-1.18.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.18.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/
|
|
@@ -73,6 +73,8 @@ A list of supported languages:
|
|
|
73
73
|
- Erlang
|
|
74
74
|
- Zig
|
|
75
75
|
- Perl
|
|
76
|
+
- Structured Text (St)
|
|
77
|
+
- R
|
|
76
78
|
|
|
77
79
|
By default lizard will search for any source code that it knows and mix
|
|
78
80
|
all the results together. This might not be what you want. You can use
|
|
@@ -413,3 +415,5 @@ Lizard is also used as a plugin for fastlane to help check code complexity and s
|
|
|
413
415
|
- `European research project FASTEN (Fine-grained Analysis of SofTware Ecosystems as Networks, <http://fasten-project.eu/)>`_
|
|
414
416
|
- `for a quality analyzer <https://github.com/fasten-project/quality-analyzer>`_
|
|
415
417
|
|
|
418
|
+
|
|
419
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
lizard.py,sha256=
|
|
1
|
+
lizard.py,sha256=B0g5lEp5me31bQpjfMKVayF0iUfH0PalVecOKeUX3-A,41365
|
|
2
2
|
lizard_ext/__init__.py,sha256=AkZYVqCgd7XX1hMwLMyh_ABm9geIHmobEch-thYXZz8,932
|
|
3
3
|
lizard_ext/auto_open.py,sha256=byD_RbeVhvSUhR2bJMRitvA3zcKEapFwv0-XaDJ6GFo,1096
|
|
4
|
-
lizard_ext/checkstyleoutput.py,sha256=
|
|
4
|
+
lizard_ext/checkstyleoutput.py,sha256=UzDHg837ErEZepXkR8I8YCoz2r1lkmzGctMA7dpyB-M,1245
|
|
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
|
|
@@ -26,10 +26,10 @@ 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=WiI5ub_2twuTf6tscOzwP6MNdZV6kq-ElL2-2HqdUXc,181
|
|
30
30
|
lizard_ext/xmloutput.py,sha256=-cbh0he4O_X-wX56gkv9AnSPNN0qvR7FACqlBeezUS4,5609
|
|
31
|
-
lizard_languages/__init__.py,sha256=
|
|
32
|
-
lizard_languages/clike.py,sha256=
|
|
31
|
+
lizard_languages/__init__.py,sha256=NsYspQ6h--XHMJrIcmOxlZRF4f_l5M7JprenLBz2oIE,1559
|
|
32
|
+
lizard_languages/clike.py,sha256=RICnhzBzLbMwpceo3X-z7_-hcTZmGAZKGqpKgJf0G0o,13598
|
|
33
33
|
lizard_languages/code_reader.py,sha256=IfEHg9lzKnyCipX9xscgyGEOovll5qr9dCe5cSX2sJM,6852
|
|
34
34
|
lizard_languages/csharp.py,sha256=EfFAIOIcJXUUhXTlZApXGSlzG34NZvHM9OSe6m7hpv0,2141
|
|
35
35
|
lizard_languages/erlang.py,sha256=7YJS2cMyXDKEV_kpH8DzBARxFCFcjKuTOPSQ3K52auU,3860
|
|
@@ -39,31 +39,31 @@ lizard_languages/go.py,sha256=sntz0jOEuj4klPipoTFd16UDK1fAUQfwK7YX_cLMZAc,1346
|
|
|
39
39
|
lizard_languages/golike.py,sha256=vRIfjTVvc0VmJf27lTOLht55ZF1AQ9wn0Fvu-9WabWk,2858
|
|
40
40
|
lizard_languages/java.py,sha256=HQBTZjUKbUJwgmtLYIzJrWtPpFP3ZdBP_NJK7YOXZC0,6424
|
|
41
41
|
lizard_languages/javascript.py,sha256=vniCNMW-ea9Jpv6c8qCcjLVDYjT8VztjXigp5XRWt0E,317
|
|
42
|
-
lizard_languages/js_style_language_states.py,sha256=6mLrHfvDC4oHttKsSRGkE-ayG8uKSEm4E4rlhaUN5lA,6396
|
|
43
42
|
lizard_languages/js_style_regex_expression.py,sha256=Xgyogch4xElYtCG4EnBKvalHTl3tjRPcIIcIQRRd61I,1970
|
|
44
|
-
lizard_languages/jsx.py,sha256=TLH93qrZO2r_tiiv1XBIbw2_mbUMjwCZAuBz5vG5oBw,11696
|
|
45
43
|
lizard_languages/kotlin.py,sha256=v_o2orEzA5gB9vM_0h-E4QXjrc5Yum-0K6W6_laOThc,2844
|
|
46
44
|
lizard_languages/lua.py,sha256=3nqBcunBzJrhv4Iqaf8xvbyqxZy3aSxJ-IiHimHFlac,1573
|
|
47
45
|
lizard_languages/objc.py,sha256=2a1teLdaXZBtCeFiIZer1j_sVx9LZ1CbF2XfnqlvLmk,2319
|
|
48
46
|
lizard_languages/perl.py,sha256=136w620eECe_t-kmlRUGrsZSxQNo2JQ_PZTSQfCSmHY,11987
|
|
49
47
|
lizard_languages/php.py,sha256=UV40p8WzNC64NQ5qElPKzcFTjVt5kenLMz-eKYlcnMY,9940
|
|
50
|
-
lizard_languages/python.py,sha256=
|
|
48
|
+
lizard_languages/python.py,sha256=AsL0SmQ73zhNS1iGi4Z8VtuUE0VjqBzo9W8W0mjqL0E,5790
|
|
49
|
+
lizard_languages/r.py,sha256=IoyMhmFtUmTji6rm6-fqss_j_kvIHu3JjABRh6RNys0,12583
|
|
51
50
|
lizard_languages/ruby.py,sha256=HL1ZckeuUUJU3QSVAOPsG_Zsl0C6X2PX5_VaWqclzkM,2277
|
|
52
51
|
lizard_languages/rubylike.py,sha256=dAGZ2wqW8nqaESMU8HkeR9gwQ-q9fmZqE6AANvVZD1Q,3426
|
|
53
|
-
lizard_languages/rust.py,sha256=
|
|
52
|
+
lizard_languages/rust.py,sha256=WarDHnFZv99Yu3_C5DpZfLS8dVWz6AcOzo2dzLW94rA,817
|
|
54
53
|
lizard_languages/scala.py,sha256=6Jr_TG945VYqB3o5weD7jN7S4beHt4aVj3r-fmKeMAM,1316
|
|
55
54
|
lizard_languages/script_language.py,sha256=SKe45AbO6Z-axbN8KW_g7jf9g7YTXZ6dWzJj4ubDsM8,1172
|
|
56
55
|
lizard_languages/solidity.py,sha256=Z0GD7U5bI5eUikdy7m_iKWeFD5yXRYq4r3zycscOhJQ,553
|
|
56
|
+
lizard_languages/st.py,sha256=7fpOfNAoUjNY8RCHSYLufnOzZTUkKwjVvcyRyM1xP2Y,4160
|
|
57
57
|
lizard_languages/swift.py,sha256=p8S2OAkQOx9YQ02yhoVXFkr7pMqUH1Nb3RVXPHRU_9M,2450
|
|
58
58
|
lizard_languages/tnsdl.py,sha256=pGcalA_lHY362v2wwPS86seYBOOBBjvmU6vd4Yy3A9g,2803
|
|
59
|
-
lizard_languages/tsx.py,sha256=
|
|
59
|
+
lizard_languages/tsx.py,sha256=1oOVCcz5yHkmYLYGhSarCMSXfGVasweklAqqapkuNR4,17160
|
|
60
60
|
lizard_languages/ttcn.py,sha256=ygjw_raBmPF-4mgoM8m6CAdyEMpTI-n1kZJK1RL4Vxo,2131
|
|
61
|
-
lizard_languages/typescript.py,sha256=
|
|
61
|
+
lizard_languages/typescript.py,sha256=C8VWmHLGGYwCrP6hJ9HmJ-PETrKux4npjOzDN6AkTGU,12347
|
|
62
62
|
lizard_languages/vue.py,sha256=KXUBUo2R1zNF8Pffrz_KsQEN44m5XFRMoGXylxKUeT0,1038
|
|
63
63
|
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.
|
|
64
|
+
lizard-1.18.0.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
|
|
65
|
+
lizard-1.18.0.dist-info/METADATA,sha256=BX2GeLwA0iQpWmDi0wYg4xXCTISh6Un8alaLcJhuRxc,16260
|
|
66
|
+
lizard-1.18.0.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
|
|
67
|
+
lizard-1.18.0.dist-info/entry_points.txt,sha256=pPMMwoHAltzGHqR2WeJQItLeeyR7pbX5R2S_POC-xoo,40
|
|
68
|
+
lizard-1.18.0.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
|
|
69
|
+
lizard-1.18.0.dist-info/RECORD,,
|
lizard.py
CHANGED
|
@@ -623,7 +623,7 @@ def whitelist_filter(warnings, script=None, whitelist=None):
|
|
|
623
623
|
|
|
624
624
|
def get_whitelist(whitelist):
|
|
625
625
|
if os.path.isfile(whitelist):
|
|
626
|
-
return
|
|
626
|
+
return auto_read(whitelist)
|
|
627
627
|
if whitelist != DEFAULT_WHITELIST:
|
|
628
628
|
print("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
629
629
|
print("WARNING: the whitelist \""+whitelist+"\" doesn't exist.")
|
|
@@ -923,12 +923,12 @@ def get_all_source_files(paths, exclude_patterns, lans):
|
|
|
923
923
|
for path in paths:
|
|
924
924
|
gitignore_path = os.path.join(path, '.gitignore')
|
|
925
925
|
if os.path.exists(gitignore_path):
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
926
|
+
gitignore_file = auto_read(gitignore_path)
|
|
927
|
+
# Read lines and strip whitespace and empty lines
|
|
928
|
+
patterns = [line.strip() for line in gitignore_file.splitlines()]
|
|
929
|
+
patterns = [p for p in patterns if p and not p.startswith('#')]
|
|
930
|
+
gitignore_spec = pathspec.PathSpec.from_lines('gitwildmatch', patterns)
|
|
931
|
+
base_path = path
|
|
932
932
|
break
|
|
933
933
|
except ImportError:
|
|
934
934
|
pass
|
lizard_ext/checkstyleoutput.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Checkstyle XML output for Lizard
|
|
3
3
|
'''
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
def checkstyle_output(all_result, verbose):
|
|
6
7
|
result = all_result.result
|
|
7
8
|
import xml.etree.ElementTree as ET
|
|
@@ -20,7 +21,8 @@ def checkstyle_output(all_result, verbose):
|
|
|
20
21
|
line=str(func.start_line),
|
|
21
22
|
column="0",
|
|
22
23
|
severity="info",
|
|
23
|
-
message=f"{func.name} has {func.nloc} NLOC, {func.cyclomatic_complexity} CCN,
|
|
24
|
+
message=f"{func.name} has {func.nloc} NLOC, {func.cyclomatic_complexity} CCN, "
|
|
25
|
+
f"{func.token_count} token, {len(func.parameters)} PARAM, {func.length} length",
|
|
24
26
|
source="lizard"
|
|
25
27
|
)
|
|
26
28
|
|
|
@@ -28,4 +30,4 @@ def checkstyle_output(all_result, verbose):
|
|
|
28
30
|
import xml.dom.minidom
|
|
29
31
|
rough_string = ET.tostring(checkstyle, 'utf-8')
|
|
30
32
|
reparsed = xml.dom.minidom.parseString(rough_string)
|
|
31
|
-
return reparsed.toprettyxml(indent=" ")
|
|
33
|
+
return reparsed.toprettyxml(indent=" ")
|
lizard_ext/version.py
CHANGED
lizard_languages/__init__.py
CHANGED
|
@@ -21,10 +21,11 @@ from .rust import RustReader
|
|
|
21
21
|
from .typescript import TypeScriptReader
|
|
22
22
|
from .fortran import FortranReader
|
|
23
23
|
from .solidity import SolidityReader
|
|
24
|
-
from .jsx import JSXReader
|
|
25
24
|
from .tsx import TSXReader
|
|
26
25
|
from .vue import VueReader
|
|
27
26
|
from .perl import PerlReader
|
|
27
|
+
from .st import StReader
|
|
28
|
+
from .r import RReader
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def languages():
|
|
@@ -50,10 +51,11 @@ def languages():
|
|
|
50
51
|
SolidityReader,
|
|
51
52
|
ErlangReader,
|
|
52
53
|
ZigReader,
|
|
53
|
-
JSXReader,
|
|
54
54
|
TSXReader,
|
|
55
55
|
VueReader,
|
|
56
56
|
PerlReader,
|
|
57
|
+
StReader,
|
|
58
|
+
RReader,
|
|
57
59
|
]
|
|
58
60
|
|
|
59
61
|
|
lizard_languages/clike.py
CHANGED
|
@@ -19,7 +19,7 @@ class CCppCommentsMixin(object): # pylint: disable=R0903
|
|
|
19
19
|
class CLikeReader(CodeReader, CCppCommentsMixin):
|
|
20
20
|
''' This is the reader for C, C++ and Java. '''
|
|
21
21
|
|
|
22
|
-
ext = ["c", "cpp", "cc", "
|
|
22
|
+
ext = ["c", "cpp", "cc", "cxx", "h", "hpp"]
|
|
23
23
|
language_names = ['cpp', 'c']
|
|
24
24
|
macro_pattern = re.compile(r"#\s*(\w+)\s*(.*)", re.M | re.S)
|
|
25
25
|
|
|
@@ -162,6 +162,11 @@ class CLikeStates(CodeStateMachine):
|
|
|
162
162
|
def _state_global(self, token):
|
|
163
163
|
if token[0].isalpha() or token[0] in '_~':
|
|
164
164
|
self.try_new_function(token)
|
|
165
|
+
elif token == '[':
|
|
166
|
+
# Check if this might be a lambda expression (C++ only)
|
|
167
|
+
# Java doesn't have lambda expressions, so skip lambda detection for Java
|
|
168
|
+
if not hasattr(self, 'class_name'): # JavaStates has class_name attribute
|
|
169
|
+
self._state = self._state_lambda_check
|
|
165
170
|
|
|
166
171
|
def _state_function(self, token):
|
|
167
172
|
if token == '(':
|
|
@@ -310,3 +315,57 @@ class CLikeStates(CodeStateMachine):
|
|
|
310
315
|
def _state_attribute(self, _):
|
|
311
316
|
"Ignores function attributes with C++11 syntax, i.e., [[ attribute ]]."
|
|
312
317
|
pass
|
|
318
|
+
|
|
319
|
+
def _state_lambda_check(self, token):
|
|
320
|
+
"""Check if this is a lambda expression or a function attribute."""
|
|
321
|
+
if token == ']':
|
|
322
|
+
# This is a lambda expression [](params) or [capture](params)
|
|
323
|
+
# Skip the lambda and continue parsing normally
|
|
324
|
+
self._state = self._state_lambda_params
|
|
325
|
+
elif token == '[':
|
|
326
|
+
# This is a function attribute [[attribute]]
|
|
327
|
+
self._state = self._state_attribute
|
|
328
|
+
else:
|
|
329
|
+
# This is a lambda with capture list [capture](params)
|
|
330
|
+
# Skip until we find the closing bracket
|
|
331
|
+
self._state = self._state_lambda_capture
|
|
332
|
+
|
|
333
|
+
def _state_lambda_params(self, token):
|
|
334
|
+
"""Handle lambda parameters and body."""
|
|
335
|
+
if token == '(':
|
|
336
|
+
# Start of parameter list, skip until closing parenthesis
|
|
337
|
+
self._state = self._state_lambda_param_list
|
|
338
|
+
else:
|
|
339
|
+
# No parameters, check for body
|
|
340
|
+
self._state = self._state_lambda_body
|
|
341
|
+
|
|
342
|
+
def _state_lambda_param_list(self, token):
|
|
343
|
+
"""Handle lambda parameter list."""
|
|
344
|
+
if token == ')':
|
|
345
|
+
# End of parameter list, check for body
|
|
346
|
+
self._state = self._state_lambda_body
|
|
347
|
+
# Otherwise, continue in parameter list
|
|
348
|
+
|
|
349
|
+
def _state_lambda_body(self, token):
|
|
350
|
+
"""Handle lambda body."""
|
|
351
|
+
if token == '{':
|
|
352
|
+
# Start of lambda body, skip until closing brace
|
|
353
|
+
self._state = self._state_lambda_body_skip
|
|
354
|
+
elif token == ';':
|
|
355
|
+
# Lambda without body, just a semicolon
|
|
356
|
+
self._state = self._state_global
|
|
357
|
+
# Otherwise, continue
|
|
358
|
+
|
|
359
|
+
def _state_lambda_body_skip(self, token):
|
|
360
|
+
"""Skip lambda body until closing brace."""
|
|
361
|
+
if token == '}':
|
|
362
|
+
# End of lambda body, continue parsing normally
|
|
363
|
+
self._state = self._state_global
|
|
364
|
+
# Otherwise, continue skipping
|
|
365
|
+
|
|
366
|
+
def _state_lambda_capture(self, token):
|
|
367
|
+
"""Handle lambda capture list."""
|
|
368
|
+
if token == ']':
|
|
369
|
+
# End of capture list, continue parsing normally
|
|
370
|
+
self._state = self._state_global
|
|
371
|
+
# Otherwise, continue in capture list
|
lizard_languages/python.py
CHANGED
|
@@ -37,6 +37,7 @@ class PythonReader(CodeReader, ScriptLanguageMixIn):
|
|
|
37
37
|
def __init__(self, context):
|
|
38
38
|
super(PythonReader, self).__init__(context)
|
|
39
39
|
self.parallel_states = [PythonStates(context, self)]
|
|
40
|
+
self._last_meaningful_token = None # Track the last meaningful token
|
|
40
41
|
|
|
41
42
|
@staticmethod
|
|
42
43
|
def generate_tokens(source_code, addition='', token_class=None):
|
|
@@ -46,6 +47,41 @@ class PythonReader(CodeReader, ScriptLanguageMixIn):
|
|
|
46
47
|
r"|(?:\'\'\'(?:\\.|[^\']|\'(?!\'\')|\'\'(?!\'))*\'\'\')",
|
|
47
48
|
token_class)
|
|
48
49
|
|
|
50
|
+
def process_token(self, token):
|
|
51
|
+
"""Process triple-quoted strings used as comments.
|
|
52
|
+
|
|
53
|
+
Triple-quoted strings that are not docstrings (i.e., not immediately
|
|
54
|
+
after function definitions) should be treated like comments and not
|
|
55
|
+
counted in NLOC, but only if they appear to be standalone statements
|
|
56
|
+
rather than part of assignments or other expressions.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
bool: True if the token was handled specially, False otherwise
|
|
60
|
+
"""
|
|
61
|
+
if (token.startswith('"""') or token.startswith("'''")) and len(token) >= 6:
|
|
62
|
+
# Check if this is likely a standalone comment (not a docstring)
|
|
63
|
+
# Docstrings are handled separately in _state_first_line
|
|
64
|
+
current_state = self.parallel_states[0]._state
|
|
65
|
+
|
|
66
|
+
# If we're not in the first line state, check if this is a standalone string
|
|
67
|
+
if current_state != current_state.__self__._state_first_line:
|
|
68
|
+
# Check if the immediate previous meaningful token suggests this is part of an expression
|
|
69
|
+
assignment_tokens = ['=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', '&=', '|=', '^=',
|
|
70
|
+
'<<=', '>>=', '(', 'return', ',', '[', '+', '-', '*', '/', '%']
|
|
71
|
+
|
|
72
|
+
is_part_of_expression = self._last_meaningful_token in assignment_tokens
|
|
73
|
+
|
|
74
|
+
# Only treat as comment if it's NOT part of an expression
|
|
75
|
+
if not is_part_of_expression:
|
|
76
|
+
# Subtract the NLOC contribution of this triple-quoted string
|
|
77
|
+
self.context.add_nloc(-(token.count('\n') + 1))
|
|
78
|
+
|
|
79
|
+
# Update last meaningful token (ignore whitespace and newlines)
|
|
80
|
+
if token not in ['\n', ' ', '\t'] and not token.isspace():
|
|
81
|
+
self._last_meaningful_token = token
|
|
82
|
+
|
|
83
|
+
return False # Continue with normal processing
|
|
84
|
+
|
|
49
85
|
def preprocess(self, tokens):
|
|
50
86
|
indents = PythonIndents(self.context)
|
|
51
87
|
current_leading_spaces = 0
|
lizard_languages/r.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Language parser for R
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from .code_reader import CodeReader, CodeStateMachine
|
|
6
|
+
from .script_language import ScriptLanguageMixIn
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RReader(CodeReader, ScriptLanguageMixIn):
|
|
10
|
+
"""R language reader for parsing R code and calculating complexity metrics."""
|
|
11
|
+
|
|
12
|
+
ext = ['r', 'R']
|
|
13
|
+
language_names = ['r', 'R']
|
|
14
|
+
|
|
15
|
+
# R-specific conditions that increase cyclomatic complexity
|
|
16
|
+
_conditions = {
|
|
17
|
+
'if', 'else if', 'for', 'while', 'repeat', 'switch',
|
|
18
|
+
'&&', '||', '&', '|', 'ifelse',
|
|
19
|
+
'tryCatch', 'try'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def __init__(self, context):
|
|
23
|
+
super(RReader, self).__init__(context)
|
|
24
|
+
self.parallel_states = [RStates(context)]
|
|
25
|
+
|
|
26
|
+
def preprocess(self, tokens):
|
|
27
|
+
"""Preprocess tokens - for now just pass them through."""
|
|
28
|
+
for token in tokens:
|
|
29
|
+
yield token
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def generate_tokens(source_code, addition='', token_class=None):
|
|
33
|
+
"""Generate tokens for R code with R-specific patterns."""
|
|
34
|
+
# R-specific token patterns
|
|
35
|
+
r_patterns = (
|
|
36
|
+
r"|<-" # Assignment operator <-
|
|
37
|
+
r"|->" # Assignment operator ->
|
|
38
|
+
r"|%[a-zA-Z_*/>]+%" # Special operators like %in%, %*%, %>%, %/%, etc.
|
|
39
|
+
r"|\.\.\." # Ellipsis for variable arguments
|
|
40
|
+
r"|:::" # Internal namespace operator (must come before ::)
|
|
41
|
+
r"|::" # Namespace operator
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return ScriptLanguageMixIn.generate_common_tokens(
|
|
45
|
+
source_code,
|
|
46
|
+
r_patterns + addition,
|
|
47
|
+
token_class
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RStates(CodeStateMachine):
|
|
52
|
+
"""State machine for parsing R function definitions and complexity."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, context):
|
|
55
|
+
super(RStates, self).__init__(context)
|
|
56
|
+
self.recent_tokens = [] # Track recent tokens to find function names
|
|
57
|
+
self.brace_count = 0 # Track brace nesting for function bodies
|
|
58
|
+
self.in_braced_function = False # Track if current function uses braces
|
|
59
|
+
self.additional_function_names = [] # Store additional names for multiple assignment
|
|
60
|
+
|
|
61
|
+
def _state_global(self, token):
|
|
62
|
+
"""Global state - looking for function definitions."""
|
|
63
|
+
# Track recent non-whitespace tokens
|
|
64
|
+
if not token.isspace() and token != '\n':
|
|
65
|
+
self.recent_tokens.append(token)
|
|
66
|
+
if len(self.recent_tokens) > 10: # Keep only last 10 tokens
|
|
67
|
+
self.recent_tokens.pop(0)
|
|
68
|
+
|
|
69
|
+
# Look for function keyword after assignment operators
|
|
70
|
+
if token == 'function':
|
|
71
|
+
# Check if we have recent tokens: [name, assignment_op, 'function']
|
|
72
|
+
if len(self.recent_tokens) >= 2:
|
|
73
|
+
# recent_tokens now contains [..., assignment_op, 'function']
|
|
74
|
+
assignment_op = self.recent_tokens[-2] # The token before 'function'
|
|
75
|
+
if assignment_op in ['<-', '=']:
|
|
76
|
+
# Handle multiple assignments by creating separate functions
|
|
77
|
+
func_names = self._extract_function_names()
|
|
78
|
+
|
|
79
|
+
# Create the first function (this will be the main one with the function body)
|
|
80
|
+
self._start_function(func_names[0])
|
|
81
|
+
self._state = self._function_params
|
|
82
|
+
|
|
83
|
+
# Store additional names for later processing
|
|
84
|
+
self.additional_function_names = func_names[1:] if len(func_names) > 1 else []
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# If we get here, it's an anonymous function or not a proper assignment
|
|
88
|
+
self._start_function("(anonymous)")
|
|
89
|
+
self._state = self._function_params
|
|
90
|
+
|
|
91
|
+
def _extract_function_names(self):
|
|
92
|
+
"""Extract all function names from recent tokens, handling multiple assignments."""
|
|
93
|
+
if len(self.recent_tokens) < 3:
|
|
94
|
+
return ["(anonymous)"]
|
|
95
|
+
|
|
96
|
+
# Look backwards from the assignment operator to find all function names
|
|
97
|
+
# For multiple assignment like: a <- b <- c <- function(...)
|
|
98
|
+
# recent_tokens ends with [..., 'a', '<-', 'b', '<-', 'c', '<-', 'function']
|
|
99
|
+
assignment_index = len(self.recent_tokens) - 2 # Position of assignment operator
|
|
100
|
+
|
|
101
|
+
function_names = []
|
|
102
|
+
i = assignment_index - 1 # Start from token before assignment operator
|
|
103
|
+
current_name_tokens = []
|
|
104
|
+
|
|
105
|
+
while i >= 0:
|
|
106
|
+
token = self.recent_tokens[i]
|
|
107
|
+
|
|
108
|
+
# If we hit an assignment operator, we've found a complete variable name
|
|
109
|
+
if token in ['<-', '=']:
|
|
110
|
+
if current_name_tokens:
|
|
111
|
+
function_names.append(''.join(reversed(current_name_tokens)))
|
|
112
|
+
current_name_tokens = []
|
|
113
|
+
i -= 1
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# Stop if we hit keywords or operators that shouldn't be part of function names
|
|
117
|
+
if token in ['function', '(', ')', '{', '}', '\n']:
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
# Valid R identifier characters and dots
|
|
121
|
+
if token.replace('_', 'a').replace('.', 'a').isalnum() or token == '.':
|
|
122
|
+
current_name_tokens.append(token)
|
|
123
|
+
i -= 1
|
|
124
|
+
else:
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
# Add the last name if we have one
|
|
128
|
+
if current_name_tokens:
|
|
129
|
+
function_names.append(''.join(reversed(current_name_tokens)))
|
|
130
|
+
|
|
131
|
+
# Return names in the correct order (left to right as they appear in code)
|
|
132
|
+
return list(reversed(function_names)) if function_names else ["(anonymous)"]
|
|
133
|
+
|
|
134
|
+
def _extract_function_name(self):
|
|
135
|
+
"""Extract the first function name (for backward compatibility)."""
|
|
136
|
+
names = self._extract_function_names()
|
|
137
|
+
return names[0] if names else "(anonymous)"
|
|
138
|
+
|
|
139
|
+
def _start_function(self, name):
|
|
140
|
+
"""Start tracking a new function."""
|
|
141
|
+
self.context.restart_new_function(name)
|
|
142
|
+
|
|
143
|
+
def _function_params(self, token):
|
|
144
|
+
"""Expecting function parameters."""
|
|
145
|
+
if token == '(':
|
|
146
|
+
self.context.add_to_long_function_name("(")
|
|
147
|
+
self._state = self._read_params
|
|
148
|
+
else:
|
|
149
|
+
# Single expression function without parentheses - rare in R
|
|
150
|
+
self._state = self._function_body
|
|
151
|
+
self._function_body(token)
|
|
152
|
+
|
|
153
|
+
def _read_params(self, token):
|
|
154
|
+
"""Read function parameters until closing parenthesis."""
|
|
155
|
+
if token == ')':
|
|
156
|
+
self.context.add_to_long_function_name(")")
|
|
157
|
+
self._state = self._function_body
|
|
158
|
+
elif token not in ['\n'] and not token.isspace():
|
|
159
|
+
self.context.parameter(token)
|
|
160
|
+
if token != '(':
|
|
161
|
+
self.context.add_to_long_function_name(" " + token)
|
|
162
|
+
|
|
163
|
+
def _function_body(self, token):
|
|
164
|
+
"""In function body - track complexity and nested functions."""
|
|
165
|
+
# Note: Complexity conditions are automatically counted by the framework
|
|
166
|
+
# based on reader.conditions, so we don't need to manually count them here
|
|
167
|
+
|
|
168
|
+
# Continue tracking tokens even in function body for nested function detection
|
|
169
|
+
if not token.isspace() and token != '\n':
|
|
170
|
+
self.recent_tokens.append(token)
|
|
171
|
+
if len(self.recent_tokens) > 10: # Keep only last 10 tokens
|
|
172
|
+
self.recent_tokens.pop(0)
|
|
173
|
+
|
|
174
|
+
# Track braces
|
|
175
|
+
if token == '{':
|
|
176
|
+
if self.brace_count == 0:
|
|
177
|
+
self.in_braced_function = True
|
|
178
|
+
self.brace_count += 1
|
|
179
|
+
elif token == '}':
|
|
180
|
+
self.brace_count -= 1
|
|
181
|
+
if self.brace_count == 0 and self.in_braced_function:
|
|
182
|
+
# End of braced function
|
|
183
|
+
self._end_current_function()
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Handle nested functions - treat them as separate functions
|
|
187
|
+
if token == 'function':
|
|
188
|
+
# Check if this is a nested function assignment
|
|
189
|
+
if len(self.recent_tokens) >= 2:
|
|
190
|
+
assignment_op = self.recent_tokens[-2] # The token before 'function'
|
|
191
|
+
if assignment_op in ['<-', '=']:
|
|
192
|
+
# End current function first
|
|
193
|
+
self.context.end_of_function()
|
|
194
|
+
|
|
195
|
+
# Handle multiple assignments for nested functions too
|
|
196
|
+
func_names = self._extract_function_names()
|
|
197
|
+
|
|
198
|
+
# Start a new function for the nested function
|
|
199
|
+
self._start_function(func_names[0])
|
|
200
|
+
self._state = self._function_params
|
|
201
|
+
# Reset brace counting for the new function
|
|
202
|
+
self.brace_count = 0
|
|
203
|
+
self.in_braced_function = False
|
|
204
|
+
|
|
205
|
+
# Store additional names for later processing
|
|
206
|
+
self.additional_function_names = func_names[1:] if len(func_names) > 1 else []
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# For single-line functions without braces, end at newline
|
|
210
|
+
elif token == '\n' and not self.in_braced_function:
|
|
211
|
+
self._end_current_function()
|
|
212
|
+
|
|
213
|
+
def _end_current_function(self):
|
|
214
|
+
"""End the current function and reset state."""
|
|
215
|
+
# Check if this might be a right assignment case
|
|
216
|
+
# We need to temporarily not end the function to see if there's a right assignment
|
|
217
|
+
self._state = self._check_right_assignment
|
|
218
|
+
self.brace_count = 0
|
|
219
|
+
self.in_braced_function = False
|
|
220
|
+
|
|
221
|
+
def _check_right_assignment(self, token):
|
|
222
|
+
"""Check if there's a right assignment after function end."""
|
|
223
|
+
# Skip whitespace and comments
|
|
224
|
+
if token.isspace() or token == '\n' or token.startswith('#'):
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
# Look for right assignment operator
|
|
228
|
+
if token == '->':
|
|
229
|
+
self._state = self._read_right_assignment_name
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
# If we encounter anything else, this is not a right assignment
|
|
233
|
+
# End the function and create additional functions for multiple assignments
|
|
234
|
+
self._finalize_function_with_multiple_assignments()
|
|
235
|
+
self._state = self._state_global
|
|
236
|
+
self._state_global(token)
|
|
237
|
+
|
|
238
|
+
def _finalize_function_with_multiple_assignments(self):
|
|
239
|
+
"""End the current function and create additional functions for multiple assignments."""
|
|
240
|
+
# Get the current function's information before ending it
|
|
241
|
+
current_func = self.context.current_function
|
|
242
|
+
|
|
243
|
+
# End the current function
|
|
244
|
+
self.context.end_of_function()
|
|
245
|
+
|
|
246
|
+
# Create additional function entries for multiple assignments
|
|
247
|
+
if self.additional_function_names and current_func:
|
|
248
|
+
for func_name in self.additional_function_names:
|
|
249
|
+
# Create a new function with the same complexity and line info
|
|
250
|
+
self.context.restart_new_function(func_name)
|
|
251
|
+
# Copy the complexity from the original function
|
|
252
|
+
if hasattr(current_func, 'cyclomatic_complexity'):
|
|
253
|
+
self.context.current_function.cyclomatic_complexity = current_func.cyclomatic_complexity
|
|
254
|
+
# Set the same line range
|
|
255
|
+
self.context.current_function.start_line = current_func.start_line
|
|
256
|
+
self.context.current_function.end_line = current_func.end_line
|
|
257
|
+
# End this function immediately
|
|
258
|
+
self.context.end_of_function()
|
|
259
|
+
|
|
260
|
+
# Clear the additional names
|
|
261
|
+
self.additional_function_names = []
|
|
262
|
+
|
|
263
|
+
def _read_right_assignment_name(self, token):
|
|
264
|
+
"""Read the function name after right assignment operator."""
|
|
265
|
+
# Skip whitespace
|
|
266
|
+
if token.isspace() or token == '\n':
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
# This should be the function name
|
|
270
|
+
if token.replace('_', 'a').replace('.', 'a').isalnum() or token == '.':
|
|
271
|
+
# Update the current function's name
|
|
272
|
+
if self.context.current_function:
|
|
273
|
+
self.context.current_function.name = token
|
|
274
|
+
|
|
275
|
+
# End the function and create additional functions for multiple assignments
|
|
276
|
+
self._finalize_function_with_multiple_assignments()
|
|
277
|
+
self._state = self._state_global
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
# If we get something unexpected, treat as anonymous function
|
|
281
|
+
self._finalize_function_with_multiple_assignments()
|
|
282
|
+
self._state = self._state_global
|
|
283
|
+
self._state_global(token)
|
|
284
|
+
|
|
285
|
+
def statemachine_before_return(self):
|
|
286
|
+
"""Called when processing is complete - end any open functions."""
|
|
287
|
+
if self._state in [self._function_body, self._check_right_assignment, self._read_right_assignment_name]:
|
|
288
|
+
# End any open function and process multiple assignments
|
|
289
|
+
if hasattr(self.context, 'current_function') and self.context.current_function:
|
|
290
|
+
self._finalize_function_with_multiple_assignments()
|
lizard_languages/rust.py
CHANGED
|
@@ -19,6 +19,11 @@ class RustReader(CodeReader, CCppCommentsMixin):
|
|
|
19
19
|
super().__init__(context)
|
|
20
20
|
self.parallel_states = [RustStates(context)]
|
|
21
21
|
|
|
22
|
+
@staticmethod
|
|
23
|
+
def generate_tokens(source_code, addition='', token_class=None):
|
|
24
|
+
addition = r"|(?:'\w+\b)" # lifetimes, labels
|
|
25
|
+
return CodeReader.generate_tokens(source_code, addition, token_class)
|
|
26
|
+
|
|
22
27
|
|
|
23
28
|
class RustStates(GoLikeStates): # pylint: disable=R0903
|
|
24
29
|
FUNC_KEYWORD = 'fn'
|