1- import pytest
21import os
3- from spice .analyzers .count_inline_comments import count_inline_comments
2+ import re
3+ from pygments import highlight
4+ from pygments .lexers import get_lexer_for_filename
5+ from pygments .token import Token
46
5- # Define the path to the sample code directory relative to the test file
6- SAMPLE_CODE_DIR = os .path .join (os .path .dirname (__file__ ), ".." , ".." , "sample-code" )
77
8- # Helper function to create a temporary file
9- def create_temp_file (content , filename = "temp_inline_test_file" ):
10- file_path = os .path .join (SAMPLE_CODE_DIR , filename )
11- with open (file_path , "w" , encoding = "utf-8" ) as f :
12- f .write (content )
13- return file_path
14-
15- # Test cases for count_inline_comments
16- @pytest .mark .parametrize (
17- "filename, expected_inline_comments" ,
18- [
19- # Based on the content of ratio_sample.* files
20- ("ratio_sample.py" , 2 ), # `import sys # ...`, `y = 2 # ...`
21- ("ratio_sample.js" , 2 ), # `const x = 1; // ...`, `let y = 2; // ...`
22- ("ratio_sample.go" , 2 ), # `package main // ...`, `func main() { // ...`
23- ("ratio_sample.rb" , 3 ), # `require ... # ...`, `x * 2 # ...`, `puts ... # ...`
24- # Based on func_sample.* files
25- ("func_sample.py" , 0 ), # No inline comments in this specific sample
26- ("func_sample.js" , 0 ), # No inline comments in this specific sample
27- ("func_sample.go" , 0 ), # No inline comments in this specific sample
28- ("func_sample.rb" , 0 ), # No inline comments in this specific sample
29- # Based on original example.* files
30- ("example.py" , 1 ), # `print("Hello, Python!") # Output greeting`
31- ("example.js" , 1 ), # `console.log("Hello, JavaScript!"); // Output greeting`
32- ("example.go" , 1 ), # `fmt.Println("Hello, Go!") // Output greeting`
33- ("example.rb" , 1 ), # `puts "Hello, Ruby!" # Output greeting`
34- ]
35- )
36- def test_count_inline_comments_sample_files (filename , expected_inline_comments ):
37- """Test count_inline_comments with various sample files."""
38- file_path = os .path .join (SAMPLE_CODE_DIR , filename )
39- assert os .path .exists (file_path ), f"Sample file not found: { file_path } "
40- assert count_inline_comments (file_path ) == expected_inline_comments
41-
42- def test_count_inline_comments_empty_file ():
43- """Test count_inline_comments with an empty file."""
44- empty_file_path = create_temp_file ("" , "empty_inline.tmp" )
45- assert count_inline_comments (empty_file_path ) == 0
46- os .remove (empty_file_path )
47-
48- def test_count_inline_comments_no_comments ():
49- """Test count_inline_comments with a file containing no comments."""
50- no_comments_path = create_temp_file ("print(\" Hello\" )\n x = 1" , "no_comments_inline.py" )
51- assert count_inline_comments (no_comments_path ) == 0
52- os .remove (no_comments_path )
53-
54- def test_count_inline_comments_only_full_line ():
55- """Test count_inline_comments with only full-line comments."""
56- full_line_comments_path = create_temp_file ("# line 1\n # line 2" , "full_line_inline.py" )
57- assert count_inline_comments (full_line_comments_path ) == 0
58- os .remove (full_line_comments_path )
59-
60- def test_count_inline_comments_mixed ():
61- """Test count_inline_comments with mixed comment types."""
62- mixed_path = create_temp_file ("# full line\n x = 1 # inline\n # another full line\n y=2" , "mixed_inline.py" )
63- assert count_inline_comments (mixed_path ) == 1
64- os .remove (mixed_path )
65-
66- def test_count_inline_comments_unsupported_extension ():
67- """Test count_inline_comments with an unsupported file extension."""
68- unsupported_path = create_temp_file ("code # inline comment" , "unsupported_inline.txt" )
69- # Should raise ValueError because lexer cannot be found
70- with pytest .raises (ValueError ):
71- count_inline_comments (unsupported_path )
72- os .remove (unsupported_path )
8+ def count_inline_comments (file_path ):
9+ """
10+ Count inline comments in a source code file.
11+
12+ An inline comment is a comment that appears on the same line as code,
13+ not on a line by itself.
14+
15+ Args:
16+ file_path (str): Path to the source code file
17+
18+ Returns:
19+ int: Number of inline comments found
20+
21+ Raises:
22+ ValueError: If the file extension is not supported by Pygments
23+ FileNotFoundError: If the file doesn't exist
24+ """
25+ if not os .path .exists (file_path ):
26+ raise FileNotFoundError (f"File not found: { file_path } " )
27+
28+ try :
29+ # Get the appropriate lexer for the file
30+ lexer = get_lexer_for_filename (file_path )
31+ except Exception :
32+ raise ValueError (f"Unsupported file extension: { file_path } " )
33+
34+ # Read the file content
35+ try :
36+ with open (file_path , 'r' , encoding = 'utf-8' ) as f :
37+ content = f .read ()
38+ except UnicodeDecodeError :
39+ # Try with different encoding if UTF-8 fails
40+ with open (file_path , 'r' , encoding = 'latin-1' ) as f :
41+ content = f .read ()
42+
43+ if not content .strip ():
44+ return 0
45+
46+ # Tokenize the content
47+ tokens = list (lexer .get_tokens (content ))
48+
49+ # Group tokens by line
50+ lines = content .splitlines ()
51+ line_tokens = {i + 1 : [] for i in range (len (lines ))}
52+
53+ current_line = 1
54+ current_pos = 0
55+
56+ for token_type , token_value in tokens :
57+ if token_value == '\n ' :
58+ current_line += 1
59+ current_pos = 0
60+ elif token_value :
61+ # Find which line this token belongs to
62+ token_lines = token_value .count ('\n ' )
63+ if token_lines == 0 :
64+ line_tokens [current_line ].append ((token_type , token_value ))
65+ else :
66+ # Multi-line token
67+ parts = token_value .split ('\n ' )
68+ for i , part in enumerate (parts ):
69+ if part :
70+ line_tokens [current_line + i ].append ((token_type , part ))
71+ current_line += token_lines
72+
73+ inline_comment_count = 0
74+
75+ # Check each line for inline comments
76+ for line_num , line_token_list in line_tokens .items ():
77+ if not line_token_list :
78+ continue
79+
80+ # Check if this line has both code and comments
81+ has_code = False
82+ has_comment = False
83+
84+ for token_type , token_value in line_token_list :
85+ # Skip whitespace tokens
86+ if token_type in (Token .Text , Token .Text .Whitespace ) and token_value .strip () == '' :
87+ continue
88+
89+ # Check if it's a comment token
90+ if token_type in Token .Comment :
91+ has_comment = True
92+ elif token_type not in (Token .Text , Token .Text .Whitespace ):
93+ # Non-whitespace, non-comment token = code
94+ has_code = True
95+
96+ # If the line has both code and comments, it contains an inline comment
97+ if has_code and has_comment :
98+ inline_comment_count += 1
99+
100+ return inline_comment_count
73101
74102
103+ # Alternative simpler implementation using regex patterns
104+ def count_inline_comments_regex (file_path ):
105+ """
106+ Alternative implementation using regex patterns for comment detection.
107+ This is simpler but less accurate than the Pygments-based approach.
108+ """
109+ if not os .path .exists (file_path ):
110+ raise FileNotFoundError (f"File not found: { file_path } " )
111+
112+ # Get file extension
113+ _ , ext = os .path .splitext (file_path )
114+
115+ # Define comment patterns for different languages
116+ comment_patterns = {
117+ '.py' : r'#.*' ,
118+ '.js' : r'//.*' ,
119+ '.go' : r'//.*' ,
120+ '.rb' : r'#.*' ,
121+ '.java' : r'//.*' ,
122+ '.cpp' : r'//.*' ,
123+ '.c' : r'//.*' ,
124+ '.cs' : r'//.*' ,
125+ '.php' : r'//.*' ,
126+ '.swift' : r'//.*' ,
127+ '.kt' : r'//.*' ,
128+ '.scala' : r'//.*' ,
129+ }
130+
131+ if ext not in comment_patterns :
132+ raise ValueError (f"Unsupported file extension: { ext } " )
133+
134+ comment_pattern = comment_patterns [ext ]
135+
136+ try :
137+ with open (file_path , 'r' , encoding = 'utf-8' ) as f :
138+ lines = f .readlines ()
139+ except UnicodeDecodeError :
140+ with open (file_path , 'r' , encoding = 'latin-1' ) as f :
141+ lines = f .readlines ()
142+
143+ inline_comment_count = 0
144+
145+ for line in lines :
146+ line = line .strip ()
147+ if not line :
148+ continue
149+
150+ # Find comment in the line
151+ comment_match = re .search (comment_pattern , line )
152+ if comment_match :
153+ # Check if there's code before the comment
154+ code_before_comment = line [:comment_match .start ()].strip ()
155+ if code_before_comment :
156+ inline_comment_count += 1
157+
158+ return inline_comment_count
0 commit comments