lizard 1.17.15__tar.gz → 1.17.17__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 (102) hide show
  1. {lizard-1.17.15 → lizard-1.17.17}/PKG-INFO +8 -1
  2. {lizard-1.17.15 → lizard-1.17.17}/README.rst +7 -0
  3. {lizard-1.17.15 → lizard-1.17.17}/lizard.egg-info/PKG-INFO +8 -1
  4. {lizard-1.17.15 → lizard-1.17.17}/lizard.egg-info/requires.txt +1 -0
  5. {lizard-1.17.15 → lizard-1.17.17}/lizard.py +32 -2
  6. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/version.py +1 -1
  7. lizard-1.17.17/lizard_languages/csharp.py +62 -0
  8. lizard-1.17.17/lizard_languages/java.py +142 -0
  9. lizard-1.17.17/lizard_languages/javascript.py +15 -0
  10. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/jsx.py +17 -3
  11. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/ruby.py +3 -2
  12. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/tsx.py +1 -2
  13. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/typescript.py +9 -5
  14. {lizard-1.17.15 → lizard-1.17.17}/setup.py +1 -1
  15. {lizard-1.17.15 → lizard-1.17.17}/test/testFilesFilter.py +31 -0
  16. {lizard-1.17.15 → lizard-1.17.17}/test/testOutput.py +1 -1
  17. lizard-1.17.17/test/testOutputFile.py +117 -0
  18. lizard-1.17.15/lizard_languages/csharp.py +0 -20
  19. lizard-1.17.15/lizard_languages/java.py +0 -44
  20. lizard-1.17.15/lizard_languages/javascript.py +0 -19
  21. lizard-1.17.15/test/testOutputFile.py +0 -58
  22. {lizard-1.17.15 → lizard-1.17.17}/LICENSE.txt +0 -0
  23. {lizard-1.17.15 → lizard-1.17.17}/lizard.egg-info/SOURCES.txt +0 -0
  24. {lizard-1.17.15 → lizard-1.17.17}/lizard.egg-info/dependency_links.txt +0 -0
  25. {lizard-1.17.15 → lizard-1.17.17}/lizard.egg-info/entry_points.txt +0 -0
  26. {lizard-1.17.15 → lizard-1.17.17}/lizard.egg-info/top_level.txt +0 -0
  27. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/__init__.py +0 -0
  28. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/auto_open.py +0 -0
  29. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/csvoutput.py +0 -0
  30. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/default_ordered_dict.py +0 -0
  31. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/extension_base.py +0 -0
  32. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/htmloutput.py +0 -0
  33. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/keywords.py +0 -0
  34. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardboolcount.py +0 -0
  35. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardcomplextags.py +0 -0
  36. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardcpre.py +0 -0
  37. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizarddependencycount.py +0 -0
  38. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizarddumpcomments.py +0 -0
  39. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardduplicate.py +0 -0
  40. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardduplicated_param_list.py +0 -0
  41. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardexitcount.py +0 -0
  42. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardgotocount.py +0 -0
  43. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardignoreassert.py +0 -0
  44. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardio.py +0 -0
  45. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardmccabe.py +0 -0
  46. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardmodified.py +0 -0
  47. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardnd.py +0 -0
  48. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardnonstrict.py +0 -0
  49. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardns.py +0 -0
  50. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardoutside.py +0 -0
  51. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardstatementcount.py +0 -0
  52. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/lizardwordcount.py +0 -0
  53. {lizard-1.17.15 → lizard-1.17.17}/lizard_ext/xmloutput.py +0 -0
  54. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/__init__.py +0 -0
  55. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/clike.py +0 -0
  56. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/code_reader.py +0 -0
  57. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/erlang.py +0 -0
  58. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/fortran.py +0 -0
  59. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/gdscript.py +0 -0
  60. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/go.py +0 -0
  61. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/golike.py +0 -0
  62. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/js_style_language_states.py +0 -0
  63. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/js_style_regex_expression.py +0 -0
  64. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/kotlin.py +0 -0
  65. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/lua.py +0 -0
  66. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/objc.py +0 -0
  67. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/php.py +0 -0
  68. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/python.py +0 -0
  69. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/rubylike.py +0 -0
  70. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/rust.py +0 -0
  71. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/scala.py +0 -0
  72. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/script_language.py +0 -0
  73. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/solidity.py +0 -0
  74. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/swift.py +0 -0
  75. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/tnsdl.py +0 -0
  76. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/ttcn.py +0 -0
  77. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/vue.py +0 -0
  78. {lizard-1.17.15 → lizard-1.17.17}/lizard_languages/zig.py +0 -0
  79. {lizard-1.17.15 → lizard-1.17.17}/setup.cfg +0 -0
  80. {lizard-1.17.15 → lizard-1.17.17}/test/testApplication.py +0 -0
  81. {lizard-1.17.15 → lizard-1.17.17}/test/testAssertionExtension.py +0 -0
  82. {lizard-1.17.15 → lizard-1.17.17}/test/testBasicFunctionInfo.py +0 -0
  83. {lizard-1.17.15 → lizard-1.17.17}/test/testCOutsideComplexity.py +0 -0
  84. {lizard-1.17.15 → lizard-1.17.17}/test/testCPreprocessorExtension.py +0 -0
  85. {lizard-1.17.15 → lizard-1.17.17}/test/testCommentOptions.py +0 -0
  86. {lizard-1.17.15 → lizard-1.17.17}/test/testCyclomaticComplexity.py +0 -0
  87. {lizard-1.17.15 → lizard-1.17.17}/test/testExtension.py +0 -0
  88. {lizard-1.17.15 → lizard-1.17.17}/test/testFunctionDependencyCount.py +0 -0
  89. {lizard-1.17.15 → lizard-1.17.17}/test/testFunctionExitCount.py +0 -0
  90. {lizard-1.17.15 → lizard-1.17.17}/test/testFunctionGotoCount.py +0 -0
  91. {lizard-1.17.15 → lizard-1.17.17}/test/testFunctionStatementCount.py +0 -0
  92. {lizard-1.17.15 → lizard-1.17.17}/test/testHelpers.py +0 -0
  93. {lizard-1.17.15 → lizard-1.17.17}/test/testLanguages.py +0 -0
  94. {lizard-1.17.15 → lizard-1.17.17}/test/testMcCabe.py +0 -0
  95. {lizard-1.17.15 → lizard-1.17.17}/test/testNestedStructures.py +0 -0
  96. {lizard-1.17.15 → lizard-1.17.17}/test/testNestingDepth.py +0 -0
  97. {lizard-1.17.15 → lizard-1.17.17}/test/testOutputCSV.py +0 -0
  98. {lizard-1.17.15 → lizard-1.17.17}/test/testOutputHTML.py +0 -0
  99. {lizard-1.17.15 → lizard-1.17.17}/test/testTokenizer.py +0 -0
  100. {lizard-1.17.15 → lizard-1.17.17}/test/test_analyzer.py +0 -0
  101. {lizard-1.17.15 → lizard-1.17.17}/test/test_auto_open.py +0 -0
  102. {lizard-1.17.15 → lizard-1.17.17}/test/test_options.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lizard
3
- Version: 1.17.15
3
+ Version: 1.17.17
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/
@@ -137,6 +137,13 @@ Exclude anything in the tests folder:
137
137
 
138
138
  lizard mySource/ -x"./tests/*"
139
139
 
140
+ Use .gitignore file:
141
+
142
+ ::
143
+
144
+ lizard mySource/
145
+
146
+ 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
147
 
141
148
  Options
142
149
  ~~~~~~~
@@ -106,6 +106,13 @@ Exclude anything in the tests folder:
106
106
 
107
107
  lizard mySource/ -x"./tests/*"
108
108
 
109
+ Use .gitignore file:
110
+
111
+ ::
112
+
113
+ lizard mySource/
114
+
115
+ 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.
109
116
 
110
117
  Options
111
118
  ~~~~~~~
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lizard
3
- Version: 1.17.15
3
+ Version: 1.17.17
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/
@@ -137,6 +137,13 @@ Exclude anything in the tests folder:
137
137
 
138
138
  lizard mySource/ -x"./tests/*"
139
139
 
140
+ Use .gitignore file:
141
+
142
+ ::
143
+
144
+ lizard mySource/
145
+
146
+ 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
147
 
141
148
  Options
142
149
  ~~~~~~~
@@ -1 +1,2 @@
1
1
  pygments
2
+ pathspec
@@ -896,14 +896,43 @@ def md5_hash_file(full_path_name):
896
896
  def get_all_source_files(paths, exclude_patterns, lans):
897
897
  '''
898
898
  Function counts md5 hash for the given file and checks if it isn't a
899
- 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
+ '''
900
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
901
924
 
902
925
  def _support(reader):
903
926
  return not lans or set(lans).intersection(
904
927
  reader.language_names)
905
928
 
906
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
907
936
  return (
908
937
  pathname in paths or (
909
938
  get_reader_for(pathname) and
@@ -926,6 +955,7 @@ def get_all_source_files(paths, exclude_patterns, lans):
926
955
  for filename in files:
927
956
  yield os.path.join(root, filename)
928
957
 
958
+ _load_gitignore()
929
959
  return filter(_validate_file, all_listed_files(paths))
930
960
 
931
961
 
@@ -967,7 +997,7 @@ def parse_args(argv):
967
997
  if inferred_printer:
968
998
  if not opt.printer:
969
999
  opt.printer = inferred_printer
970
- else:
1000
+ elif opt.printer != inferred_printer:
971
1001
  msg = "Warning: overriding output file extension.\n"
972
1002
  sys.stderr.write(msg)
973
1003
  return opt
@@ -3,4 +3,4 @@
3
3
  #
4
4
  # pylint: disable=missing-docstring,invalid-name
5
5
 
6
- version = "1.17.15"
6
+ version = "1.17.17"
@@ -0,0 +1,62 @@
1
+ '''
2
+ Language parser for C Sharp
3
+ '''
4
+
5
+ from .clike import CLikeReader, CLikeStates, CLikeNestingStackStates
6
+
7
+
8
+ class CSharpReader(CLikeReader):
9
+ # pylint: disable=R0903
10
+
11
+ ext = ['cs']
12
+ language_names = ['csharp']
13
+
14
+ _conditions = set(['if', 'for', 'while', '&&', '||', '?', 'catch',
15
+ 'case', '??'])
16
+
17
+ def __init__(self, context):
18
+ super(CSharpReader, self).__init__(context)
19
+ self.parallel_states = [
20
+ CSharpStates(context),
21
+ CLikeNestingStackStates(context)
22
+ ]
23
+
24
+ @staticmethod
25
+ def generate_tokens(source_code, addition='', token_class=None):
26
+ return CLikeReader.generate_tokens(
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
@@ -0,0 +1,142 @@
1
+ '''
2
+ Language parser for Java
3
+ '''
4
+
5
+ from lizard_languages.code_reader import CodeStateMachine
6
+ from .clike import CLikeReader, CLikeStates, CLikeNestingStackStates
7
+
8
+
9
+ class JavaReader(CLikeReader):
10
+ # pylint: disable=R0903
11
+
12
+ ext = ['java']
13
+ language_names = ['java']
14
+
15
+ def __init__(self, context):
16
+ super(JavaReader, self).__init__(context)
17
+ self.parallel_states = [
18
+ JavaStates(context),
19
+ CLikeNestingStackStates(context)]
20
+
21
+
22
+ class JavaStates(CLikeStates): # pylint: disable=R0903
23
+ def __init__(self, context):
24
+ super(JavaStates, self).__init__(context)
25
+ self.class_name = None
26
+ self.is_record = False
27
+ self.in_record_constructor = False
28
+
29
+ def _state_old_c_params(self, token):
30
+ if token == '{':
31
+ self._state_dec_to_imp(token)
32
+
33
+ def _state_imp(self, token):
34
+ def callback():
35
+ self.next(self._state_global)
36
+ self.sub_state(JavaFunctionBodyStates(self.context), callback, token)
37
+
38
+ def try_new_function(self, name):
39
+ # Don't create a function for record compact constructor
40
+ if self.is_record and name == self.class_name:
41
+ self.in_record_constructor = True
42
+ self._state = self._state_record_compact_constructor
43
+ return
44
+ self.context.try_new_function(name)
45
+ self._state = self._state_function
46
+ if self.class_name and self.context.current_function:
47
+ self.context.current_function.name = f"{self.class_name}::{name}"
48
+
49
+ def _try_start_a_class(self, token):
50
+ if token in ("class", "record", "enum"):
51
+ self.class_name = None
52
+ self.is_record = token == "record"
53
+ self.in_record_constructor = False
54
+ self._state = self._state_class_declaration
55
+ return True
56
+
57
+ def _state_global(self, token):
58
+ if token == '@':
59
+ self._state = self._state_decorator
60
+ return
61
+ if self._try_start_a_class(token):
62
+ return
63
+ if not self.in_record_constructor: # Only process as potential function if not in record constructor
64
+ super(JavaStates, self)._state_global(token)
65
+
66
+ def _state_decorator(self, _):
67
+ self._state = self._state_post_decorator
68
+
69
+ def _state_post_decorator(self, token):
70
+ if token == '.':
71
+ self._state = self._state_decorator
72
+ else:
73
+ self._state = self._state_global
74
+ self._state(token)
75
+
76
+ def _state_class_declaration(self, token):
77
+ if token == '{':
78
+ def callback():
79
+ self._state = self._state_global
80
+ self.sub_state(JavaClassBodyStates(self.class_name, self.is_record, self.context), callback, token)
81
+ elif token == '(': # Record parameters
82
+ self._state = self._state_record_parameters
83
+ elif token[0].isalpha():
84
+ if not self.class_name: # Only set class name if not already set
85
+ self.class_name = token
86
+
87
+ def _state_record_parameters(self, token):
88
+ if token == ')':
89
+ self._state = self._state_class_declaration
90
+
91
+ def _state_record_compact_constructor(self, token):
92
+ if token == '{':
93
+ self._state = self._state_record_constructor_body
94
+ return
95
+ self._state = self._state_global
96
+ self._state(token)
97
+
98
+ def _state_record_constructor_body(self, token):
99
+ if token == '}':
100
+ self.in_record_constructor = False
101
+ self._state = self._state_global
102
+
103
+ class JavaFunctionBodyStates(JavaStates):
104
+ def __init__(self, context):
105
+ super(JavaFunctionBodyStates, self).__init__(context)
106
+
107
+ @CodeStateMachine.read_inside_brackets_then("{}", "_state_dummy")
108
+ @CodeStateMachine.read_inside_brackets_then("()", "_state_dummy")
109
+ def _state_global(self, token):
110
+ if token == "new":
111
+ self.next(self._state_new)
112
+ self._try_start_a_class(token)
113
+ if self.br_count == 0:
114
+ self.statemachine_return()
115
+
116
+ def _state_dummy(self, _):
117
+ pass
118
+
119
+ def _state_new(self, token):
120
+ self.next(self._state_new_parameters)
121
+
122
+ def _state_new_parameters(self, token):
123
+ if token == "(":
124
+ self.sub_state(JavaFunctionBodyStates(self.context), None, token)
125
+ return
126
+ if token == "{":
127
+ def callback():
128
+ self.next(self._state_global)
129
+ self.sub_state(JavaClassBodyStates("(anonymous)", False, self.context), callback, token)
130
+ return
131
+ self.next(self._state_global)
132
+
133
+ class JavaClassBodyStates(JavaStates):
134
+ def __init__(self, class_name, is_record, context):
135
+ super(JavaClassBodyStates, self).__init__(context)
136
+ self.class_name = class_name
137
+ self.is_record = is_record
138
+
139
+ def _state_global(self, token):
140
+ super()._state_global(token)
141
+ if token == '}':
142
+ self.statemachine_return()
@@ -0,0 +1,15 @@
1
+ '''
2
+ Language parser for JavaScript
3
+ '''
4
+
5
+ from .typescript import TypeScriptReader
6
+
7
+
8
+ class JavaScriptReader(TypeScriptReader):
9
+ # pylint: disable=R0903
10
+
11
+ ext = ['js', 'cjs', 'mjs']
12
+ language_names = ['javascript', 'js']
13
+
14
+ def __init__(self, context):
15
+ super(JavaScriptReader, self).__init__(context)
@@ -8,6 +8,20 @@ from .code_reader import CodeReader
8
8
  from .js_style_regex_expression import js_style_regex_expression
9
9
 
10
10
 
11
+ class TSXTokenizer(JSTokenizer):
12
+ def __init__(self):
13
+ super().__init__()
14
+
15
+ def process_token(self, token):
16
+ if token == "<":
17
+ from .jsx import XMLTagWithAttrTokenizer # Import only when needed
18
+ self.sub_tokenizer = XMLTagWithAttrTokenizer()
19
+ return
20
+
21
+ for tok in super().process_token(token):
22
+ yield tok
23
+
24
+
11
25
  class JSXMixin:
12
26
  '''Base mixin class for JSX/TSX shared functionality'''
13
27
  @staticmethod
@@ -17,7 +31,7 @@ class JSXMixin:
17
31
  r"|(?:\$\w+)" + \
18
32
  r"|(?:\<\/\w+\>)" + \
19
33
  r"|`.*?`"
20
- js_tokenizer = JSTokenizer()
34
+ js_tokenizer = TSXTokenizer()
21
35
  for token in CodeReader.generate_tokens(
22
36
  source_code, addition, token_class):
23
37
  for tok in js_tokenizer(token):
@@ -102,7 +116,7 @@ class XMLTagWithAttrTokenizer(Tokenizer):
102
116
  self.state = self._after_tag
103
117
  elif token == "{":
104
118
  self.cache.append("}")
105
- self.sub_tokenizer = JSTokenizer()
119
+ self.sub_tokenizer = TSXTokenizer()
106
120
  self.state = self._after_tag
107
121
 
108
122
  def _body(self, token):
@@ -116,7 +130,7 @@ class XMLTagWithAttrTokenizer(Tokenizer):
116
130
  return self.flush()
117
131
 
118
132
  if token == '{':
119
- self.sub_tokenizer = JSTokenizer()
133
+ self.sub_tokenizer = TSXTokenizer()
120
134
  return self.flush()
121
135
 
122
136
 
@@ -9,11 +9,12 @@ from .script_language import ScriptLanguageMixIn
9
9
 
10
10
  class MyToken(str):
11
11
  def __new__(cls, value, *_):
12
- return super(MyToken, cls).__new__(cls, value.group(0))
12
+ val = value.group(0) if hasattr(value, 'group') else value
13
+ return super(MyToken, cls).__new__(cls, val)
13
14
 
14
15
  def __init__(self, value):
15
16
  super(MyToken, self).__init__()
16
- self.begin = value.start()
17
+ self.begin = value.start() if hasattr(value, 'start') else 0
17
18
 
18
19
 
19
20
  class RubyReader(RubylikeReader):
@@ -2,9 +2,8 @@
2
2
  Language parser for TSX
3
3
  '''
4
4
 
5
- from .typescript import TypeScriptReader, TypeScriptStates, JSTokenizer
5
+ from .typescript import TypeScriptReader
6
6
  from .jsx import JSXMixin
7
- from .code_reader import CodeReader
8
7
  from .js_style_regex_expression import js_style_regex_expression
9
8
 
10
9
 
@@ -37,10 +37,6 @@ class JSTokenizer(Tokenizer):
37
37
  self.depth = 1
38
38
 
39
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
40
  if token == "{":
45
41
  self.depth += 1
46
42
  elif token == "}":
@@ -115,10 +111,18 @@ class TypeScriptTypeAnnotationStates(CodeStateMachine):
115
111
  self.next(self._state_simple_type, token)
116
112
 
117
113
  def _state_simple_type(self, token):
118
- if token in '{=;':
114
+ print(token)
115
+ if token == '<':
116
+ print(token)
117
+ self.next(self._state_generic_type, token)
118
+ elif token in '{=;':
119
119
  self.saved_token = token
120
120
  self.statemachine_return()
121
121
 
122
122
  @CodeStateMachine.read_inside_brackets_then("{}")
123
123
  def _inline_type_annotation(self, _):
124
124
  self.statemachine_return()
125
+
126
+ @CodeStateMachine.read_inside_brackets_then("<>")
127
+ def _state_generic_type(self, token):
128
+ self.statemachine_return()
@@ -95,7 +95,7 @@ setup(
95
95
  packages=['lizard_ext', 'lizard_languages'],
96
96
  #data_files=[('lizard_ext', [])],
97
97
  py_modules=['lizard'],
98
- install_requires=['pygments'],
98
+ install_requires=['pygments', 'pathspec'],
99
99
  entry_points={'console_scripts': ['lizard = lizard:main']},
100
100
  author='Terry Yin',
101
101
  author_email='terry@odd-e.com',
@@ -126,3 +126,34 @@ class TestFilesFilter(unittest.TestCase):
126
126
  else:
127
127
  file_names = ["./f1.cpp", "./f2.cpp"]
128
128
  self.assertEqual(file_names, list(files))
129
+
130
+ @patch.object(os.path, "exists")
131
+ @patch.object(os.path, "relpath")
132
+ @patch.object(os, "walk")
133
+ @patch("builtins.open", create=True)
134
+ def test_gitignore_filter(self, mock_open, mock_os_walk, mock_relpath, mock_exists):
135
+ mock_os_walk.return_value = (['.',
136
+ None,
137
+ ['temp.c', 'node_modules/file.js', 'useful.cpp']], )
138
+
139
+ def exists_side_effect(path):
140
+ return path.endswith('.gitignore')
141
+ mock_exists.side_effect = exists_side_effect
142
+
143
+ def relpath_side_effect(path, start):
144
+ # Return paths in a normalized format
145
+ if path.startswith('./'):
146
+ path = path[2:]
147
+ return path.replace(os.sep, '/')
148
+ mock_relpath.side_effect = relpath_side_effect
149
+
150
+ mock_file = mock_open.return_value.__enter__.return_value
151
+ mock_file.readlines.return_value = ["node_modules/\n", "*.c\n"]
152
+ mock_file.read.return_value = "node_modules/\n*.c\n"
153
+
154
+ files = get_all_source_files(["dir"], [], [])
155
+ if which_system() == "Windows":
156
+ file_names = [".\\useful.cpp"]
157
+ else:
158
+ file_names = ["./useful.cpp"]
159
+ self.assertEqual(file_names, list(files))
@@ -125,7 +125,7 @@ class TestAllOutput(StreamStdoutTestCase):
125
125
  def test_should_not_print_extension_results_when_not_implemented(self):
126
126
  file_infos = []
127
127
  option = Mock(CCN=15, number = 0, thresholds={}, extensions = [object()], whitelist='')
128
- return print_result_with_scheme(file_infos, option)
128
+ print_result_with_scheme(file_infos, option)
129
129
 
130
130
  def test_print_result(self):
131
131
  file_infos = [FileInformation('f1.c', 1, []), FileInformation('f2.c', 1, [])]
@@ -0,0 +1,117 @@
1
+ from os.path import join
2
+ from shutil import rmtree
3
+ from tempfile import mkdtemp
4
+ import unittest
5
+ from lizard import html_output, print_xml, parse_args, main
6
+
7
+
8
+ class TestFileOutputArgParsing(unittest.TestCase):
9
+
10
+ def test_short(self):
11
+ args = parse_args(["lizard", "-o test_file"])
12
+ self.assertEqual("test_file", args.output_file.strip())
13
+
14
+ def test_long(self):
15
+ args = parse_args(["lizard", "--output_file", "test_file"])
16
+ self.assertEqual("test_file", args.output_file.strip())
17
+
18
+ def test_standalone(self):
19
+ args = parse_args(["lizard", "--output_file", "test_file.html"])
20
+ self.assertEqual(html_output, args.printer)
21
+
22
+ def test_override(self):
23
+ args = parse_args(["lizard", "--output_file test_file.html", "--xml"])
24
+ self.assertEqual(print_xml, args.printer)
25
+
26
+
27
+ class TestFileOutputIntegration(unittest.TestCase):
28
+
29
+ def setUp(self):
30
+ self.tmp_dir = mkdtemp()
31
+ print("Tmp directory '{}' created.\n".format(self.tmp_dir))
32
+
33
+ def tearDown(self):
34
+ rmtree(self.tmp_dir)
35
+ print("Tmp directory '{}' deleted.\n".format(self.tmp_dir))
36
+
37
+ def output_test(self, file_name, expected_first_line):
38
+ path = join(self.tmp_dir, file_name)
39
+ args = ["lizard", "--verbose", "--output_file", path, "test/data"]
40
+ main(args)
41
+ first_line = open(path, 'r').readline().strip('\n')
42
+ self.assertEqual(first_line, expected_first_line)
43
+
44
+ def test_default(self):
45
+ header = "================================================"
46
+ self.output_test("test", header)
47
+
48
+ def test_csv(self):
49
+ header = "NLOC,CCN,token,PARAM,length,location,file,function,long_name,start,end"
50
+ self.output_test("test.csv", header)
51
+
52
+ def test_html(self):
53
+ header = "<!DOCTYPE HTML PUBLIC"
54
+ self.output_test("test.html", header)
55
+
56
+ def test_xml(self):
57
+ header = "<?xml version=\"1.0\" ?>"
58
+ self.output_test("test.xml", header)
59
+
60
+ def test_html_output_with_extension(self):
61
+ """Test that using --html with .html extension works correctly without warnings"""
62
+ path = join(self.tmp_dir, "test.html")
63
+ args = ["lizard", "--length", "75", "--CCN", "20", "--arguments", "3",
64
+ "--warnings_only", "--sort", "cyclomatic_complexity", "--html",
65
+ "--output_file", path, "test/data"]
66
+
67
+ # Capture stdout to check for warnings
68
+ import sys
69
+ from io import StringIO
70
+ stdout = StringIO()
71
+ old_stdout = sys.stdout
72
+ sys.stdout = stdout
73
+
74
+ try:
75
+ main(args)
76
+ output = stdout.getvalue()
77
+ self.assertNotIn("Warning: overriding output file extension", output,
78
+ "Should not show extension warning when using .html extension with --html")
79
+
80
+ # Verify the file exists and has HTML content
81
+ with open(path, 'r') as f:
82
+ content = f.read()
83
+ self.assertTrue(content.startswith("<!DOCTYPE HTML PUBLIC"),
84
+ "Output file should contain HTML content")
85
+ finally:
86
+ sys.stdout = old_stdout
87
+
88
+ def test_html_extension_warning(self):
89
+ """Test that using --html with .html extension works correctly without warnings,
90
+ but using a different format with .html extension shows a warning"""
91
+ path = join(self.tmp_dir, "test.html")
92
+
93
+ # Capture stderr to check for warnings
94
+ import sys
95
+ from io import StringIO
96
+ stderr = StringIO()
97
+ old_stderr = sys.stderr
98
+ sys.stderr = stderr
99
+
100
+ try:
101
+ # First test: --html with .html extension should not warn
102
+ args = ["lizard", "--html", "--output_file", path, "test/data"]
103
+ main(args)
104
+ output = stderr.getvalue()
105
+ self.assertNotIn("Warning: overriding output file extension", output,
106
+ "Should not show warning when format matches extension")
107
+ stderr.truncate(0)
108
+ stderr.seek(0)
109
+
110
+ # Second test: --xml with .html extension should warn
111
+ args = ["lizard", "--xml", "--output_file", path, "test/data"]
112
+ main(args)
113
+ output = stderr.getvalue()
114
+ self.assertIn("Warning: overriding output file extension", output,
115
+ "Should show warning when format doesn't match extension")
116
+ finally:
117
+ sys.stderr = old_stderr
@@ -1,20 +0,0 @@
1
- '''
2
- Language parser for C Sharp
3
- '''
4
-
5
- from .clike import CLikeReader
6
-
7
-
8
- class CSharpReader(CLikeReader):
9
- # pylint: disable=R0903
10
-
11
- ext = ['cs']
12
- language_names = ['csharp']
13
-
14
- _conditions = set(['if', 'for', 'while', '&&', '||', '?', 'catch',
15
- 'case', '??'])
16
-
17
- @staticmethod
18
- def generate_tokens(source_code, addition='', token_class=None):
19
- return CLikeReader.generate_tokens(
20
- source_code, r"|(?:\?\?)", token_class)
@@ -1,44 +0,0 @@
1
- '''
2
- Language parser for Java
3
- '''
4
-
5
- from .clike import CLikeReader, CLikeStates, CLikeNestingStackStates
6
-
7
-
8
- class JavaReader(CLikeReader):
9
- # pylint: disable=R0903
10
-
11
- ext = ['java']
12
- language_names = ['java']
13
-
14
- def __init__(self, context):
15
- super(JavaReader, self).__init__(context)
16
- self.parallel_states = [
17
- JavaStates(context),
18
- CLikeNestingStackStates(context)]
19
-
20
-
21
- class JavaStates(CLikeStates): # pylint: disable=R0903
22
- def _state_old_c_params(self, token):
23
- if token == '{':
24
- self._state_dec_to_imp(token)
25
-
26
- def try_new_function(self, name):
27
- self.context.try_new_function(name)
28
- self._state = self._state_function
29
-
30
- def _state_global(self, token):
31
- if token == '@':
32
- self._state = self._state_decorator
33
- return
34
- super(JavaStates, self)._state_global(token)
35
-
36
- def _state_decorator(self, _):
37
- self._state = self._state_post_decorator
38
-
39
- def _state_post_decorator(self, token):
40
- if token == '.':
41
- self._state = self._state_decorator
42
- else:
43
- self._state = self._state_global
44
- self._state(token)
@@ -1,19 +0,0 @@
1
- '''
2
- Language parser for JavaScript
3
- '''
4
-
5
- from .code_reader import CodeReader
6
- from .clike import CCppCommentsMixin
7
- from .js_style_regex_expression import js_style_regex_expression
8
- from .js_style_language_states import JavaScriptStyleLanguageStates
9
- from .typescript import TypeScriptReader, JSTokenizer
10
-
11
-
12
- class JavaScriptReader(TypeScriptReader):
13
- # pylint: disable=R0903
14
-
15
- ext = ['js']
16
- language_names = ['javascript', 'js']
17
-
18
- def __init__(self, context):
19
- super(JavaScriptReader, self).__init__(context)
@@ -1,58 +0,0 @@
1
- from os.path import join
2
- from shutil import rmtree
3
- from tempfile import mkdtemp
4
- import unittest
5
- from lizard import html_output, print_xml, parse_args, main
6
-
7
-
8
- class TestFileOutputArgParsing(unittest.TestCase):
9
-
10
- def test_short(self):
11
- args = parse_args(["lizard", "-o test_file"])
12
- self.assertEqual("test_file", args.output_file.strip())
13
-
14
- def test_long(self):
15
- args = parse_args(["lizard", "--output_file", "test_file"])
16
- self.assertEqual("test_file", args.output_file.strip())
17
-
18
- def test_standalone(self):
19
- args = parse_args(["lizard", "--output_file", "test_file.html"])
20
- self.assertEqual(html_output, args.printer)
21
-
22
- def test_override(self):
23
- args = parse_args(["lizard", "--output_file test_file.html", "--xml"])
24
- self.assertEqual(print_xml, args.printer)
25
-
26
-
27
- class TestFileOutputIntegration(unittest.TestCase):
28
-
29
- def setUp(self):
30
- self.tmp_dir = mkdtemp()
31
- print("Tmp directory '{}' created.\n".format(self.tmp_dir))
32
-
33
- def tearDown(self):
34
- rmtree(self.tmp_dir)
35
- print("Tmp directory '{}' deleted.\n".format(self.tmp_dir))
36
-
37
- def output_test(self, file_name, expected_first_line):
38
- path = join(self.tmp_dir, file_name)
39
- args = ["lizard", "--verbose", "--output_file", path, "test/data"]
40
- main(args)
41
- first_line = open(path, 'r').readline().strip('\n')
42
- self.assertEqual(first_line, expected_first_line)
43
-
44
- def test_default(self):
45
- header = "================================================"
46
- self.output_test("test", header)
47
-
48
- def test_csv(self):
49
- header = "NLOC,CCN,token,PARAM,length,location,file,function,long_name,start,end"
50
- self.output_test("test.csv", header)
51
-
52
- def test_html(self):
53
- header = "<!DOCTYPE HTML PUBLIC"
54
- self.output_test("test.html", header)
55
-
56
- def test_xml(self):
57
- header = "<?xml version=\"1.0\" ?>"
58
- self.output_test("test.xml", header)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes