lizard 1.17.29__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.29 → lizard-1.17.31}/PKG-INFO +2 -1
  2. {lizard-1.17.29 → lizard-1.17.31}/README.rst +1 -0
  3. {lizard-1.17.29 → lizard-1.17.31}/lizard.egg-info/PKG-INFO +2 -1
  4. {lizard-1.17.29 → lizard-1.17.31}/lizard.egg-info/SOURCES.txt +1 -0
  5. {lizard-1.17.29 → lizard-1.17.31}/lizard.py +36 -15
  6. {lizard-1.17.29 → 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.29 → lizard-1.17.31}/lizard_ext/version.py +1 -1
  9. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/code_reader.py +19 -0
  10. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/go.py +1 -1
  11. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/java.py +6 -3
  12. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/javascript.py +1 -1
  13. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/js_style_language_states.py +51 -5
  14. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/jsx.py +95 -23
  15. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/perl.py +20 -21
  16. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/php.py +14 -14
  17. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/python.py +1 -1
  18. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/rust.py +2 -2
  19. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/script_language.py +2 -2
  20. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/tsx.py +3 -2
  21. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/typescript.py +54 -11
  22. {lizard-1.17.29 → lizard-1.17.31}/test/testOutput.py +19 -0
  23. {lizard-1.17.29 → lizard-1.17.31}/test/testOutputFile.py +31 -0
  24. {lizard-1.17.29 → lizard-1.17.31}/LICENSE.txt +0 -0
  25. {lizard-1.17.29 → lizard-1.17.31}/lizard.egg-info/dependency_links.txt +0 -0
  26. {lizard-1.17.29 → lizard-1.17.31}/lizard.egg-info/entry_points.txt +0 -0
  27. {lizard-1.17.29 → lizard-1.17.31}/lizard.egg-info/requires.txt +0 -0
  28. {lizard-1.17.29 → lizard-1.17.31}/lizard.egg-info/top_level.txt +0 -0
  29. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/auto_open.py +0 -0
  30. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/csvoutput.py +0 -0
  31. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/default_ordered_dict.py +0 -0
  32. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/extension_base.py +0 -0
  33. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/htmloutput.py +0 -0
  34. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/keywords.py +0 -0
  35. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardboolcount.py +0 -0
  36. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardcomplextags.py +0 -0
  37. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardcpre.py +0 -0
  38. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizarddependencycount.py +0 -0
  39. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizarddumpcomments.py +0 -0
  40. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardduplicate.py +0 -0
  41. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardduplicated_param_list.py +0 -0
  42. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardexitcount.py +0 -0
  43. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardgotocount.py +0 -0
  44. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardignoreassert.py +0 -0
  45. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardio.py +0 -0
  46. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardmccabe.py +0 -0
  47. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardmodified.py +0 -0
  48. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardnd.py +0 -0
  49. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardnonstrict.py +0 -0
  50. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardns.py +0 -0
  51. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardoutside.py +0 -0
  52. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardstatementcount.py +0 -0
  53. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/lizardwordcount.py +0 -0
  54. {lizard-1.17.29 → lizard-1.17.31}/lizard_ext/xmloutput.py +0 -0
  55. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/__init__.py +0 -0
  56. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/clike.py +0 -0
  57. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/csharp.py +0 -0
  58. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/erlang.py +0 -0
  59. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/fortran.py +0 -0
  60. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/gdscript.py +0 -0
  61. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/golike.py +0 -0
  62. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/js_style_regex_expression.py +0 -0
  63. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/kotlin.py +0 -0
  64. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/lua.py +0 -0
  65. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/objc.py +0 -0
  66. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/ruby.py +0 -0
  67. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/rubylike.py +0 -0
  68. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/scala.py +0 -0
  69. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/solidity.py +0 -0
  70. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/swift.py +0 -0
  71. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/tnsdl.py +0 -0
  72. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/ttcn.py +0 -0
  73. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/vue.py +0 -0
  74. {lizard-1.17.29 → lizard-1.17.31}/lizard_languages/zig.py +0 -0
  75. {lizard-1.17.29 → lizard-1.17.31}/setup.cfg +0 -0
  76. {lizard-1.17.29 → lizard-1.17.31}/setup.py +0 -0
  77. {lizard-1.17.29 → lizard-1.17.31}/test/testApplication.py +0 -0
  78. {lizard-1.17.29 → lizard-1.17.31}/test/testAssertionExtension.py +0 -0
  79. {lizard-1.17.29 → lizard-1.17.31}/test/testBasicFunctionInfo.py +0 -0
  80. {lizard-1.17.29 → lizard-1.17.31}/test/testCOutsideComplexity.py +0 -0
  81. {lizard-1.17.29 → lizard-1.17.31}/test/testCPreprocessorExtension.py +0 -0
  82. {lizard-1.17.29 → lizard-1.17.31}/test/testCommentOptions.py +0 -0
  83. {lizard-1.17.29 → lizard-1.17.31}/test/testCyclomaticComplexity.py +0 -0
  84. {lizard-1.17.29 → lizard-1.17.31}/test/testExtension.py +0 -0
  85. {lizard-1.17.29 → lizard-1.17.31}/test/testFilesFilter.py +0 -0
  86. {lizard-1.17.29 → lizard-1.17.31}/test/testFunctionDependencyCount.py +0 -0
  87. {lizard-1.17.29 → lizard-1.17.31}/test/testFunctionExitCount.py +0 -0
  88. {lizard-1.17.29 → lizard-1.17.31}/test/testFunctionGotoCount.py +0 -0
  89. {lizard-1.17.29 → lizard-1.17.31}/test/testFunctionStatementCount.py +0 -0
  90. {lizard-1.17.29 → lizard-1.17.31}/test/testHelpers.py +0 -0
  91. {lizard-1.17.29 → lizard-1.17.31}/test/testLanguages.py +0 -0
  92. {lizard-1.17.29 → lizard-1.17.31}/test/testMcCabe.py +0 -0
  93. {lizard-1.17.29 → lizard-1.17.31}/test/testNestedStructures.py +0 -0
  94. {lizard-1.17.29 → lizard-1.17.31}/test/testNestingDepth.py +0 -0
  95. {lizard-1.17.29 → lizard-1.17.31}/test/testOutputCSV.py +0 -0
  96. {lizard-1.17.29 → lizard-1.17.31}/test/testOutputHTML.py +0 -0
  97. {lizard-1.17.29 → lizard-1.17.31}/test/testTokenizer.py +0 -0
  98. {lizard-1.17.29 → lizard-1.17.31}/test/test_analyzer.py +0 -0
  99. {lizard-1.17.29 → lizard-1.17.31}/test/test_auto_open.py +0 -0
  100. {lizard-1.17.29 → 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.29
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.29
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.29"
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):
@@ -12,14 +12,30 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
12
12
  self.function_name = ''
13
13
  self.started_function = None
14
14
  self.as_object = False
15
+ self._getter_setter_prefix = None
16
+ self.arrow_function_pending = False
15
17
 
16
18
  def _state_global(self, token):
17
19
  if self.as_object:
20
+ # Support for getter/setter: look for 'get' or 'set' before method name
21
+ if token in ('get', 'set'):
22
+ self._getter_setter_prefix = token
23
+ return
24
+ if hasattr(self, '_getter_setter_prefix') and self._getter_setter_prefix:
25
+ # Next token is the property name
26
+ self.last_tokens = f"{self._getter_setter_prefix} {token}"
27
+ self._getter_setter_prefix = None
28
+ return
29
+ if token == '[':
30
+ self._collect_computed_name()
31
+ return
18
32
  if token == ':':
19
33
  self.function_name = self.last_tokens
20
34
  return
21
35
  elif token == '(':
22
- self._function(self.last_tokens)
36
+ if not self.started_function:
37
+ self.arrow_function_pending = True
38
+ self._function(self.last_tokens)
23
39
  self.next(self._function, token)
24
40
  return
25
41
 
@@ -34,8 +50,7 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
34
50
  elif token in ('else', 'do', 'try', 'final'):
35
51
  self.next(self._expecting_statement_or_block)
36
52
  elif token in ('=>',):
37
- if self.function_name:
38
- self._push_function_to_stack()
53
+ # Only handle arrow function body, do not push function here
39
54
  self._state = self._arrow_function
40
55
  elif token == '=':
41
56
  self.function_name = self.last_tokens
@@ -116,7 +131,9 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
116
131
  if token != '(':
117
132
  self.function_name = token
118
133
  else:
119
- self._push_function_to_stack()
134
+ if not self.started_function:
135
+ self._push_function_to_stack()
136
+ self.arrow_function_pending = False
120
137
  self._state = self._dec
121
138
  if token == '(':
122
139
  self._dec(token)
@@ -134,6 +151,35 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
134
151
  self.context.add_to_long_function_name(" " + token)
135
152
 
136
153
  def _expecting_func_opening_bracket(self, token):
137
- if token != '{':
154
+ # Do not reset started_function for arrow functions (=>)
155
+ if token != '{' and token != '=>':
138
156
  self.started_function = None
139
157
  self.next(self._state_global, token)
158
+
159
+ def _collect_computed_name(self):
160
+ # Collect tokens between [ and ]
161
+ tokens = []
162
+
163
+ def collect(token):
164
+ if token == ']':
165
+ # Try to join tokens and camelCase if possible
166
+ name = ''.join(tokens)
167
+ # Remove quotes and pluses for simple cases
168
+ name = name.replace("'", '').replace('"', '').replace('+', '').replace(' ', '')
169
+ # Lowercase first char, uppercase next word's first char
170
+ name = self._to_camel_case(name)
171
+ self.last_tokens = name
172
+ self.next(self._state_global)
173
+ return True
174
+ tokens.append(token)
175
+ return False
176
+ self.next(collect)
177
+
178
+ def _to_camel_case(self, s):
179
+ # Simple camelCase conversion for test case
180
+ if not s:
181
+ return s
182
+ parts = s.split()
183
+ if not parts:
184
+ return s
185
+ return parts[0][0].lower() + parts[0][1:] + ''.join(p.capitalize() for p in parts[1:])
@@ -3,12 +3,78 @@ Language parser for JSX
3
3
  '''
4
4
 
5
5
  from .javascript import JavaScriptReader
6
- from .typescript import JSTokenizer, Tokenizer
6
+ from .typescript import JSTokenizer, Tokenizer, TypeScriptStates
7
7
  from .code_reader import CodeReader
8
8
  from .js_style_regex_expression import js_style_regex_expression
9
9
  from .js_style_language_states import JavaScriptStyleLanguageStates
10
10
 
11
11
 
12
+ class JSXTypeScriptStates(TypeScriptStates):
13
+ """State machine for JSX/TSX files extending TypeScriptStates"""
14
+
15
+ def __init__(self, context):
16
+ super().__init__(context)
17
+ # Initialize attributes that might be accessed later
18
+ self._parent_function_name = None
19
+ self.in_variable_declaration = False
20
+ self.last_variable_name = None
21
+
22
+ def statemachine_before_return(self):
23
+ # Ensure the main function is closed at the end
24
+ if self.started_function:
25
+ self._pop_function_from_stack()
26
+ # After popping, if current_function is not *global*, pop again to add to function_list
27
+ if self.context.current_function and self.context.current_function.name != "*global*":
28
+ self.context.end_of_function()
29
+
30
+ def _state_global(self, token):
31
+ # Handle variable declarations
32
+ if token in ('const', 'let', 'var'):
33
+ self.in_variable_declaration = True
34
+ super()._state_global(token)
35
+ return
36
+
37
+ if self.in_variable_declaration:
38
+ if token == '=':
39
+ # Save the variable name when we see the assignment
40
+ self.last_variable_name = self.last_tokens.strip()
41
+ super()._state_global(token)
42
+ return
43
+ elif token == '=>':
44
+ # We're in an arrow function with a variable assignment
45
+ if self.last_variable_name and not self.started_function:
46
+ self.function_name = self.last_variable_name
47
+ self._push_function_to_stack()
48
+ self.in_variable_declaration = False
49
+ # Switch to arrow function state to handle the body
50
+ self._state = self._arrow_function
51
+ return
52
+ elif token == ';' or self.context.newline:
53
+ self.in_variable_declaration = False
54
+
55
+ # Handle arrow function in JSX/TSX prop context
56
+ if token == '=>' and not self.in_variable_declaration:
57
+ if not self.started_function:
58
+ self.function_name = '(anonymous)'
59
+ self._push_function_to_stack()
60
+ return
61
+
62
+ if not self.as_object:
63
+ if token == ':':
64
+ self._consume_type_annotation()
65
+ return
66
+
67
+ # Pop anonymous function after closing '}' in TSX/JSX prop
68
+ if token == '}' and self.started_function and self.function_name == '(anonymous)':
69
+ self._pop_function_from_stack()
70
+
71
+ # Continue with regular TypeScript state handling
72
+ super()._state_global(token)
73
+
74
+ def _arrow_function(self, token):
75
+ self.next(self._state_global, token)
76
+
77
+
12
78
  class TSXTokenizer(JSTokenizer):
13
79
  def __init__(self):
14
80
  super().__init__()
@@ -18,12 +84,12 @@ class TSXTokenizer(JSTokenizer):
18
84
  from .jsx import XMLTagWithAttrTokenizer # Import only when needed
19
85
  self.sub_tokenizer = XMLTagWithAttrTokenizer()
20
86
  return
21
-
87
+
22
88
  if token == "=>":
23
89
  # Special handling for arrow functions
24
90
  yield token
25
91
  return
26
-
92
+
27
93
  for tok in super().process_token(token):
28
94
  yield tok
29
95
 
@@ -57,18 +123,18 @@ class JSXMixin:
57
123
  def _handle_arrow_function(self):
58
124
  # Process arrow function in JSX context
59
125
  self.context.add_to_long_function_name(" => ")
60
-
126
+
61
127
  # Store the current function
62
128
  current_function = self.context.current_function
63
-
129
+
64
130
  # Create a new anonymous function
65
131
  self.context.restart_new_function('(anonymous)')
66
-
132
+
67
133
  # Set up for the arrow function body
68
134
  def callback():
69
135
  # Return to the original function when done
70
136
  self.context.current_function = current_function
71
-
137
+
72
138
  self.sub_state(self.__class__(self.context), callback)
73
139
 
74
140
  def _expecting_arrow_function_body(self, token):
@@ -78,7 +144,7 @@ class JSXMixin:
78
144
  else:
79
145
  # Arrow function with expression body
80
146
  self.next(self._expecting_func_opening_bracket)
81
-
147
+
82
148
  def _function_body(self, token):
83
149
  if token == '}':
84
150
  # End of arrow function body
@@ -93,38 +159,40 @@ class JSXMixin:
93
159
  class JSXJavaScriptStyleLanguageStates(JavaScriptStyleLanguageStates):
94
160
  def __init__(self, context):
95
161
  super(JSXJavaScriptStyleLanguageStates, self).__init__(context)
96
-
162
+
97
163
  def _state_global(self, token):
98
- if token == 'const' or token == 'let' or token == 'var':
164
+ # Handle variable declarations
165
+ if token in ('const', 'let', 'var'):
99
166
  # Remember that we're in a variable declaration
100
167
  self.in_variable_declaration = True
101
168
  super()._state_global(token)
102
169
  return
103
-
170
+
104
171
  if hasattr(self, 'in_variable_declaration') and self.in_variable_declaration:
105
172
  if token == '=':
106
173
  # We're in a variable assignment
107
- self.function_name = self.last_tokens
174
+ self.function_name = self.last_tokens.strip()
108
175
  super()._state_global(token)
109
176
  return
110
177
  elif token == '=>':
111
178
  # We're in an arrow function with a variable assignment
112
- self._push_function_to_stack()
179
+ if not self.started_function and self.function_name:
180
+ self._push_function_to_stack()
113
181
  self._state = self._arrow_function
114
182
  return
115
183
  elif token == ';' or self.context.newline:
116
184
  # End of variable declaration
117
185
  self.in_variable_declaration = False
118
-
186
+
119
187
  super()._state_global(token)
120
-
188
+
121
189
  def _expecting_func_opening_bracket(self, token):
122
190
  if token == ':':
123
191
  # Handle type annotations like TypeScript does
124
192
  self._consume_type_annotation()
125
193
  return
126
194
  super()._expecting_func_opening_bracket(token)
127
-
195
+
128
196
  def _consume_type_annotation(self):
129
197
  # Skip over type annotations (simplified version of TypeScript's behavior)
130
198
  def skip_until_terminator(token):
@@ -132,7 +200,7 @@ class JSXJavaScriptStyleLanguageStates(JavaScriptStyleLanguageStates):
132
200
  self.next(self._state_global, token)
133
201
  return True
134
202
  return False
135
-
203
+
136
204
  self.next(skip_until_terminator)
137
205
 
138
206
 
@@ -153,8 +221,8 @@ class JSXReader(JavaScriptReader, JSXMixin):
153
221
 
154
222
  def __init__(self, context):
155
223
  super(JSXReader, self).__init__(context)
156
- # Use our custom JavaScriptStyleLanguageStates subclass
157
- self.parallel_states = [JSXJavaScriptStyleLanguageStates(context)]
224
+ # Use our JSXTypeScriptStates for better handling of JSX
225
+ self.parallel_states = [JSXTypeScriptStates(context)]
158
226
 
159
227
 
160
228
  class XMLTagWithAttrTokenizer(Tokenizer):
@@ -171,7 +239,11 @@ class XMLTagWithAttrTokenizer(Tokenizer):
171
239
  if not token.isspace():
172
240
  result = self.state(token)
173
241
  if result is not None:
174
- return result
242
+ if isinstance(result, list):
243
+ for tok in result:
244
+ yield tok
245
+ else:
246
+ return result
175
247
  return ()
176
248
 
177
249
  def abort(self):
@@ -219,7 +291,7 @@ class XMLTagWithAttrTokenizer(Tokenizer):
219
291
  # Don't add the closing brace automatically
220
292
  # self.cache.append("}")
221
293
  self.sub_tokenizer = TSXTokenizer()
222
-
294
+
223
295
  def _jsx_expression(self, token):
224
296
  # Handle nested braces in expressions
225
297
  if token == "{":
@@ -230,13 +302,13 @@ class XMLTagWithAttrTokenizer(Tokenizer):
230
302
  # We've found the matching closing brace
231
303
  self.state = self._after_tag
232
304
  return
233
-
305
+
234
306
  # Handle arrow functions in JSX attributes
235
307
  if token == "=>":
236
308
  self.arrow_function_detected = True
237
309
  # Explicitly yield the arrow token to ensure it's processed
238
310
  return ["=>"]
239
-
311
+
240
312
  # Handle type annotations in JSX attributes
241
313
  if token == "<":
242
314
  # This might be a TypeScript generic type annotation