Skip to content
Merged
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
17 changes: 16 additions & 1 deletion .github/actions/prose-gate/prose_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,21 @@ def python_comments(raw: str) -> list[Comment] | None:
return out


PARAMETER_HASH = ("$", "${")


def line_marker(line: str, masked: str, marker: str, pos: int) -> int:
"""Where the first line comment marker at or after `pos` sits, or -1 when there is none.

A `#` right after `$` or `${` names a parameter, as in `$#` or `${#items[@]}`, not a comment.
The prefix is read off the raw line, since an escaped `$` is blanked in the masked one.
"""
where = masked.find(marker, pos)
while marker == "#" and where >= 0 and line[:where].endswith(PARAMETER_HASH):
where = masked.find(marker, where + 1)
return where


def extracted_comments(path: Path, lines: list[str], spec: Syntax | None = None) -> list[Comment]:
"""Every comment in the file as (line, text, starts-the-line), for any syntax the fleet uses.

Expand Down Expand Up @@ -1837,7 +1852,7 @@ def extracted_comments(path: Path, lines: list[str], spec: Syntax | None = None)
found: str | tuple[str, str] | None = None
at = len(line)
for marker in spec["line"]:
where = masked.find(marker, pos)
where = line_marker(line, masked, marker, pos)
if 0 <= where < at:
at, found = where, marker
for opener, closer in spec["block"]:
Expand Down
29 changes: 29 additions & 0 deletions scripts/tests/test_prose_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,35 @@ def test_only_a_c_style_continuation_loses_its_leading_asterisk(self) -> None:
[(c.line, c.body, c.leading) for c in found],
)

def test_a_parameter_expansion_hash_opens_no_comment(self) -> None:
"""A `#` after `$` or `${` is bash naming a count, so neither spelling reads as a comment.

A real comment on the same kind of line is still found, leading or trailing.
"""
for text in (
" count=${#items[@]}",
' echo "::error::${#items[@]} open issues. Close them by hand."',
" echo $#",
):
with self.subTest(line=text):
self.assertEqual([], prose_lint.extracted_comments(Path("x.yml"), [text]))
self.assertEqual(
[],
self.flag(
"x.yml",
'run: |\n echo "::error::${#items[@]} open issues. Close them by hand."\n',
),
)
for text, leading in (
(" count=${#items[@]} # Count the open items.", False),
(" # Count the open items.", True),
):
with self.subTest(line=text):
found = prose_lint.extracted_comments(Path("x.yml"), [text])
self.assertEqual(
[("Count the open items.", leading)], [(c.body, c.leading) for c in found]
)

def test_a_format_with_no_comment_syntax_is_skipped(self) -> None:
for name in ("a.lock", "a.csv", "a.txt"):
with self.subTest(file=name):
Expand Down
Loading