Skip to content

Parser#

flet_pkg.core.parser #

Dart source code parser.

Parses Dart package source files to extract classes, methods, enums, and their metadata into structured DartPackageAPI models.

camel_to_snake #

camel_to_snake(name: str) -> str

Convert CamelCase or camelCase to snake_case.

Handles consecutive uppercase letters (acronyms) properly: JWT -> jwt, loginWithJWT -> login_with_jwt, ABCDef -> abc_def.

Source code in src/flet_pkg/core/parser.py
def camel_to_snake(name: str) -> str:
    """Convert ``CamelCase`` or ``camelCase`` to ``snake_case``.

    Handles consecutive uppercase letters (acronyms) properly:
    ``JWT`` -> ``jwt``, ``loginWithJWT`` -> ``login_with_jwt``,
    ``ABCDef`` -> ``abc_def``.
    """
    # Handle consecutive uppercase (acronyms): ABCDef -> ABC_Def
    s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", name)
    # Handle transitions from lowercase/digit to uppercase: camelCase -> camel_Case
    s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s)
    return s.lower()

detect_extension_type #

detect_extension_type(package_path: Path) -> str

Detect whether a Flutter package is a service or UI control.

Scans Dart sources for widget classes (StatefulWidget/StatelessWidget). If any are found, returns "ui_control"; otherwise "service".

Parameters:

Name Type Description Default
package_path Path

Path to the extracted Flutter package root.

required

Returns:

Type Description
str

"ui_control" or "service".

Source code in src/flet_pkg/core/parser.py
def detect_extension_type(package_path: Path) -> str:
    """Detect whether a Flutter package is a service or UI control.

    Scans Dart sources for widget classes (StatefulWidget/StatelessWidget).
    If any are found, returns ``"ui_control"``; otherwise ``"service"``.

    Args:
        package_path: Path to the extracted Flutter package root.

    Returns:
        ``"ui_control"`` or ``"service"``.
    """
    lib_dir = package_path / "lib"
    if not lib_dir.exists():
        return "service"

    widget_pattern = re.compile(
        r"class\s+\w+\s+extends\s+(?:Stateful|Stateless)Widget",
    )
    for dart_file in lib_dir.rglob("*.dart"):
        content = dart_file.read_text(encoding="utf-8")
        if widget_pattern.search(content):
            return "ui_control"

    return "service"

parse_dart_package_api #

parse_dart_package_api(package_path: Path, strict: bool = False, include_widgets: bool = False) -> DartPackageAPI

Parse a Dart package and return a structured DartPackageAPI.

Parameters:

Name Type Description Default
package_path Path

Path to the extracted Flutter package root.

required
strict bool

If True, only include classes with 2+ public methods.

False
include_widgets bool

If True, include widget classes and extract their constructor parameters. Used for ui_control extensions where widget constructor params become Flet control properties.

False

Returns:

Type Description
DartPackageAPI

DartPackageAPI with parsed classes, enums, helper classes, and typedefs.

Source code in src/flet_pkg/core/parser.py
def parse_dart_package_api(
    package_path: Path,
    strict: bool = False,
    include_widgets: bool = False,
) -> DartPackageAPI:
    """Parse a Dart package and return a structured DartPackageAPI.

    Args:
        package_path: Path to the extracted Flutter package root.
        strict: If True, only include classes with 2+ public methods.
        include_widgets: If True, include widget classes and extract their
            constructor parameters. Used for ``ui_control`` extensions where
            widget constructor params become Flet control properties.

    Returns:
        DartPackageAPI with parsed classes, enums, helper classes, and typedefs.
    """
    lib_dir = package_path / "lib"
    if not lib_dir.exists():
        raise FileNotFoundError(f"Directory {lib_dir} does not exist.")

    all_classes: list[DartClass] = []
    all_enums: list[DartEnum] = []
    all_helpers: list[DartClass] = []
    all_typedefs: dict[str, str] = {}
    all_top_level_functions: list[DartMethod] = []
    # Track all parsed class blocks for component class resolution
    all_parsed_blocks: dict[str, tuple[str, str]] = {}  # class_name → (block, source_file)

    for dart_file in lib_dir.rglob("*.dart"):
        content = dart_file.read_text(encoding="utf-8")
        relative_path = str(dart_file.relative_to(lib_dir))

        # Parse typedefs
        all_typedefs.update(_parse_typedefs(content))

        # Parse enums
        all_enums.extend(_parse_enums(content))

        # Parse top-level functions (not inside any class)
        all_top_level_functions.extend(_parse_top_level_functions(content, relative_path))

        # Parse classes
        class_matches = re.finditer(
            r"(?:@[^\n]*\n)*"
            r"(?:abstract\s+)?class\s+(\w+)"
            r"(?:\s+extends\s+(\w+))?"
            r"(?:\s+with\s+[\w\s,]+)?"
            r"(?:\s+implements\s+[\w\s,]+)?"
            r"\s*\{",
            content,
        )

        for match in class_matches:
            class_name = match.group(1)
            parent_class = match.group(2) or ""

            # Check for @Deprecated annotation on the class
            annotation_text = match.group(0)
            if re.search(r"@[Dd]eprecated", annotation_text):
                continue

            doc_match = re.search(
                r"(///.*?$|/\*\*(.*?)\*/)",
                content[: match.start()],
                re.DOTALL | re.MULTILINE,
            )
            class_doc = _clean_docstring(doc_match) if doc_match else ""

            class_block = _extract_code_block(content[match.end() - 1 :])
            if not class_block:
                continue

            # Skip private, deprecated, internal, and platform impl classes early
            if (
                class_name.startswith("_")
                or "nodoc" in class_doc.lower()
                or "internal" in class_doc.lower()
                or _is_platform_impl(parent_class)
                or parent_class in _INTERNAL_PARENT_BASES
            ):
                continue

            # Track all viable classes for component resolution
            if include_widgets and class_name not in all_parsed_blocks:
                all_parsed_blocks[class_name] = (class_block, relative_path)

            # Helper classes (Event, Data, Result, State, etc.) or data model classes
            if _is_helper_class(class_name, parent_class):
                helper_methods = _parse_class_methods(class_block, skip_filter=False)
                all_helpers.append(
                    DartClass(
                        name=class_name,
                        methods=helper_methods,
                        docstring=class_doc,
                        parent_class=parent_class,
                        source_file=relative_path,
                    )
                )
                continue

            # Widget classes: include when building ui_control extensions
            is_widget = parent_class in WIDGET_BASES
            if is_widget and include_widgets:
                # Skip widgets with internal-looking suffixes.
                # Uses a reduced set that intentionally excludes "Widget" —
                # many packages name their main user-facing class FooWidget
                # (e.g. RiveWidget, VideoWidget).
                if class_name.endswith(_INTERNAL_WIDGET_SUFFIXES):
                    continue
                ctor_params = _parse_constructor_params(class_name, class_block)
                if ctor_params:
                    all_classes.append(
                        DartClass(
                            name=class_name,
                            methods=[],
                            constructor_params=ctor_params,
                            docstring=class_doc,
                            parent_class=parent_class,
                            source_file=relative_path,
                        )
                    )
                continue

            if _should_skip_class(class_name, class_doc, parent_class):
                continue

            methods = _parse_class_methods(class_block, skip_filter=True)

            if strict and len(methods) < 2:
                continue

            if methods:
                all_classes.append(
                    DartClass(
                        name=class_name,
                        methods=methods,
                        docstring=class_doc,
                        parent_class=parent_class,
                        source_file=relative_path,
                    )
                )

    # Parse re-exported types from barrel files
    reexported = _parse_reexports(lib_dir)

    # Collect component classes (non-widget classes referenced by widget params)
    component_classes: list[DartClass] = []
    if include_widgets and all_parsed_blocks:
        component_classes = _collect_component_classes(
            all_classes,
            all_parsed_blocks,
            {h.name for h in all_helpers},
        )

    return DartPackageAPI(
        classes=all_classes,
        enums=all_enums,
        helper_classes=all_helpers,
        typedefs=all_typedefs,
        reexported_types=reexported,
        top_level_functions=all_top_level_functions,
        component_classes=component_classes,
    )