Source code for pyfcstm.diagnostics.analyzers.naming

"""Naming and scope design-health diagnostics."""

from typing import TYPE_CHECKING, Dict, Iterable, List

from ...utils.validate import ModelDiagnostic

if TYPE_CHECKING:  # pragma: no cover
    from ..inspect import ActionInfo


[docs] def collect_naming_warnings(actions: Iterable['ActionInfo']) -> List[ModelDiagnostic]: """Collect diagnostics for confusing named-action scopes.""" return _named_action_shadows_ancestor_warnings(list(actions))
def _named_action_shadows_ancestor_warnings(actions) -> List[ModelDiagnostic]: by_name: Dict[str, List[object]] = {} for action in actions: if not action.name or action.is_ref: continue by_name.setdefault(action.name, []).append(action) diagnostics: List[ModelDiagnostic] = [] for function_name, items in by_name.items(): sorted_items = sorted(items, key=lambda item: item.state_path) for inner in sorted_items: ancestors = [ outer for outer in sorted_items if _is_strict_ancestor(outer.state_path, inner.state_path) ] if not ancestors: continue outer = max(ancestors, key=lambda item: len(item.state_path)) diagnostics.append(ModelDiagnostic( code='W_NAMED_ACTION_SHADOWS_ANCESTOR', span=inner.span, severity='warning', message=( f'Named action {function_name!r} in {inner.state_path!r} ' f'shadows ancestor action in {outer.state_path!r}.' ), refs={ 'function_name': function_name, 'inner_state_path': inner.state_path, 'outer_state_path': outer.state_path, }, )) return diagnostics def _is_strict_ancestor(outer_path: str, inner_path: str) -> bool: return bool(outer_path) and inner_path.startswith(f'{outer_path}.')