#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::*;
impl Npa for PythonCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
use Python::*;
if !matches!(node.kind_id().into(), ClassDefinition) {
return;
}
if stats.is_disabled() {
stats.is_class_space = true;
}
let Some(body) = python_class_body(node) else {
return;
};
let mut seen: std::collections::HashSet<&[u8]> =
std::collections::HashSet::with_capacity(8);
python_collect_class_level_attrs(&body, code, &mut seen);
python_collect_unique_self_attrs(&body, code, &mut seen);
let total = seen.len();
stats.class_na += total;
stats.class_npa += total;
}
}
fn python_class_body<'a>(class_def: &Node<'a>) -> Option<Node<'a>> {
class_def.children().find(python_is_block)
}
fn python_collect_class_level_attrs<'a>(
body: &Node<'a>,
code: &'a [u8],
seen: &mut std::collections::HashSet<&'a [u8]>,
) {
use Python::*;
for stmt in body.children() {
if stmt.kind_id() != ExpressionStatement {
continue;
}
for child in stmt.children() {
if child.kind_id() == Assignment && child.first_child(|id| id == EQ).is_some() {
python_collect_bound_names_from_target(&child, code, seen);
}
}
}
}
fn python_walk_target_elements<'a>(target: &Node<'a>, collect: &mut impl FnMut(&Node<'a>)) {
match target.kind_id().into() {
Python::PatternList
| Python::ExpressionList
| Python::TuplePattern
| Python::TuplePattern2
| Python::ListPattern
| Python::ListPattern2 => {
for element in target.children() {
python_walk_target_elements(&element, collect);
}
}
_ => collect(target),
}
}
fn python_collect_bound_names_from_target<'a>(
assignment: &Node<'a>,
code: &'a [u8],
seen: &mut std::collections::HashSet<&'a [u8]>,
) {
let Some(target) = assignment.child(0) else {
return;
};
python_walk_target_elements(&target, &mut |element| {
if element.kind_id() == Python::Identifier
&& let Some(name) = code.get(element.start_byte()..element.end_byte())
{
seen.insert(name);
}
});
if let Some(value) = assignment.child(assignment.child_count().saturating_sub(1))
&& value.kind_id() == Python::Assignment
{
python_collect_bound_names_from_target(&value, code, seen);
}
}
fn python_collect_unique_self_attrs<'a>(
body: &Node<'a>,
code: &'a [u8],
seen: &mut std::collections::HashSet<&'a [u8]>,
) {
for stmt in body.children() {
if let Some(func) = python_unwrap_function(&stmt) {
python_collect_self_attrs_in_subtree(&func, code, seen);
}
}
}
fn python_collect_self_attrs_in_subtree<'a>(
root: &Node<'a>,
code: &'a [u8],
seen: &mut std::collections::HashSet<&'a [u8]>,
) {
use Python::*;
let mut stack: Vec<Node<'a>> = Vec::with_capacity(32);
for child in root.children() {
stack.push(child);
}
while let Some(node) = stack.pop() {
if matches!(
node.kind_id().into(),
FunctionDefinition | ClassDefinition | DecoratedDefinition | Lambda
) {
continue;
}
if node.kind_id() == Assignment {
python_collect_self_attrs_from_target(&node, code, seen);
}
for child in node.children() {
stack.push(child);
}
}
}
fn python_collect_self_attrs_from_target<'a>(
assignment: &Node<'a>,
code: &'a [u8],
seen: &mut std::collections::HashSet<&'a [u8]>,
) {
let Some(target) = assignment.child(0) else {
return;
};
python_walk_target_elements(&target, &mut |element| {
if element.kind_id() == Python::Attribute
&& let Some(name) = python_self_attr_name_bytes(element, code)
{
seen.insert(name);
}
});
}
const PYTHON_SELF_RECEIVERS: [&[u8]; 2] = [b"self", b"cls"];
fn python_self_attr_name_bytes<'a>(attr: &Node<'a>, code: &'a [u8]) -> Option<&'a [u8]> {
let receiver = attr.child(0)?;
if receiver.kind_id() != Python::Identifier {
return None;
}
let receiver_bytes = code.get(receiver.start_byte()..receiver.end_byte())?;
if !PYTHON_SELF_RECEIVERS.contains(&receiver_bytes) {
return None;
}
let id = attr
.children()
.filter(|c| c.kind_id() == Python::Identifier)
.last()?;
if id.start_byte() == receiver.start_byte() {
return None;
}
code.get(id.start_byte()..id.end_byte())
}
fn python_unwrap_function<'a>(node: &Node<'a>) -> Option<Node<'a>> {
match node.kind_id().into() {
Python::FunctionDefinition => Some(*node),
Python::DecoratedDefinition => node
.children()
.find(|c| c.kind_id() == Python::FunctionDefinition),
_ => None,
}
}