mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #7545 from jakobandersen/cpp-semicolon2
C and C++, allow semicolons in the end of declarations
This commit is contained in:
commit
85ae035d61
1
CHANGES
1
CHANGES
@ -54,6 +54,7 @@ Features added
|
|||||||
* #7533: html theme: Avoid whitespace at the beginning of genindex.html
|
* #7533: html theme: Avoid whitespace at the beginning of genindex.html
|
||||||
* #7541: html theme: Add a "clearer" at the end of the "body"
|
* #7541: html theme: Add a "clearer" at the end of the "body"
|
||||||
* #7542: html theme: Make admonition/topic/sidebar scrollable
|
* #7542: html theme: Make admonition/topic/sidebar scrollable
|
||||||
|
* C and C++: allow semicolon in the end of declarations.
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -1272,10 +1272,12 @@ class ASTEnumerator(ASTBase):
|
|||||||
|
|
||||||
|
|
||||||
class ASTDeclaration(ASTBaseBase):
|
class ASTDeclaration(ASTBaseBase):
|
||||||
def __init__(self, objectType: str, directiveType: str, declaration: Any) -> None:
|
def __init__(self, objectType: str, directiveType: str, declaration: Any,
|
||||||
|
semicolon: bool = False) -> None:
|
||||||
self.objectType = objectType
|
self.objectType = objectType
|
||||||
self.directiveType = directiveType
|
self.directiveType = directiveType
|
||||||
self.declaration = declaration
|
self.declaration = declaration
|
||||||
|
self.semicolon = semicolon
|
||||||
|
|
||||||
self.symbol = None # type: Symbol
|
self.symbol = None # type: Symbol
|
||||||
# set by CObject._add_enumerator_to_parent
|
# set by CObject._add_enumerator_to_parent
|
||||||
@ -1304,7 +1306,10 @@ class ASTDeclaration(ASTBaseBase):
|
|||||||
return self.get_id(_max_id, True)
|
return self.get_id(_max_id, True)
|
||||||
|
|
||||||
def _stringify(self, transform: StringifyTransform) -> str:
|
def _stringify(self, transform: StringifyTransform) -> str:
|
||||||
return transform(self.declaration)
|
res = transform(self.declaration)
|
||||||
|
if self.semicolon:
|
||||||
|
res += ';'
|
||||||
|
return res
|
||||||
|
|
||||||
def describe_signature(self, signode: TextElement, mode: str,
|
def describe_signature(self, signode: TextElement, mode: str,
|
||||||
env: "BuildEnvironment", options: Dict) -> None:
|
env: "BuildEnvironment", options: Dict) -> None:
|
||||||
@ -1340,6 +1345,8 @@ class ASTDeclaration(ASTBaseBase):
|
|||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
|
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
|
||||||
|
if self.semicolon:
|
||||||
|
mainDeclNode += nodes.Text(';')
|
||||||
|
|
||||||
|
|
||||||
class SymbolLookupResult:
|
class SymbolLookupResult:
|
||||||
@ -2742,7 +2749,7 @@ class DefinitionParser(BaseParser):
|
|||||||
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
|
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
|
||||||
decl = self._parse_declarator(named=True, paramMode=outer,
|
decl = self._parse_declarator(named=True, paramMode=outer,
|
||||||
typed=False)
|
typed=False)
|
||||||
self.assert_end()
|
self.assert_end(allowSemicolon=True)
|
||||||
except DefinitionError as exUntyped:
|
except DefinitionError as exUntyped:
|
||||||
desc = "If just a name"
|
desc = "If just a name"
|
||||||
prevErrors.append((exUntyped, desc))
|
prevErrors.append((exUntyped, desc))
|
||||||
@ -2875,7 +2882,12 @@ class DefinitionParser(BaseParser):
|
|||||||
declaration = self._parse_type(named=True, outer='type')
|
declaration = self._parse_type(named=True, outer='type')
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
return ASTDeclaration(objectType, directiveType, declaration)
|
if objectType != 'macro':
|
||||||
|
self.skip_ws()
|
||||||
|
semicolon = self.skip_string(';')
|
||||||
|
else:
|
||||||
|
semicolon = False
|
||||||
|
return ASTDeclaration(objectType, directiveType, declaration, semicolon)
|
||||||
|
|
||||||
def parse_namespace_object(self) -> ASTNestedName:
|
def parse_namespace_object(self) -> ASTNestedName:
|
||||||
return self._parse_nested_name()
|
return self._parse_nested_name()
|
||||||
|
@ -109,7 +109,8 @@ T = TypeVar('T')
|
|||||||
simple-declaration ->
|
simple-declaration ->
|
||||||
attribute-specifier-seq[opt] decl-specifier-seq[opt]
|
attribute-specifier-seq[opt] decl-specifier-seq[opt]
|
||||||
init-declarator-list[opt] ;
|
init-declarator-list[opt] ;
|
||||||
# Drop the semi-colon. For now: drop the attributes (TODO).
|
# Make the semicolon optional.
|
||||||
|
# For now: drop the attributes (TODO).
|
||||||
# Use at most 1 init-declarator.
|
# Use at most 1 init-declarator.
|
||||||
-> decl-specifier-seq init-declarator
|
-> decl-specifier-seq init-declarator
|
||||||
-> decl-specifier-seq declarator initializer
|
-> decl-specifier-seq declarator initializer
|
||||||
@ -3465,12 +3466,14 @@ class ASTTemplateDeclarationPrefix(ASTBase):
|
|||||||
|
|
||||||
class ASTDeclaration(ASTBase):
|
class ASTDeclaration(ASTBase):
|
||||||
def __init__(self, objectType: str, directiveType: str, visibility: str,
|
def __init__(self, objectType: str, directiveType: str, visibility: str,
|
||||||
templatePrefix: ASTTemplateDeclarationPrefix, declaration: Any) -> None:
|
templatePrefix: ASTTemplateDeclarationPrefix, declaration: Any,
|
||||||
|
semicolon: bool = False) -> None:
|
||||||
self.objectType = objectType
|
self.objectType = objectType
|
||||||
self.directiveType = directiveType
|
self.directiveType = directiveType
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
self.templatePrefix = templatePrefix
|
self.templatePrefix = templatePrefix
|
||||||
self.declaration = declaration
|
self.declaration = declaration
|
||||||
|
self.semicolon = semicolon
|
||||||
|
|
||||||
self.symbol = None # type: Symbol
|
self.symbol = None # type: Symbol
|
||||||
# set by CPPObject._add_enumerator_to_parent
|
# set by CPPObject._add_enumerator_to_parent
|
||||||
@ -3483,7 +3486,7 @@ class ASTDeclaration(ASTBase):
|
|||||||
templatePrefixClone = None
|
templatePrefixClone = None
|
||||||
return ASTDeclaration(self.objectType, self.directiveType,
|
return ASTDeclaration(self.objectType, self.directiveType,
|
||||||
self.visibility, templatePrefixClone,
|
self.visibility, templatePrefixClone,
|
||||||
self.declaration.clone())
|
self.declaration.clone(), self.semicolon)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> ASTNestedName:
|
def name(self) -> ASTNestedName:
|
||||||
@ -3525,6 +3528,8 @@ class ASTDeclaration(ASTBase):
|
|||||||
if self.templatePrefix:
|
if self.templatePrefix:
|
||||||
res.append(transform(self.templatePrefix))
|
res.append(transform(self.templatePrefix))
|
||||||
res.append(transform(self.declaration))
|
res.append(transform(self.declaration))
|
||||||
|
if self.semicolon:
|
||||||
|
res.append(';')
|
||||||
return ''.join(res)
|
return ''.join(res)
|
||||||
|
|
||||||
def describe_signature(self, signode: desc_signature, mode: str,
|
def describe_signature(self, signode: desc_signature, mode: str,
|
||||||
@ -3578,6 +3583,8 @@ class ASTDeclaration(ASTBase):
|
|||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
|
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
|
||||||
|
if self.semicolon:
|
||||||
|
mainDeclNode += nodes.Text(';')
|
||||||
|
|
||||||
|
|
||||||
class ASTNamespace(ASTBase):
|
class ASTNamespace(ASTBase):
|
||||||
@ -5875,7 +5882,7 @@ class DefinitionParser(BaseParser):
|
|||||||
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
|
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
|
||||||
decl = self._parse_declarator(named=True, paramMode=outer,
|
decl = self._parse_declarator(named=True, paramMode=outer,
|
||||||
typed=False)
|
typed=False)
|
||||||
self.assert_end()
|
self.assert_end(allowSemicolon=True)
|
||||||
except DefinitionError as exUntyped:
|
except DefinitionError as exUntyped:
|
||||||
if outer == 'type':
|
if outer == 'type':
|
||||||
desc = "If just a name"
|
desc = "If just a name"
|
||||||
@ -6286,8 +6293,10 @@ class DefinitionParser(BaseParser):
|
|||||||
templatePrefix,
|
templatePrefix,
|
||||||
fullSpecShorthand=False,
|
fullSpecShorthand=False,
|
||||||
isMember=objectType == 'member')
|
isMember=objectType == 'member')
|
||||||
|
self.skip_ws()
|
||||||
|
semicolon = self.skip_string(';')
|
||||||
return ASTDeclaration(objectType, directiveType, visibility,
|
return ASTDeclaration(objectType, directiveType, visibility,
|
||||||
templatePrefix, declaration)
|
templatePrefix, declaration, semicolon)
|
||||||
|
|
||||||
def parse_namespace_object(self) -> ASTNamespace:
|
def parse_namespace_object(self) -> ASTNamespace:
|
||||||
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
|
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
|
||||||
|
@ -338,10 +338,14 @@ class BaseParser:
|
|||||||
self.pos = self.end
|
self.pos = self.end
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def assert_end(self) -> None:
|
def assert_end(self, *, allowSemicolon: bool = False) -> None:
|
||||||
self.skip_ws()
|
self.skip_ws()
|
||||||
if not self.eof:
|
if allowSemicolon:
|
||||||
self.fail('Expected end of definition.')
|
if not self.eof and self.definition[self.pos:] != ';':
|
||||||
|
self.fail('Expected end of definition or ;.')
|
||||||
|
else:
|
||||||
|
if not self.eof:
|
||||||
|
self.fail('Expected end of definition.')
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
10
tests/roots/test-domain-c/semicolon.rst
Normal file
10
tests/roots/test-domain-c/semicolon.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.. c:member:: int member;
|
||||||
|
.. c:var:: int var;
|
||||||
|
.. c:function:: void f();
|
||||||
|
.. .. c:macro:: NO_SEMICOLON;
|
||||||
|
.. c:struct:: Struct;
|
||||||
|
.. c:union:: Union;
|
||||||
|
.. c:enum:: Enum;
|
||||||
|
.. c:enumerator:: Enumerator;
|
||||||
|
.. c:type:: Type;
|
||||||
|
.. c:type:: int TypeDef;
|
14
tests/roots/test-domain-cpp/semicolon.rst
Normal file
14
tests/roots/test-domain-cpp/semicolon.rst
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.. cpp:class:: Class;
|
||||||
|
.. cpp:struct:: Struct;
|
||||||
|
.. cpp:union:: Union;
|
||||||
|
.. cpp:function:: void f();
|
||||||
|
.. cpp:member:: int member;
|
||||||
|
.. cpp:var:: int var;
|
||||||
|
.. cpp:type:: Type;
|
||||||
|
.. cpp:type:: int TypeDef;
|
||||||
|
.. cpp:type:: Alias = int;
|
||||||
|
.. cpp:concept:: template<typename T> Concept;
|
||||||
|
.. cpp:enum:: Enum;
|
||||||
|
.. cpp:enum-struct:: EnumStruct;
|
||||||
|
.. cpp:enum-class:: EnumClass;
|
||||||
|
.. cpp:enumerator:: Enumerator;
|
@ -27,10 +27,8 @@ def parse(name, string):
|
|||||||
return ast
|
return ast
|
||||||
|
|
||||||
|
|
||||||
def check(name, input, idDict, output=None):
|
def _check(name, input, idDict, output):
|
||||||
# first a simple check of the AST
|
# first a simple check of the AST
|
||||||
if output is None:
|
|
||||||
output = input
|
|
||||||
ast = parse(name, input)
|
ast = parse(name, input)
|
||||||
res = str(ast)
|
res = str(ast)
|
||||||
if res != output:
|
if res != output:
|
||||||
@ -77,6 +75,16 @@ def check(name, input, idDict, output=None):
|
|||||||
raise DefinitionError("")
|
raise DefinitionError("")
|
||||||
|
|
||||||
|
|
||||||
|
def check(name, input, idDict, output=None):
|
||||||
|
if output is None:
|
||||||
|
output = input
|
||||||
|
# First, check without semicolon
|
||||||
|
_check(name, input, idDict, output)
|
||||||
|
if name != 'macro':
|
||||||
|
# Second, check with semicolon
|
||||||
|
_check(name, input + ' ;', idDict, output + ';')
|
||||||
|
|
||||||
|
|
||||||
def test_expressions():
|
def test_expressions():
|
||||||
def exprCheck(expr, output=None):
|
def exprCheck(expr, output=None):
|
||||||
class Config:
|
class Config:
|
||||||
@ -469,8 +477,9 @@ def test_build_domain_c(app, status, warning):
|
|||||||
ws = filter_warnings(warning, "index")
|
ws = filter_warnings(warning, "index")
|
||||||
assert len(ws) == 0
|
assert len(ws) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
|
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
|
||||||
def test_build_domain_c(app, status, warning):
|
def test_build_domain_c_namespace(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
ws = filter_warnings(warning, "namespace")
|
ws = filter_warnings(warning, "namespace")
|
||||||
assert len(ws) == 0
|
assert len(ws) == 0
|
||||||
@ -478,6 +487,7 @@ def test_build_domain_c(app, status, warning):
|
|||||||
for id_ in ('NS.NSVar', 'NULLVar', 'ZeroVar', 'NS2.NS3.NS2NS3Var', 'PopVar'):
|
for id_ in ('NS.NSVar', 'NULLVar', 'ZeroVar', 'NS2.NS3.NS2NS3Var', 'PopVar'):
|
||||||
assert 'id="c.{}"'.format(id_) in t
|
assert 'id="c.{}"'.format(id_) in t
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
|
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
|
||||||
def test_build_domain_c_anon_dup_decl(app, status, warning):
|
def test_build_domain_c_anon_dup_decl(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
@ -487,6 +497,13 @@ def test_build_domain_c_anon_dup_decl(app, status, warning):
|
|||||||
assert "WARNING: c:identifier reference target not found: @b" in ws[1]
|
assert "WARNING: c:identifier reference target not found: @b" in ws[1]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
|
||||||
|
def test_build_domain_c_semicolon(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
ws = filter_warnings(warning, "semicolon")
|
||||||
|
assert len(ws) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_cfunction(app):
|
def test_cfunction(app):
|
||||||
text = (".. c:function:: PyObject* "
|
text = (".. c:function:: PyObject* "
|
||||||
"PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
|
"PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
|
||||||
|
@ -33,10 +33,8 @@ def parse(name, string):
|
|||||||
return ast
|
return ast
|
||||||
|
|
||||||
|
|
||||||
def check(name, input, idDict, output=None):
|
def _check(name, input, idDict, output):
|
||||||
# first a simple check of the AST
|
# first a simple check of the AST
|
||||||
if output is None:
|
|
||||||
output = input
|
|
||||||
ast = parse(name, input)
|
ast = parse(name, input)
|
||||||
res = str(ast)
|
res = str(ast)
|
||||||
if res != output:
|
if res != output:
|
||||||
@ -83,6 +81,15 @@ def check(name, input, idDict, output=None):
|
|||||||
raise DefinitionError("")
|
raise DefinitionError("")
|
||||||
|
|
||||||
|
|
||||||
|
def check(name, input, idDict, output=None):
|
||||||
|
if output is None:
|
||||||
|
output = input
|
||||||
|
# First, check without semicolon
|
||||||
|
_check(name, input, idDict, output)
|
||||||
|
# Second, check with semicolon
|
||||||
|
_check(name, input + ' ;', idDict, output + ';')
|
||||||
|
|
||||||
|
|
||||||
def test_fundamental_types():
|
def test_fundamental_types():
|
||||||
# see https://en.cppreference.com/w/cpp/language/types
|
# see https://en.cppreference.com/w/cpp/language/types
|
||||||
for t, id_v2 in cppDomain._id_fundamental_v2.items():
|
for t, id_v2 in cppDomain._id_fundamental_v2.items():
|
||||||
@ -903,6 +910,13 @@ def test_build_domain_cpp_backslash_ok(app, status, warning):
|
|||||||
assert len(ws) == 0
|
assert len(ws) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True})
|
||||||
|
def test_build_domain_cpp_semicolon(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
ws = filter_warnings(warning, "semicolon")
|
||||||
|
assert len(ws) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='domain-cpp',
|
@pytest.mark.sphinx(testroot='domain-cpp',
|
||||||
confoverrides={'nitpicky': True, 'strip_signature_backslash': True})
|
confoverrides={'nitpicky': True, 'strip_signature_backslash': True})
|
||||||
def test_build_domain_cpp_backslash_ok(app, status, warning):
|
def test_build_domain_cpp_backslash_ok(app, status, warning):
|
||||||
|
Loading…
Reference in New Issue
Block a user