Repository: sphinx-doc/sphinx

Issue:
autosummary and autodoc-skip-member don't work well together
**Describe the bug**
The `autodoc-skip-member` event can be used modify which members are documented by autodoc.  However, those modifications are ignored by `autosummary`, which breaks in two ways:
- If a member is skipped by autodoc-skip-member then the member still shows up in the set of members used by autosummary templates (e.g. a class that is now suppressed in autodoc is still an element of the `classes` collection used by the module.rst template).  This results in broken links in generated tables.
- If a member is included by autodoc-skip-member when it would normally be included, then it does not appear in the set of members used by the autosummary template, which can result in missing entries in the tables generated by the template, even though the documentation itself is generated by the section.  For instance, the `.. automodule::` section of the `module.rst` default template might generate the documentation for a normally hidden class because of `autodoc-skip-member`, but that class won't appear in the `Classes` section that the template creates.

**To Reproduce**
Steps to reproduce the behavior:

1. Add something like this to the bottom of a conf.py that uses autodoc and autosummary:

        def skip_member(app, what, name, obj, skip, opts):
            # we can document otherwise excluded entities here by returning False
            # or skip otherwise included entities by returning True
            if name == "_something_that_wouldnt_normally_appear":
                return False
            elif name == "something_that_would_appear":
                return True
            return None

        def setup(app):
            app.connect('autodoc-skip-member', skip_member)

2. Run sphinx

**Expected behavior**

There is some straightforward way to get the list of members post-filtering into an autosummary template.

**Actual behavior**

The member table generated by autosummary wrongly contains a broken link to "something_that_would_appear", and wrongly does not contain a link to "_something_that_wouldnt_normally_appear" even though documentation is generated for it.

**Environment info**
- OS: any
- Python version: any
- Sphinx version: 2.2.1
- Sphinx extensions:  sphinx.ext.autodoc, sphinx.ext.autosummary



Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=h1) Report
> Merging [#6817](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=desc) into [2.0](https://codecov.io/gh/sphinx-doc/sphinx/commit/c7fd4cc82701baf26e1f2a4cc5812b97e91374d3?src=pr&el=desc) will **decrease** coverage by `0.03%`.
> The diff coverage is `78.57%`.

[![Impacted file tree graph](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817/graphs/tree.svg?width=650&token=VFlFSELbkw&height=150&src=pr)](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              2.0    #6817      +/-   ##
==========================================
- Coverage   83.76%   83.73%   -0.04%     
==========================================
  Files         269      271       +2     
  Lines       41141    41188      +47     
  Branches     6008     6012       +4     
==========================================
+ Hits        34460    34487      +27     
- Misses       5353     5370      +17     
- Partials     1328     1331       +3
```


| [Impacted Files](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tests/test\_ext\_autosummary.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9leHRfYXV0b3N1bW1hcnkucHk=) | `98.62% <100%> (+0.05%)` | :arrow_up: |
| [sphinx/ext/autosummary/generate.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817/diff?src=pr&el=tree#diff-c3BoaW54L2V4dC9hdXRvc3VtbWFyeS9nZW5lcmF0ZS5weQ==) | `78.54% <62.5%> (-0.63%)` | :arrow_down: |
| [sphinx/\_\_init\_\_.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817/diff?src=pr&el=tree#diff-c3BoaW54L19faW5pdF9fLnB5) | `73.91% <0%> (ø)` | |
| [sphinx/make\_mode.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817/diff?src=pr&el=tree#diff-c3BoaW54L21ha2VfbW9kZS5weQ==) | `0% <0%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=footer). Last update [c7fd4cc...395e8c1](https://codecov.io/gh/sphinx-doc/sphinx/pull/6817?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGES b/CHANGES
index 9780573c9..cb772974e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -37,6 +37,7 @@ Features added
 * #6812: Improve a warning message when extensions are not parallel safe
 * #6818: Improve Intersphinx performance for multiple remote inventories.
 * #2546: apidoc: .so file support
+* #6798: autosummary: emit ``autodoc-skip-member`` event on generating stub file
 * #6483: i18n: make explicit titles in toctree translatable
 
 Bugs fixed
diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst
index 16a8cea7e..c5211639e 100644
--- a/doc/usage/extensions/autosummary.rst
+++ b/doc/usage/extensions/autosummary.rst
@@ -143,6 +143,11 @@ also use these config values:
    The new files will be placed in the directories specified in the
    ``:toctree:`` options of the directives.
 
+   .. versionchanged:: 2.3
+
+      Emits :event:`autodoc-skip-member` event as :mod:`~sphinx.ext.autodoc`
+      does.
+
 .. confval:: autosummary_mock_imports
 
    This value contains a list of modules to be mocked up.  See
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index c4726f528..e1dd1be51 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -138,9 +138,20 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
         if not template.exists(template_name):
             template_name = 'autosummary/base.rst'
 
+    def skip_member(obj: Any, name: str, objtype: str) -> bool:
+        try:
+            return app.emit_firstresult('autodoc-skip-member', objtype, name,
+                                        obj, False, {})
+        except Exception as exc:
+            logger.warning(__('autosummary: failed to determine %r to be documented.'
+                              'the following exception was raised:\n%s'),
+                           name, exc, type='autosummary')
+            return False
+
     def get_members(obj: Any, types: Set[str], include_public: List[str] = [],
                     imported: bool = True) -> Tuple[List[str], List[str]]:
         items = []  # type: List[str]
+        public = []  # type: List[str]
         for name in dir(obj):
             try:
                 value = safe_getattr(obj, name)
@@ -148,11 +159,20 @@ def get_members(obj: Any, types: Set[str], include_public: List[str] = [],
                 continue
             documenter = get_documenter(app, value, obj)
             if documenter.objtype in types:
+                # skip imported members if expected
                 if imported or getattr(value, '__module__', None) == obj.__name__:
-                    # skip imported members if expected
-                    items.append(name)
-        public = [x for x in items
-                  if x in include_public or not x.startswith('_')]
+                    skipped = skip_member(value, name, documenter.objtype)
+                    if skipped is True:
+                        pass
+                    elif skipped is False:
+                        # show the member forcedly
+                        items.append(name)
+                        public.append(name)
+                    else:
+                        items.append(name)
+                        if name in include_public or not name.startswith('_'):
+                            # considers member as public
+                            public.append(name)
         return public, items
 
     ns = {}  # type: Dict[str, Any]


Test patch:
diff --git a/tests/roots/test-ext-autosummary-skip-member/conf.py b/tests/roots/test-ext-autosummary-skip-member/conf.py
new file mode 100644
index 000000000..7c8f0e9cf
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-skip-member/conf.py
@@ -0,0 +1,20 @@
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('.'))
+
+
+extensions = ['sphinx.ext.autosummary']
+autosummary_generate = True
+autodoc_default_options = {'members': True}
+
+
+def skip_member(app, what, name, obj, skip, options):
+    if name == 'skipmeth':
+        return True
+    elif name == '_privatemeth':
+        return False
+
+
+def setup(app):
+    app.connect('autodoc-skip-member', skip_member)
diff --git a/tests/roots/test-ext-autosummary-skip-member/index.rst b/tests/roots/test-ext-autosummary-skip-member/index.rst
new file mode 100644
index 000000000..c376a13c6
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-skip-member/index.rst
@@ -0,0 +1,4 @@
+.. autosummary::
+   :toctree: generate
+
+   target.Foo
diff --git a/tests/roots/test-ext-autosummary-skip-member/target.py b/tests/roots/test-ext-autosummary-skip-member/target.py
new file mode 100644
index 000000000..fdf557eee
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-skip-member/target.py
@@ -0,0 +1,14 @@
+class Foo:
+    """docstring of Foo."""
+
+    def meth(self):
+        """docstring of meth."""
+        pass
+
+    def skipmeth(self):
+        """docstring of skipmeth."""
+        pass
+
+    def _privatemeth(self):
+        """docstring of _privatemeth."""
+        pass
diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py
index 21841cc74..1948f6ae7 100644
--- a/tests/test_ext_autosummary.py
+++ b/tests/test_ext_autosummary.py
@@ -36,6 +36,11 @@
 }
 
 
+@pytest.fixture(scope='function', autouse=True)
+def unload_target_module():
+    sys.modules.pop('target', None)
+
+
 def test_mangle_signature():
     TEST = """
     () :: ()
@@ -304,6 +309,15 @@ def test_generate_autosummary_docs_property(app):
                        ".. autoproperty:: Base.prop")
 
 
+@pytest.mark.sphinx(testroot='ext-autosummary-skip-member')
+def test_autosummary_skip_member(app):
+    app.build()
+
+    content = (app.srcdir / 'generate' / 'target.Foo.rst').text()
+    assert 'Foo.skipmeth' not in content
+    assert 'Foo._privatemeth' in content
+
+
 @pytest.mark.sphinx('dummy', testroot='ext-autosummary',
                     confoverrides={'autosummary_generate': []})
 def test_empty_autosummary_generate(app, status, warning):