Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions cxxheaderparser/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class ClassScope:
forward_decls: typing.List[ForwardDecl] = field(default_factory=list)
using: typing.List[UsingDecl] = field(default_factory=list)
using_alias: typing.List[UsingAlias] = field(default_factory=list)
#: Line number where this scope was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -136,17 +138,23 @@ class NamespaceScope:
@dataclass
class Pragma:
content: Value
#: Line number where this pragma was found (if available)
lineno: typing.Optional[int] = None


@dataclass
class Include:
#: The filename includes the surrounding ``<>`` or ``"``
filename: str
#: Line number where this include was found (if available)
lineno: typing.Optional[int] = None


@dataclass
class UsingNamespace:
ns: str
#: Line number where this using-namespace was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -213,10 +221,20 @@ def on_parse_start(self, state: SNamespaceBlockState) -> None:
state.user_data = ns

def on_pragma(self, state: SState, content: Value) -> None:
self.data.pragmas.append(Pragma(content))
p = Pragma(content)
try:
p.lineno = state.location[1]
except Exception:
p.lineno = None
self.data.pragmas.append(p)

def on_include(self, state: SState, filename: str) -> None:
self.data.includes.append(Include(filename))
inc = Include(filename)
try:
inc.lineno = state.location[1]
except Exception:
inc.lineno = None
self.data.includes.append(inc)

def on_extern_block_start(self, state: SExternBlockState) -> typing.Optional[bool]:
state.user_data = state.parent.user_data
Expand Down Expand Up @@ -254,31 +272,63 @@ def on_namespace_end(self, state: SNamespaceBlockState) -> None:
pass

def on_concept(self, state: SNonClassBlockState, concept: Concept) -> None:
try:
concept.lineno = state.location[1]
except Exception:
concept.lineno = None
state.user_data.concepts.append(concept)

def on_namespace_alias(
self, state: SNonClassBlockState, alias: NamespaceAlias
) -> None:
try:
alias.lineno = state.location[1]
except Exception:
alias.lineno = None
state.user_data.ns_alias.append(alias)

def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None:
try:
fdecl.lineno = state.location[1]
except Exception:
fdecl.lineno = None
state.user_data.forward_decls.append(fdecl)

def on_template_inst(self, state: SState, inst: TemplateInst) -> None:
assert isinstance(state.user_data, NamespaceScope)
try:
inst.lineno = state.location[1]
except Exception:
inst.lineno = None
state.user_data.template_insts.append(inst)

def on_variable(self, state: SState, v: Variable) -> None:
assert isinstance(state.user_data, NamespaceScope)
try:
v.lineno = state.location[1]
except Exception:
v.lineno = None
state.user_data.variables.append(v)

def on_function(self, state: SNonClassBlockState, fn: Function) -> None:
try:
fn.lineno = state.location[1]
except Exception:
fn.lineno = None
state.user_data.functions.append(fn)

def on_method_impl(self, state: SNonClassBlockState, method: Method) -> None:
try:
method.lineno = state.location[1]
except Exception:
method.lineno = None
state.user_data.method_impls.append(method)

def on_typedef(self, state: SState, typedef: Typedef) -> None:
try:
typedef.lineno = state.location[1]
except Exception:
typedef.lineno = None
state.user_data.typedefs.append(typedef)

def on_using_namespace(
Expand All @@ -288,16 +338,28 @@ def on_using_namespace(
state.user_data.using_ns.append(ns)

def on_using_alias(self, state: SState, using: UsingAlias) -> None:
try:
using.lineno = state.location[1]
except Exception:
using.lineno = None
state.user_data.using_alias.append(using)

def on_using_declaration(self, state: SState, using: UsingDecl) -> None:
try:
using.lineno = state.location[1]
except Exception:
using.lineno = None
state.user_data.using.append(using)

#
# Enums
#

def on_enum(self, state: SState, enum: EnumDecl) -> None:
try:
enum.lineno = state.location[1]
except Exception:
enum.lineno = None
state.user_data.enums.append(enum)

#
Expand All @@ -307,17 +369,33 @@ def on_enum(self, state: SState, enum: EnumDecl) -> None:
def on_class_start(self, state: SClassBlockState) -> typing.Optional[bool]:
parent = state.parent.user_data
block = ClassScope(state.class_decl)
try:
block.lineno = state.location[1]
except Exception:
block.lineno = None
parent.classes.append(block)
state.user_data = block
return None

def on_class_field(self, state: SClassBlockState, f: Field) -> None:
try:
f.lineno = state.location[1]
except Exception:
f.lineno = None
state.user_data.fields.append(f)

def on_class_method(self, state: SClassBlockState, method: Method) -> None:
try:
method.lineno = state.location[1]
except Exception:
method.lineno = None
state.user_data.methods.append(method)

def on_class_friend(self, state: SClassBlockState, friend: FriendDecl) -> None:
try:
friend.lineno = state.location[1]
except Exception:
friend.lineno = None
state.user_data.friends.append(friend)

def on_class_end(self, state: SClassBlockState) -> None:
Expand Down
29 changes: 29 additions & 0 deletions cxxheaderparser/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class NamespaceAlias:
#: refers to, but does not include any parent namespace names. It may
#: include a leading "::", but does not include a following :: string.
names: typing.List[str]
#: Line number where this alias was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -150,6 +152,9 @@ class NameSpecifier:

specialization: typing.Optional["TemplateSpecialization"] = None

#: Line number where this identifier was found (if available)
lineno: typing.Optional[int] = None

def format(self) -> str:
if self.specialization:
return f"{self.name}{self.specialization.format()}"
Expand Down Expand Up @@ -486,6 +491,8 @@ class EnumDecl:

#: If within a class, the access level for this decl
access: typing.Optional[str] = None
#: Line number where this enum decl was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -615,6 +622,8 @@ class TemplateInst:
typename: PQName
extern: bool
doxygen: typing.Optional[str] = None
#: Line number where this explicit template instantiation was found
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -640,6 +649,8 @@ class Concept:
raw_constraint: Value

doxygen: typing.Optional[str] = None
#: Line number where this concept was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -659,6 +670,8 @@ class ForwardDecl:

#: If within a class, the access level for this decl
access: typing.Optional[str] = None
#: Line number where this forward declaration was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -798,6 +811,8 @@ class Function:
#:
#: template <typename T> int main() requires ...
raw_requires: typing.Optional[Value] = None
#: Line number where this function was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -831,6 +846,8 @@ class Method(Function):
virtual: bool = False
final: bool = False
override: bool = False
#: Line number where this method was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -842,6 +859,8 @@ class FriendDecl:
cls: typing.Optional[ForwardDecl] = None

fn: typing.Optional[Function] = None
#: Line number where this friend declaration was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down Expand Up @@ -876,6 +895,8 @@ class Typedef:
access: typing.Optional[str] = None
#: Any attributes attached to this typedef
attributes: typing.List[Attribute] = field(default_factory=list)
#: Line number where this typedef was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -900,6 +921,8 @@ class Variable:
doxygen: typing.Optional[str] = None
#: Any attributes attached to this variable
attributes: typing.List[Attribute] = field(default_factory=list)
#: Line number where this variable was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -925,6 +948,8 @@ class Field:
doxygen: typing.Optional[str] = None
#: Any attributes attached to this field
attributes: typing.List[Attribute] = field(default_factory=list)
#: Line number where this field was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -942,6 +967,8 @@ class UsingDecl:

#: Documentation if present
doxygen: typing.Optional[str] = None
#: Line number where this using declaration was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand All @@ -966,6 +993,8 @@ class UsingAlias:

#: Documentation if present
doxygen: typing.Optional[str] = None
#: Line number where this using alias was found (if available)
lineno: typing.Optional[int] = None


@dataclass
Expand Down
56 changes: 56 additions & 0 deletions tests/test_lineno.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import inspect

from cxxheaderparser.simple import parse_string


def test_lineno_basic() -> None:
content = """
namespace N {
typedef char *ABC;
using Str = const char*;
enum Color { RED, GREEN };
extern int g;
void f(int x);
class C { public: int m; void meth(); };
}
"""

data = parse_string(content, cleandoc=True)
ns = data.namespace.namespaces.get("N", data.namespace)
lines = inspect.cleandoc(content).splitlines()

def lineno_for(substr: str) -> int:
for i, l in enumerate(lines):
if substr in l:
return i + 1
raise AssertionError(f"could not find {substr!r} in sample")

# typedef
assert len(ns.typedefs) >= 1
assert getattr(ns.typedefs[0], "lineno", None) == lineno_for("typedef char *ABC")

# using alias
assert len(ns.using_alias) >= 1
assert getattr(ns.using_alias[0], "lineno", None) == lineno_for("using Str =")

# enum
assert len(ns.enums) >= 1
assert getattr(ns.enums[0], "lineno", None) == lineno_for("enum Color")

# variable
assert len(ns.variables) >= 1
assert getattr(ns.variables[0], "lineno", None) == lineno_for("extern int g")

# function
assert len(ns.functions) >= 1
assert getattr(ns.functions[0], "lineno", None) == lineno_for("void f(int x)")

# class + class members
assert len(ns.classes) >= 1
cls = ns.classes[0]
assert getattr(cls, "lineno", None) == lineno_for("class C")
# fields and methods
assert len(cls.fields) >= 1
assert getattr(cls.fields[0], "lineno", None) == lineno_for("int m")
assert len(cls.methods) >= 1
assert getattr(cls.methods[0], "lineno", None) == lineno_for("void meth")
Loading