lizard 1.17.30__tar.gz → 1.17.31__tar.gz

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 (100) hide show
  1. {lizard-1.17.30 → lizard-1.17.31}/PKG-INFO +2 -1
  2. {lizard-1.17.30 → lizard-1.17.31}/README.rst +1 -0
  3. {lizard-1.17.30 → lizard-1.17.31}/lizard.egg-info/PKG-INFO +2 -1
  4. {lizard-1.17.30 → lizard-1.17.31}/lizard.egg-info/SOURCES.txt +1 -0
  5. {lizard-1.17.30 → lizard-1.17.31}/lizard.py +36 -15
  6. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/__init__.py +14 -0
  7. lizard-1.17.31/lizard_ext/checkstyleoutput.py +31 -0
  8. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/version.py +1 -1
  9. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/code_reader.py +19 -0
  10. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/go.py +1 -1
  11. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/java.py +6 -3
  12. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/javascript.py +1 -1
  13. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/js_style_language_states.py +1 -0
  14. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/jsx.py +23 -23
  15. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/perl.py +20 -21
  16. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/php.py +14 -14
  17. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/python.py +1 -1
  18. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/rust.py +2 -2
  19. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/script_language.py +2 -2
  20. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/typescript.py +1 -1
  21. {lizard-1.17.30 → lizard-1.17.31}/test/testOutput.py +19 -0
  22. {lizard-1.17.30 → lizard-1.17.31}/test/testOutputFile.py +31 -0
  23. {lizard-1.17.30 → lizard-1.17.31}/LICENSE.txt +0 -0
  24. {lizard-1.17.30 → lizard-1.17.31}/lizard.egg-info/dependency_links.txt +0 -0
  25. {lizard-1.17.30 → lizard-1.17.31}/lizard.egg-info/entry_points.txt +0 -0
  26. {lizard-1.17.30 → lizard-1.17.31}/lizard.egg-info/requires.txt +0 -0
  27. {lizard-1.17.30 → lizard-1.17.31}/lizard.egg-info/top_level.txt +0 -0
  28. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/auto_open.py +0 -0
  29. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/csvoutput.py +0 -0
  30. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/default_ordered_dict.py +0 -0
  31. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/extension_base.py +0 -0
  32. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/htmloutput.py +0 -0
  33. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/keywords.py +0 -0
  34. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardboolcount.py +0 -0
  35. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardcomplextags.py +0 -0
  36. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardcpre.py +0 -0
  37. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizarddependencycount.py +0 -0
  38. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizarddumpcomments.py +0 -0
  39. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardduplicate.py +0 -0
  40. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardduplicated_param_list.py +0 -0
  41. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardexitcount.py +0 -0
  42. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardgotocount.py +0 -0
  43. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardignoreassert.py +0 -0
  44. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardio.py +0 -0
  45. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardmccabe.py +0 -0
  46. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardmodified.py +0 -0
  47. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardnd.py +0 -0
  48. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardnonstrict.py +0 -0
  49. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardns.py +0 -0
  50. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardoutside.py +0 -0
  51. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardstatementcount.py +0 -0
  52. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/lizardwordcount.py +0 -0
  53. {lizard-1.17.30 → lizard-1.17.31}/lizard_ext/xmloutput.py +0 -0
  54. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/__init__.py +0 -0
  55. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/clike.py +0 -0
  56. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/csharp.py +0 -0
  57. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/erlang.py +0 -0
  58. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/fortran.py +0 -0
  59. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/gdscript.py +0 -0
  60. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/golike.py +0 -0
  61. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/js_style_regex_expression.py +0 -0
  62. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/kotlin.py +0 -0
  63. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/lua.py +0 -0
  64. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/objc.py +0 -0
  65. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/ruby.py +0 -0
  66. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/rubylike.py +0 -0
  67. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/scala.py +0 -0
  68. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/solidity.py +0 -0
  69. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/swift.py +0 -0
  70. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/tnsdl.py +0 -0
  71. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/tsx.py +0 -0
  72. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/ttcn.py +0 -0
  73. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/vue.py +0 -0
  74. {lizard-1.17.30 → lizard-1.17.31}/lizard_languages/zig.py +0 -0
  75. {lizard-1.17.30 → lizard-1.17.31}/setup.cfg +0 -0
  76. {lizard-1.17.30 → lizard-1.17.31}/setup.py +0 -0
  77. {lizard-1.17.30 → lizard-1.17.31}/test/testApplication.py +0 -0
  78. {lizard-1.17.30 → lizard-1.17.31}/test/testAssertionExtension.py +0 -0
  79. {lizard-1.17.30 → lizard-1.17.31}/test/testBasicFunctionInfo.py +0 -0
  80. {lizard-1.17.30 → lizard-1.17.31}/test/testCOutsideComplexity.py +0 -0
  81. {lizard-1.17.30 → lizard-1.17.31}/test/testCPreprocessorExtension.py +0 -0
  82. {lizard-1.17.30 → lizard-1.17.31}/test/testCommentOptions.py +0 -0
  83. {lizard-1.17.30 → lizard-1.17.31}/test/testCyclomaticComplexity.py +0 -0
  84. {lizard-1.17.30 → lizard-1.17.31}/test/testExtension.py +0 -0
  85. {lizard-1.17.30 → lizard-1.17.31}/test/testFilesFilter.py +0 -0
  86. {lizard-1.17.30 → lizard-1.17.31}/test/testFunctionDependencyCount.py +0 -0
  87. {lizard-1.17.30 → lizard-1.17.31}/test/testFunctionExitCount.py +0 -0
  88. {lizard-1.17.30 → lizard-1.17.31}/test/testFunctionGotoCount.py +0 -0
  89. {lizard-1.17.30 → lizard-1.17.31}/test/testFunctionStatementCount.py +0 -0
  90. {lizard-1.17.30 → lizard-1.17.31}/test/testHelpers.py +0 -0
  91. {lizard-1.17.30 → lizard-1.17.31}/test/testLanguages.py +0 -0
  92. {lizard-1.17.30 → lizard-1.17.31}/test/testMcCabe.py +0 -0
  93. {lizard-1.17.30 → lizard-1.17.31}/test/testNestedStructures.py +0 -0
  94. {lizard-1.17.30 → lizard-1.17.31}/test/testNestingDepth.py +0 -0
  95. {lizard-1.17.30 → lizard-1.17.31}/test/testOutputCSV.py +0 -0
  96. {lizard-1.17.30 → lizard-1.17.31}/test/testOutputHTML.py +0 -0
  97. {lizard-1.17.30 → lizard-1.17.31}/test/testTokenizer.py +0 -0
  98. {lizard-1.17.30 → lizard-1.17.31}/test/test_analyzer.py +0 -0
  99. {lizard-1.17.30 → lizard-1.17.31}/test/test_auto_open.py +0 -0
  100. {lizard-1.17.30 → lizard-1.17.31}/test/test_options.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lizard
3
- Version: 1.17.30
3
+ Version: 1.17.31
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/
@@ -194,6 +194,7 @@ Options
194
194
  generate report in Jenkins server
195
195
  --csv Generate CSV output as a transform of the default output
196
196
  -H, --html Output HTML report
197
+ --checkstyle Generate Checkstyle XML output for integration with Jenkins and other tools
197
198
  -m, --modified Calculate modified cyclomatic complexity number , which count a
198
199
  switch/case with multiple cases as one CCN.
199
200
  -E EXTENSIONS, --extension EXTENSIONS
@@ -163,6 +163,7 @@ Options
163
163
  generate report in Jenkins server
164
164
  --csv Generate CSV output as a transform of the default output
165
165
  -H, --html Output HTML report
166
+ --checkstyle Generate Checkstyle XML output for integration with Jenkins and other tools
166
167
  -m, --modified Calculate modified cyclomatic complexity number , which count a
167
168
  switch/case with multiple cases as one CCN.
168
169
  -E EXTENSIONS, --extension EXTENSIONS
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lizard
3
- Version: 1.17.30
3
+ Version: 1.17.31
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/
@@ -194,6 +194,7 @@ Options
194
194
  generate report in Jenkins server
195
195
  --csv Generate CSV output as a transform of the default output
196
196
  -H, --html Output HTML report
197
+ --checkstyle Generate Checkstyle XML output for integration with Jenkins and other tools
197
198
  -m, --modified Calculate modified cyclomatic complexity number , which count a
198
199
  switch/case with multiple cases as one CCN.
199
200
  -E EXTENSIONS, --extension EXTENSIONS
@@ -11,6 +11,7 @@ lizard.egg-info/requires.txt
11
11
  lizard.egg-info/top_level.txt
12
12
  lizard_ext/__init__.py
13
13
  lizard_ext/auto_open.py
14
+ lizard_ext/checkstyleoutput.py
14
15
  lizard_ext/csvoutput.py
15
16
  lizard_ext/default_ordered_dict.py
16
17
  lizard_ext/extension_base.py
@@ -43,6 +43,7 @@ try:
43
43
  from lizard_ext import print_csv
44
44
  from lizard_ext import html_output
45
45
  from lizard_ext import auto_open, auto_read
46
+ from lizard_ext import print_checkstyle
46
47
  except ImportError:
47
48
  sys.stderr.write("Cannot find the lizard_ext modules.")
48
49
 
@@ -217,6 +218,11 @@ def arg_parser(prog=None):
217
218
  const="modified",
218
219
  dest="extensions",
219
220
  default=[])
221
+ parser.add_argument("--checkstyle",
222
+ help='''Generate Checkstyle XML output for integration with Jenkins and other tools.''',
223
+ action="store_const",
224
+ const=print_checkstyle,
225
+ dest="printer")
220
226
  _extension_arg(parser)
221
227
  parser.add_argument("-s", "--sort",
222
228
  help='''Sort the warning with field. The field can be
@@ -902,7 +908,7 @@ def get_all_source_files(paths, exclude_patterns, lans):
902
908
  '''
903
909
  Function counts md5 hash for the given file and checks if it isn't a
904
910
  duplicate using set of hashes for previous files.
905
-
911
+
906
912
  If a .gitignore file is found in any of the given paths, it will be used
907
913
  to filter out files that match the gitignore patterns.
908
914
  '''
@@ -1000,7 +1006,10 @@ def parse_args(argv):
1000
1006
  if opt.output_file:
1001
1007
  inferred_printer = infer_printer_from_file_ext(opt.output_file)
1002
1008
  if inferred_printer:
1003
- if not opt.printer:
1009
+ # Always use print_checkstyle for .checkstyle.xml
1010
+ if opt.output_file.lower().endswith('.checkstyle.xml'):
1011
+ opt.printer = inferred_printer
1012
+ elif not opt.printer:
1004
1013
  opt.printer = inferred_printer
1005
1014
  elif opt.printer != inferred_printer:
1006
1015
  msg = "Warning: overriding output file extension.\n"
@@ -1009,15 +1018,16 @@ def parse_args(argv):
1009
1018
 
1010
1019
 
1011
1020
  def infer_printer_from_file_ext(path):
1012
- mapping = {
1013
- '.csv': print_csv,
1014
- '.htm': html_output,
1015
- '.html': html_output,
1016
- '.xml': print_xml
1017
- }
1018
- _, ext = os.path.splitext(path)
1019
- printer = mapping.get(ext)
1020
- return printer
1021
+ lower_path = path.lower()
1022
+ if lower_path.endswith(".checkstyle.xml"):
1023
+ return print_checkstyle
1024
+ if lower_path.endswith(".html"):
1025
+ return html_output
1026
+ if lower_path.endswith(".xml"):
1027
+ return print_xml
1028
+ if lower_path.endswith(".csv"):
1029
+ return print_csv
1030
+ return None
1021
1031
 
1022
1032
 
1023
1033
  def open_output_file(path):
@@ -1072,16 +1082,27 @@ def main(argv=None):
1072
1082
  options.paths = auto_read(options.input_file).splitlines()
1073
1083
  original_stdout = sys.stdout
1074
1084
  output_file = None
1075
- if options.output_file:
1076
- output_file = open_output_file(options.output_file)
1077
- sys.stdout = output_file
1078
1085
  result = analyze(
1079
1086
  options.paths,
1080
1087
  options.exclude,
1081
1088
  options.working_threads,
1082
1089
  options.extensions,
1083
1090
  options.languages)
1084
- warning_count = printer(result, options, schema, AllResult)
1091
+ warning_count = None
1092
+ if options.output_file:
1093
+ output_file = open_output_file(options.output_file)
1094
+ sys.stdout = output_file
1095
+ # Special handling for checkstyle output
1096
+ if getattr(printer, "__name__", "") == "print_checkstyle":
1097
+ warning_count = printer(result, options, schema, AllResult, file=output_file)
1098
+ else:
1099
+ warning_count = printer(result, options, schema, AllResult)
1100
+ else:
1101
+ # Special handling for checkstyle output
1102
+ if getattr(printer, "__name__", "") == "print_checkstyle":
1103
+ warning_count = printer(result, options, schema, AllResult, file=original_stdout)
1104
+ else:
1105
+ warning_count = printer(result, options, schema, AllResult)
1085
1106
  print_extension_results(options.extensions)
1086
1107
  list(result)
1087
1108
  if output_file:
@@ -6,6 +6,7 @@ from .htmloutput import html_output
6
6
  from .csvoutput import csv_output
7
7
  from .xmloutput import xml_output
8
8
  from .auto_open import auto_open, auto_read
9
+ from .checkstyleoutput import checkstyle_output
9
10
 
10
11
 
11
12
  def print_xml(results, options, _, total_factory):
@@ -16,3 +17,16 @@ def print_xml(results, options, _, total_factory):
16
17
  def print_csv(results, options, _, total_factory):
17
18
  csv_output(total_factory(list(results)), options)
18
19
  return 0
20
+
21
+
22
+ def print_checkstyle(results, options, _, total_factory, file=None):
23
+ import sys
24
+ print("DEBUG: print_checkstyle called", file=sys.stderr)
25
+ output = checkstyle_output(total_factory(list(results)), options.verbose)
26
+ if file is None:
27
+ file = sys.stdout
28
+ file.write(output)
29
+ if not output.endswith("\n"):
30
+ file.write("\n")
31
+ file.flush()
32
+ return 0
@@ -0,0 +1,31 @@
1
+ '''
2
+ Checkstyle XML output for Lizard
3
+ '''
4
+
5
+ def checkstyle_output(all_result, verbose):
6
+ result = all_result.result
7
+ import xml.etree.ElementTree as ET
8
+
9
+ checkstyle = ET.Element('checkstyle', version="4.3")
10
+
11
+ for source_file in result:
12
+ if source_file:
13
+ file_elem = ET.SubElement(checkstyle, 'file', name=source_file.filename)
14
+ for func in source_file.function_list:
15
+ # Each function with a warning (e.g., CCN > threshold) could be an <error>
16
+ # For now, output all functions as <error> for demonstration
17
+ ET.SubElement(
18
+ file_elem,
19
+ 'error',
20
+ line=str(func.start_line),
21
+ column="0",
22
+ severity="info",
23
+ message=f"{func.name} has {func.nloc} NLOC, {func.cyclomatic_complexity} CCN, {func.token_count} token, {len(func.parameters)} PARAM, {func.length} length",
24
+ source="lizard"
25
+ )
26
+
27
+ # Pretty print
28
+ import xml.dom.minidom
29
+ rough_string = ET.tostring(checkstyle, 'utf-8')
30
+ reparsed = xml.dom.minidom.parseString(rough_string)
31
+ return reparsed.toprettyxml(indent=" ")
@@ -3,4 +3,4 @@
3
3
  #
4
4
  # pylint: disable=missing-docstring,invalid-name
5
5
 
6
- version = "1.17.30"
6
+ version = "1.17.31"
@@ -184,6 +184,13 @@ class CodeReader:
184
184
  def __call__(self, tokens, reader):
185
185
  self.context = reader.context
186
186
  for token in tokens:
187
+ # Allow language-specific token processing
188
+ if self.process_token(token):
189
+ for state in self.parallel_states:
190
+ state(token)
191
+ yield token
192
+ continue
193
+
187
194
  for state in self.parallel_states:
188
195
  state(token)
189
196
  yield token
@@ -193,3 +200,15 @@ class CodeReader:
193
200
 
194
201
  def eof(self):
195
202
  pass
203
+
204
+ def process_token(self, token):
205
+ """Process a token before normal handling.
206
+ Return True if the token has been handled specially and should skip normal processing.
207
+
208
+ Args:
209
+ token: The token being processed
210
+
211
+ Returns:
212
+ bool: True if the token should skip normal processing, False otherwise
213
+ """
214
+ return False
@@ -31,7 +31,7 @@ class GoReader(CodeReader, CCppCommentsMixin):
31
31
  state(token)
32
32
  yield token
33
33
  continue
34
-
34
+
35
35
  # For non-backtick tokens, process normally
36
36
  for state in self.parallel_states:
37
37
  state(token)
@@ -34,6 +34,7 @@ class JavaStates(CLikeStates): # pylint: disable=R0903
34
34
  def _state_imp(self, token):
35
35
  # When entering a function implementation, set the flag
36
36
  self.in_method_body = True
37
+
37
38
  def callback():
38
39
  # When exiting the function implementation, clear the flag
39
40
  self.in_method_body = False
@@ -105,6 +106,7 @@ class JavaStates(CLikeStates): # pylint: disable=R0903
105
106
  self.in_record_constructor = False
106
107
  self._state = self._state_global
107
108
 
109
+
108
110
  class JavaFunctionBodyStates(JavaStates):
109
111
  def __init__(self, context):
110
112
  super(JavaFunctionBodyStates, self).__init__(context)
@@ -132,12 +134,12 @@ class JavaFunctionBodyStates(JavaStates):
132
134
  self.handling_dot_class = False
133
135
  if token == "class":
134
136
  return # Skip the 'class' token after a dot
135
-
137
+
136
138
  # Special handling for tokens that could confuse the parser
137
139
  if self.ignore_tokens:
138
140
  self.ignore_tokens = False
139
141
  return
140
-
142
+
141
143
  if token == "new":
142
144
  self.next(self._state_new)
143
145
  else:
@@ -145,7 +147,7 @@ class JavaFunctionBodyStates(JavaStates):
145
147
  # This ensures that local classes are properly detected
146
148
  if self._try_start_a_class(token):
147
149
  return
148
-
150
+
149
151
  if self.br_count == 0:
150
152
  self.statemachine_return()
151
153
 
@@ -166,6 +168,7 @@ class JavaFunctionBodyStates(JavaStates):
166
168
  return
167
169
  self.next(self._state_global, token)
168
170
 
171
+
169
172
  class JavaClassBodyStates(JavaStates):
170
173
  def __init__(self, class_name, is_record, context):
171
174
  super(JavaClassBodyStates, self).__init__(context)
@@ -2,7 +2,7 @@
2
2
  Language parser for JavaScript
3
3
  '''
4
4
 
5
- from .typescript import TypeScriptReader
5
+ from .typescript import TypeScriptReader
6
6
 
7
7
 
8
8
  class JavaScriptReader(TypeScriptReader):
@@ -159,6 +159,7 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
159
159
  def _collect_computed_name(self):
160
160
  # Collect tokens between [ and ]
161
161
  tokens = []
162
+
162
163
  def collect(token):
163
164
  if token == ']':
164
165
  # Try to join tokens and camelCase if possible
@@ -11,14 +11,14 @@ from .js_style_language_states import JavaScriptStyleLanguageStates
11
11
 
12
12
  class JSXTypeScriptStates(TypeScriptStates):
13
13
  """State machine for JSX/TSX files extending TypeScriptStates"""
14
-
14
+
15
15
  def __init__(self, context):
16
16
  super().__init__(context)
17
17
  # Initialize attributes that might be accessed later
18
18
  self._parent_function_name = None
19
19
  self.in_variable_declaration = False
20
20
  self.last_variable_name = None
21
-
21
+
22
22
  def statemachine_before_return(self):
23
23
  # Ensure the main function is closed at the end
24
24
  if self.started_function:
@@ -33,7 +33,7 @@ class JSXTypeScriptStates(TypeScriptStates):
33
33
  self.in_variable_declaration = True
34
34
  super()._state_global(token)
35
35
  return
36
-
36
+
37
37
  if self.in_variable_declaration:
38
38
  if token == '=':
39
39
  # Save the variable name when we see the assignment
@@ -51,23 +51,23 @@ class JSXTypeScriptStates(TypeScriptStates):
51
51
  return
52
52
  elif token == ';' or self.context.newline:
53
53
  self.in_variable_declaration = False
54
-
54
+
55
55
  # Handle arrow function in JSX/TSX prop context
56
56
  if token == '=>' and not self.in_variable_declaration:
57
57
  if not self.started_function:
58
58
  self.function_name = '(anonymous)'
59
59
  self._push_function_to_stack()
60
60
  return
61
-
61
+
62
62
  if not self.as_object:
63
63
  if token == ':':
64
64
  self._consume_type_annotation()
65
65
  return
66
-
66
+
67
67
  # Pop anonymous function after closing '}' in TSX/JSX prop
68
68
  if token == '}' and self.started_function and self.function_name == '(anonymous)':
69
69
  self._pop_function_from_stack()
70
-
70
+
71
71
  # Continue with regular TypeScript state handling
72
72
  super()._state_global(token)
73
73
 
@@ -84,12 +84,12 @@ class TSXTokenizer(JSTokenizer):
84
84
  from .jsx import XMLTagWithAttrTokenizer # Import only when needed
85
85
  self.sub_tokenizer = XMLTagWithAttrTokenizer()
86
86
  return
87
-
87
+
88
88
  if token == "=>":
89
89
  # Special handling for arrow functions
90
90
  yield token
91
91
  return
92
-
92
+
93
93
  for tok in super().process_token(token):
94
94
  yield tok
95
95
 
@@ -123,18 +123,18 @@ class JSXMixin:
123
123
  def _handle_arrow_function(self):
124
124
  # Process arrow function in JSX context
125
125
  self.context.add_to_long_function_name(" => ")
126
-
126
+
127
127
  # Store the current function
128
128
  current_function = self.context.current_function
129
-
129
+
130
130
  # Create a new anonymous function
131
131
  self.context.restart_new_function('(anonymous)')
132
-
132
+
133
133
  # Set up for the arrow function body
134
134
  def callback():
135
135
  # Return to the original function when done
136
136
  self.context.current_function = current_function
137
-
137
+
138
138
  self.sub_state(self.__class__(self.context), callback)
139
139
 
140
140
  def _expecting_arrow_function_body(self, token):
@@ -144,7 +144,7 @@ class JSXMixin:
144
144
  else:
145
145
  # Arrow function with expression body
146
146
  self.next(self._expecting_func_opening_bracket)
147
-
147
+
148
148
  def _function_body(self, token):
149
149
  if token == '}':
150
150
  # End of arrow function body
@@ -159,7 +159,7 @@ class JSXMixin:
159
159
  class JSXJavaScriptStyleLanguageStates(JavaScriptStyleLanguageStates):
160
160
  def __init__(self, context):
161
161
  super(JSXJavaScriptStyleLanguageStates, self).__init__(context)
162
-
162
+
163
163
  def _state_global(self, token):
164
164
  # Handle variable declarations
165
165
  if token in ('const', 'let', 'var'):
@@ -167,7 +167,7 @@ class JSXJavaScriptStyleLanguageStates(JavaScriptStyleLanguageStates):
167
167
  self.in_variable_declaration = True
168
168
  super()._state_global(token)
169
169
  return
170
-
170
+
171
171
  if hasattr(self, 'in_variable_declaration') and self.in_variable_declaration:
172
172
  if token == '=':
173
173
  # We're in a variable assignment
@@ -183,16 +183,16 @@ class JSXJavaScriptStyleLanguageStates(JavaScriptStyleLanguageStates):
183
183
  elif token == ';' or self.context.newline:
184
184
  # End of variable declaration
185
185
  self.in_variable_declaration = False
186
-
186
+
187
187
  super()._state_global(token)
188
-
188
+
189
189
  def _expecting_func_opening_bracket(self, token):
190
190
  if token == ':':
191
191
  # Handle type annotations like TypeScript does
192
192
  self._consume_type_annotation()
193
193
  return
194
194
  super()._expecting_func_opening_bracket(token)
195
-
195
+
196
196
  def _consume_type_annotation(self):
197
197
  # Skip over type annotations (simplified version of TypeScript's behavior)
198
198
  def skip_until_terminator(token):
@@ -200,7 +200,7 @@ class JSXJavaScriptStyleLanguageStates(JavaScriptStyleLanguageStates):
200
200
  self.next(self._state_global, token)
201
201
  return True
202
202
  return False
203
-
203
+
204
204
  self.next(skip_until_terminator)
205
205
 
206
206
 
@@ -291,7 +291,7 @@ class XMLTagWithAttrTokenizer(Tokenizer):
291
291
  # Don't add the closing brace automatically
292
292
  # self.cache.append("}")
293
293
  self.sub_tokenizer = TSXTokenizer()
294
-
294
+
295
295
  def _jsx_expression(self, token):
296
296
  # Handle nested braces in expressions
297
297
  if token == "{":
@@ -302,13 +302,13 @@ class XMLTagWithAttrTokenizer(Tokenizer):
302
302
  # We've found the matching closing brace
303
303
  self.state = self._after_tag
304
304
  return
305
-
305
+
306
306
  # Handle arrow functions in JSX attributes
307
307
  if token == "=>":
308
308
  self.arrow_function_detected = True
309
309
  # Explicitly yield the arrow token to ensure it's processed
310
310
  return ["=>"]
311
-
311
+
312
312
  # Handle type annotations in JSX attributes
313
313
  if token == "<":
314
314
  # This might be a TypeScript generic type annotation
@@ -19,7 +19,8 @@ class PerlReader(CodeReader, ScriptLanguageMixIn):
19
19
 
20
20
  ext = ['pl', 'pm']
21
21
  language_names = ['perl']
22
- _conditions = set(['if', 'elsif', 'unless', 'while', 'until', 'for', 'foreach', '&&', '||', '?', ':', 'when', 'given', 'default', 'do'])
22
+ _conditions = set(['if', 'elsif', 'unless', 'while', 'until', 'for', 'foreach',
23
+ '&&', '||', '?', ':', 'when', 'given', 'default', 'do'])
23
24
 
24
25
  def __init__(self, context):
25
26
  super(PerlReader, self).__init__(context)
@@ -55,7 +56,7 @@ class PerlReader(CodeReader, ScriptLanguageMixIn):
55
56
  stripped = token.lstrip('#').strip()
56
57
  if stripped.startswith('lizard forgives') or stripped.startswith('#lizard forgives'):
57
58
  return '#lizard forgives' # Return standardized forgiveness comment
58
- return stripped # Return the stripped comment for other cases
59
+ return stripped # Return the stripped comment for other case
59
60
  return None
60
61
 
61
62
  @staticmethod
@@ -64,7 +65,8 @@ class PerlReader(CodeReader, ScriptLanguageMixIn):
64
65
 
65
66
 
66
67
  class PerlStates(CodeStateMachine):
67
- _conditions = set(['if', 'elsif', 'unless', 'while', 'until', 'for', 'foreach', '&&', '||', '?', ':', 'when', 'given', 'default', 'do'])
68
+ _conditions = set(['if', 'elsif', 'unless', 'while', 'until', 'for', 'foreach',
69
+ '&&', '||', '?', ':', 'when', 'given', 'default', 'do'])
68
70
 
69
71
  def __init__(self, context):
70
72
  super(PerlStates, self).__init__(context)
@@ -90,7 +92,7 @@ class PerlStates(CodeStateMachine):
90
92
  elif token == '(':
91
93
  self.paren_count += 1
92
94
  self.next(self._state_function_call)
93
- elif token == '$' or token == 'my' or token == 'our' or token == 'local':
95
+ elif token in ('$', 'my', 'our', 'local'):
94
96
  self.variable_name = ''
95
97
  self.next(self._state_variable)
96
98
 
@@ -101,7 +103,7 @@ class PerlStates(CodeStateMachine):
101
103
 
102
104
  def _state_variable(self, token):
103
105
  if token == '$':
104
- # Skip the $ in variable names
106
+ # Skip the $ in variable name
105
107
  pass
106
108
  elif token == '=':
107
109
  self.next(self._state_assignment)
@@ -122,10 +124,10 @@ class PerlStates(CodeStateMachine):
122
124
  if token == 'sub':
123
125
  # Inline anonymous subroutine as argument
124
126
  self.anonymous_count += 1
125
- full_name = f"<anonymous>"
127
+ full_name = "<anonymous>"
126
128
  if self.package_name:
127
129
  full_name = f"{self.package_name}::{full_name}"
128
-
130
+
129
131
  self.context.try_new_function(full_name)
130
132
  self.context.confirm_new_function()
131
133
  self.next(self._state_anon_brace_search)
@@ -140,13 +142,13 @@ class PerlStates(CodeStateMachine):
140
142
  if token == '{':
141
143
  self.brace_count = 1
142
144
  full_name = '<anonymous>'
143
- # Use variable name if available for more readable function names
145
+ # Use variable name if available for more readable function name
144
146
  if self.variable_name:
145
147
  full_name = '$' + self.variable_name
146
-
148
+
147
149
  if self.package_name:
148
150
  full_name = f"{self.package_name}::{full_name}"
149
-
151
+
150
152
  self.context.try_new_function(full_name)
151
153
  self.context.confirm_new_function()
152
154
  self.next(self._state_function_body)
@@ -182,7 +184,7 @@ class PerlStates(CodeStateMachine):
182
184
  elif token == 'sub':
183
185
  # Handle anonymous subroutine like 'callback(sub { ... })'
184
186
  self.anonymous_count += 1
185
- full_name = f"<anonymous>"
187
+ full_name = "<anonymous>"
186
188
  if self.package_name:
187
189
  full_name = f"{self.package_name}::{full_name}"
188
190
  self.context.try_new_function(full_name)
@@ -231,7 +233,7 @@ class PerlStates(CodeStateMachine):
231
233
  # Colon part of ternary operator also increases complexity
232
234
  self.context.add_condition()
233
235
  elif token == 'sub':
234
- # Check if it's a nested named subroutine or anonymous
236
+ # Check if it's a nested named subroutine or anonymou
235
237
  self.next(self._state_nested_sub_dec)
236
238
  elif token == '(':
237
239
  # Track function calls inside function body
@@ -241,11 +243,11 @@ class PerlStates(CodeStateMachine):
241
243
  def _state_nested_sub_dec(self, token):
242
244
  if token.isspace():
243
245
  return
244
- elif token == '{':
246
+ if token == '{':
245
247
  # Anonymous sub
246
248
  self.brace_count += 1
247
249
  self.anonymous_count += 1
248
- anon_name = f"<anonymous>"
250
+ anon_name = "<anonymous>"
249
251
  if self.package_name:
250
252
  anon_name = f"{self.package_name}::{anon_name}"
251
253
  self.context.add_condition() # Count sub as a condition
@@ -256,10 +258,7 @@ class PerlStates(CodeStateMachine):
256
258
  full_name = nested_func_name
257
259
  if self.package_name:
258
260
  full_name = f"{self.package_name}::{nested_func_name}"
259
-
260
- # Save current function state
261
- saved_func_context = self.context
262
-
261
+
263
262
  # Create a new function for the nested sub
264
263
  self.context.try_new_function(full_name)
265
264
  self.context.confirm_new_function()
@@ -295,10 +294,10 @@ class PerlStates(CodeStateMachine):
295
294
  if token == 'sub':
296
295
  # Inline anonymous subroutine as argument
297
296
  self.anonymous_count += 1
298
- full_name = f"<anonymous>"
297
+ full_name = "<anonymous>"
299
298
  if self.package_name:
300
299
  full_name = f"{self.package_name}::{full_name}"
301
-
300
+
302
301
  self.context.try_new_function(full_name)
303
302
  self.context.confirm_new_function()
304
303
  self.next(self._state_nested_anon_search)
@@ -327,4 +326,4 @@ class PerlStates(CodeStateMachine):
327
326
  self.brace_count -= 1
328
327
  if self.brace_count == 1: # Back to outer function level
329
328
  self.context.end_of_function()
330
- self.next(self._state_function_body)
329
+ self.next(self._state_function_body)
@@ -12,7 +12,7 @@ class PHPLanguageStates(CodeStateMachine):
12
12
  PHP-specific state machine that properly handles modern PHP syntax
13
13
  including classes, visibility modifiers and return types.
14
14
  """
15
-
15
+
16
16
  def __init__(self, context):
17
17
  super(PHPLanguageStates, self).__init__(context)
18
18
  self.function_name = ''
@@ -29,7 +29,7 @@ class PHPLanguageStates(CodeStateMachine):
29
29
  self.assignments = []
30
30
  self.in_match = False
31
31
  self.match_case_count = 0
32
-
32
+
33
33
  def _state_global(self, token):
34
34
  if token == 'class':
35
35
  self._state = self._class_declaration
@@ -76,7 +76,7 @@ class PHPLanguageStates(CodeStateMachine):
76
76
  if self.in_trait:
77
77
  self.in_trait = False
78
78
  self.trait_name = None
79
-
79
+
80
80
  # Update tokens
81
81
  self.last_token = token
82
82
  if token not in [' ', '\t', '\n']:
@@ -87,7 +87,7 @@ class PHPLanguageStates(CodeStateMachine):
87
87
  pass
88
88
  else:
89
89
  self.last_tokens = ''
90
-
90
+
91
91
  def _trait_declaration(self, token):
92
92
  if token and not token.isspace() and token not in ['{', '(']:
93
93
  self.trait_name = token
@@ -96,7 +96,7 @@ class PHPLanguageStates(CodeStateMachine):
96
96
  elif token == '{':
97
97
  self.brace_level += 1
98
98
  self._state = self._state_global
99
-
99
+
100
100
  def _class_declaration(self, token):
101
101
  if token and not token.isspace() and token not in ['{', '(', 'extends', 'implements']:
102
102
  self.class_name = token
@@ -105,7 +105,7 @@ class PHPLanguageStates(CodeStateMachine):
105
105
  elif token == '{':
106
106
  self.brace_level += 1
107
107
  self._state = self._state_global
108
-
108
+
109
109
  def _function_name(self, token):
110
110
  if token and not token.isspace() and token != '(':
111
111
  method_name = token
@@ -140,7 +140,7 @@ class PHPLanguageStates(CodeStateMachine):
140
140
  self._state = self._function_args_continue
141
141
  self.context.push_new_function(self.function_name)
142
142
  self.started_function = True
143
-
143
+
144
144
  def _function_args(self, token):
145
145
  if token == '(':
146
146
  self.bracket_level = 1
@@ -151,7 +151,7 @@ class PHPLanguageStates(CodeStateMachine):
151
151
  self.context.push_new_function(self.function_name)
152
152
  self.started_function = True
153
153
  self._state = self._function_args_continue
154
-
154
+
155
155
  def _function_args_continue(self, token):
156
156
  if token == '(':
157
157
  self.bracket_level += 1
@@ -165,7 +165,7 @@ class PHPLanguageStates(CodeStateMachine):
165
165
  # Make sure we count each parameter uniquely
166
166
  self.context.add_to_long_function_name(" " + token)
167
167
  self.context.parameter(token)
168
-
168
+
169
169
  def _function_return_type_or_body(self, token):
170
170
  if token == ':':
171
171
  # Skip return type declaration
@@ -180,13 +180,13 @@ class PHPLanguageStates(CodeStateMachine):
180
180
  self.context.end_of_function()
181
181
  self.started_function = False
182
182
  self._state = self._state_global
183
-
183
+
184
184
  def _function_body_or_return_type(self, token):
185
185
  if token == '{':
186
186
  # Found the function body opening after return type
187
187
  self.brace_level += 1
188
188
  self._state = self._function_body
189
-
189
+
190
190
  def _function_body(self, token):
191
191
  if token == '{':
192
192
  self.brace_level += 1
@@ -198,12 +198,12 @@ class PHPLanguageStates(CodeStateMachine):
198
198
  self.context.end_of_function()
199
199
  self.started_function = False
200
200
  self._state = self._state_global
201
-
201
+
202
202
  def _condition_expected(self, token):
203
203
  if token == '(':
204
204
  self.bracket_level = 1
205
205
  self._state = self._condition_continue
206
-
206
+
207
207
  def _condition_continue(self, token):
208
208
  if token == '(':
209
209
  self.bracket_level += 1
@@ -216,7 +216,7 @@ class PHPLanguageStates(CodeStateMachine):
216
216
  if token == '(':
217
217
  self.bracket_level = 1
218
218
  self._state = self._match_expression_continue
219
-
219
+
220
220
  def _match_expression_continue(self, token):
221
221
  if token == '(':
222
222
  self.bracket_level += 1
@@ -42,7 +42,7 @@ class PythonReader(CodeReader, ScriptLanguageMixIn):
42
42
  def generate_tokens(source_code, addition='', token_class=None):
43
43
  return ScriptLanguageMixIn.generate_common_tokens(
44
44
  source_code,
45
- r"|(?:\"\"\"(?:\\.|[^\"]|\"(?!\"\")|\"\"(?!\"))*\"\"\")" +
45
+ r"|(?:\"\"\"(?:\\.|[^\"]|\"(?!\"\")|\"\"(?!\"))*\"\"\")" +
46
46
  r"|(?:\'\'\'(?:\\.|[^\']|\'(?!\'\')|\'\'(?!\'))*\'\'\')",
47
47
  token_class)
48
48
 
@@ -1,5 +1,5 @@
1
1
  '''
2
- Language parser for Go lang
2
+ Language parser for Rust lang
3
3
  '''
4
4
 
5
5
  from .code_reader import CodeReader
@@ -16,7 +16,7 @@ class RustReader(CodeReader, CCppCommentsMixin):
16
16
  'case', 'match', 'where'])
17
17
 
18
18
  def __init__(self, context):
19
- super(RustReader, self).__init__(context)
19
+ super().__init__(context)
20
20
  self.parallel_states = [RustStates(context)]
21
21
 
22
22
 
@@ -12,13 +12,13 @@ class ScriptLanguageMixIn:
12
12
  if token.startswith("#"):
13
13
  # For forgiveness comments, return the entire comment with directive intact
14
14
  stripped = token.lstrip('#').strip()
15
-
15
+
16
16
  # Handle forgiveness directives with proper formatting
17
17
  if stripped.startswith('lizard forgive global') or stripped.startswith('#lizard forgive global'):
18
18
  return '#lizard forgive global' # Preserve global directive
19
19
  elif stripped.startswith('lizard forgive') or stripped.startswith('#lizard forgive'):
20
20
  return '#lizard forgive' # Return standardized forgiveness comment
21
-
21
+
22
22
  return stripped # Return the stripped comment for other cases
23
23
  return None
24
24
 
@@ -169,7 +169,7 @@ class TypeScriptTypeAnnotationStates(CodeStateMachine):
169
169
  @CodeStateMachine.read_inside_brackets_then("<>")
170
170
  def _state_generic_type(self, token):
171
171
  self.statemachine_return()
172
-
172
+
173
173
  @CodeStateMachine.read_inside_brackets_then("()")
174
174
  def _function_type_annotation(self, _):
175
175
  self.statemachine_return()
@@ -7,6 +7,7 @@ from lizard import print_warnings, print_and_save_modules, FunctionInfo, FileInf
7
7
  print_result, print_extension_results, get_extensions, OutputScheme, get_warnings, print_clang_style_warning,\
8
8
  parse_args, AllResult
9
9
  from lizard_ext import xml_output
10
+ from lizard_ext.checkstyleoutput import checkstyle_output
10
11
 
11
12
  def print_result_with_scheme(result, option):
12
13
  return print_result(result, option, OutputScheme(option.extensions), AllResult)
@@ -180,3 +181,21 @@ class TestXMLOutput(unittest.TestCase):
180
181
  self.assertIn('''<sum label="NCSS" value="0"/>''', xml_empty)
181
182
  self.assertIn('''<sum label="CCN" value="0"/>''', xml_empty)
182
183
  self.assertIn('''<sum label="Functions" value="0"/>''', xml_empty)
184
+
185
+
186
+ class TestCheckstyleOutput(unittest.TestCase):
187
+ foo = FunctionInfo("foo", '', 100)
188
+ foo.cyclomatic_complexity = 16
189
+ file_infos = [FileInformation('f1.c', 1, [foo])]
190
+ from lizard_ext.checkstyleoutput import checkstyle_output
191
+ checkstyle_xml = checkstyle_output(AllResult(file_infos), True)
192
+
193
+ def test_checkstyle_output(self):
194
+ self.assertIn('<checkstyle', self.checkstyle_xml)
195
+ self.assertIn('<file', self.checkstyle_xml)
196
+ self.assertIn('<error', self.checkstyle_xml)
197
+ self.assertIn('foo has', self.checkstyle_xml)
198
+
199
+ def test_checkstyle_output_on_empty_folder(self):
200
+ xml_empty = checkstyle_output(AllResult([]), True)
201
+ self.assertIn('<checkstyle', xml_empty)
@@ -115,3 +115,34 @@ class TestFileOutputIntegration(unittest.TestCase):
115
115
  "Should show warning when format doesn't match extension")
116
116
  finally:
117
117
  sys.stderr = old_stderr
118
+
119
+ def test_checkstyle(self):
120
+ header = "<?xml version=\"1.0\" ?>"
121
+ # Checkstyle output should start with XML declaration
122
+ self.output_test("test.checkstyle.xml", header)
123
+
124
+ def test_checkstyle_option(self):
125
+ # Create a temp C file with a function
126
+ c_path = join(self.tmp_dir, "foo.c")
127
+ with open(c_path, "w") as f:
128
+ f.write("int foo() { return 42; }\n")
129
+ path = join(self.tmp_dir, "test.checkstyle.xml")
130
+ args = ["lizard", "--checkstyle", "--output_file", path, c_path]
131
+ main(args)
132
+ with open(path, 'r') as f:
133
+ content = f.read()
134
+ self.assertIn('<checkstyle', content)
135
+ self.assertIn('<file', content)
136
+ self.assertIn('<error', content)
137
+
138
+ def test_checkstyle_extension_inference(self):
139
+ # Create a temp C file with a function
140
+ c_path = join(self.tmp_dir, "bar.c")
141
+ with open(c_path, "w") as f:
142
+ f.write("int bar() { return 24; }\n")
143
+ path = join(self.tmp_dir, "test.checkstyle.xml")
144
+ args = ["lizard", "--output_file", path, c_path]
145
+ main(args)
146
+ with open(path, 'r') as f:
147
+ content = f.read()
148
+ self.assertIn('<checkstyle', content)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes