diff --git a/CHANGES b/CHANGES index e27ff063d..b5ac7c817 100644 --- a/CHANGES +++ b/CHANGES @@ -120,6 +120,18 @@ Features added Bugs fixed ---------- +* C++: + + - Don't crash when using the ``struct`` role in some cases. + - Don't warn when using the ``var``/``member`` role for function + parameters. + - Render call and braced-init expressions correctly. + +* #7097: Filenames of images generated by + ``sphinx.transforms.post_transforms.images.ImageConverter`` + or its subclasses (used for latex build) are now sanitized, + to prevent broken paths + Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index d8eec6330..53c958601 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -3004,7 +3004,7 @@ class ASTParenExprList(ASTBase): signode.append(nodes.Text(', ')) else: first = False - e.describe_signature(signode, mode, env, symbol) + e.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(')')) @@ -3031,7 +3031,7 @@ class ASTBracedInitList(ASTBase): signode.append(nodes.Text(', ')) else: first = False - e.describe_signature(signode, mode, env, symbol) + e.describe_signature(signode, mode, env, symbol) if self.trailingComma: signode.append(nodes.Text(',')) signode.append(nodes.Text('}')) @@ -6862,23 +6862,30 @@ class CPPDomain(Domain): if typ.startswith('cpp:'): typ = typ[4:] + origTyp = typ if typ == 'func': typ = 'function' + if typ == 'struct': + typ = 'class' declTyp = s.declaration.objectType def checkType(): if typ == 'any' or typ == 'identifier': return True if declTyp == 'templateParam': + # TODO: perhaps this should be strengthened one day return True + if declTyp == 'functionParam': + if typ == 'var' or typ == 'member': + return True objtypes = self.objtypes_for_role(typ) if objtypes: return declTyp in objtypes - print("Type is %s, declType is %s" % (typ, declTyp)) + print("Type is %s (originally: %s), declType is %s" % (typ, origTyp, declTyp)) assert False if not checkType(): warner.warn("cpp:%s targets a %s (%s)." - % (typ, s.declaration.objectType, + % (origTyp, s.declaration.objectType, s.get_full_nested_name())) declaration = s.declaration diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 758e92f0d..d1b513b27 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -9,6 +9,7 @@ """ import os +import re from hashlib import sha1 from math import ceil from typing import Any, Dict, List, Tuple @@ -27,6 +28,7 @@ from sphinx.util.osutil import ensuredir, movefile logger = logging.getLogger(__name__) MAX_FILENAME_LEN = 32 +CRITICAL_PATH_CHAR_RE = re.compile('[:;<>|*" ]') class BaseImageConverter(SphinxTransform): @@ -65,6 +67,7 @@ class ImageDownloader(BaseImageConverter): if basename == '' or len(basename) > MAX_FILENAME_LEN: filename, ext = os.path.splitext(node['uri']) basename = sha1(filename.encode()).hexdigest() + ext + basename = re.sub(CRITICAL_PATH_CHAR_RE, "_", basename) dirname = node['uri'].replace('://', '/').translate({ord("?"): "/", ord("&"): "/"}) @@ -146,6 +149,7 @@ class DataURIExtractor(BaseImageConverter): def get_filename_for(filename: str, mimetype: str) -> str: basename = os.path.basename(filename) + basename = re.sub(CRITICAL_PATH_CHAR_RE, "_", basename) return os.path.splitext(basename)[0] + get_image_extension(mimetype) diff --git a/tests/roots/test-domain-cpp/roles-targets-ok.rst b/tests/roots/test-domain-cpp/roles-targets-ok.rst new file mode 100644 index 000000000..e70b9259f --- /dev/null +++ b/tests/roots/test-domain-cpp/roles-targets-ok.rst @@ -0,0 +1,170 @@ +.. default-domain:: cpp + +.. namespace:: RolesTargetsOk + +.. class:: Class + + :cpp:any:`Class` + :class:`Class` + :struct:`Class` + union + func + member + var + :type:`Class` + concept + enum + enumerator + +.. union:: Union + + :cpp:any:`Union` + class + struct + :union:`Union` + func + member + var + :type:`Union` + concept + enum + enumerator + +.. function:: void Function() + + :cpp:any:`Function` + class + struct + union + :func:`Function` + member + var + :type:`Function` + concept + enum + enumerator + +.. var:: int Variable + + :cpp:any:`Variable` + class + struct + union + function + :member:`Variable` + :var:`Variables` + type + concept + enum + enumerator + +.. type:: Type = void + + :cpp:any:`Type` + class + struct + union + function + member + var + :type:`Type` + concept + enum + enumerator + +.. concept:: template Concept + + :cpp:any:`Concept` + class + struct + union + function + member + var + type + :concept:`Concept` + enum + enumerator + +.. enum-struct:: Enum + + :cpp:any:`Enum` + class + struct + union + function + member + var + :type:`Enum` + concept + :enum:`Enum` + enumerator + + .. enumerator:: Enumerator + + :cpp:any:`Enumerator` + class + struct + union + function + member + var + type + concept + enum + :enumerator:`Enumerator` + +.. class:: template typename TParamTemplate \ + > ClassTemplate + + :cpp:any:`TParamType` + :class:`TParamType` + :struct:`TParamType` + :union:`TParamType` + :func:`TParamType` + :member:`TParamType` + :var:`TParamType` + :type:`TParamType` + :concept:`TParamType` + :enum:`TParamType` + :enumerator:`TParamType` + + :cpp:any:`TParamVar` + :class:`TParamVar` + :struct:`TParamVar` + :union:`TParamVar` + :func:`TParamVar` + :member:`TParamVar` + :var:`TParamVar` + :type:`TParamVar` + :concept:`TParamVar` + :enum:`TParamVar` + :enumerator:`TParamVar` + + :cpp:any:`TParamTemplate` + :class:`TParamTemplate` + :struct:`TParamTemplate` + :union:`TParamTemplate` + :func:`TParamTemplate` + :member:`TParamTemplate` + :var:`TParamTemplate` + :type:`TParamTemplate` + :concept:`TParamTemplate` + :enum:`TParamTemplate` + :enumerator:`TParamTemplate` + +.. function:: void FunctionParams(int FunctionParam) + + :cpp:any:`FunctionParam` + class + struct + union + function + :member:`FunctionParam` + :var:`FunctionParam` + type + concept + enum + enumerator diff --git a/tests/roots/test-domain-cpp/roles-targets-warn.rst b/tests/roots/test-domain-cpp/roles-targets-warn.rst new file mode 100644 index 000000000..decebe170 --- /dev/null +++ b/tests/roots/test-domain-cpp/roles-targets-warn.rst @@ -0,0 +1,158 @@ +.. default-domain:: cpp + +.. namespace:: RolesTargetsWarn + +.. class:: Class + + class + struct + :union:`Class` + :func:`Class` + :member:`Class` + :var:`Class` + type + :concept:`Class` + :enum:`Class` + :enumerator:`Class` + +.. union:: Union + + :class:`Union` + :struct:`Union` + union + :func:`Union` + :member:`Union` + :var:`Union` + type + :concept:`Union` + :enum:`Union` + :enumerator:`Union` + +.. function:: void Function() + + :class:`Function` + :struct:`Function` + :union:`Function` + func + :member:`Function` + :var:`Function` + type + :concept:`Function` + :enum:`Function` + :enumerator:`Function` + +.. var:: int Variable + + :class:`Variable` + :struct:`Variable` + :union:`Variable` + :func:`Variable` + member + var + :type:`Variable` + :concept:`Variable` + :enum:`Variable` + :enumerator:`Variable` + +.. type:: Type = void + + :class:`Type` + :struct:`Type` + :union:`Type` + :func:`Type` + :member:`Type` + :var:`Type` + type + :concept:`Type` + :enum:`Type` + :enumerator:`Type` + +.. concept:: template Concept + + :class:`Concept` + :struct:`Concept` + :union:`Concept` + :func:`Concept` + :member:`Concept` + :var:`Concept` + :type:`Concept` + concept + :enum:`Concept` + :enumerator:`Concept` + +.. enum-struct:: Enum + + :class:`Enum` + :struct:`Enum` + :union:`Enum` + :func:`Enum` + :member:`Enum` + :var:`Enum` + type + :concept:`Enum` + enum + :enumerator:`Enum` + + .. enumerator:: Enumerator + + :class:`Enumerator` + :struct:`Enumerator` + :union:`Enumerator` + :func:`Enumerator` + :member:`Enumerator` + :var:`Enumerator` + :type:`Enumerator` + :concept:`Enumerator` + :enum:`Enumerator` + enumerator + +.. class:: template typename TParamTemplate \ + > ClassTemplate + + class + struct + union + func + member + var + type + concept + enum + enumerator + + class + struct + union + func + member + var + type + concept + enum + enumerator + + class + struct + union + func + member + var + type + concept + enum + enumerator + +.. function:: void FunctionParams(int FunctionParam) + + :class:`FunctionParam` + :struct:`FunctionParam` + :union:`FunctionParam` + :func:`FunctionParam` + member + var + :type:`FunctionParam` + :concept:`FunctionParam` + :enum:`FunctionParam` + :enumerator:`FunctionParam` diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index cd5d760d8..b2cbf3035 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -786,11 +786,59 @@ def test_xref_parsing(): # raise DefinitionError("") +def filter_warnings(warning, file): + lines = warning.getvalue().split("\n"); + res = [l for l in lines if "domain-cpp" in l and "{}.rst".format(file) in l and + "WARNING: document isn't included in any toctree" not in l] + print("Filtered warnings for file '{}':".format(file)) + for w in res: + print(w) + return res + + @pytest.mark.sphinx(testroot='domain-cpp') def test_build_domain_cpp_misuse_of_roles(app, status, warning): app.builder.build_all() + ws = filter_warnings(warning, "roles-targets-ok") + assert len(ws) == 0 - # TODO: properly check for the warnings we expect + ws = filter_warnings(warning, "roles-targets-warn") + # the roles that should be able to generate warnings: + allRoles = ['class', 'struct', 'union', 'func', 'member', 'var', 'type', 'concept', 'enum', 'enumerator'] + ok = [ # targetType, okRoles + ('class', ['class', 'struct', 'type']), + ('union', ['union', 'type']), + ('func', ['func', 'type']), + ('member', ['member', 'var']), + ('type', ['type']), + ('concept', ['concept']), + ('enum', ['type', 'enum']), + ('enumerator', ['enumerator']), + ('tParam', ['class', 'struct', 'union', 'func', 'member', 'var', 'type', 'concept', 'enum', 'enumerator', 'functionParam']), + ('functionParam', ['member', 'var']), + ] + warn = [] + for targetType, roles in ok: + txtTargetType = "function" if targetType == "func" else targetType + for r in allRoles: + if r not in roles: + warn.append("WARNING: cpp:{} targets a {} (".format(r, txtTargetType)) + warn = list(sorted(warn)) + for w in ws: + assert "targets a" in w + ws = [w[w.index("WARNING:"):] for w in ws] + ws = list(sorted(ws)) + print("Expected warnings:") + for w in warn: + print(w) + print("Actual warnings:") + for w in ws: + print(w) + + for i in range(min(len(warn), len(ws))): + assert ws[i].startswith(warn[i]) + + assert len(ws) == len(warn) @pytest.mark.skipif(docutils.__version_info__ < (0, 13),