This PR finishes adding types (partial types were added in #1592, #1593 did some prerequisite rearrangement).

To generate the types correctly in `pyproxy.js`, I changed the protocols like `PyProxyLengthMethods` into ES6 classes (they used to just be objects). This was needed because otherwise Typescript makes the protocol into a namespace but I want it to be a type to indicate that an object has certain methods. To compensate for this change, I had to modify `getPyProxyClass` accordingly. This change required a number of syntactical changes.

I also added a bunch of predicate methods to the base `PyProxy` class to test whether the proxy supports certain extra methods. These are needed to use as Typescript type guards, but they probably are also useful by themselves when code needs to do reflection on the proxies.

I don't know whether the exposed types or the type guard predicates are well named, I would be happy for recommendations of improvements to the names -- both the type names and the predicates are public and deserve to be named with care.

JsDoc does not recognize typescript type guards or typescript type definitions, so I added a JsDoc plugin which strips out type definitions and transforms typeguards `x is y` ==> `boolean`. There's one JsDoc typescript parse error that still happens but it doesn't cause any harm to the output.

I also changed the way that we collect javascript API docs to be a bit more robust. In particular, it used to be organized by file. I changed it as follows: 
1. any API part of `globalThis` or marked with `@memberof globalThis` is treated as a global, 
2. any API of a class whose name starts with `PyProxy` is treated as a part of the `PyProxy` API,
3. anything else is assumed to be part of the pyodide API

As before, things marked `@private` are left out.

I also added tests for the type declarations using `tpd`. They are a new CI step.


ENH More complete typescript types. This PR finishes adding types (partial types were added in #1592, #1593 did some prerequisite rearrangement).

To generate the types correctly in `pyproxy.js`, I changed the protocols like `PyProxyLengthMethods` into ES6 classes (they used to just be objects). This was needed because otherwise Typescript makes the protocol into a namespace but I want it to be a type to indicate that an object has certain methods. To compensate for this change, I had to modify `getPyProxyClass` accordingly. This change required a number of syntactical changes.

I also added a bunch of predicate methods to the base `PyProxy` class to test whether the proxy supports certain extra methods. These are needed to use as Typescript type guards, but they probably are also useful by themselves when code needs to do reflection on the proxies.

I don't know whether the exposed types or the type guard predicates are well named, I would be happy for recommendations of improvements to the names -- both the type names and the predicates are public and deserve to be named with care.

JsDoc does not recognize typescript type guards or typescript type definitions, so I added a JsDoc plugin which strips out type definitions and transforms typeguards `x is y` ==> `boolean`. There's one JsDoc typescript parse error that still happens but it doesn't cause any harm to the output.

I also changed the way that we collect javascript API docs to be a bit more robust. In particular, it used to be organized by file. I changed it as follows: 
1. any API part of `globalThis` or marked with `@memberof globalThis` is treated as a global, 
2. any API of a class whose name starts with `PyProxy` is treated as a part of the `PyProxy` API,
3. anything else is assumed to be part of the pyodide API

As before, things marked `@private` are left out.

I also added tests for the type declarations using `tpd`. They are a new CI step.


from .jsdoc import PyodideAnalyzer
from .lexers import PyodideLexer, HtmlPyodideLexer
from .jsdoc import get_jsdoc_summary_directive, get_jsdoc_content_directive


def wrap_analyzer(app):
    app._sphinxjs_analyzer = PyodideAnalyzer(app._sphinxjs_analyzer)


from typing import Any, Dict, List, Tuple
from sphinx.util.inspect import safe_getattr
from sphinx.ext.autodoc import ModuleDocumenter, ObjectMember  # type: ignore

# Monkey patch autodoc to include submodules as well.
# We have to import the submodules for it to find them.
def get_module_members(module: Any) -> List[Tuple[str, Any]]:
    members = {}  # type: Dict[str, Tuple[str, Any]]
    for name in dir(module):
        try:
            value = safe_getattr(module, name, None)
            # Before patch this used to always do
            # members[name] = (name, value)
            # We want to also recursively look up names on submodules.
            if type(value).__name__ != "module":
                members[name] = (name, value)
                continue
            if name.startswith("_"):
                continue
            submodule = value  # Rename for clarity
            [base, _, rest] = submodule.__name__.partition(".")
            if not base == module.__name__:
                # Not part of package, don't document
                continue

            for (sub_name, sub_val) in get_module_members(submodule):
                # Skip names not in __all__
                if hasattr(submodule, "__all__") and sub_name not in submodule.__all__:
                    continue
                qual_name = rest + "." + sub_name
                members[qual_name] = (qual_name, sub_val)
            continue
        except AttributeError:
            continue

    return sorted(list(members.values()))


# For some reason I was unable to monkey patch get_module_members
# so we monkey patch get_object_members instead...
# This is similar to the original function but I dropped a branch (for brevity only)
def get_object_members(self, want_all: bool):
    members = get_module_members(self.object)
    if not self.__all__:
        # for implicit module members, check __module__ to avoid
        # documenting imported objects
        return True, members
    else:
        ret = []
        for name, value in members:
            if name in self.__all__ or "." in name:
                ret.append(ObjectMember(name, value))
            else:
                ret.append(ObjectMember(name, value, skipped=True))

        return False, ret


ModuleDocumenter.get_object_members = get_object_members  # type: ignore


def setup(app):
    app.add_lexer("pyodide", PyodideLexer)
    app.add_lexer("html-pyodide", HtmlPyodideLexer)
    app.setup_extension("sphinx_js")
    app.connect("builder-inited", wrap_analyzer)
    app.add_directive("js-doc-summary", get_jsdoc_summary_directive(app))
    app.add_directive("js-doc-content", get_jsdoc_content_directive(app))

from .jsdoc import PyodideAnalyzer
from .lexers import PyodideLexer, HtmlPyodideLexer
from .jsdoc import get_jsdoc_summary_directive, get_jsdoc_content_directive


def wrap_analyzer(app):
    app._sphinxjs_analyzer = PyodideAnalyzer(app._sphinxjs_analyzer)


from typing import Any, Dict, List, Tuple
from sphinx.util.inspect import safe_getattr
from sphinx.ext.autodoc import ModuleDocumenter, ObjectMember  # type: ignore

# Monkey patch autodoc to include submodules as well.
# We have to import the submodules for it to find them.
def get_module_members(module: Any) -> List[Tuple[str, Any]]:
    members = {}  # type: Dict[str, Tuple[str, Any]]
    for name in dir(module):
        try:
            value = safe_getattr(module, name, None)
            # Before patch this used to always do
            # members[name] = (name, value)
            # We want to also recursively look up names on submodules.
            if type(value).__name__ != "module":
                members[name] = (name, value)
                continue
            if name.startswith("_"):
                continue
            submodule = value  # Rename for clarity
            [base, _, rest] = submodule.__name__.partition(".")
            if not base == module.__name__:
                # Not part of package, don't document
                continue

            for (sub_name, sub_val) in get_module_members(submodule):
                # Skip names not in __all__
                if hasattr(submodule, "__all__") and sub_name not in submodule.__all__:
                    continue
                qual_name = rest + "." + sub_name
                members[qual_name] = (qual_name, sub_val)
            continue
        except AttributeError:
            continue

    return sorted(list(members.values()))


# For some reason I was unable to monkey patch get_module_members
# so we monkey patch get_object_members instead...
# This is similar to the original function but I dropped a branch (for brevity only)
def get_object_members(self, want_all: bool):
    members = get_module_members(self.object)
    if not self.__all__:
        # for implicit module members, check __module__ to avoid
        # documenting imported objects
        return True, members
    else:
        ret = []
        for name, value in members:
            if name in self.__all__ or "." in name:
                ret.append(ObjectMember(name, value))
            else:
                ret.append(ObjectMember(name, value, skipped=True))

        return False, ret


ModuleDocumenter.get_object_members = get_object_members  # type: ignore


def setup(app):
    app.add_lexer("pyodide", PyodideLexer)
    app.add_lexer("html-pyodide", HtmlPyodideLexer)
    app.setup_extension("sphinx_js")
    app.connect("builder-inited", wrap_analyzer)
    app.add_directive("js-doc-summary", get_jsdoc_summary_directive(app))
    app.add_directive("js-doc-content", get_jsdoc_content_directive(app))
