Skip to content

Commit

Permalink
get line numbers right in /// content errors
Browse files Browse the repository at this point in the history
  • Loading branch information
bkietz committed Nov 12, 2024
1 parent 288b4f2 commit 12c7aa1
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 49 deletions.
22 changes: 14 additions & 8 deletions cmake_modules/trike/test_trike.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ def get_inline_expectations(path):
if not line.startswith("///"):
continue

text = [line]
text = [line.removeprefix("///")]
for i, line in lines:
if not line.startswith("///"):
break
text.append(line)
text.append(line.removeprefix("///"))
else:
line = ""

Expand All @@ -48,7 +48,7 @@ def get_inline_expectations(path):
floating_comments.append(comment)
continue

*_, directive, argument, namespace = comment.stripped_text
*_, directive, argument, namespace = comment.text
directive_comments.append((directive, argument, namespace, comment))

return floating_comments, directive_comments
Expand Down Expand Up @@ -226,9 +226,9 @@ def test_dropping_non_triple(tmp_path):
path,
next_line=6,
text=[
"/// Interleaved // are elided from the /// text",
" Interleaved // are elided from the /// text",
(
"/// something clang-format would mangle like a long line with a"
" something clang-format would mangle like a long line with a"
" url https://clang.llvm.org/docs/ClangFormatStyleOptions.html"
),
],
Expand Down Expand Up @@ -261,7 +261,13 @@ def test_escaped_line_ending(tmp_path):
Comment(
path,
next_line=9,
text=["\n".join(source.splitlines()[1:6]), *source.splitlines()[6:8]],
text=[
line.removeprefix("///")
for line in [
"\n".join(source.splitlines()[1:6]),
*source.splitlines()[6:8],
]
],
),
),
]
Expand Down Expand Up @@ -327,14 +333,14 @@ def test_comment_from_tokens(tmp_path):
comment = Comment.read_from_tokens(path, tokens)
assert comment is not None
assert comment.next_line == 6
assert comment.text == ["/// Y", "/// Z"]
assert comment.text == [" Y", " Z"]

assert next(tokens).spelling == "int"

comment = Comment.read_from_tokens(path, tokens)
assert comment is not None
assert comment.next_line == 10
assert comment.text == ["/// Foo", "/// Bar"]
assert comment.text == [" Foo", " Bar"]

assert Comment.read_from_tokens(path, tokens) is None

Expand Down
70 changes: 33 additions & 37 deletions cmake_modules/trike/trike/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import docutils.nodes
import docutils.parsers.rst.directives
import difflib
import base64
import multiprocessing

from clang.cindex import (
Expand All @@ -24,7 +23,7 @@
from sphinx.util.docutils import SphinxDirective
from docutils.nodes import Node
from docutils.statemachine import StringList
from typing import Self, Sequence, Iterator, Mapping
from typing import Self, Sequence, Mapping

logger = sphinx.util.logging.getLogger(__name__)

Expand Down Expand Up @@ -76,36 +75,31 @@ def read_from_tokens(file: Path, tokens: Tokens) -> Self | None:
else:
return None

comment = Comment(file, t.extent.end.line + 1, [t.spelling])
next_line = t.extent.end.line
tokens.unget(t)
text = []

for t in tokens:
if t.spelling == "#":
t = next(tokens)
if t.kind != TokenKind.COMMENT or t.extent.start.line > comment.next_line:
if t.kind != TokenKind.COMMENT or t.extent.start.line > next_line:
tokens.unget(t)
break
comment.next_line = t.extent.end.line + 1
next_line = t.extent.end.line + 1

if t.spelling.startswith(Comment.PREFIX):
comment.text.append(t.spelling)
text.append(t.spelling[len(Comment.PREFIX) :])
elif t.spelling.startswith("/*"):
raise RuntimeError(
"/// interspersed with C-style comments are not supported"
)

return comment

@property
def first_line(self) -> int:
return self.next_line - len(self.text)

@property
def stripped_text(self) -> list[str]:
return [line[len(Comment.PREFIX) :] for line in self.text]
return Comment(file, next_line, text)

@property
def explicit_directive(self) -> tuple[str, str] | None:
if self.text[0].startswith("///.. "):
directive, argument = self.text[0].removeprefix("///.. ").split("::", 1)
if self.text[0].startswith(".. "):
directive, argument = self.text[0].removeprefix(".. ").split("::", 1)
directive, argument = directive.strip(), argument.strip()
argument = " ".join(a.strip() for a in argument.split("\\\n"))
if directive == "cpp:class":
Expand Down Expand Up @@ -537,20 +531,20 @@ class PutDirective(SphinxDirective):

@contextmanager
def cpp(self):
"Temporarily set the default language/domain to C++"
tmp = {}
tmp["highlight_language"] = self.env.temp_data.get("highlight_language", None)
"Temporarily set the default domain/language to C++"
stashed = {
key: val
for key, val in self.env.temp_data.items()
if key in {"default_domain", "highlight_language"}
}
self.env.temp_data["default_domain"] = self.env.domains["cpp"]
self.env.temp_data["highlight_language"] = "cpp"
tmp["default_domain"] = self.env.temp_data.get("default_domain", None)
self.env.temp_data["default_domain"] = self.env.domains.get("cpp")
try:
yield
finally:
for key, value in tmp.items():
if value is None:
del self.env.temp_data[key]
else:
self.env.temp_data[key] = value
for key in stashed.keys():
del self.env.temp_data[key]
self.env.temp_data.update(stashed)

def get_directive(self) -> tuple[str, str]:
"""
Expand Down Expand Up @@ -578,11 +572,17 @@ def parse_comment_to_nodes(
with_members: bool = True,
before_members: list[Node] = [],
) -> list[Node]:
if comment.explicit_directive:
text = comment.stripped_text
else:
text = [f".. {directive}:: {argument}", "", *comment.stripped_text]
nodes = self.parse_text_to_nodes(StringList(text)) # type: ignore
injected_directive = (
[] if comment.explicit_directive else [f".. {directive}:: {argument}", ""]
)
offset = comment.next_line - 1 - len(comment.text) - len(injected_directive)
text = injected_directive + comment.text
text = StringList(
text, items=[(str(comment.file), i + offset) for i in range(len(text))]
)

with sphinx.util.docutils.switch_source_input(self.state, text):
nodes = self.parse_text_to_nodes(text, offset=0) # type: ignore

get_uri = self.config.trike_get_uri or (lambda file, line: None)
if uri := get_uri(comment.file, comment.next_line):
Expand Down Expand Up @@ -626,10 +626,6 @@ def run(self) -> list[Node]:
)
if comment is not None:
self.env.note_dependency(comment.file)
# FIXME "test_.hxx:73:<trike>" should appear in the sphinx error log if
# a /// fails to parse; I'm not sure what's wrong with the below
# text = StringList(text, f"{comment.file}:{comment.first_line}:<trike>")
# , sphinx.util.docutils.switch_source_input(self.state, text):
with self.cpp():
return self.parse_comment_to_nodes(
comment,
Expand Down Expand Up @@ -668,7 +664,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
description="Arguments which will be passed to clang (or per-file mapping)",
)

# FIXME silence the warning when a function is provided; we don't need to pickle that
# FIXME an unpickleable value causes the environment to *never* be reloadable
app.add_config_value(
"trike_get_uri",
None,
Expand Down
4 changes: 0 additions & 4 deletions sphinx_configuration/conf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import sphinx.util.docutils
import sphinx.util.logging
import docutils.nodes
import docutils.statemachine
import pygments.lexers.c_cpp
import sphinx.highlighting
import pathlib
import json

from pathlib import Path

Expand Down

0 comments on commit 12c7aa1

Please sign in to comment.