-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Create separate documentation page for each message #5396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
34d67e3
17a1817
ea8b4d0
ad84b37
470445f
fee2e58
b878409
61b4b76
0a0b366
8fede13
4797640
3bafd10
35eca44
8903b95
5acdb54
e6c50d4
ca8d2c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE | ||
|
||
"""Script used to generate the messages files.""" | ||
|
||
import os | ||
from collections import defaultdict | ||
from pathlib import Path | ||
from typing import DefaultDict, Dict, List, NamedTuple, Optional, Tuple | ||
|
||
from sphinx.application import Sphinx | ||
|
||
from pylint.checkers import initialize as initialize_checkers | ||
from pylint.constants import MSG_TYPES | ||
from pylint.extensions import initialize as initialize_extensions | ||
from pylint.lint import PyLinter | ||
from pylint.message import MessageDefinition | ||
from pylint.utils import get_rst_title | ||
|
||
PYLINT_BASE_PATH = Path(__file__).resolve().parent.parent.parent | ||
"""Base path to the project folder""" | ||
|
||
PYLINT_MESSAGES_PATH = PYLINT_BASE_PATH / "doc" / "messages" | ||
"""Path to the messages documentation folder""" | ||
|
||
|
||
MSG_TYPES_DOC = {k: v if v != "info" else "information" for k, v in MSG_TYPES.items()} | ||
|
||
|
||
class MessageData(NamedTuple): | ||
checker: str | ||
id: str | ||
name: str | ||
definition: MessageDefinition | ||
|
||
|
||
MessagesDict = Dict[str, List[MessageData]] | ||
OldMessagesDict = Dict[str, DefaultDict[Tuple[str, str], List[Tuple[str, str]]]] | ||
"""DefaultDict is indexed by tuples of (old name symbol, old name id) and values are | ||
tuples of (new name symbol, new name category) | ||
""" | ||
|
||
|
||
def _register_all_checkers_and_extensions(linter: PyLinter) -> None: | ||
"""Registers all checkers and extensions found in the default folders.""" | ||
initialize_checkers(linter) | ||
initialize_extensions(linter) | ||
|
||
|
||
def _get_all_messages( | ||
linter: PyLinter, | ||
) -> Tuple[MessagesDict, OldMessagesDict]: | ||
"""Get all messages registered to a linter and return a dictionary indexed by message | ||
type. | ||
Also return a dictionary of old message and the new messages they can be mapped to. | ||
""" | ||
messages_dict: MessagesDict = { | ||
"fatal": [], | ||
"error": [], | ||
"warning": [], | ||
"convention": [], | ||
"refactor": [], | ||
"information": [], | ||
} | ||
old_messages: OldMessagesDict = { | ||
"fatal": defaultdict(list), | ||
"error": defaultdict(list), | ||
"warning": defaultdict(list), | ||
"convention": defaultdict(list), | ||
"refactor": defaultdict(list), | ||
"information": defaultdict(list), | ||
} | ||
for message in linter.msgs_store.messages: | ||
message_data = MessageData( | ||
message.checker_name, message.msgid, message.symbol, message | ||
) | ||
messages_dict[MSG_TYPES_DOC[message.msgid[0]]].append(message_data) | ||
|
||
if message.old_names: | ||
for old_name in message.old_names: | ||
category = MSG_TYPES_DOC[old_name[0][0]] | ||
old_messages[category][(old_name[1], old_name[0])].append( | ||
(message.symbol, MSG_TYPES_DOC[message.msgid[0]]) | ||
) | ||
|
||
return messages_dict, old_messages | ||
|
||
|
||
def _write_message_page(messages_dict: MessagesDict) -> None: | ||
"""Create or overwrite the file for each message.""" | ||
for category, messages in messages_dict.items(): | ||
category_dir = PYLINT_MESSAGES_PATH / category | ||
if not category_dir.exists(): | ||
category_dir.mkdir(parents=True, exist_ok=True) | ||
for message in messages: | ||
messages_file = os.path.join(category_dir, f"{message.name}.rst") | ||
with open(messages_file, "w", encoding="utf-8") as stream: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. message_file = category_dir / f"{message.name}.rst"
text = f""".. _{message.name}:
{get_rst_title(f"{message.name} / {message.id}", "=")}
**Message emitted:**
{message.definition.msg}
**Description:**
*{message.definition.description}*
Created by ``{message.checker}`` checker
"""
with message_file.open("w", encoding="utf-8") as stream:
stream.write_text(text) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really understand what you're proposing? Perhaps I'm missing something but what's better about declaring text before opening the |
||
stream.write( | ||
f""".. _{message.name}: | ||
|
||
{get_rst_title(f"{message.name} / {message.id}", "=")} | ||
**Message emitted:** | ||
|
||
{message.definition.msg} | ||
|
||
**Description:** | ||
|
||
*{message.definition.description}* | ||
|
||
Created by ``{message.checker}`` checker | ||
""" | ||
) | ||
|
||
|
||
def _write_messages_list_page( | ||
messages_dict: MessagesDict, old_messages_dict: OldMessagesDict | ||
) -> None: | ||
"""Create or overwrite the page with the list of all messages.""" | ||
messages_file = os.path.join(PYLINT_MESSAGES_PATH, "messages_list.rst") | ||
with open(messages_file, "w", encoding="utf-8") as stream: | ||
# Write header of file | ||
stream.write( | ||
f""".. _messages-list: | ||
|
||
{get_rst_title("Pylint Messages", "=")} | ||
Pylint can emit the following messages: | ||
|
||
""" | ||
) | ||
|
||
# Iterate over tuple to keep same order | ||
for category in ( | ||
"fatal", | ||
"error", | ||
"warning", | ||
"convention", | ||
"refactor", | ||
"information", | ||
): | ||
messages = sorted(messages_dict[category], key=lambda item: item.name) | ||
old_messages = sorted(old_messages_dict[category], key=lambda item: item[0]) | ||
messages_string = "".join( | ||
f" {category}/{message.name}.rst\n" for message in messages | ||
) | ||
old_messages_string = "".join( | ||
f" {category}/{old_message[0]}.rst\n" for old_message in old_messages | ||
) | ||
|
||
# Write list per category | ||
stream.write( | ||
f"""{get_rst_title(category.capitalize(), "-")} | ||
All messages in the {category} category: | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
:titlesonly: | ||
|
||
{messages_string} | ||
All renamed messages in the {category} category: | ||
|
||
.. toctree:: | ||
:maxdepth: 1 | ||
:titlesonly: | ||
|
||
{old_messages_string} | ||
|
||
""" | ||
) | ||
|
||
|
||
def _write_redirect_pages(old_messages: OldMessagesDict) -> None: | ||
"""Create redirect pages for old-messages.""" | ||
for category, old_names in old_messages.items(): | ||
category_dir = PYLINT_MESSAGES_PATH / category | ||
if not os.path.exists(category_dir): | ||
os.makedirs(category_dir) | ||
for old_name, new_names in old_names.items(): | ||
old_name_file = os.path.join(category_dir, f"{old_name[0]}.rst") | ||
with open(old_name_file, "w", encoding="utf-8") as stream: | ||
new_names_string = "".join( | ||
f" ../{new_name[1]}/{new_name[0]}.rst\n" for new_name in new_names | ||
) | ||
stream.write( | ||
f""".. _{old_name[0]}: | ||
|
||
{get_rst_title(" / ".join(old_name), "=")} | ||
"{old_name[0]} has been renamed. The new message can be found at: | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
:titlesonly: | ||
|
||
{new_names_string} | ||
""" | ||
) | ||
|
||
|
||
# pylint: disable-next=unused-argument | ||
def build_messages_pages(app: Optional[Sphinx]) -> None: | ||
"""Overwrite messages files by printing the documentation to a stream. | ||
Documentation is written in ReST format. | ||
""" | ||
# Create linter, register all checkers and extensions and get all messages | ||
linter = PyLinter() | ||
_register_all_checkers_and_extensions(linter) | ||
messages, old_messages = _get_all_messages(linter) | ||
|
||
# Write message and category pages | ||
_write_message_page(messages) | ||
_write_messages_list_page(messages, old_messages) | ||
|
||
# Write redirect pages | ||
_write_redirect_pages(old_messages) | ||
|
||
|
||
def setup(app: Sphinx) -> None: | ||
"""Connects the extension to the Sphinx process""" | ||
# Register callback at the builder-inited Sphinx event | ||
# See https://www.sphinx-doc.org/en/master/extdev/appapi.html | ||
app.connect("builder-inited", build_messages_pages) | ||
|
||
|
||
if __name__ == "__main__": | ||
pass | ||
# Uncomment to allow running this script by your local python interpreter | ||
# build_messages_pages(None) | ||
Pierre-Sassoulas marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.. _messages: | ||
|
||
Messages | ||
=================== | ||
|
||
.. toctree:: | ||
:maxdepth: 1 | ||
:titlesonly: | ||
|
||
messages_introduction | ||
messages_list |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.. _messages-introduction: | ||
|
||
Pylint messages | ||
================ | ||
|
||
Pylint can emit various messages. These are categorized according to categories:: | ||
|
||
Convention | ||
Error | ||
Fatal | ||
Information | ||
Refactor | ||
Warning | ||
|
||
A list of these messages can be found here: :ref:`messages-list` |
Uh oh!
There was an error while loading. Please reload this page.