Skip to content

NodeID

Represents an id of a node.

Attributes:

Name Type Description
module str | None

The module of the node. Is None for combined nodes.

name str

The name of the node.

line int | None

The line of the node in the source code. Is None for combined nodes, builtins or any other node that do not have a line.

col int | None

The column of the node in the source code. Is None for combined nodes, builtins or any other node that do not have a line.

Source code in src/library_analyzer/processing/api/purity_analysis/model/_module_data.py
@dataclass
class NodeID:
    """Represents an id of a node.

    Attributes
    ----------
    module :
        The module of the node.
        Is None for combined nodes.
    name :
        The name of the node.
    line :
        The line of the node in the source code.
        Is None for combined nodes, builtins or any other node that do not have a line.
    col :
        The column of the node in the source code.
        Is None for combined nodes, builtins or any other node that do not have a line.
    """

    module: str | None
    name: str
    line: int | None = None
    col: int | None = None

    def __str__(self) -> str:
        if self.module is not None:
            if self.line is not None and self.col is not None:
                return f"{self.module}.{self.name}.{self.line}.{self.col}"
            else:
                return f"{self.module}.{self.name}"
        elif self.line is not None and self.col is not None:
            if self.line == 0 and self.col == 0:
                return f"{self.name}"
            return f"{self.name}.{self.line}.{self.col}"
        else:
            return f"{self.name}"

    def __hash__(self) -> int:
        return hash(str(self))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, NodeID):
            if isinstance(other, Symbol):
                return self == other.id
            raise NotImplementedError(f"Cannot compare NodeID with {type(other)}")
        return (
            self.module == other.module
            and self.name == other.name
            and self.line == other.line
            and self.col == other.col
        )

    def __lt__(self, other: NodeID) -> bool:
        if not isinstance(other, NodeID):
            raise TypeError(f"Cannot compare NodeID with {type(other)}")

        if self.line is None:
            if other.line is None:
                return False  # Both lines are None, consider them equal
            return True  # self.line is None, other.line is not, so other is greater
        elif other.line is None:
            return False  # other.line is None, self.line is not, so self is greater

        if self.line != other.line and self.line is not None and other.line is not None:
            return self.line < other.line

        if self.col != other.col and self.col is not None and other.col is not None:
            return self.col < other.col

        # If both line and column are equal, compare by name,
        return self.name < other.name

    @classmethod
    def calc_node_id(
        cls,
        node: (
            astroid.NodeNG
            | astroid.Module
            | astroid.ClassDef
            | astroid.FunctionDef
            | astroid.AssignName
            | astroid.Name
            | astroid.AssignAttr
            | astroid.Import
            | astroid.ImportFrom
            | astroid.Call
            | astroid.Lambda
            | astroid.ListComp
            | MemberAccess
        ),
    ) -> NodeID:
        """Calculate the NodeID of the given node.

        The NodeID is calculated by using the name of the module, the name of the node, the line number and the column offset.
        The NodeID is used to identify nodes in the module.

        Parameters
        ----------
        node :

        Returns
        -------
        NodeID
            The NodeID of the given node.
        """
        if isinstance(node, MemberAccess):
            module = node.node.root().name
        else:
            module = node.root().name

        match node:
            case astroid.Module():
                return NodeID(None, node.name, 0, 0)
            case astroid.ClassDef():
                return NodeID(module, node.name, node.lineno, node.col_offset)
            case astroid.FunctionDef():
                return NodeID(module, node.name, node.fromlineno, node.col_offset)
            case astroid.AssignName():
                return NodeID(module, node.name, node.lineno, node.col_offset)
            case astroid.Name():
                return NodeID(module, node.name, node.lineno, node.col_offset)
            case MemberAccess():
                return NodeID(module, node.name, node.node.lineno, node.node.col_offset)
            case astroid.Import():  # TODO: we need a special treatment for imports and import from
                return NodeID(module, node.names[0][0], node.lineno, node.col_offset)
            case astroid.ImportFrom():
                return NodeID(module, node.names[0][1], node.lineno, node.col_offset)
            case astroid.AssignAttr():
                return NodeID(module, node.attrname, node.lineno, node.col_offset)
            case astroid.Call():
                # Make sure there is no AttributeError because of the inconsistent names in the astroid API.
                if isinstance(node.func, astroid.Attribute):
                    return NodeID(module, node.func.attrname, node.lineno, node.col_offset)
                elif isinstance(node.func, astroid.Name):
                    return NodeID(module, node.func.name, node.lineno, node.col_offset)
                else:
                    return NodeID(module, "UNKNOWN", node.lineno, node.col_offset)
            case astroid.Lambda():
                if isinstance(node.parent, astroid.Assign) and node.name != "LAMBDA":
                    return NodeID(module, node.name, node.lineno, node.col_offset)
                return NodeID(module, "LAMBDA", node.lineno, node.col_offset)
            case astroid.ListComp():
                return NodeID(module, "LIST_COMP", node.lineno, node.col_offset)
            case astroid.NodeNG():
                return NodeID(module, node.as_string(), node.lineno, node.col_offset)
            case _:
                raise ValueError(f"Node type {node.__class__.__name__} is not supported yet.")

col: int | None = None class-attribute instance-attribute

line: int | None = None class-attribute instance-attribute

module: str | None instance-attribute

name: str instance-attribute

__eq__(other)

Source code in src/library_analyzer/processing/api/purity_analysis/model/_module_data.py
def __eq__(self, other: object) -> bool:
    if not isinstance(other, NodeID):
        if isinstance(other, Symbol):
            return self == other.id
        raise NotImplementedError(f"Cannot compare NodeID with {type(other)}")
    return (
        self.module == other.module
        and self.name == other.name
        and self.line == other.line
        and self.col == other.col
    )

__hash__()

Source code in src/library_analyzer/processing/api/purity_analysis/model/_module_data.py
def __hash__(self) -> int:
    return hash(str(self))

__init__(module, name, line=None, col=None)

__lt__(other)

Source code in src/library_analyzer/processing/api/purity_analysis/model/_module_data.py
def __lt__(self, other: NodeID) -> bool:
    if not isinstance(other, NodeID):
        raise TypeError(f"Cannot compare NodeID with {type(other)}")

    if self.line is None:
        if other.line is None:
            return False  # Both lines are None, consider them equal
        return True  # self.line is None, other.line is not, so other is greater
    elif other.line is None:
        return False  # other.line is None, self.line is not, so self is greater

    if self.line != other.line and self.line is not None and other.line is not None:
        return self.line < other.line

    if self.col != other.col and self.col is not None and other.col is not None:
        return self.col < other.col

    # If both line and column are equal, compare by name,
    return self.name < other.name

__str__()

Source code in src/library_analyzer/processing/api/purity_analysis/model/_module_data.py
def __str__(self) -> str:
    if self.module is not None:
        if self.line is not None and self.col is not None:
            return f"{self.module}.{self.name}.{self.line}.{self.col}"
        else:
            return f"{self.module}.{self.name}"
    elif self.line is not None and self.col is not None:
        if self.line == 0 and self.col == 0:
            return f"{self.name}"
        return f"{self.name}.{self.line}.{self.col}"
    else:
        return f"{self.name}"

calc_node_id(node) classmethod

Calculate the NodeID of the given node.

The NodeID is calculated by using the name of the module, the name of the node, the line number and the column offset. The NodeID is used to identify nodes in the module.

Parameters:

Name Type Description Default
node NodeNG | Module | ClassDef | FunctionDef | AssignName | Name | AssignAttr | Import | ImportFrom | Call | Lambda | ListComp | MemberAccess
required

Returns:

Type Description
NodeID

The NodeID of the given node.

Source code in src/library_analyzer/processing/api/purity_analysis/model/_module_data.py
@classmethod
def calc_node_id(
    cls,
    node: (
        astroid.NodeNG
        | astroid.Module
        | astroid.ClassDef
        | astroid.FunctionDef
        | astroid.AssignName
        | astroid.Name
        | astroid.AssignAttr
        | astroid.Import
        | astroid.ImportFrom
        | astroid.Call
        | astroid.Lambda
        | astroid.ListComp
        | MemberAccess
    ),
) -> NodeID:
    """Calculate the NodeID of the given node.

    The NodeID is calculated by using the name of the module, the name of the node, the line number and the column offset.
    The NodeID is used to identify nodes in the module.

    Parameters
    ----------
    node :

    Returns
    -------
    NodeID
        The NodeID of the given node.
    """
    if isinstance(node, MemberAccess):
        module = node.node.root().name
    else:
        module = node.root().name

    match node:
        case astroid.Module():
            return NodeID(None, node.name, 0, 0)
        case astroid.ClassDef():
            return NodeID(module, node.name, node.lineno, node.col_offset)
        case astroid.FunctionDef():
            return NodeID(module, node.name, node.fromlineno, node.col_offset)
        case astroid.AssignName():
            return NodeID(module, node.name, node.lineno, node.col_offset)
        case astroid.Name():
            return NodeID(module, node.name, node.lineno, node.col_offset)
        case MemberAccess():
            return NodeID(module, node.name, node.node.lineno, node.node.col_offset)
        case astroid.Import():  # TODO: we need a special treatment for imports and import from
            return NodeID(module, node.names[0][0], node.lineno, node.col_offset)
        case astroid.ImportFrom():
            return NodeID(module, node.names[0][1], node.lineno, node.col_offset)
        case astroid.AssignAttr():
            return NodeID(module, node.attrname, node.lineno, node.col_offset)
        case astroid.Call():
            # Make sure there is no AttributeError because of the inconsistent names in the astroid API.
            if isinstance(node.func, astroid.Attribute):
                return NodeID(module, node.func.attrname, node.lineno, node.col_offset)
            elif isinstance(node.func, astroid.Name):
                return NodeID(module, node.func.name, node.lineno, node.col_offset)
            else:
                return NodeID(module, "UNKNOWN", node.lineno, node.col_offset)
        case astroid.Lambda():
            if isinstance(node.parent, astroid.Assign) and node.name != "LAMBDA":
                return NodeID(module, node.name, node.lineno, node.col_offset)
            return NodeID(module, "LAMBDA", node.lineno, node.col_offset)
        case astroid.ListComp():
            return NodeID(module, "LIST_COMP", node.lineno, node.col_offset)
        case astroid.NodeNG():
            return NodeID(module, node.as_string(), node.lineno, node.col_offset)
        case _:
            raise ValueError(f"Node type {node.__class__.__name__} is not supported yet.")