code-gauge 10.4.0 → 10.4.2
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.
- package/THIRD-PARTY-NOTICES.txt +4 -4
- package/native/Cargo.lock +8 -8
- package/native/Cargo.toml +4 -4
- package/native/code-gauge.wasm +0 -0
- package/native/src/complexity.rs +36 -35
- package/native/src/container.rs +7 -8
- package/native/src/dep_degree.rs +190 -97
- package/native/src/functions.rs +5 -3
- package/native/src/languages.rs +6 -1
- package/native/src/measure.rs +157 -83
- package/native/src/ncss.rs +16 -24
- package/native/src/tree_index.rs +145 -44
- package/native/src/util.rs +36 -5
- package/package.json +8 -8
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -8366,7 +8366,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
8366
8366
|
DEALINGS IN THE SOFTWARE.
|
|
8367
8367
|
|
|
8368
8368
|
====================================================================================================
|
|
8369
|
-
willbooster-tree-sitter
|
|
8369
|
+
willbooster-tree-sitter 18.0.0 — MIT — https://github.com/WillBooster/tree-sitter
|
|
8370
8370
|
====================================================================================================
|
|
8371
8371
|
----- LICENSE -----
|
|
8372
8372
|
The MIT License (MIT)
|
|
@@ -9004,7 +9004,7 @@ permissive licensing, and of not having licensing issues being an
|
|
|
9004
9004
|
obstacle to adoption, that text has been removed.
|
|
9005
9005
|
|
|
9006
9006
|
====================================================================================================
|
|
9007
|
-
willbooster-tree-sitter-c 34.0.
|
|
9007
|
+
willbooster-tree-sitter-c 34.0.1 — MIT — https://github.com/WillBooster/tree-sitter-c
|
|
9008
9008
|
====================================================================================================
|
|
9009
9009
|
----- LICENSE -----
|
|
9010
9010
|
The MIT License (MIT)
|
|
@@ -9112,7 +9112,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9112
9112
|
SOFTWARE.
|
|
9113
9113
|
|
|
9114
9114
|
====================================================================================================
|
|
9115
|
-
willbooster-tree-sitter-java 32.0.
|
|
9115
|
+
willbooster-tree-sitter-java 32.0.1 — MIT — https://github.com/WillBooster/tree-sitter-java
|
|
9116
9116
|
====================================================================================================
|
|
9117
9117
|
----- LICENSE -----
|
|
9118
9118
|
MIT License
|
|
@@ -9139,7 +9139,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
9139
9139
|
SOFTWARE.
|
|
9140
9140
|
|
|
9141
9141
|
====================================================================================================
|
|
9142
|
-
willbooster-tree-sitter-javascript
|
|
9142
|
+
willbooster-tree-sitter-javascript 22.0.1 — MIT — https://github.com/WillBooster/tree-sitter-javascript
|
|
9143
9143
|
====================================================================================================
|
|
9144
9144
|
----- LICENSE -----
|
|
9145
9145
|
The MIT License (MIT)
|
package/native/Cargo.lock
CHANGED
|
@@ -444,9 +444,9 @@ checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
|
|
444
444
|
|
|
445
445
|
[[package]]
|
|
446
446
|
name = "willbooster-tree-sitter"
|
|
447
|
-
version = "
|
|
447
|
+
version = "18.0.0"
|
|
448
448
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
449
|
-
checksum = "
|
|
449
|
+
checksum = "19067d0eaf4269b5a32dada864141421f52d3202851293a2e728b2e61d747042"
|
|
450
450
|
dependencies = [
|
|
451
451
|
"cc",
|
|
452
452
|
"regex",
|
|
@@ -457,9 +457,9 @@ dependencies = [
|
|
|
457
457
|
|
|
458
458
|
[[package]]
|
|
459
459
|
name = "willbooster-tree-sitter-c"
|
|
460
|
-
version = "34.0.
|
|
460
|
+
version = "34.0.1"
|
|
461
461
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
462
|
-
checksum = "
|
|
462
|
+
checksum = "98b15e051a2686771682b936a6619d3e437d11bb788fc74d8e00b21636537276"
|
|
463
463
|
dependencies = [
|
|
464
464
|
"cc",
|
|
465
465
|
"tree-sitter-language",
|
|
@@ -497,9 +497,9 @@ dependencies = [
|
|
|
497
497
|
|
|
498
498
|
[[package]]
|
|
499
499
|
name = "willbooster-tree-sitter-java"
|
|
500
|
-
version = "32.0.
|
|
500
|
+
version = "32.0.1"
|
|
501
501
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
502
|
-
checksum = "
|
|
502
|
+
checksum = "11ca2f843522dadfeacd962372acc115d461b9451e743eb6fc45ee80653516e6"
|
|
503
503
|
dependencies = [
|
|
504
504
|
"cc",
|
|
505
505
|
"tree-sitter-language",
|
|
@@ -507,9 +507,9 @@ dependencies = [
|
|
|
507
507
|
|
|
508
508
|
[[package]]
|
|
509
509
|
name = "willbooster-tree-sitter-javascript"
|
|
510
|
-
version = "
|
|
510
|
+
version = "22.0.1"
|
|
511
511
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
512
|
-
checksum = "
|
|
512
|
+
checksum = "1509fcae81aebd53f005ca3c09a21c76ddd6c76ad581715f1d1f1a96d969f7e4"
|
|
513
513
|
dependencies = [
|
|
514
514
|
"cc",
|
|
515
515
|
"tree-sitter-language",
|
package/native/Cargo.toml
CHANGED
|
@@ -22,13 +22,13 @@ rustc-hash = "2"
|
|
|
22
22
|
# `willbooster-*` crates are WillBooster's forks of the runtime and grammars. Every grammar crate
|
|
23
23
|
# depends only on `tree-sitter-language`, never on upstream's `tree-sitter` crate, whose
|
|
24
24
|
# `links = "tree-sitter"` would conflict with the forked runtime's.
|
|
25
|
-
tree-sitter = { package = "willbooster-tree-sitter", version = "
|
|
26
|
-
tree-sitter-c = { package = "willbooster-tree-sitter-c", version = "=34.0.
|
|
25
|
+
tree-sitter = { package = "willbooster-tree-sitter", version = "18" }
|
|
26
|
+
tree-sitter-c = { package = "willbooster-tree-sitter-c", version = "=34.0.1" }
|
|
27
27
|
tree-sitter-c-sharp = { package = "willbooster-tree-sitter-c-sharp", version = "=19.0.0" }
|
|
28
28
|
tree-sitter-cpp = { package = "willbooster-tree-sitter-cpp", version = "=28.0.0" }
|
|
29
29
|
tree-sitter-go = { package = "willbooster-tree-sitter-go", version = "=9.0.0" }
|
|
30
|
-
tree-sitter-java = { package = "willbooster-tree-sitter-java", version = "=32.0.
|
|
31
|
-
tree-sitter-javascript = { package = "willbooster-tree-sitter-javascript", version = "=
|
|
30
|
+
tree-sitter-java = { package = "willbooster-tree-sitter-java", version = "=32.0.1" }
|
|
31
|
+
tree-sitter-javascript = { package = "willbooster-tree-sitter-javascript", version = "=22.0.1" }
|
|
32
32
|
tree-sitter-kotlin = { package = "willbooster-tree-sitter-kotlin", version = "=19.0.0" }
|
|
33
33
|
tree-sitter-python = { package = "willbooster-tree-sitter-python", version = "=16.0.0" }
|
|
34
34
|
tree-sitter-ruby = { package = "willbooster-tree-sitter-ruby", version = "=15.0.0" }
|
package/native/code-gauge.wasm
CHANGED
|
Binary file
|
package/native/src/complexity.rs
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
use rustc_hash::
|
|
1
|
+
use rustc_hash::FxHashMap;
|
|
2
|
+
use std::sync::OnceLock;
|
|
2
3
|
use tree_sitter::Node;
|
|
3
4
|
|
|
4
|
-
use crate::
|
|
5
|
+
use crate::languages::LANGUAGES;
|
|
6
|
+
use crate::tree_index::{KindSet, NodeExt};
|
|
5
7
|
use crate::types::CognitiveBlock;
|
|
6
8
|
use crate::util::{all_children, node_text, Source};
|
|
7
9
|
|
|
8
|
-
/// Node-type lookup sets
|
|
10
|
+
/// Node-type lookup sets of a language.
|
|
9
11
|
pub struct LanguageSets {
|
|
10
|
-
pub function_nodes:
|
|
11
|
-
pub decision_nodes:
|
|
12
|
-
pub nesting_nodes:
|
|
13
|
-
pub ncss_nodes:
|
|
14
|
-
pub ncss_containers:
|
|
15
|
-
pub ncss_bare_body_parents:
|
|
12
|
+
pub function_nodes: KindSet,
|
|
13
|
+
pub decision_nodes: KindSet,
|
|
14
|
+
pub nesting_nodes: KindSet,
|
|
15
|
+
pub ncss_nodes: KindSet,
|
|
16
|
+
pub ncss_containers: KindSet,
|
|
17
|
+
pub ncss_bare_body_parents: KindSet,
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
impl LanguageSets {
|
|
19
|
-
pub fn
|
|
20
|
-
LanguageSets
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
pub fn of(language: &crate::languages::LanguageDefinition) -> &'static LanguageSets {
|
|
22
|
+
static SETS: [OnceLock<LanguageSets>; LANGUAGES.len()] =
|
|
23
|
+
[const { OnceLock::new() }; LANGUAGES.len()];
|
|
24
|
+
let index = LANGUAGES
|
|
25
|
+
.iter()
|
|
26
|
+
.position(|candidate| candidate.name == language.name)
|
|
27
|
+
.expect("every language is listed in LANGUAGES");
|
|
28
|
+
SETS[index].get_or_init(|| LanguageSets {
|
|
29
|
+
function_nodes: KindSet::new(language.function_node_types),
|
|
30
|
+
decision_nodes: KindSet::new(language.decision_node_types),
|
|
31
|
+
nesting_nodes: KindSet::new(language.nesting_node_types),
|
|
32
|
+
ncss_nodes: KindSet::new(language.ncss_node_types),
|
|
33
|
+
ncss_containers: KindSet::new(language.ncss_container_node_types),
|
|
34
|
+
ncss_bare_body_parents: KindSet::new(language.ncss_bare_body_parent_node_types),
|
|
35
|
+
})
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -54,15 +58,15 @@ pub fn is_lambda_body_block(node: Node<'_>) -> bool {
|
|
|
54
58
|
/// A function node with a body of its own: bodyless declarations (abstract methods, auto-property
|
|
55
59
|
/// accessors, accessor-list properties) open no nesting frame, so members inside them are not
|
|
56
60
|
/// charged as nested functions.
|
|
57
|
-
pub fn is_function_boundary(node: Node<'_>, function_nodes: &
|
|
58
|
-
function_nodes.contains(node
|
|
61
|
+
pub fn is_function_boundary(node: Node<'_>, function_nodes: &KindSet) -> bool {
|
|
62
|
+
function_nodes.contains(node)
|
|
59
63
|
&& !is_lambda_body_block(node)
|
|
60
64
|
&& crate::functions::is_implemented_function(node)
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// Sonar cognitive complexity charges a switch/match once as a whole, not per case label. Only
|
|
64
68
|
// named nodes are consulted, so anonymous keyword tokens never match.
|
|
65
|
-
|
|
69
|
+
static SWITCH_LIKE_KINDS: KindSet = KindSet::new(&[
|
|
66
70
|
"switch_statement",
|
|
67
71
|
"switch_expression",
|
|
68
72
|
"expression_switch_statement",
|
|
@@ -73,10 +77,10 @@ const SWITCH_LIKE_NODE_TYPES: &[&str] = &[
|
|
|
73
77
|
"when_expression",
|
|
74
78
|
"case",
|
|
75
79
|
"case_match",
|
|
76
|
-
];
|
|
80
|
+
]);
|
|
77
81
|
|
|
78
82
|
// Per-case decision nodes: cyclomatic-only, because the switch itself carries the cognitive cost.
|
|
79
|
-
|
|
83
|
+
static CASE_CLAUSE_KINDS: KindSet = KindSet::new(&[
|
|
80
84
|
"case_clause",
|
|
81
85
|
"switch_case",
|
|
82
86
|
"switch_block_statement_group",
|
|
@@ -91,7 +95,7 @@ const CASE_CLAUSE_NODE_TYPES: &[&str] = &[
|
|
|
91
95
|
"when_entry",
|
|
92
96
|
"when",
|
|
93
97
|
"in_clause",
|
|
94
|
-
];
|
|
98
|
+
]);
|
|
95
99
|
|
|
96
100
|
const IF_LIKE_NODE_TYPES: &[&str] = &["if_statement", "if_expression", "if", "unless"];
|
|
97
101
|
|
|
@@ -303,13 +307,12 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
303
307
|
let counts_for_own_body = !inside_nested_region && !opens_frame;
|
|
304
308
|
|
|
305
309
|
let is_decision = self.is_decision(current);
|
|
306
|
-
let is_case_clause =
|
|
307
|
-
current.is_named() && CASE_CLAUSE_NODE_TYPES.contains(¤t.kind_name());
|
|
310
|
+
let is_case_clause = current.is_named() && CASE_CLAUSE_KINDS.contains(current);
|
|
308
311
|
// Ruby's `case ... else` arm is an `else` node; like every other language's default branch
|
|
309
312
|
// it nests its contents inside the switch (it cannot go in the Ruby nesting set because
|
|
310
313
|
// `if`/`begin` else branches would then double-nest under their already-nesting parent).
|
|
311
314
|
let is_nesting = current.is_named()
|
|
312
|
-
&& (self.sets.nesting_nodes.contains(current
|
|
315
|
+
&& (self.sets.nesting_nodes.contains(current)
|
|
313
316
|
|| (current.kind_name() == "else" && is_case_else_parent(parent)));
|
|
314
317
|
// `elsif`/`elif`/`else if` continue a flat chain: they add a decision without a nesting
|
|
315
318
|
// surcharge (Sonar cognitive-complexity semantics).
|
|
@@ -317,8 +320,7 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
317
320
|
let is_boolean_operator = is_boolean_operator(current, parent, self.code);
|
|
318
321
|
let is_pattern_guard = is_pattern_guard(current, parent);
|
|
319
322
|
|
|
320
|
-
let is_switch_like =
|
|
321
|
-
current.is_named() && SWITCH_LIKE_NODE_TYPES.contains(¤t.kind_name());
|
|
323
|
+
let is_switch_like = current.is_named() && SWITCH_LIKE_KINDS.contains(current);
|
|
322
324
|
// Opened before the node's own increments so that the block includes them. The sentinel
|
|
323
325
|
// frame's blocks would be those of top-level code, which no function reports.
|
|
324
326
|
if self.frames.len() > 1
|
|
@@ -513,8 +515,7 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
513
515
|
// Anonymous keyword tokens can share a type with named nodes (Ruby's `if` node contains an
|
|
514
516
|
// `if` keyword token), so only named nodes count as decisions.
|
|
515
517
|
node.is_named()
|
|
516
|
-
&& (self.sets.decision_nodes.contains(node
|
|
517
|
-
|| crate::util::is_else_if_clause(node))
|
|
518
|
+
&& (self.sets.decision_nodes.contains(node) || crate::util::is_else_if_clause(node))
|
|
518
519
|
&& !is_pathless_switch_branch(node)
|
|
519
520
|
}
|
|
520
521
|
|
package/native/src/container.rs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
use rustc_hash::FxHashSet;
|
|
2
1
|
use tree_sitter::Node;
|
|
3
2
|
|
|
4
3
|
use crate::functions::{
|
|
@@ -6,7 +5,7 @@ use crate::functions::{
|
|
|
6
5
|
find_string_literal_content, find_value_binding, is_value_group, next_declarator,
|
|
7
6
|
unwrap_transparent_value_wrappers,
|
|
8
7
|
};
|
|
9
|
-
use crate::tree_index::NodeExt;
|
|
8
|
+
use crate::tree_index::{KindSet, NodeExt};
|
|
10
9
|
use crate::util::{named_children, node_text, Source};
|
|
11
10
|
|
|
12
11
|
const CONTAINER_NODE_TYPES: &[&str] = &[
|
|
@@ -58,7 +57,7 @@ const CPP_SCOPE_NODE_TYPES: &[&str] = &[
|
|
|
58
57
|
/// enclosing it.
|
|
59
58
|
fn enclosing_cpp_scopes<'s>(
|
|
60
59
|
node: Node<'_>,
|
|
61
|
-
function_nodes: &
|
|
60
|
+
function_nodes: &KindSet,
|
|
62
61
|
code: &Source<'s>,
|
|
63
62
|
namespaces_only: bool,
|
|
64
63
|
) -> Vec<&'s str> {
|
|
@@ -98,7 +97,7 @@ fn explicit_ruby_owner(object: Option<Node<'_>>, code: &Source<'_>) -> Option<St
|
|
|
98
97
|
/// method); or the path a JavaScript object literal holding it is bound to.
|
|
99
98
|
pub fn find_container_name(
|
|
100
99
|
node: Node<'_>,
|
|
101
|
-
function_nodes: &
|
|
100
|
+
function_nodes: &KindSet,
|
|
102
101
|
code: &Source<'_>,
|
|
103
102
|
inline_namespaces: &InlineNamespaces<'_>,
|
|
104
103
|
) -> Option<String> {
|
|
@@ -201,7 +200,7 @@ fn assigns_attribute(assignment: Node<'_>, target: Node<'_>) -> bool {
|
|
|
201
200
|
/// to nothing the syntax names.
|
|
202
201
|
fn object_owner(
|
|
203
202
|
object: Node<'_>,
|
|
204
|
-
function_nodes: &
|
|
203
|
+
function_nodes: &KindSet,
|
|
205
204
|
code: &Source<'_>,
|
|
206
205
|
inline_namespaces: &InlineNamespaces<'_>,
|
|
207
206
|
) -> Option<String> {
|
|
@@ -293,7 +292,7 @@ fn find_qualified_declarator(node: Node<'_>) -> Option<Node<'_>> {
|
|
|
293
292
|
fn cpp_spelled_owner(
|
|
294
293
|
node: Node<'_>,
|
|
295
294
|
qualified: Node<'_>,
|
|
296
|
-
function_nodes: &
|
|
295
|
+
function_nodes: &KindSet,
|
|
297
296
|
code: &Source<'_>,
|
|
298
297
|
inline_namespaces: &InlineNamespaces<'_>,
|
|
299
298
|
) -> Option<String> {
|
|
@@ -431,7 +430,7 @@ fn holds_namespaces_without_scope(node: Node<'_>) -> bool {
|
|
|
431
430
|
/// the function no member of it.
|
|
432
431
|
fn enclosing_owner(
|
|
433
432
|
node: Node<'_>,
|
|
434
|
-
function_nodes: &
|
|
433
|
+
function_nodes: &KindSet,
|
|
435
434
|
code: &Source<'_>,
|
|
436
435
|
inline_namespaces: &InlineNamespaces<'_>,
|
|
437
436
|
) -> Option<String> {
|
|
@@ -463,7 +462,7 @@ fn enclosing_owner(
|
|
|
463
462
|
fn spell_owner(
|
|
464
463
|
container: Node<'_>,
|
|
465
464
|
is_friend: bool,
|
|
466
|
-
function_nodes: &
|
|
465
|
+
function_nodes: &KindSet,
|
|
467
466
|
code: &Source<'_>,
|
|
468
467
|
inline_namespaces: &InlineNamespaces<'_>,
|
|
469
468
|
) -> Option<String> {
|
package/native/src/dep_degree.rs
CHANGED
|
@@ -2,7 +2,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|
|
2
2
|
use tree_sitter::Node;
|
|
3
3
|
|
|
4
4
|
use crate::complexity::is_function_boundary;
|
|
5
|
-
use crate::tree_index::NodeExt;
|
|
5
|
+
use crate::tree_index::{KindSet, NodeExt};
|
|
6
6
|
use crate::util::{node_text, Source};
|
|
7
7
|
|
|
8
8
|
/// Leaf node types treated as variable references by the def-use approximation.
|
|
@@ -89,65 +89,153 @@ const DEFINITION_LIST_HOLDER_TYPES: &[&str] = &[
|
|
|
89
89
|
/// Parameter-position fields that annotate or initialize rather than bind (`x: T`, `x = default`).
|
|
90
90
|
const NON_BINDING_PARAMETER_FIELDS: &[&str] = &["type", "value"];
|
|
91
91
|
|
|
92
|
-
/// One leaf of the def-use walk: its field in the parent and its function
|
|
92
|
+
/// One leaf of the def-use walk: its field in the parent and its function scope.
|
|
93
93
|
struct DepDegreeLeaf<'t> {
|
|
94
94
|
node: Node<'t>,
|
|
95
95
|
field_name: Option<&'t str>,
|
|
96
|
-
///
|
|
97
|
-
|
|
98
|
-
scope: String,
|
|
96
|
+
/// Preorder number of the innermost function scope holding the leaf (see LeafWalk).
|
|
97
|
+
scope: usize,
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
///
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
/// The non-comment leaves of a subtree in walk order, with the function scopes they lie in.
|
|
101
|
+
struct LeafWalk<'t> {
|
|
102
|
+
leaves: Vec<DepDegreeLeaf<'t>>,
|
|
103
|
+
/// The last scope nested in each scope, by preorder number; scope 0 is the walked subtree's
|
|
104
|
+
/// own, and scope `outer` encloses scope `inner` exactly when
|
|
105
|
+
/// `outer <= inner && inner <= last_nested_scopes[outer]`.
|
|
106
|
+
last_nested_scopes: Vec<usize>,
|
|
107
|
+
/// The leaf range and body scope of each function, by node id, whose subtree the walk entered.
|
|
108
|
+
function_spans: FxHashMap<usize, (std::ops::Range<usize>, usize)>,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// What a variable leaf does in the def-use walk, which depends only on the leaf and the leaf
|
|
112
|
+
/// after it.
|
|
113
|
+
#[derive(Clone, Copy)]
|
|
114
|
+
struct VariableUse<'a> {
|
|
115
|
+
name: &'a str,
|
|
116
|
+
next_operator: Option<AssignmentOperator>,
|
|
117
|
+
/// Whether the leaf is a definition even when no assignment operator follows it.
|
|
118
|
+
is_binding: bool,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[derive(Clone, Copy, PartialEq)]
|
|
122
|
+
enum AssignmentOperator {
|
|
123
|
+
Pure,
|
|
124
|
+
Compound,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Approximate def-use pairs of the subtree of each function in `functions`, by node id: one walk
|
|
128
|
+
/// of the file gathers every function's leaves, since a function's leaves and the scopes they lie
|
|
129
|
+
/// in are the same wherever the walk starts.
|
|
130
|
+
pub fn measure_dep_degrees(
|
|
131
|
+
root: Node<'_>,
|
|
132
|
+
functions: &[Node<'_>],
|
|
104
133
|
code: &Source<'_>,
|
|
105
|
-
function_nodes: &
|
|
106
|
-
) -> u64 {
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
collect_dep_degree_leaves(
|
|
110
|
-
function_node,
|
|
111
|
-
None,
|
|
112
|
-
"",
|
|
113
|
-
&mut next_scope_id,
|
|
134
|
+
function_nodes: &KindSet,
|
|
135
|
+
) -> FxHashMap<usize, u64> {
|
|
136
|
+
let walk = LeafWalk::new(
|
|
137
|
+
root,
|
|
114
138
|
function_nodes,
|
|
115
|
-
&
|
|
116
|
-
true,
|
|
139
|
+
&functions.iter().map(Node::id).collect(),
|
|
117
140
|
);
|
|
118
|
-
let mut
|
|
141
|
+
let mut variable_uses = vec![None; walk.leaves.len()];
|
|
142
|
+
functions
|
|
143
|
+
.iter()
|
|
144
|
+
.map(|&function_node| {
|
|
145
|
+
let (leaf_range, body_scope) = walk
|
|
146
|
+
.function_spans
|
|
147
|
+
.get(&function_node.id())
|
|
148
|
+
.expect("the walk enters every function boundary");
|
|
149
|
+
let pairs = count_pairs(
|
|
150
|
+
&walk,
|
|
151
|
+
leaf_range.clone(),
|
|
152
|
+
*body_scope,
|
|
153
|
+
function_node,
|
|
154
|
+
code,
|
|
155
|
+
&mut variable_uses,
|
|
156
|
+
);
|
|
157
|
+
(function_node.id(), pairs)
|
|
158
|
+
})
|
|
159
|
+
.collect()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// The def-use pairs among the leaves in `leaf_range` of the function whose own body is
|
|
163
|
+
/// `body_scope`; `variable_uses` caches what each leaf of the walk does, as `Some(None)` for a
|
|
164
|
+
/// leaf that is no variable.
|
|
165
|
+
fn count_pairs<'a>(
|
|
166
|
+
walk: &LeafWalk<'_>,
|
|
167
|
+
leaf_range: std::ops::Range<usize>,
|
|
168
|
+
body_scope: usize,
|
|
169
|
+
function_node: Node<'_>,
|
|
170
|
+
code: &Source<'a>,
|
|
171
|
+
variable_uses: &mut [Option<Option<VariableUse<'a>>>],
|
|
172
|
+
) -> u64 {
|
|
173
|
+
let is_visible = |definition_scopes: Option<&Vec<usize>>, scope: usize| {
|
|
174
|
+
definition_scopes.is_some_and(|scopes| {
|
|
175
|
+
scopes
|
|
176
|
+
.iter()
|
|
177
|
+
.any(|&outer| outer <= scope && scope <= walk.last_nested_scopes[outer])
|
|
178
|
+
})
|
|
179
|
+
};
|
|
180
|
+
let mut definition_scopes_by_name: FxHashMap<&str, Vec<usize>> = FxHashMap::default();
|
|
119
181
|
for name in implicit_accessor_definitions(function_node, code) {
|
|
120
|
-
add_definition(&mut definition_scopes_by_name, name,
|
|
182
|
+
add_definition(&mut definition_scopes_by_name, name, body_scope);
|
|
121
183
|
}
|
|
184
|
+
let leaf_end = leaf_range.end;
|
|
122
185
|
let mut pairs = 0u64;
|
|
123
|
-
for index in
|
|
124
|
-
let leaf = &leaves[index];
|
|
125
|
-
|
|
186
|
+
for index in leaf_range {
|
|
187
|
+
let leaf = &walk.leaves[index];
|
|
188
|
+
let Some(variable_use) =
|
|
189
|
+
*variable_uses[index].get_or_insert_with(|| variable_use(&walk.leaves, index, code))
|
|
190
|
+
else {
|
|
126
191
|
continue;
|
|
127
|
-
}
|
|
128
|
-
let name =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
192
|
+
};
|
|
193
|
+
let name = variable_use.name;
|
|
194
|
+
// The function's last leaf has no next leaf within the function.
|
|
195
|
+
let next_operator = variable_use.next_operator.filter(|_| index + 1 != leaf_end);
|
|
196
|
+
if next_operator == Some(AssignmentOperator::Compound) {
|
|
197
|
+
if is_visible(definition_scopes_by_name.get(name), leaf.scope) {
|
|
132
198
|
pairs += 1;
|
|
133
199
|
}
|
|
134
|
-
add_definition(&mut definition_scopes_by_name, name,
|
|
200
|
+
add_definition(&mut definition_scopes_by_name, name, leaf.scope);
|
|
135
201
|
continue;
|
|
136
202
|
}
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
|| is_parameter_definition(leaf)
|
|
140
|
-
{
|
|
141
|
-
add_definition(&mut definition_scopes_by_name, name, &leaf.scope);
|
|
203
|
+
if next_operator == Some(AssignmentOperator::Pure) || variable_use.is_binding {
|
|
204
|
+
add_definition(&mut definition_scopes_by_name, name, leaf.scope);
|
|
142
205
|
continue;
|
|
143
206
|
}
|
|
144
|
-
if
|
|
207
|
+
if is_visible(definition_scopes_by_name.get(name), leaf.scope) {
|
|
145
208
|
pairs += 1;
|
|
146
209
|
}
|
|
147
210
|
}
|
|
148
211
|
pairs
|
|
149
212
|
}
|
|
150
213
|
|
|
214
|
+
fn variable_use<'a>(
|
|
215
|
+
leaves: &[DepDegreeLeaf<'_>],
|
|
216
|
+
index: usize,
|
|
217
|
+
code: &Source<'a>,
|
|
218
|
+
) -> Option<VariableUse<'a>> {
|
|
219
|
+
let leaf = &leaves[index];
|
|
220
|
+
if !is_variable_leaf(leaf) {
|
|
221
|
+
return None;
|
|
222
|
+
}
|
|
223
|
+
let next_text = leaves.get(index + 1).map(|next| node_text(next.node, code));
|
|
224
|
+
Some(VariableUse {
|
|
225
|
+
name: node_text(leaf.node, code),
|
|
226
|
+
next_operator: next_text.and_then(|next| {
|
|
227
|
+
if COMPOUND_ASSIGNMENT_OPERATORS.contains(&next) {
|
|
228
|
+
Some(AssignmentOperator::Compound)
|
|
229
|
+
} else if PURE_ASSIGNMENT_OPERATORS.contains(&next) {
|
|
230
|
+
Some(AssignmentOperator::Pure)
|
|
231
|
+
} else {
|
|
232
|
+
None
|
|
233
|
+
}
|
|
234
|
+
}),
|
|
235
|
+
is_binding: is_structural_definition(leaf) || is_parameter_definition(leaf),
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
151
239
|
/// Kotlin spells type names as identifiers: in a `user_type`, or as a type parameter's fieldless
|
|
152
240
|
/// name (C# puts its type parameter in a `name` field).
|
|
153
241
|
fn is_variable_leaf(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
@@ -215,81 +303,86 @@ fn implicit_accessor_definitions<'a>(function_node: Node<'_>, code: &Source<'a>)
|
|
|
215
303
|
names
|
|
216
304
|
}
|
|
217
305
|
|
|
218
|
-
|
|
219
|
-
///
|
|
220
|
-
|
|
221
|
-
fn
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
if crate::util::is_token(node) {
|
|
237
|
-
leaves.push(DepDegreeLeaf {
|
|
238
|
-
node,
|
|
239
|
-
field_name,
|
|
240
|
-
scope: scope.to_string(),
|
|
241
|
-
});
|
|
242
|
-
return;
|
|
306
|
+
impl<'t> LeafWalk<'t> {
|
|
307
|
+
/// Walks the subtree of `root`, recording the span of each function in `function_ids` it
|
|
308
|
+
/// enters.
|
|
309
|
+
fn new(
|
|
310
|
+
root: Node<'t>,
|
|
311
|
+
function_nodes: &KindSet,
|
|
312
|
+
function_ids: &FxHashSet<usize>,
|
|
313
|
+
) -> LeafWalk<'t> {
|
|
314
|
+
let mut walk = LeafWalk {
|
|
315
|
+
leaves: Vec::new(),
|
|
316
|
+
last_nested_scopes: vec![0],
|
|
317
|
+
function_spans: FxHashMap::default(),
|
|
318
|
+
};
|
|
319
|
+
walk.visit(root, None, 0, function_nodes, function_ids);
|
|
320
|
+
// The walked subtree's own scope encloses every scope opened inside it.
|
|
321
|
+
walk.last_nested_scopes[0] = walk.last_nested_scopes.len() - 1;
|
|
322
|
+
walk
|
|
243
323
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
scope
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
324
|
+
|
|
325
|
+
fn visit(
|
|
326
|
+
&mut self,
|
|
327
|
+
node: Node<'t>,
|
|
328
|
+
field_name: Option<&'t str>,
|
|
329
|
+
scope: usize,
|
|
330
|
+
function_nodes: &KindSet,
|
|
331
|
+
function_ids: &FxHashSet<usize>,
|
|
332
|
+
) {
|
|
333
|
+
if matches!(
|
|
334
|
+
node.kind_name(),
|
|
335
|
+
"comment" | "line_comment" | "block_comment"
|
|
336
|
+
) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if crate::util::is_token(node) {
|
|
340
|
+
self.leaves.push(DepDegreeLeaf {
|
|
341
|
+
node,
|
|
342
|
+
field_name,
|
|
343
|
+
scope,
|
|
344
|
+
});
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
let opens_scope = is_function_boundary(node, function_nodes);
|
|
348
|
+
let child_scope = if opens_scope {
|
|
349
|
+
self.last_nested_scopes.push(0);
|
|
350
|
+
self.last_nested_scopes.len() - 1
|
|
351
|
+
} else {
|
|
352
|
+
scope
|
|
353
|
+
};
|
|
354
|
+
let first_leaf = self.leaves.len();
|
|
355
|
+
for (child, child_field_name) in crate::util::children_with_field_names(node) {
|
|
356
|
+
self.visit(
|
|
357
|
+
child,
|
|
358
|
+
child_field_name,
|
|
359
|
+
child_scope,
|
|
259
360
|
function_nodes,
|
|
260
|
-
|
|
261
|
-
false,
|
|
361
|
+
function_ids,
|
|
262
362
|
);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
363
|
+
}
|
|
364
|
+
if opens_scope {
|
|
365
|
+
self.last_nested_scopes[child_scope] = self.last_nested_scopes.len() - 1;
|
|
366
|
+
}
|
|
367
|
+
// Every measured function is a function boundary.
|
|
368
|
+
if opens_scope && function_ids.contains(&node.id()) {
|
|
369
|
+
self.function_spans
|
|
370
|
+
.insert(node.id(), (first_leaf..self.leaves.len(), child_scope));
|
|
266
371
|
}
|
|
267
372
|
}
|
|
268
373
|
}
|
|
269
374
|
|
|
270
375
|
fn add_definition<'a>(
|
|
271
|
-
definition_scopes_by_name: &mut FxHashMap<&'a str, Vec<
|
|
376
|
+
definition_scopes_by_name: &mut FxHashMap<&'a str, Vec<usize>>,
|
|
272
377
|
name: &'a str,
|
|
273
|
-
scope:
|
|
378
|
+
scope: usize,
|
|
274
379
|
) {
|
|
275
380
|
let scopes = definition_scopes_by_name.entry(name).or_default();
|
|
276
|
-
if !scopes.
|
|
277
|
-
scopes.push(scope
|
|
381
|
+
if !scopes.contains(&scope) {
|
|
382
|
+
scopes.push(scope);
|
|
278
383
|
}
|
|
279
384
|
}
|
|
280
385
|
|
|
281
|
-
/// A definition reaches a read only from the read's own or an enclosing function scope.
|
|
282
|
-
fn is_definition_visible(definition_scopes: Option<&Vec<String>>, scope: &str) -> bool {
|
|
283
|
-
definition_scopes.is_some_and(|scopes| {
|
|
284
|
-
scopes.iter().any(|definition_scope| {
|
|
285
|
-
scope == definition_scope
|
|
286
|
-
|| (scope.len() > definition_scope.len()
|
|
287
|
-
&& scope.starts_with(definition_scope.as_str())
|
|
288
|
-
&& scope.as_bytes()[definition_scope.len()] == b'/')
|
|
289
|
-
})
|
|
290
|
-
})
|
|
291
|
-
}
|
|
292
|
-
|
|
293
386
|
fn is_structural_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
294
387
|
let Some(parent) = leaf.node.parent_node() else {
|
|
295
388
|
return false;
|