diff --git a/CHANGES b/CHANGES index be1a398a1..461df9bad 100644 --- a/CHANGES +++ b/CHANGES @@ -81,6 +81,8 @@ Bugs fixed * #6527: :confval:`last_updated` wrongly assumes timezone as UTC * #5592: std domain: :rst:dir:`option` directive registers an index entry for each comma separated option +* #6549: sphinx-build: Escaped characters in error messages +* #6545: doctest comments not getting trimmed since Sphinx 1.8.0 Testing -------- diff --git a/setup.py b/setup.py index 96bffccfa..f92de9250 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ extras_require = { 'html5lib', 'flake8>=3.5.0', 'flake8-import-order', - 'mypy>=0.711', + 'mypy>=0.720', 'docutils-stubs', ], } diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index d2f6c13b7..8f7b33a3b 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -47,7 +47,7 @@ def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: I print(terminal_safe(exception.args[0]), file=stderr) elif isinstance(exception, SphinxError): print(red('%s:' % exception.category), file=stderr) - print(terminal_safe(str(exception)), file=stderr) + print(str(exception), file=stderr) elif isinstance(exception, UnicodeError): print(red(__('Encoding error:')), file=stderr) print(terminal_safe(str(exception)), file=stderr) diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 68df253d5..5e4d6681a 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -285,7 +285,7 @@ class DocTestBuilder(Builder): # for doctest examples but unusable for multi-statement code such # as setup code -- to be able to use doctest error reporting with # that code nevertheless, we monkey-patch the "compile" it uses. - doctest.compile = self.compile # type: ignore + doctest.compile = self.compile sys.path[0:0] = self.config.doctest_path diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index e627da62a..e76e0836e 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -399,6 +399,6 @@ def inspect_main(argv: List[str]) -> None: if __name__ == '__main__': import logging # type: ignore - logging.basicConfig() # type: ignore + logging.basicConfig() inspect_main(argv=sys.argv[1:]) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 49cd5019e..5da0342f8 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -159,7 +159,7 @@ class TodoListProcessor: self.process(doctree, docname) def process(self, doctree: nodes.document, docname: str) -> None: - todos = sum(self.domain.todos.values(), []) + todos = sum(self.domain.todos.values(), []) # type: List[todo_node] for node in doctree.traverse(todolist): if not self.config.todo_include_todos: node.parent.remove(node) @@ -220,7 +220,7 @@ def process_todo_nodes(app: Sphinx, doctree: nodes.document, fromdocname: str) - warnings.warn('process_todo_nodes() is deprecated.', RemovedInSphinx40Warning) domain = cast(TodoDomain, app.env.get_domain('todo')) - todos = sum(domain.todos.values(), []) + todos = sum(domain.todos.values(), []) # type: List[todo_node] for node in doctree.traverse(todolist): if node.get('ids'): diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 3603e3cc3..cbcb3b17d 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -274,7 +274,7 @@ class VariableCommentPicker(ast.NodeVisitor): """Handles Assign node and pick up a variable comment.""" try: targets = get_assign_targets(node) - varnames = sum([get_lvar_names(t, self=self.get_self()) for t in targets], []) + varnames = sum([get_lvar_names(t, self=self.get_self()) for t in targets], []) # type: List[str] # NOQA current_line = self.get_line(node.lineno) except TypeError: return # this assignment is not new definition! diff --git a/sphinx/roles.py b/sphinx/roles.py index 1a2daa36a..1b80e1ec3 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -400,7 +400,7 @@ class GUILabel(SphinxRole): class MenuSelection(GUILabel): def run(self): # type: () -> Tuple[List[nodes.Node], List[nodes.system_message]] - self.text = self.text.replace('-->', '\N{TRIANGULAR BULLET}') # type: ignore + self.text = self.text.replace('-->', '\N{TRIANGULAR BULLET}') return super().run() diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 1ba3237c4..f7ce33eac 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -120,8 +120,8 @@ class SphinxTestApp(application.Sphinx): warningiserror = False self._saved_path = sys.path[:] - self._saved_directives = directives._directives.copy() # type: ignore - self._saved_roles = roles._roles.copy() # type: ignore + self._saved_directives = directives._directives.copy() + self._saved_roles = roles._roles.copy() self._saved_nodeclasses = {v for v in dir(nodes.GenericNodeVisitor) if v.startswith('visit_')} @@ -140,8 +140,8 @@ class SphinxTestApp(application.Sphinx): locale.translators.clear() sys.path[:] = self._saved_path sys.modules.pop('autodoc_fodder', None) - directives._directives = self._saved_directives # type: ignore - roles._roles = self._saved_roles # type: ignore + directives._directives = self._saved_directives + roles._roles = self._saved_roles for method in dir(nodes.GenericNodeVisitor): if method.startswith('visit_') and \ method not in self._saved_nodeclasses: diff --git a/sphinx/transforms/post_transforms/code.py b/sphinx/transforms/post_transforms/code.py index a42de4bf1..bae1216bd 100644 --- a/sphinx/transforms/post_transforms/code.py +++ b/sphinx/transforms/post_transforms/code.py @@ -9,7 +9,7 @@ """ import sys -from typing import NamedTuple +from typing import NamedTuple, Union from docutils import nodes from pygments.lexers import PythonConsoleLexer, guess_lexer @@ -110,13 +110,21 @@ class TrimDoctestFlagsTransform(SphinxTransform): if not self.config.trim_doctest_flags: return - for node in self.document.traverse(nodes.literal_block): - if self.is_pyconsole(node): - source = node.rawsource - source = doctest.blankline_re.sub('', source) - source = doctest.doctestopt_re.sub('', source) - node.rawsource = source - node[:] = [nodes.Text(source)] + for lbnode in self.document.traverse(nodes.literal_block): # type: nodes.literal_block + if self.is_pyconsole(lbnode): + self.strip_doctest_flags(lbnode) + + for dbnode in self.document.traverse(nodes.doctest_block): # type: nodes.doctest_block + self.strip_doctest_flags(dbnode) + + @staticmethod + def strip_doctest_flags(node): + # type: (Union[nodes.literal_block, nodes.doctest_block]) -> None + source = node.rawsource + source = doctest.blankline_re.sub('', source) + source = doctest.doctestopt_re.sub('', source) + node.rawsource = source + node[:] = [nodes.Text(source)] @staticmethod def is_pyconsole(node): diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 7bfb3ad3e..0d54e1482 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -220,7 +220,7 @@ def save_traceback(app: "Sphinx") -> str: platform.python_version(), platform.python_implementation(), docutils.__version__, docutils.__version_details__, - jinja2.__version__, # type: ignore + jinja2.__version__, last_msgs)).encode()) if app is not None: for ext in app.extensions.values(): diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 25010c41f..66fcc3374 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -49,13 +49,13 @@ additional_nodes = set() # type: Set[Type[nodes.Element]] def docutils_namespace() -> Generator[None, None, None]: """Create namespace for reST parsers.""" try: - _directives = copy(directives._directives) # type: ignore - _roles = copy(roles._roles) # type: ignore + _directives = copy(directives._directives) + _roles = copy(roles._roles) yield finally: - directives._directives = _directives # type: ignore - roles._roles = _roles # type: ignore + directives._directives = _directives + roles._roles = _roles for node in list(additional_nodes): unregister_node(node) @@ -64,7 +64,7 @@ def docutils_namespace() -> Generator[None, None, None]: def is_directive_registered(name: str) -> bool: """Check the *name* directive is already registered.""" - return name in directives._directives # type: ignore + return name in directives._directives def register_directive(name: str, directive: Type[Directive]) -> None: @@ -78,7 +78,7 @@ def register_directive(name: str, directive: Type[Directive]) -> None: def is_role_registered(name: str) -> bool: """Check the *name* role is already registered.""" - return name in roles._roles # type: ignore + return name in roles._roles def register_role(name: str, role: RoleFunction) -> None: @@ -92,7 +92,7 @@ def register_role(name: str, role: RoleFunction) -> None: def unregister_role(name: str) -> None: """Unregister a role from docutils.""" - roles._roles.pop(name, None) # type: ignore + roles._roles.pop(name, None) def is_node_registered(node: Type[Element]) -> bool: @@ -107,7 +107,7 @@ def register_node(node: Type[Element]) -> None: inside ``docutils_namespace()`` to prevent side-effects. """ if not hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__): - nodes._add_node_class_names([node.__name__]) # type: ignore + nodes._add_node_class_names([node.__name__]) additional_nodes.add(node) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index fc6dafe9f..66bb950c6 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -514,7 +514,7 @@ class Signature: qualname = repr(annotation) if (hasattr(typing, 'TupleMeta') and - isinstance(annotation, typing.TupleMeta) and # type: ignore + isinstance(annotation, typing.TupleMeta) and not hasattr(annotation, '__tuple_params__')): # This is for Python 3.6+, 3.5 case is handled below params = annotation.__args__ @@ -545,7 +545,7 @@ class Signature: param_str = ', '.join(self.format_annotation(p) for p in params) return '%s[%s]' % (qualname, param_str) elif (hasattr(typing, 'UnionMeta') and # for py35 or below - isinstance(annotation, typing.UnionMeta) and # type: ignore + isinstance(annotation, typing.UnionMeta) and hasattr(annotation, '__union_params__')): params = annotation.__union_params__ if params is not None: @@ -565,7 +565,7 @@ class Signature: param_str = ', '.join(self.format_annotation(p) for p in params) return 'Union[%s]' % param_str elif (hasattr(typing, 'CallableMeta') and # for py36 or below - isinstance(annotation, typing.CallableMeta) and # type: ignore + isinstance(annotation, typing.CallableMeta) and getattr(annotation, '__args__', None) is not None and hasattr(annotation, '__result__')): # Skipped in the case of plain typing.Callable @@ -581,7 +581,7 @@ class Signature: args_str, self.format_annotation(annotation.__result__)) elif (hasattr(typing, 'TupleMeta') and # for py36 or below - isinstance(annotation, typing.TupleMeta) and # type: ignore + isinstance(annotation, typing.TupleMeta) and hasattr(annotation, '__tuple_params__') and hasattr(annotation, '__tuple_use_ellipsis__')): params = annotation.__tuple_params__ diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 47ab9f2e4..89d999275 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -375,7 +375,7 @@ class TextWriter(writers.Writer): settings_spec = ('No options here.', '', ()) settings_defaults = {} # type: Dict - output = None + output = None # type: str def __init__(self, builder): # type: (TextBuilder) -> None diff --git a/tests/roots/test-trim_doctest_flags/index.rst b/tests/roots/test-trim_doctest_flags/index.rst index 1934d617b..63ce234cd 100644 --- a/tests/roots/test-trim_doctest_flags/index.rst +++ b/tests/roots/test-trim_doctest_flags/index.rst @@ -21,3 +21,8 @@ test-trim_doctest_flags >>> datetime.date.now() # doctest: +QUX datetime.date(2008, 1, 1) + +.. doctest_block:: + + >>> datetime.date.now() # doctest: +QUUX + datetime.date(2008, 1, 1) diff --git a/tests/test_transforms_post_transforms_code.py b/tests/test_transforms_post_transforms_code.py index c70dbd839..8ea337b0b 100644 --- a/tests/test_transforms_post_transforms_code.py +++ b/tests/test_transforms_post_transforms_code.py @@ -18,6 +18,7 @@ def test_trim_doctest_flags_html(app, status, warning): assert 'BAR' in result assert 'BAZ' not in result assert 'QUX' not in result + assert 'QUUX' not in result @pytest.mark.sphinx('latex', testroot='trim_doctest_flags') @@ -29,3 +30,4 @@ def test_trim_doctest_flags_latex(app, status, warning): assert 'BAR' in result assert 'BAZ' not in result assert 'QUX' not in result + assert 'QUUX' not in result