Skip to content

Commit 69c313e

Browse files
authored
Merge pull request #158 from codecov/dana/js-analyzer-improvements
JS analyzer improvements
2 parents a3fbcfd + e2415b8 commit 69c313e

6 files changed

Lines changed: 293 additions & 82 deletions

File tree

codecov_cli/services/staticanalysis/analyzers/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from codecov_cli.services.staticanalysis.analyzers.ecmascriptsix import ES6Analyzer
2-
from codecov_cli.services.staticanalysis.analyzers.general import GeneralAnalyzer
1+
from codecov_cli.services.staticanalysis.analyzers.general import BaseAnalyzer
2+
from codecov_cli.services.staticanalysis.analyzers.javascript_es6 import ES6Analyzer
33
from codecov_cli.services.staticanalysis.analyzers.python import PythonAnalyzer
44

55

6-
def get_best_analyzer(filename, actual_code):
6+
def get_best_analyzer(filename, actual_code) -> BaseAnalyzer:
77
if filename.actual_filepath.suffix == ".py":
88
return PythonAnalyzer(filename, actual_code)
99
if filename.actual_filepath.suffix == ".js":
Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,56 @@
1-
class GeneralAnalyzer(object):
1+
class BaseAnalyzer(object):
22
def __init__(self, filename, actual_code):
33
pass
44

55
def process(self):
66
return {}
7+
8+
def _count_elements(self, node, types):
9+
count = 0
10+
for c in node.children:
11+
count += self._count_elements(c, types)
12+
if node.type in types:
13+
count += 1
14+
return count
15+
16+
def _get_max_nested_conditional(self, node):
17+
return (1 if node.type in self.condition_statements else 0) + max(
18+
(self._get_max_nested_conditional(x) for x in node.children), default=0
19+
)
20+
21+
def _get_complexity_metrics(self, body_node):
22+
number_conditions = self._count_elements(
23+
body_node,
24+
self.condition_statements,
25+
)
26+
return {
27+
"conditions": number_conditions,
28+
"mccabe_cyclomatic_complexity": number_conditions + 1,
29+
"returns": self._count_elements(body_node, ["return_statement"]),
30+
"max_nested_conditional": self._get_max_nested_conditional(body_node),
31+
}
32+
33+
def _get_name(self, node):
34+
name_node = node.child_by_field_name("name")
35+
actual_name = self.actual_code[
36+
name_node.start_byte : name_node.end_byte
37+
].decode()
38+
try:
39+
wrapping_class = next(
40+
x for x in self._get_parent_chain(node) if x.type in self.wrappers
41+
)
42+
except StopIteration:
43+
wrapping_class = None
44+
if wrapping_class is not None:
45+
class_name_node = wrapping_class.child_by_field_name("name")
46+
class_name = self.actual_code[
47+
class_name_node.start_byte : class_name_node.end_byte
48+
].decode()
49+
return f"{class_name}::{actual_name}"
50+
return actual_name
51+
52+
def _get_parent_chain(self, node):
53+
cur = node.parent
54+
while cur:
55+
yield cur
56+
cur = cur.parent

codecov_cli/services/staticanalysis/analyzers/ecmascriptsix.py renamed to codecov_cli/services/staticanalysis/analyzers/javascript_es6/__init__.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,29 @@
33
from tree_sitter import Language, Parser
44

55
import staticcodecov_languages
6+
from codecov_cli.services.staticanalysis.analyzers.general import BaseAnalyzer
67

78
function_query_str = """
89
(function_declaration) @elemen
910
"""
1011

12+
method_query_str = """
13+
(method_definition) @elemen
14+
"""
15+
16+
17+
class ES6Analyzer(BaseAnalyzer):
18+
condition_statements = [
19+
"if_statement",
20+
"switch_statement",
21+
"for_statement",
22+
"for_in_statement",
23+
"while_statement",
24+
"do_statement",
25+
]
26+
27+
wrappers = ["class_declaration", "function_declaration"]
1128

12-
class ES6Analyzer(object):
1329
def __init__(self, path, actual_code, **options):
1430
self.actual_code = actual_code
1531
self.lines = self.actual_code.split(b"\n")
@@ -30,21 +46,21 @@ def process(self):
3046
tree = self.parser.parse(self.actual_code)
3147
root_node = tree.root_node
3248
function_query = self.JS_LANGUAGE.query(function_query_str)
33-
for func_node, _ in function_query.captures(root_node):
34-
name_node = func_node.child_by_field_name("name")
35-
actual_name = self.actual_code[
36-
name_node.start_byte : name_node.end_byte
37-
].decode()
49+
method_query = self.JS_LANGUAGE.query(method_query_str)
50+
combined_results = function_query.captures(root_node) + method_query.captures(
51+
root_node
52+
)
53+
for func_node, _ in combined_results:
3854
body_node = func_node.child_by_field_name("body")
3955
self.functions.append(
4056
{
41-
"identifier": actual_name,
57+
"identifier": self._get_name(func_node),
4258
"start_line": func_node.start_point[0] + 1,
4359
"end_line": func_node.end_point[0] + 1,
4460
"code_hash": self.get_code_hash(
4561
body_node.start_byte, body_node.end_byte
4662
),
47-
"complexity_metrics": {},
63+
"complexity_metrics": self._get_complexity_metrics(body_node),
4864
}
4965
)
5066
h = hashlib.md5()

codecov_cli/services/staticanalysis/analyzers/python/__init__.py

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from tree_sitter import Language, Parser
44

55
import staticcodecov_languages
6+
from codecov_cli.services.staticanalysis.analyzers.general import BaseAnalyzer
67
from codecov_cli.services.staticanalysis.analyzers.python.node_wrappers import (
78
NodeVisitor,
89
)
@@ -41,14 +42,15 @@
4142
"""
4243

4344

44-
class PythonAnalyzer(object):
45+
class PythonAnalyzer(BaseAnalyzer):
4546

4647
condition_statements = [
4748
"if_statement",
4849
"while_statement",
4950
"for_statement",
5051
"conditional_expression",
5152
]
53+
wrappers = ["class_definition", "function_definition"]
5254

5355
def __init__(self, path, actual_code, **options):
5456
self.actual_code = actual_code
@@ -127,64 +129,7 @@ def process(self):
127129
"import_lines": sorted(self.import_lines),
128130
}
129131

130-
def _count_elements(self, node, types):
131-
count = 0
132-
for c in node.children:
133-
count += self._count_elements(c, types)
134-
if node.type in types:
135-
count += 1
136-
return count
137-
138-
def _get_max_nested_conditional(self, node):
139-
return (1 if node.type in self.condition_statements else 0) + max(
140-
(self._get_max_nested_conditional(x) for x in node.children), default=0
141-
)
142-
143-
def _get_complexity_metrics(self, body_node):
144-
number_conditions = self._count_elements(
145-
body_node,
146-
[
147-
"if_statement",
148-
"while_statement",
149-
"for_statement",
150-
"conditional_expression",
151-
],
152-
)
153-
return {
154-
"conditions": number_conditions,
155-
"mccabe_cyclomatic_complexity": number_conditions + 1,
156-
"returns": self._count_elements(body_node, ["return_statement"]),
157-
"max_nested_conditional": self._get_max_nested_conditional(body_node),
158-
}
159-
160132
def _get_code_hash(self, start_byte, end_byte):
161133
j = hashlib.md5()
162134
j.update(self.actual_code[start_byte:end_byte].strip())
163135
return j.hexdigest()
164-
165-
def _get_parent_chain(self, node):
166-
cur = node.parent
167-
while cur:
168-
yield cur
169-
cur = cur.parent
170-
171-
def _get_name(self, node):
172-
name_node = node.child_by_field_name("name")
173-
actual_name = self.actual_code[
174-
name_node.start_byte : name_node.end_byte
175-
].decode()
176-
try:
177-
wrapping_class = next(
178-
x
179-
for x in self._get_parent_chain(node)
180-
if x.type in ("class_definition", "function_definition")
181-
)
182-
except StopIteration:
183-
wrapping_class = None
184-
if wrapping_class is not None:
185-
class_name_node = wrapping_class.child_by_field_name("name")
186-
class_name = self.actual_code[
187-
class_name_node.start_byte : class_name_node.end_byte
188-
].decode()
189-
return f"{class_name}::{actual_name}"
190-
return actual_name

samples/inputs/sample_003.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,98 @@
11
function myFunction(p1, p2) {
22
return p1 * p2; // The function returns the product of p1 and p2
33
}
4+
5+
// Variable declaration and assignment
6+
let x = 10;
7+
const PI = 3.14159;
8+
var y = "Hello";
9+
10+
// Function declaration and invocation
11+
function add(a, b) {
12+
return a + b;
13+
}
14+
15+
let result = add(x, 5);
16+
17+
// Conditional statements
18+
if (x > 0) {
19+
console.log("x is positive");
20+
} else if (x < 0) {
21+
console.log("x is negative");
22+
} else {
23+
console.log("x is zero");
24+
}
25+
26+
// Looping constructs
27+
for (let i = 0; i < 5; i++) {
28+
console.log(i);
29+
}
30+
31+
let numbers = [1, 2, 3, 4, 5];
32+
for (let number of numbers) {
33+
console.log(number);
34+
}
35+
36+
// Objects and properties
37+
let person = {
38+
name: "John",
39+
age: 30,
40+
address: {
41+
street: "123 Main St",
42+
city: "New York",
43+
},
44+
};
45+
46+
console.log(person.name);
47+
console.log(person.address.city);
48+
49+
// Arrays and array methods
50+
let fruits = ["apple", "banana", "orange"];
51+
fruits.push("grape");
52+
console.log(fruits.length);
53+
54+
// Classes and inheritance
55+
class Shape {
56+
constructor(color) {
57+
this.color = color;
58+
}
59+
60+
getColor() {
61+
return this.color;
62+
}
63+
}
64+
65+
class Circle extends Shape {
66+
constructor(color, radius) {
67+
super(color);
68+
this.radius = radius;
69+
}
70+
71+
getArea() {
72+
return Math.PI * this.radius * this.radius;
73+
}
74+
}
75+
76+
let myCircle = new Circle("red", 5);
77+
console.log(myCircle.getColor());
78+
console.log(myCircle.getArea());
79+
80+
// Promises and async/await
81+
function fetchData() {
82+
return new Promise((resolve, reject) => {
83+
setTimeout(() => {
84+
resolve("Data received");
85+
}, 2000);
86+
});
87+
}
88+
89+
async function getData() {
90+
try {
91+
let data = await fetchData();
92+
console.log(data);
93+
} catch (error) {
94+
console.error(error);
95+
}
96+
}
97+
98+
getData();

0 commit comments

Comments
 (0)