lizard 1.17.29__py2.py3-none-any.whl → 1.17.31__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.
@@ -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/
@@ -196,6 +196,7 @@ Options
196
196
  generate report in Jenkins server
197
197
  --csv Generate CSV output as a transform of the default output
198
198
  -H, --html Output HTML report
199
+ --checkstyle Generate Checkstyle XML output for integration with Jenkins and other tools
199
200
  -m, --modified Calculate modified cyclomatic complexity number , which count a
200
201
  switch/case with multiple cases as one CCN.
201
202
  -E EXTENSIONS, --extension EXTENSIONS
@@ -1,6 +1,7 @@
1
- lizard.py,sha256=8Um-Twbq0JgVn3V_YQNl5iyNH9-Xno8U-zD_Jb5F22g,40247
2
- lizard_ext/__init__.py,sha256=Cz0pvH6QHyadK-rAeeEhQVsm0hW5M-fBQgV4FBaX9zk,500
1
+ lizard.py,sha256=H0EgGsU0ou4IlRY9AxIyY_O4_WO8trZda4GGM7a4kNQ,41403
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=puuTq5IfM48NkDdds2FjF1BFq1QDGfsf5GahLTVfN6Y,1213
4
5
  lizard_ext/csvoutput.py,sha256=43fhmo8kB85qcdujCwySGNuTC4FkKUPLqIApPeljPnA,2663
5
6
  lizard_ext/default_ordered_dict.py,sha256=YbVz6nPlQ6DjWc_EOFBz6AJN2XLo9dpnUdeyejQvUDE,831
6
7
  lizard_ext/extension_base.py,sha256=rnjUL2mqSGToUVYydju7fa8ZwynLPY8S1F17gIJP55I,346
@@ -25,44 +26,44 @@ lizard_ext/lizardns.py,sha256=8pztUoRS_UWN24MawwxeHEJgYh49id5PWODUBb6O72U,4184
25
26
  lizard_ext/lizardoutside.py,sha256=FGm2tbBZ17-2OCgmQlD-vobUCfQKb0FAygf86eM3xuM,336
26
27
  lizard_ext/lizardstatementcount.py,sha256=xYk6ixSIItSE1BWQXzrWmduFgGhA3VR817SNKLffyVQ,1182
27
28
  lizard_ext/lizardwordcount.py,sha256=2QYXD7-AtkkgAbi9VSidunMbSsGQ7MKYb6IT-bS-cok,7575
28
- lizard_ext/version.py,sha256=QVDZyG9GV7lwS_Xtimsh3RbooOAR53x-mhgHy51HIjk,182
29
+ lizard_ext/version.py,sha256=N4oSGH5svpjDluQQOHRznK3TrM-OyaJq6FN_Yuc5pvw,182
29
30
  lizard_ext/xmloutput.py,sha256=-cbh0he4O_X-wX56gkv9AnSPNN0qvR7FACqlBeezUS4,5609
30
31
  lizard_languages/__init__.py,sha256=ArNmUrVSU6HFxhDka1-vWMZpVIM39P-gqv6BoOLNMV8,1522
31
32
  lizard_languages/clike.py,sha256=INd5tkvwEVZm7dx2yHG2OIFHZn7JzQGmnT9WQNFZ2XU,11110
32
- lizard_languages/code_reader.py,sha256=P0PkE4QZBWOEj6cFHA4hj3hcLZLcGnqH31EmtltxlaE,6240
33
+ lizard_languages/code_reader.py,sha256=IfEHg9lzKnyCipX9xscgyGEOovll5qr9dCe5cSX2sJM,6852
33
34
  lizard_languages/csharp.py,sha256=EfFAIOIcJXUUhXTlZApXGSlzG34NZvHM9OSe6m7hpv0,2141
34
35
  lizard_languages/erlang.py,sha256=7YJS2cMyXDKEV_kpH8DzBARxFCFcjKuTOPSQ3K52auU,3860
35
36
  lizard_languages/fortran.py,sha256=KATDsnfjob5W3579A_VxbwrbTkK7Rx3p0eXdBgjx25I,8973
36
37
  lizard_languages/gdscript.py,sha256=KwlGoODilQnFgXvODpq_XlA6fV3hGbN9fd7bsiEUn78,637
37
- lizard_languages/go.py,sha256=iU2wZ0Iuo4OifscZhoHS_jDbdGYxquocqmvYX0l9MnE,1358
38
+ lizard_languages/go.py,sha256=sntz0jOEuj4klPipoTFd16UDK1fAUQfwK7YX_cLMZAc,1346
38
39
  lizard_languages/golike.py,sha256=vRIfjTVvc0VmJf27lTOLht55ZF1AQ9wn0Fvu-9WabWk,2858
39
- lizard_languages/java.py,sha256=i6CWkpUpZDMkvYf_W7DY609oP7uWCtv8T8OdSXxl0BU,6453
40
- lizard_languages/javascript.py,sha256=qqxocZbZ6ivchhswRBBSjEJKiTClnm9ScOy4nlX4JKA,318
41
- lizard_languages/js_style_language_states.py,sha256=AdiehzSziZIbuWMomYmchNsJr7ZqtxN6l29osj3SA3A,4431
40
+ lizard_languages/java.py,sha256=HQBTZjUKbUJwgmtLYIzJrWtPpFP3ZdBP_NJK7YOXZC0,6424
41
+ lizard_languages/javascript.py,sha256=vniCNMW-ea9Jpv6c8qCcjLVDYjT8VztjXigp5XRWt0E,317
42
+ lizard_languages/js_style_language_states.py,sha256=6mLrHfvDC4oHttKsSRGkE-ayG8uKSEm4E4rlhaUN5lA,6396
42
43
  lizard_languages/js_style_regex_expression.py,sha256=Xgyogch4xElYtCG4EnBKvalHTl3tjRPcIIcIQRRd61I,1970
43
- lizard_languages/jsx.py,sha256=DMagQSEvXZwy9AUj8B2l39PqgcAoZntAmEm67IKF7iM,8890
44
+ lizard_languages/jsx.py,sha256=TLH93qrZO2r_tiiv1XBIbw2_mbUMjwCZAuBz5vG5oBw,11696
44
45
  lizard_languages/kotlin.py,sha256=v_o2orEzA5gB9vM_0h-E4QXjrc5Yum-0K6W6_laOThc,2844
45
46
  lizard_languages/lua.py,sha256=3nqBcunBzJrhv4Iqaf8xvbyqxZy3aSxJ-IiHimHFlac,1573
46
47
  lizard_languages/objc.py,sha256=2a1teLdaXZBtCeFiIZer1j_sVx9LZ1CbF2XfnqlvLmk,2319
47
- lizard_languages/perl.py,sha256=WUHuO9lIBRpL0fJKpAtEVsEatGMqzY2WymF8kSdPGRs,12143
48
- lizard_languages/php.py,sha256=gAI-UZckwjOKJ7HbINWS5w5iYsNRVyONb092dAsRLhs,10096
49
- lizard_languages/python.py,sha256=1e0dKbl82z-i_5dswkKaFIV5WuQDKztPHh9UNmSTLyE,3848
48
+ lizard_languages/perl.py,sha256=136w620eECe_t-kmlRUGrsZSxQNo2JQ_PZTSQfCSmHY,11987
49
+ lizard_languages/php.py,sha256=UV40p8WzNC64NQ5qElPKzcFTjVt5kenLMz-eKYlcnMY,9940
50
+ lizard_languages/python.py,sha256=4AK0JAPBSM7eKrqDdSaHfewCGAYMzdhUhi5kZK4u23k,3847
50
51
  lizard_languages/ruby.py,sha256=HL1ZckeuUUJU3QSVAOPsG_Zsl0C6X2PX5_VaWqclzkM,2277
51
52
  lizard_languages/rubylike.py,sha256=dAGZ2wqW8nqaESMU8HkeR9gwQ-q9fmZqE6AANvVZD1Q,3426
52
- lizard_languages/rust.py,sha256=DG2RkR9JWwcD6gIw-BPzg-Yo_lxQtSvfgHioFWIQm9o,610
53
+ lizard_languages/rust.py,sha256=vfBBktNwIZL8AiaAJ5EW8o4rL7_TwjDo3QhpfxxecfE,596
53
54
  lizard_languages/scala.py,sha256=6Jr_TG945VYqB3o5weD7jN7S4beHt4aVj3r-fmKeMAM,1316
54
- lizard_languages/script_language.py,sha256=sDw-ssikPaouKjeKwdyGoAyv6F8650lh2e1X_FP8TN0,1200
55
+ lizard_languages/script_language.py,sha256=SKe45AbO6Z-axbN8KW_g7jf9g7YTXZ6dWzJj4ubDsM8,1172
55
56
  lizard_languages/solidity.py,sha256=Z0GD7U5bI5eUikdy7m_iKWeFD5yXRYq4r3zycscOhJQ,553
56
57
  lizard_languages/swift.py,sha256=p8S2OAkQOx9YQ02yhoVXFkr7pMqUH1Nb3RVXPHRU_9M,2450
57
58
  lizard_languages/tnsdl.py,sha256=pGcalA_lHY362v2wwPS86seYBOOBBjvmU6vd4Yy3A9g,2803
58
- lizard_languages/tsx.py,sha256=4fhi3sUnvjU4XfGaFUEGOdAYOx6ThGpIMOIS9VSL6cQ,1160
59
+ lizard_languages/tsx.py,sha256=AgpULWV9VEF4wJGtNZeeOl6NuTiQOstM8P3jObz-_xY,1249
59
60
  lizard_languages/ttcn.py,sha256=ygjw_raBmPF-4mgoM8m6CAdyEMpTI-n1kZJK1RL4Vxo,2131
60
- lizard_languages/typescript.py,sha256=P_rphg5AXJAk9QetmvVKc2911ilPmiRa0Qa5fHWleJg,3829
61
+ lizard_languages/typescript.py,sha256=fiEiGs0VO8uofsLcOD7t8UzKp0Un9SSbfQTU87mhNVo,5626
61
62
  lizard_languages/vue.py,sha256=KXUBUo2R1zNF8Pffrz_KsQEN44m5XFRMoGXylxKUeT0,1038
62
63
  lizard_languages/zig.py,sha256=NX1iyBstBuJFeAGBOAIaRfrmeBREne2HX6Pt4fXZZTQ,586
63
- lizard-1.17.29.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
64
- lizard-1.17.29.dist-info/METADATA,sha256=ZhoBV3shfqxrAcPAQgNRdyMHnTrkQdON2PEY5owuMGo,16130
65
- lizard-1.17.29.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
66
- lizard-1.17.29.dist-info/entry_points.txt,sha256=ZBqPhu-J3NoGGW5vn2Gfyoo0vdVlgBgM-wlNm0SGYUQ,39
67
- lizard-1.17.29.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
68
- lizard-1.17.29.dist-info/RECORD,,
64
+ lizard-1.17.31.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
65
+ lizard-1.17.31.dist-info/METADATA,sha256=8P82zQ9bK0enmWJ9bOSezN6Ly1oO5WvgfWO6i0jSEx4,16230
66
+ lizard-1.17.31.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
67
+ lizard-1.17.31.dist-info/entry_points.txt,sha256=ZBqPhu-J3NoGGW5vn2Gfyoo0vdVlgBgM-wlNm0SGYUQ,39
68
+ lizard-1.17.31.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
69
+ lizard-1.17.31.dist-info/RECORD,,
lizard.py CHANGED
@@ -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:
lizard_ext/__init__.py CHANGED
@@ -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=" ")
lizard_ext/version.py CHANGED
@@ -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
lizard_languages/go.py CHANGED
@@ -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)
lizard_languages/java.py CHANGED
@@ -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:])
lizard_languages/jsx.py CHANGED
@@ -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
lizard_languages/perl.py CHANGED
@@ -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)
lizard_languages/php.py CHANGED
@@ -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
 
lizard_languages/rust.py CHANGED
@@ -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
 
lizard_languages/tsx.py CHANGED
@@ -3,7 +3,7 @@ Language parser for TSX
3
3
  '''
4
4
 
5
5
  from .typescript import TypeScriptReader
6
- from .jsx import JSXMixin
6
+ from .jsx import JSXMixin, JSXTypeScriptStates
7
7
  from .js_style_regex_expression import js_style_regex_expression
8
8
 
9
9
 
@@ -24,7 +24,8 @@ class TSXReader(TypeScriptReader, JSXMixin):
24
24
 
25
25
  def __init__(self, context):
26
26
  super(TSXReader, self).__init__(context)
27
- # No need for parallel states since JSX handling is in the mixin
27
+ # Use JSXTypeScriptStates for better handling of TSX specific features
28
+ self.parallel_states = [JSXTypeScriptStates(context)]
28
29
 
29
30
  def _expecting_func_opening_bracket(self, token):
30
31
  # Handle TypeScript arrow functions with type annotations in JSX attributes
@@ -62,21 +62,60 @@ class TypeScriptReader(CodeReader, CCppCommentsMixin):
62
62
  @staticmethod
63
63
  @js_style_regex_expression
64
64
  def generate_tokens(source_code, addition='', token_class=None):
65
- addition = addition +\
66
- r"|(?:\$\w+)" + \
67
- r"|(?:\w+\?)" + \
68
- r"|`.*?`"
69
- js_tokenizer = JSTokenizer()
70
- for token in CodeReader.generate_tokens(
71
- source_code, addition, token_class):
72
- for tok in js_tokenizer(token):
73
- yield tok
65
+ def split_template_literal(token, quote):
66
+ content = token[1:-1]
67
+ i = 0
68
+ # Special case for double-quoted strings starting with ${
69
+ if quote == '"' and content.startswith('${'):
70
+ yield '""'
71
+ while i < len(content):
72
+ idx = content.find('${', i)
73
+ if idx == -1:
74
+ if i < len(content):
75
+ yield quote + content[i:] + quote
76
+ break
77
+ if idx > i and not (quote == '"' and idx == 0 and content.startswith('${')):
78
+ yield quote + content[i:idx] + quote
79
+ yield '${'
80
+ i = idx + 2
81
+ expr_start = i
82
+ brace_count = 1
83
+ while i < len(content) and brace_count > 0:
84
+ if content[i] == '{':
85
+ brace_count += 1
86
+ elif content[i] == '}':
87
+ brace_count -= 1
88
+ i += 1
89
+ expr = content[expr_start:i-1]
90
+ yield expr
91
+ yield '}'
92
+ content = content[i:]
93
+ i = 0
94
+ # Restore original addition pattern for template literals
95
+ addition = addition + r"|(?:\$\w+)" + r"|(?:\w+\?)" + r"|`.*?`"
96
+ for token in CodeReader.generate_tokens(source_code, addition, token_class):
97
+ if (
98
+ isinstance(token, str)
99
+ and (token.startswith('`') or token.startswith('"'))
100
+ and token[0] == token[-1]
101
+ and '${' in token
102
+ ):
103
+ quote = token[0]
104
+ for t in split_template_literal(token, quote):
105
+ yield t
106
+ continue
107
+ yield token
74
108
 
75
109
 
76
110
  class TypeScriptStates(JavaScriptStyleLanguageStates):
77
111
  def __init__(self, context):
78
112
  super().__init__(context)
79
113
 
114
+ def statemachine_before_return(self):
115
+ # Ensure the main function is closed at the end
116
+ if self.started_function:
117
+ self._pop_function_from_stack()
118
+
80
119
  def _state_global(self, token):
81
120
  if not self.as_object:
82
121
  if token == ':':
@@ -113,11 +152,15 @@ class TypeScriptTypeAnnotationStates(CodeStateMachine):
113
152
  def _state_simple_type(self, token):
114
153
  if token == '<':
115
154
  self.next(self._state_generic_type, token)
116
- elif token in '{=;':
155
+ elif token in '{=;)':
117
156
  self.saved_token = token
118
157
  self.statemachine_return()
119
158
  elif token == '(':
120
159
  self.next(self._function_type_annotation, token)
160
+ elif token == '=>':
161
+ # Handle arrow function after type annotation
162
+ self.saved_token = token
163
+ self.statemachine_return()
121
164
 
122
165
  @CodeStateMachine.read_inside_brackets_then("{}")
123
166
  def _inline_type_annotation(self, _):
@@ -126,7 +169,7 @@ class TypeScriptTypeAnnotationStates(CodeStateMachine):
126
169
  @CodeStateMachine.read_inside_brackets_then("<>")
127
170
  def _state_generic_type(self, token):
128
171
  self.statemachine_return()
129
-
172
+
130
173
  @CodeStateMachine.read_inside_brackets_then("()")
131
174
  def _function_type_annotation(self, _):
132
175
  self.statemachine_return()