lizard 1.17.14__py2.py3-none-any.whl → 1.17.16__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.14
3
+ Version: 1.17.16
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/
@@ -29,6 +29,7 @@ Classifier: Programming Language :: Python :: 3.10
29
29
  Classifier: Programming Language :: Python :: 3.11
30
30
  License-File: LICENSE.txt
31
31
  Requires-Dist: pygments
32
+ Requires-Dist: pathspec
32
33
 
33
34
  |Web Site| Lizard
34
35
  =================
@@ -54,6 +55,7 @@ A list of supported languages:
54
55
  - C# (C Sharp)
55
56
  - JavaScript (With ES6 and JSX)
56
57
  - TypeScript (With TSX)
58
+ - VueJS
57
59
  - Objective-C
58
60
  - Swift
59
61
  - Python
@@ -137,6 +139,13 @@ Exclude anything in the tests folder:
137
139
 
138
140
  lizard mySource/ -x"./tests/*"
139
141
 
142
+ Use .gitignore file:
143
+
144
+ ::
145
+
146
+ lizard mySource/
147
+
148
+ If there is a .gitignore file in the given path, lizard will automatically use it as an additional filter to exclude files that match the gitignore patterns. This is useful when you want to analyze only the tracked files in your git repository.
140
149
 
141
150
  Options
142
151
  ~~~~~~~
@@ -1,7 +1,7 @@
1
- lizard.py,sha256=WfzR6yEkclgbV2CTpDZrwWDnmNUnXyd6BIL6LR--QWk,38609
1
+ lizard.py,sha256=m-wnNDtLaug8yvK65Ex1IMRTubo6uFQAfdcdRN9x5Mg,39927
2
2
  lizard_ext/__init__.py,sha256=Cz0pvH6QHyadK-rAeeEhQVsm0hW5M-fBQgV4FBaX9zk,500
3
3
  lizard_ext/auto_open.py,sha256=byD_RbeVhvSUhR2bJMRitvA3zcKEapFwv0-XaDJ6GFo,1096
4
- lizard_ext/csvoutput.py,sha256=IGTGICiK_IJcldRGDhd_jNDohxFWJEWYOVxkemaY20Q,3051
4
+ lizard_ext/csvoutput.py,sha256=43fhmo8kB85qcdujCwySGNuTC4FkKUPLqIApPeljPnA,2663
5
5
  lizard_ext/default_ordered_dict.py,sha256=YbVz6nPlQ6DjWc_EOFBz6AJN2XLo9dpnUdeyejQvUDE,831
6
6
  lizard_ext/extension_base.py,sha256=rnjUL2mqSGToUVYydju7fa8ZwynLPY8S1F17gIJP55I,346
7
7
  lizard_ext/htmloutput.py,sha256=oavhEzCIJtoJrFveiWIp0pMAOwg4pukJ6l07IpmPiag,4426
@@ -25,22 +25,22 @@ lizard_ext/lizardns.py,sha256=8pztUoRS_UWN24MawwxeHEJgYh49id5PWODUBb6O72U,4184
25
25
  lizard_ext/lizardoutside.py,sha256=FGm2tbBZ17-2OCgmQlD-vobUCfQKb0FAygf86eM3xuM,336
26
26
  lizard_ext/lizardstatementcount.py,sha256=xYk6ixSIItSE1BWQXzrWmduFgGhA3VR817SNKLffyVQ,1182
27
27
  lizard_ext/lizardwordcount.py,sha256=2QYXD7-AtkkgAbi9VSidunMbSsGQ7MKYb6IT-bS-cok,7575
28
- lizard_ext/version.py,sha256=Q_IWUX-GJQsIg-nJXStY9pJaspkNNkBE5mLUB-v3UKw,182
28
+ lizard_ext/version.py,sha256=s8Ej-6FrgTUH06Kp-lskbDvVReimxGKBZNzim7QUtbI,182
29
29
  lizard_ext/xmloutput.py,sha256=-cbh0he4O_X-wX56gkv9AnSPNN0qvR7FACqlBeezUS4,5609
30
- lizard_languages/__init__.py,sha256=PsUTjwesQ_Kk2SLsaI2Sn7ePtWLEQs2XW6EOPJJybcA,1427
30
+ lizard_languages/__init__.py,sha256=2mvrPWMJVCq981kMjoAPEi2O07C8vo-vUWOG_wp7vKQ,1473
31
31
  lizard_languages/clike.py,sha256=Lnwkma-jEu8FNVEPqTAKPkz1Hi0T_dP7H9Jyq0Bd0kQ,10725
32
- lizard_languages/code_reader.py,sha256=sMkHfIjZTom3d5gINfm42vUY28kTOOdxEUboc7eRMUU,6211
33
- lizard_languages/csharp.py,sha256=lVi-g-0JjHagqDR5r-lRdi2BuCdtR5aer9CYY-Nzw44,478
32
+ lizard_languages/code_reader.py,sha256=P0PkE4QZBWOEj6cFHA4hj3hcLZLcGnqH31EmtltxlaE,6240
33
+ lizard_languages/csharp.py,sha256=EfFAIOIcJXUUhXTlZApXGSlzG34NZvHM9OSe6m7hpv0,2141
34
34
  lizard_languages/erlang.py,sha256=7YJS2cMyXDKEV_kpH8DzBARxFCFcjKuTOPSQ3K52auU,3860
35
35
  lizard_languages/fortran.py,sha256=KATDsnfjob5W3579A_VxbwrbTkK7Rx3p0eXdBgjx25I,8973
36
36
  lizard_languages/gdscript.py,sha256=KwlGoODilQnFgXvODpq_XlA6fV3hGbN9fd7bsiEUn78,637
37
37
  lizard_languages/go.py,sha256=dSbWxtWve7KSRW5i9w4DxhODtJ72EcBNrgp-7Xhn0Sg,465
38
38
  lizard_languages/golike.py,sha256=vRIfjTVvc0VmJf27lTOLht55ZF1AQ9wn0Fvu-9WabWk,2858
39
- lizard_languages/java.py,sha256=mfXCRicmZFZFxPW7QuTOQtAKzOZD3d-uh4tiALcGdX4,1186
40
- lizard_languages/javascript.py,sha256=dc_P3fS39S-5sefTNKPzi7Hype7WkM3rMKONpd1iklM,4143
41
- lizard_languages/js_style_language_states.py,sha256=2_dgrXLe113xEdfATByioohGquW428pPo9gI3I1duS4,4257
42
- lizard_languages/js_style_regex_expression.py,sha256=1d4eK5RR0GBAFg2eDb2cKA_YHIYlxjhG7PXAuMOKnRA,743
43
- lizard_languages/jsx.py,sha256=trMT1ruo6Wi3kJB0qQYJL8Ms0GFEapqD9_SaTe0vCDk,785
39
+ lizard_languages/java.py,sha256=N9V1nbK_kGN4T2A1xpU1f9wZxGL6UEwVuOCA7D_tjPQ,3036
40
+ lizard_languages/javascript.py,sha256=sGZeTqFqCjcl87ncR-Rmj-HTp-4s4ptoTMxeDgc8Jvw,522
41
+ lizard_languages/js_style_language_states.py,sha256=Ie0eA15rd0gfahdNzUlSkQa-o_j5idTIB82JU32c378,4165
42
+ lizard_languages/js_style_regex_expression.py,sha256=Xgyogch4xElYtCG4EnBKvalHTl3tjRPcIIcIQRRd61I,1970
43
+ lizard_languages/jsx.py,sha256=tNoGFQ9hap2yS5NjRPgxMRlYZY-k5Gn07yI0o815Ghk,3566
44
44
  lizard_languages/kotlin.py,sha256=v_o2orEzA5gB9vM_0h-E4QXjrc5Yum-0K6W6_laOThc,2844
45
45
  lizard_languages/lua.py,sha256=3nqBcunBzJrhv4Iqaf8xvbyqxZy3aSxJ-IiHimHFlac,1573
46
46
  lizard_languages/objc.py,sha256=2a1teLdaXZBtCeFiIZer1j_sVx9LZ1CbF2XfnqlvLmk,2319
@@ -50,17 +50,18 @@ lizard_languages/ruby.py,sha256=gUrFsaS6ZJbjb9CixBHP9n0BxGOb8HsDEg7jJ2bz_80,2187
50
50
  lizard_languages/rubylike.py,sha256=dAGZ2wqW8nqaESMU8HkeR9gwQ-q9fmZqE6AANvVZD1Q,3426
51
51
  lizard_languages/rust.py,sha256=DG2RkR9JWwcD6gIw-BPzg-Yo_lxQtSvfgHioFWIQm9o,610
52
52
  lizard_languages/scala.py,sha256=6Jr_TG945VYqB3o5weD7jN7S4beHt4aVj3r-fmKeMAM,1316
53
- lizard_languages/script_language.py,sha256=FOzB9Rg5-XTnFDeeRRyIrp2rDomRDHhaPsxsxeCx9GU,539
53
+ lizard_languages/script_language.py,sha256=UUO3Wjkoa-ZqwwvcwvhOr5tg1rVavcrEYx3dNdoYSBc,531
54
54
  lizard_languages/solidity.py,sha256=Z0GD7U5bI5eUikdy7m_iKWeFD5yXRYq4r3zycscOhJQ,553
55
55
  lizard_languages/swift.py,sha256=p8S2OAkQOx9YQ02yhoVXFkr7pMqUH1Nb3RVXPHRU_9M,2450
56
56
  lizard_languages/tnsdl.py,sha256=pGcalA_lHY362v2wwPS86seYBOOBBjvmU6vd4Yy3A9g,2803
57
- lizard_languages/tsx.py,sha256=Ey7IxFfF2nwt-MJisEO2FaIOl47m4EPf3jF6x62YTVA,1332
57
+ lizard_languages/tsx.py,sha256=aTxmM_-u-R-pRVXRVyITJxrrFZfCeMe9kxMgaoL-qfk,706
58
58
  lizard_languages/ttcn.py,sha256=ygjw_raBmPF-4mgoM8m6CAdyEMpTI-n1kZJK1RL4Vxo,2131
59
- lizard_languages/typescript.py,sha256=fyJeimadxFXqtfTt8MyVzAg6XqpVZeFJ1qp8RGurXbk,2594
59
+ lizard_languages/typescript.py,sha256=c_Kez4pOGMXhdo6q3HT-clEJDP7LuRIbBkkJSipComU,3568
60
+ lizard_languages/vue.py,sha256=KXUBUo2R1zNF8Pffrz_KsQEN44m5XFRMoGXylxKUeT0,1038
60
61
  lizard_languages/zig.py,sha256=NX1iyBstBuJFeAGBOAIaRfrmeBREne2HX6Pt4fXZZTQ,586
61
- lizard-1.17.14.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
62
- lizard-1.17.14.dist-info/METADATA,sha256=RhLjrf5029-qdpfH3yGvH35sEaktWyHVmypIZHzhniA,15419
63
- lizard-1.17.14.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
64
- lizard-1.17.14.dist-info/entry_points.txt,sha256=ZBqPhu-J3NoGGW5vn2Gfyoo0vdVlgBgM-wlNm0SGYUQ,39
65
- lizard-1.17.14.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
66
- lizard-1.17.14.dist-info/RECORD,,
62
+ lizard-1.17.16.dist-info/LICENSE.txt,sha256=05ZjgQ8Cl1dD9p0BhW-Txzkc5rhCogGJVEuf1GT2Y_M,1303
63
+ lizard-1.17.16.dist-info/METADATA,sha256=m4ndIad8zK__BY3lidM_6WBys_A7GQY0TNDWFq8XIk8,15745
64
+ lizard-1.17.16.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
65
+ lizard-1.17.16.dist-info/entry_points.txt,sha256=ZBqPhu-J3NoGGW5vn2Gfyoo0vdVlgBgM-wlNm0SGYUQ,39
66
+ lizard-1.17.16.dist-info/top_level.txt,sha256=5NTrTaOLhHuTzXaGcZPKfuaOgUv7WafNGe0Zl5aycpg,35
67
+ lizard-1.17.16.dist-info/RECORD,,
lizard.py CHANGED
@@ -298,8 +298,7 @@ class FunctionInfo(Nesting): # pylint: disable=R0902
298
298
  return self.name.split('::')[-1]
299
299
 
300
300
  location = property(lambda self:
301
- " %(name)s@%(start_line)s-%(end_line)s@%(filename)s"
302
- % self.__dict__)
301
+ f" {self.name}@{self.start_line}-{self.end_line}@{self.filename}")
303
302
 
304
303
  parameter_count = property(lambda self: len(self.parameters))
305
304
 
@@ -723,17 +722,13 @@ class OutputScheme(object):
723
722
  if e.get("avg_caption", None)])
724
723
 
725
724
  def clang_warning_format(self):
726
- return (
727
- "{f.filename}:{f.start_line}: warning: {f.name} has " +
728
- ", ".join([
729
- "{{f.{ext[value]}}} {caption}"
730
- .format(ext=e, caption=e['caption'].strip())
731
- for e in self.items[:-1]
732
- ]))
725
+ return ("{f.filename}:{f.start_line}: warning: {f.name} has {f.nloc} NLOC, "
726
+ "{f.cyclomatic_complexity} CCN, {f.token_count} token, {f.parameter_count} PARAM, "
727
+ "{f.length} length, {f.max_nesting_depth} ND")
733
728
 
734
729
  def msvs_warning_format(self):
735
730
  return (
736
- "{f.filename}({f.start_line}): warning: {f.name} has " +
731
+ "{f.filename}({f.start_line}): warning: {f.name} ({f.long_name}) has " +
737
732
  ", ".join([
738
733
  "{{f.{ext[value]}}} {caption}"
739
734
  .format(ext=e, caption=e['caption'].strip())
@@ -901,14 +896,43 @@ def md5_hash_file(full_path_name):
901
896
  def get_all_source_files(paths, exclude_patterns, lans):
902
897
  '''
903
898
  Function counts md5 hash for the given file and checks if it isn't a
904
- duplicate using set of hashes for previous files '''
899
+ duplicate using set of hashes for previous files.
900
+
901
+ If a .gitignore file is found in any of the given paths, it will be used
902
+ to filter out files that match the gitignore patterns.
903
+ '''
905
904
  hash_set = set()
905
+ gitignore_spec = None
906
+ base_path = None
907
+
908
+ def _load_gitignore():
909
+ nonlocal gitignore_spec, base_path
910
+ try:
911
+ import pathspec
912
+ for path in paths:
913
+ gitignore_path = os.path.join(path, '.gitignore')
914
+ if os.path.exists(gitignore_path):
915
+ with open(gitignore_path, 'r') as gitignore_file:
916
+ # Read lines and strip whitespace and empty lines
917
+ patterns = [line.strip() for line in gitignore_file.readlines()]
918
+ patterns = [p for p in patterns if p and not p.startswith('#')]
919
+ gitignore_spec = pathspec.PathSpec.from_lines('gitwildmatch', patterns)
920
+ base_path = path
921
+ break
922
+ except ImportError:
923
+ pass
906
924
 
907
925
  def _support(reader):
908
926
  return not lans or set(lans).intersection(
909
927
  reader.language_names)
910
928
 
911
929
  def _validate_file(pathname):
930
+ if gitignore_spec is not None and base_path is not None:
931
+ rel_path = os.path.relpath(pathname, base_path)
932
+ # Normalize path separators for consistent matching
933
+ rel_path = rel_path.replace(os.sep, '/')
934
+ if gitignore_spec.match_file(rel_path):
935
+ return False
912
936
  return (
913
937
  pathname in paths or (
914
938
  get_reader_for(pathname) and
@@ -931,6 +955,7 @@ def get_all_source_files(paths, exclude_patterns, lans):
931
955
  for filename in files:
932
956
  yield os.path.join(root, filename)
933
957
 
958
+ _load_gitignore()
934
959
  return filter(_validate_file, all_listed_files(paths))
935
960
 
936
961
 
@@ -972,7 +997,7 @@ def parse_args(argv):
972
997
  if inferred_printer:
973
998
  if not opt.printer:
974
999
  opt.printer = inferred_printer
975
- else:
1000
+ elif opt.printer != inferred_printer:
976
1001
  msg = "Warning: overriding output file extension.\n"
977
1002
  sys.stderr.write(msg)
978
1003
  return opt
lizard_ext/csvoutput.py CHANGED
@@ -36,9 +36,9 @@ def csv_output(result, options):
36
36
  if options.verbose:
37
37
  extension_caption = ""
38
38
  for caption in extension_captions:
39
- extension_caption = "{},{}".format(extension_caption, caption)
40
- print("NLOC,CCN,token,PARAM,length,location,file,function," +
41
- "long_name,start,end{}".format(extension_caption))
39
+ extension_caption = f"{extension_caption},{caption}"
40
+ print(f"NLOC,CCN,token,PARAM,length,location,file,function," +
41
+ f"long_name,start,end{extension_caption}")
42
42
 
43
43
  for source_file in result:
44
44
  if source_file:
@@ -46,25 +46,11 @@ def csv_output(result, options):
46
46
  if source_function:
47
47
  extension_string = ''
48
48
  for variable in extension_variables:
49
- extension_string = '{},{}'.\
50
- format(extension_string,
51
- source_function.__getattribute__(variable))
52
- print('{},{},{},{},{},"{}","{}","{}","{}",{},{}{}'.format(
53
- source_function.nloc,
54
- source_function.cyclomatic_complexity,
55
- source_function.token_count,
56
- len(source_function.parameters),
57
- source_function.length,
58
- "{}@{}-{}@{}".format(
59
- source_function.name.replace("\"", "'"),
60
- source_function.start_line,
61
- source_function.end_line,
62
- source_file.filename
63
- ),
64
- source_file.filename,
65
- source_function.name.replace("\"", "'"),
66
- source_function.long_name.replace("\"", "'"),
67
- source_function.start_line,
68
- source_function.end_line,
69
- extension_string
70
- ))
49
+ extension_string = f"{extension_string},{source_function.__getattribute__(variable)}"
50
+ print(f"{source_function.nloc},{source_function.cyclomatic_complexity},"
51
+ f"{source_function.token_count},{len(source_function.parameters)},"
52
+ f'{source_function.length},"{source_function.name.replace(chr(34), chr(39))}@'
53
+ f'{source_function.start_line}-{source_function.end_line}@{source_file.filename}",'
54
+ f'"{source_file.filename}","{source_function.name.replace(chr(34), chr(39))}",'
55
+ f'"{source_function.long_name.replace(chr(34), chr(39))}",'
56
+ f"{source_function.start_line},{source_function.end_line}{extension_string}")
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.14"
6
+ version = "1.17.16"
@@ -23,6 +23,7 @@ from .fortran import FortranReader
23
23
  from .solidity import SolidityReader
24
24
  from .jsx import JSXReader
25
25
  from .tsx import TSXReader
26
+ from .vue import VueReader
26
27
 
27
28
 
28
29
  def languages():
@@ -50,6 +51,7 @@ def languages():
50
51
  ZigReader,
51
52
  JSXReader,
52
53
  TSXReader,
54
+ VueReader,
53
55
  ]
54
56
 
55
57
 
@@ -8,7 +8,7 @@ from functools import reduce
8
8
  from operator import or_
9
9
 
10
10
 
11
- class CodeStateMachine(object):
11
+ class CodeStateMachine:
12
12
  """ the state machine """
13
13
  # pylint: disable=R0903
14
14
  # pylint: disable=R0902
@@ -50,6 +50,7 @@ class CodeStateMachine(object):
50
50
  self.next(self.saved_state)
51
51
  if self.callback:
52
52
  self.callback()
53
+ self.callback = None
53
54
  self.last_token = token
54
55
  if self.to_exit:
55
56
  return True
@@ -2,7 +2,7 @@
2
2
  Language parser for C Sharp
3
3
  '''
4
4
 
5
- from .clike import CLikeReader
5
+ from .clike import CLikeReader, CLikeStates, CLikeNestingStackStates
6
6
 
7
7
 
8
8
  class CSharpReader(CLikeReader):
@@ -14,7 +14,49 @@ class CSharpReader(CLikeReader):
14
14
  _conditions = set(['if', 'for', 'while', '&&', '||', '?', 'catch',
15
15
  'case', '??'])
16
16
 
17
+ def __init__(self, context):
18
+ super(CSharpReader, self).__init__(context)
19
+ self.parallel_states = [
20
+ CSharpStates(context),
21
+ CLikeNestingStackStates(context)
22
+ ]
23
+
17
24
  @staticmethod
18
25
  def generate_tokens(source_code, addition='', token_class=None):
19
26
  return CLikeReader.generate_tokens(
20
27
  source_code, r"|(?:\?\?)", token_class)
28
+
29
+
30
+ class CSharpStates(CLikeStates):
31
+ def __init__(self, context):
32
+ super(CSharpStates, self).__init__(context)
33
+ self.in_primary_constructor = False
34
+ self.class_name = None
35
+
36
+ def try_new_function(self, name):
37
+ if not self.in_primary_constructor:
38
+ super(CSharpStates, self).try_new_function(name)
39
+ if self.class_name and self.context.current_function:
40
+ self.context.current_function.name = f"{self.class_name}::{name}"
41
+
42
+ def _state_global(self, token):
43
+ if token in ("class", "struct", "record"):
44
+ self.class_name = None
45
+ self._state = self._state_class_declaration
46
+ else:
47
+ super(CSharpStates, self)._state_global(token)
48
+
49
+ def _state_class_declaration(self, token):
50
+ if token == '(': # Primary constructor
51
+ self.in_primary_constructor = True
52
+ self._state = self._state_primary_constructor
53
+ elif token == '{':
54
+ self._state = self._state_global
55
+ elif token[0].isalpha():
56
+ if not self.class_name: # Only set class name if not already set
57
+ self.class_name = token
58
+
59
+ @CLikeStates.read_inside_brackets_then("()", "_state_class_declaration")
60
+ def _state_primary_constructor(self, _):
61
+ """Skip primary constructor parameters without counting them as a function"""
62
+ self.in_primary_constructor = False
lizard_languages/java.py CHANGED
@@ -19,19 +19,39 @@ class JavaReader(CLikeReader):
19
19
 
20
20
 
21
21
  class JavaStates(CLikeStates): # pylint: disable=R0903
22
+ def __init__(self, context):
23
+ super(JavaStates, self).__init__(context)
24
+ self.class_name = None
25
+ self.is_record = False
26
+ self.in_record_constructor = False
27
+
22
28
  def _state_old_c_params(self, token):
23
29
  if token == '{':
24
30
  self._state_dec_to_imp(token)
25
31
 
26
32
  def try_new_function(self, name):
33
+ # Don't create a function for record compact constructor
34
+ if self.is_record and name == self.class_name:
35
+ self.in_record_constructor = True
36
+ self._state = self._state_record_compact_constructor
37
+ return
27
38
  self.context.try_new_function(name)
28
39
  self._state = self._state_function
40
+ if self.class_name and self.context.current_function:
41
+ self.context.current_function.name = f"{self.class_name}::{name}"
29
42
 
30
43
  def _state_global(self, token):
31
44
  if token == '@':
32
45
  self._state = self._state_decorator
33
46
  return
34
- super(JavaStates, self)._state_global(token)
47
+ if token in ("class", "record", "enum"):
48
+ self.class_name = None
49
+ self.is_record = token == "record"
50
+ self.in_record_constructor = False
51
+ self._state = self._state_class_declaration
52
+ return
53
+ if not self.in_record_constructor: # Only process as potential function if not in record constructor
54
+ super(JavaStates, self)._state_global(token)
35
55
 
36
56
  def _state_decorator(self, _):
37
57
  self._state = self._state_post_decorator
@@ -42,3 +62,28 @@ class JavaStates(CLikeStates): # pylint: disable=R0903
42
62
  else:
43
63
  self._state = self._state_global
44
64
  self._state(token)
65
+
66
+ def _state_class_declaration(self, token):
67
+ if token == '{':
68
+ self._state = self._state_global
69
+ elif token == '(': # Record parameters
70
+ self._state = self._state_record_parameters
71
+ elif token[0].isalpha():
72
+ if not self.class_name: # Only set class name if not already set
73
+ self.class_name = token
74
+
75
+ def _state_record_parameters(self, token):
76
+ if token == ')':
77
+ self._state = self._state_class_declaration
78
+
79
+ def _state_record_compact_constructor(self, token):
80
+ if token == '{':
81
+ self._state = self._state_record_constructor_body
82
+ return
83
+ self._state = self._state_global
84
+ self._state(token)
85
+
86
+ def _state_record_constructor_body(self, token):
87
+ if token == '}':
88
+ self.in_record_constructor = False
89
+ self._state = self._state_global
@@ -6,148 +6,14 @@ from .code_reader import CodeReader
6
6
  from .clike import CCppCommentsMixin
7
7
  from .js_style_regex_expression import js_style_regex_expression
8
8
  from .js_style_language_states import JavaScriptStyleLanguageStates
9
+ from .typescript import TypeScriptReader, JSTokenizer
9
10
 
10
11
 
11
- class JavaScriptReader(CodeReader, CCppCommentsMixin):
12
+ class JavaScriptReader(TypeScriptReader):
12
13
  # pylint: disable=R0903
13
14
 
14
15
  ext = ['js']
15
16
  language_names = ['javascript', 'js']
16
17
 
17
- @staticmethod
18
- @js_style_regex_expression
19
- def generate_tokens(source_code, addition='', token_class=None):
20
- addition = addition +\
21
- r"|(?:\$\w+)" + \
22
- r"|`.*?`"
23
- js_tokenizer = JSTokenizer()
24
- for token in CodeReader.generate_tokens(
25
- source_code, addition, token_class):
26
- for tok in js_tokenizer(token):
27
- yield tok
28
-
29
18
  def __init__(self, context):
30
19
  super(JavaScriptReader, self).__init__(context)
31
- self.parallel_states = [JavaScriptStyleLanguageStates(context)]
32
-
33
-
34
- class Tokenizer(object):
35
- def __init__(self):
36
- self.sub_tokenizer = None
37
- self._ended = False
38
-
39
- def __call__(self, token):
40
- if self.sub_tokenizer:
41
- for tok in self.sub_tokenizer(token):
42
- yield tok
43
- if self.sub_tokenizer._ended:
44
- self.sub_tokenizer = None
45
- return
46
- for tok in self.process_token(token):
47
- yield tok
48
-
49
- def stop(self):
50
- self._ended = True
51
-
52
- def process_token(self, token):
53
- pass
54
-
55
-
56
- class JSTokenizer(Tokenizer):
57
- def __init__(self):
58
- super(JSTokenizer, self).__init__()
59
- self.depth = 1
60
-
61
- def process_token(self, token):
62
- if token == "<":
63
- self.sub_tokenizer = XMLTagWithAttrTokenizer()
64
- return
65
- if token == "{":
66
- self.depth += 1
67
- elif token == "}":
68
- self.depth -= 1
69
- if self.depth == 0:
70
- self.stop()
71
- return
72
- yield token
73
-
74
-
75
- class XMLTagWithAttrTokenizer(Tokenizer):
76
- def __init__(self):
77
- super(XMLTagWithAttrTokenizer, self).__init__()
78
- self.tag = None
79
- self.state = self._global_state
80
- self.cache = ['<']
81
-
82
- def process_token(self, token):
83
- self.cache.append(token)
84
- if not token.isspace():
85
- result = self.state(token)
86
- if result is not None:
87
- return result
88
- return ()
89
-
90
- def abort(self):
91
- self.stop()
92
- return self.cache
93
-
94
- def flush(self):
95
- tmp, self.cache = self.cache, []
96
- return [''.join(tmp)]
97
-
98
- def _global_state(self, token):
99
- if not isidentifier(token):
100
- return self.abort()
101
- self.tag = token
102
- self.state = self._after_tag
103
-
104
- def _after_tag(self, token):
105
- if token == '>':
106
- self.state = self._body
107
- elif token == "/":
108
- self.state = self._expecting_self_closing
109
- elif isidentifier(token):
110
- self.state = self._expecting_equal_sign
111
- else:
112
- return self.abort()
113
-
114
- def _expecting_self_closing(self, token):
115
- if token == ">":
116
- self.stop()
117
- return self.flush()
118
- return self.abort()
119
-
120
- def _expecting_equal_sign(self, token):
121
- if token == '=':
122
- self.state = self._expecting_value
123
- else:
124
- return self.abort()
125
-
126
- def _expecting_value(self, token):
127
- if token[0] in "'\"":
128
- self.state = self._after_tag
129
- elif token == "{":
130
- self.cache.append("}")
131
- self.sub_tokenizer = JSTokenizer()
132
- self.state = self._after_tag
133
-
134
- def _body(self, token):
135
- if token == "<":
136
- self.sub_tokenizer = XMLTagWithAttrTokenizer()
137
- self.cache.pop()
138
- return self.flush()
139
-
140
- if token.startswith("</"):
141
- self.stop()
142
- return self.flush()
143
-
144
- if token == '{':
145
- self.sub_tokenizer = JSTokenizer()
146
- return self.flush()
147
-
148
-
149
- def isidentifier(token):
150
- try:
151
- return token.isidentifier()
152
- except AttributeError:
153
- return token.encode(encoding='UTF-8')[0].isalpha()
@@ -11,8 +11,18 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
11
11
  self.last_tokens = ''
12
12
  self.function_name = ''
13
13
  self.started_function = None
14
+ self.as_object = False
14
15
 
15
16
  def _state_global(self, token):
17
+ if self.as_object:
18
+ if token == ':':
19
+ self.function_name = self.last_tokens
20
+ return
21
+ elif token == '(':
22
+ self._function(self.last_tokens)
23
+ self.next(self._function, token)
24
+ return
25
+
16
26
  if token in '.':
17
27
  self._state = self._field
18
28
  self.last_tokens += token
@@ -29,11 +39,11 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
29
39
  self.function_name = self.last_tokens
30
40
  elif token == "(":
31
41
  self.sub_state(
32
- JavaScriptStyleLanguageStates(self.context))
42
+ self.__class__(self.context))
33
43
  elif token in '{':
34
44
  if self.started_function:
35
45
  self.sub_state(
36
- JavaScriptStyleLanguageStates(self.context),
46
+ self.__class__(self.context),
37
47
  self._pop_function_from_stack)
38
48
  else:
39
49
  self.read_object()
@@ -46,7 +56,12 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
46
56
  self.last_tokens = token
47
57
 
48
58
  def read_object(self):
49
- self.sub_state(ES6ObjectStates(self.context))
59
+ def callback():
60
+ self.next(self._state_global)
61
+
62
+ object_reader = self.__class__(self.context)
63
+ object_reader.as_object = True
64
+ self.sub_state(object_reader, callback)
50
65
 
51
66
  def statemachine_before_return(self):
52
67
  self._pop_function_from_stack()
@@ -63,16 +78,14 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
63
78
  return
64
79
 
65
80
  self.sub_state(
66
- JavaScriptStyleLanguageStates(self.context),
67
- callback)
81
+ self.__class__(self.context), callback)
68
82
 
69
83
  def _expecting_statement_or_block(self, token):
70
84
  def callback():
71
85
  self.next(self._state_global)
72
86
  if token == "{":
73
87
  self.sub_state(
74
- JavaScriptStyleLanguageStates(self.context),
75
- callback)
88
+ self.__class__(self.context), callback)
76
89
  else:
77
90
  self.next(self._state_global, token)
78
91
 
@@ -116,17 +129,3 @@ class JavaScriptStyleLanguageStates(CodeStateMachine): # pylint: disable=R0903
116
129
  if token != '{':
117
130
  self.started_function = None
118
131
  self.next(self._state_global, token)
119
-
120
-
121
- class ES6ObjectStates(JavaScriptStyleLanguageStates): # pylint: disable=R0903
122
- def __init__(self, context):
123
- super(ES6ObjectStates, self).__init__(context)
124
-
125
- def _state_global(self, token):
126
- if token == ':':
127
- self.function_name = self.last_tokens
128
- elif token == '(':
129
- self._function(self.last_tokens)
130
- self.next(self._function, token)
131
- else:
132
- super(ES6ObjectStates, self)._state_global(token)
@@ -6,18 +6,48 @@ import re
6
6
 
7
7
 
8
8
  def js_style_regex_expression(func):
9
- def generate_tokens_with_regex(source_code, _=""):
9
+ def generate_tokens_with_regex(source_code, addition='', token_class=None):
10
10
  regx_regx = r"\/(\S*?[^\s\\]\/)+?(igm)*"
11
11
  regx_pattern = re.compile(regx_regx)
12
- word_pattern = re.compile(r'\w+')
13
- tokens = func(source_code, r"|"+regx_regx)
14
- leading_by_word = False
15
- for token in tokens:
16
- if leading_by_word and regx_pattern.match(token):
17
- for subtoken in func(token, _):
18
- yield subtoken
12
+ tokens = list(func(source_code, addition, token_class))
13
+ result = []
14
+ i = 0
15
+ while i < len(tokens):
16
+ token = tokens[i]
17
+ if token == '/':
18
+ # Check if this could be a regex pattern
19
+ is_regex = False
20
+ if i == 0:
21
+ is_regex = True
22
+ elif i > 0:
23
+ prev_token = tokens[i-1].strip()
24
+ if prev_token and prev_token[-1] in '=,({[?:!&|;':
25
+ is_regex = True
26
+
27
+ if is_regex:
28
+ # This is likely a regex pattern start
29
+ regex_tokens = [token]
30
+ i += 1
31
+ while i < len(tokens) and not tokens[i].endswith('/'):
32
+ regex_tokens.append(tokens[i])
33
+ i += 1
34
+ if i < len(tokens):
35
+ regex_tokens.append(tokens[i])
36
+ i += 1
37
+ # Check for regex flags
38
+ if i < len(tokens) and re.match(r'^[igm]+$', tokens[i]):
39
+ regex_tokens.append(tokens[i])
40
+ i += 1
41
+ combined = ''.join(regex_tokens)
42
+ if regx_pattern.match(combined):
43
+ result.append(combined)
44
+ else:
45
+ result.extend(regex_tokens)
46
+ else:
47
+ # This is a division operator
48
+ result.append(token)
19
49
  else:
20
- yield token
21
- if not token.isspace():
22
- leading_by_word = word_pattern.match(token)
50
+ result.append(token)
51
+ i += 1
52
+ return result
23
53
  return generate_tokens_with_regex
lizard_languages/jsx.py CHANGED
@@ -2,17 +2,14 @@
2
2
  Language parser for JSX
3
3
  '''
4
4
 
5
- from .javascript import JavaScriptReader, JSTokenizer, XMLTagWithAttrTokenizer, isidentifier
5
+ from .javascript import JavaScriptReader
6
+ from .typescript import JSTokenizer, Tokenizer
6
7
  from .code_reader import CodeReader
7
8
  from .js_style_regex_expression import js_style_regex_expression
8
9
 
9
10
 
10
- class JSXReader(JavaScriptReader):
11
- # pylint: disable=R0903
12
-
13
- ext = ['jsx']
14
- language_names = ['jsx']
15
-
11
+ class JSXMixin:
12
+ '''Base mixin class for JSX/TSX shared functionality'''
16
13
  @staticmethod
17
14
  @js_style_regex_expression
18
15
  def generate_tokens(source_code, addition='', token_class=None):
@@ -25,3 +22,106 @@ class JSXReader(JavaScriptReader):
25
22
  source_code, addition, token_class):
26
23
  for tok in js_tokenizer(token):
27
24
  yield tok
25
+
26
+ def _expecting_func_opening_bracket(self, token):
27
+ if token == '<':
28
+ self.next(self._expecting_jsx)
29
+ return
30
+ super()._expecting_func_opening_bracket(token)
31
+
32
+ def _expecting_jsx(self, token):
33
+ if token == '>':
34
+ self.next(self._expecting_func_opening_bracket)
35
+
36
+
37
+ class JSXReader(JavaScriptReader, JSXMixin):
38
+ # pylint: disable=R0903
39
+
40
+ ext = ['jsx']
41
+ language_names = ['jsx']
42
+
43
+ @staticmethod
44
+ @js_style_regex_expression
45
+ def generate_tokens(source_code, addition='', token_class=None):
46
+ return JSXMixin.generate_tokens(source_code, addition, token_class)
47
+
48
+
49
+ class XMLTagWithAttrTokenizer(Tokenizer):
50
+ def __init__(self):
51
+ super(XMLTagWithAttrTokenizer, self).__init__()
52
+ self.tag = None
53
+ self.state = self._global_state
54
+ self.cache = ['<']
55
+
56
+ def process_token(self, token):
57
+ self.cache.append(token)
58
+ if not token.isspace():
59
+ result = self.state(token)
60
+ if result is not None:
61
+ return result
62
+ return ()
63
+
64
+ def abort(self):
65
+ self.stop()
66
+ return self.cache
67
+
68
+ def flush(self):
69
+ tmp, self.cache = self.cache, []
70
+ return [''.join(tmp)]
71
+
72
+ def _global_state(self, token):
73
+ if not isidentifier(token):
74
+ return self.abort()
75
+ self.tag = token
76
+ self.state = self._after_tag
77
+
78
+ def _after_tag(self, token):
79
+ if token == '>':
80
+ self.state = self._body
81
+ elif token == "/":
82
+ self.state = self._expecting_self_closing
83
+ elif isidentifier(token):
84
+ self.state = self._expecting_equal_sign
85
+ else:
86
+ return self.abort()
87
+
88
+ def _expecting_self_closing(self, token):
89
+ if token == ">":
90
+ self.stop()
91
+ return self.flush()
92
+ return self.abort()
93
+
94
+ def _expecting_equal_sign(self, token):
95
+ if token == '=':
96
+ self.state = self._expecting_value
97
+ else:
98
+ return self.abort()
99
+
100
+ def _expecting_value(self, token):
101
+ if token[0] in "'\"":
102
+ self.state = self._after_tag
103
+ elif token == "{":
104
+ self.cache.append("}")
105
+ self.sub_tokenizer = JSTokenizer()
106
+ self.state = self._after_tag
107
+
108
+ def _body(self, token):
109
+ if token == "<":
110
+ self.sub_tokenizer = XMLTagWithAttrTokenizer()
111
+ self.cache.pop()
112
+ return self.flush()
113
+
114
+ if token.startswith("</"):
115
+ self.stop()
116
+ return self.flush()
117
+
118
+ if token == '{':
119
+ self.sub_tokenizer = JSTokenizer()
120
+ return self.flush()
121
+
122
+
123
+ def isidentifier(token):
124
+ try:
125
+ return token.isidentifier()
126
+ except AttributeError:
127
+ return token.encode(encoding='UTF-8')[0].isalpha()
@@ -4,7 +4,7 @@ Common behaviours of script languages
4
4
  from .code_reader import CodeReader
5
5
 
6
6
 
7
- class ScriptLanguageMixIn(object):
7
+ class ScriptLanguageMixIn:
8
8
  # pylint: disable=R0903
9
9
 
10
10
  @staticmethod
lizard_languages/tsx.py CHANGED
@@ -2,13 +2,13 @@
2
2
  Language parser for TSX
3
3
  '''
4
4
 
5
- from .typescript import TypeScriptReader, TypeScriptStates
6
- from .javascript import JSTokenizer, XMLTagWithAttrTokenizer, isidentifier
5
+ from .typescript import TypeScriptReader, TypeScriptStates, JSTokenizer
6
+ from .jsx import JSXMixin
7
7
  from .code_reader import CodeReader
8
8
  from .js_style_regex_expression import js_style_regex_expression
9
9
 
10
10
 
11
- class TSXReader(TypeScriptReader):
11
+ class TSXReader(TypeScriptReader, JSXMixin):
12
12
  # pylint: disable=R0903
13
13
 
14
14
  ext = ['tsx']
@@ -17,28 +17,8 @@ class TSXReader(TypeScriptReader):
17
17
  @staticmethod
18
18
  @js_style_regex_expression
19
19
  def generate_tokens(source_code, addition='', token_class=None):
20
- addition = addition +\
21
- r"|(?:\$\w+)" + \
22
- r"|(?:\<\/\w+\>)" + \
23
- r"|`.*?`"
24
- js_tokenizer = JSTokenizer()
25
- for token in CodeReader.generate_tokens(
26
- source_code, addition, token_class):
27
- for tok in js_tokenizer(token):
28
- yield tok
20
+ return JSXMixin.generate_tokens(source_code, addition, token_class)
29
21
 
30
22
  def __init__(self, context):
31
23
  super(TSXReader, self).__init__(context)
32
- self.parallel_states = [TSXStates(context)]
33
-
34
-
35
- class TSXStates(TypeScriptStates):
36
- def _expecting_func_opening_bracket(self, token):
37
- if token == '<':
38
- self.next(self._expecting_jsx)
39
- return
40
- super(TSXStates, self)._expecting_func_opening_bracket(token)
41
-
42
- def _expecting_jsx(self, token):
43
- if token == '>':
44
- self.next(self._expecting_func_opening_bracket)
24
+ # No need for parallel states since JSX handling is in the mixin
@@ -3,12 +3,54 @@ Language parser for JavaScript
3
3
  '''
4
4
 
5
5
  import re
6
- from .code_reader import CodeReader
6
+ from .code_reader import CodeReader, CodeStateMachine
7
7
  from .clike import CCppCommentsMixin
8
8
  from .js_style_language_states import JavaScriptStyleLanguageStates
9
9
  from .js_style_regex_expression import js_style_regex_expression
10
10
 
11
11
 
12
+ class Tokenizer(object):
13
+ def __init__(self):
14
+ self.sub_tokenizer = None
15
+ self._ended = False
16
+
17
+ def __call__(self, token):
18
+ if self.sub_tokenizer:
19
+ for tok in self.sub_tokenizer(token):
20
+ yield tok
21
+ if self.sub_tokenizer._ended:
22
+ self.sub_tokenizer = None
23
+ return
24
+ for tok in self.process_token(token):
25
+ yield tok
26
+
27
+ def stop(self):
28
+ self._ended = True
29
+
30
+ def process_token(self, token):
31
+ pass
32
+
33
+
34
+ class JSTokenizer(Tokenizer):
35
+ def __init__(self):
36
+ super().__init__()
37
+ self.depth = 1
38
+
39
+ def process_token(self, token):
40
+ if token == "<":
41
+ from .jsx import XMLTagWithAttrTokenizer # Import only when needed
42
+ self.sub_tokenizer = XMLTagWithAttrTokenizer()
43
+ return
44
+ if token == "{":
45
+ self.depth += 1
46
+ elif token == "}":
47
+ self.depth -= 1
48
+ if self.depth == 0:
49
+ self.stop()
50
+ return
51
+ yield token
52
+
53
+
12
54
  class TypeScriptReader(CodeReader, CCppCommentsMixin):
13
55
  # pylint: disable=R0903
14
56
 
@@ -18,58 +60,65 @@ class TypeScriptReader(CodeReader, CCppCommentsMixin):
18
60
  'catch', 'case'])
19
61
 
20
62
  def __init__(self, context):
21
- super(TypeScriptReader, self).__init__(context)
63
+ super().__init__(context)
22
64
  self.parallel_states = [TypeScriptStates(context)]
23
65
 
24
66
  @staticmethod
25
67
  @js_style_regex_expression
26
68
  def generate_tokens(source_code, addition='', token_class=None):
27
69
  addition = addition +\
28
- r"|(?:\w+\?)"
29
- return CodeReader.generate_tokens(source_code, addition, token_class)
70
+ r"|(?:\$\w+)" + \
71
+ r"|(?:\w+\?)" + \
72
+ r"|`.*?`"
73
+ js_tokenizer = JSTokenizer()
74
+ for token in CodeReader.generate_tokens(
75
+ source_code, addition, token_class):
76
+ for tok in js_tokenizer(token):
77
+ yield tok
30
78
 
31
79
 
32
80
  class TypeScriptStates(JavaScriptStyleLanguageStates):
81
+ def __init__(self, context):
82
+ super().__init__(context)
33
83
 
34
84
  def _state_global(self, token):
35
- if token == ':':
36
- # When we see a type annotation in global state, store the last tokens
37
- # but don't treat it as a function name yet
38
- self._potential_name = self.last_tokens
39
- self.next(self._type_annotation)
40
- return
41
- if token == '=>':
42
- # For arrow functions, we want to treat them as anonymous
43
- self.function_name = ''
44
- self._state = self._arrow_function
45
- return
46
- super(TypeScriptStates, self)._state_global(token)
47
-
48
- def _type_annotation(self, token):
49
- if token == '=':
50
- # We're back to an assignment, restore the potential name
51
- if hasattr(self, '_potential_name'):
52
- self.last_tokens = self._potential_name
53
- delattr(self, '_potential_name')
54
- self.next(self._state_global, token)
55
- else:
56
- self.next(self._type_annotation)
85
+ if not self.as_object:
86
+ if token == ':':
87
+ self._consume_type_annotation()
88
+ return
89
+ super()._state_global(token)
57
90
 
58
91
  def _expecting_func_opening_bracket(self, token):
59
92
  if token == ':':
60
- self.next(self._expecting_default)
61
- return
62
- super(TypeScriptStates, self)._expecting_func_opening_bracket(token)
93
+ self._consume_type_annotation()
94
+ else:
95
+ super()._expecting_func_opening_bracket(token)
96
+
97
+ def _consume_type_annotation(self):
98
+ typeStates = TypeScriptTypeAnnotationStates(self.context)
99
+
100
+ def callback():
101
+ if typeStates.saved_token:
102
+ self(typeStates.saved_token)
103
+ self.sub_state(typeStates, callback)
104
+
63
105
 
64
- def _expecting_default(self, token):
65
- self.next(self._function_return_type)
106
+ class TypeScriptTypeAnnotationStates(CodeStateMachine):
107
+ def __init__(self, context):
108
+ super().__init__(context)
109
+ self.saved_token = None
110
+
111
+ def _state_global(self, token):
66
112
  if token == '{':
67
- self.read_object()
68
-
69
- def _function_return_type(self, token):
70
- if token == ';':
71
- self.next(self._state_global)
72
- elif token == '{':
73
- self.next(self._expecting_func_opening_bracket, token)
74
- elif token == '=':
75
- self.next(self._state_global, token)
113
+ self.next(self._inline_type_annotation, token)
114
+ else:
115
+ self.next(self._state_simple_type, token)
116
+
117
+ def _state_simple_type(self, token):
118
+ if token in '{=;':
119
+ self.saved_token = token
120
+ self.statemachine_return()
121
+
122
+ @CodeStateMachine.read_inside_brackets_then("{}")
123
+ def _inline_type_annotation(self, _):
124
+ self.statemachine_return()
@@ -0,0 +1,34 @@
1
+ '''
2
+ Language parser for Vue.js files
3
+ '''
4
+
5
+ from .code_reader import CodeReader, CodeStateMachine
6
+ from .typescript import TypeScriptReader
7
+
8
+
9
+ class VueReader(TypeScriptReader):
10
+ # pylint: disable=R0903
11
+
12
+ ext = ['vue']
13
+ language_names = ['vue', 'vuejs']
14
+
15
+ def __init__(self, context):
16
+ super(VueReader, self).__init__(context)
17
+
18
+ @staticmethod
19
+ def generate_tokens(source_code, addition='', token_class=None):
20
+ # Use the base token generator but ensure we capture Vue block tags
21
+ addition = addition + r"|(?:\<\/?\w+.*?\>)"
22
+ return TypeScriptReader.generate_tokens(source_code, addition, token_class)
23
+
24
+ def preprocess(self, tokens):
25
+ current_block = None
26
+
27
+ for token in tokens:
28
+ if token.startswith('<script'):
29
+ current_block = 'script'
30
+ elif token.startswith('</script'):
31
+ current_block = None
32
+ elif current_block == 'script':
33
+ if not token.isspace() or token == '\n':
34
+ yield token