fix: read the file a detected literal names

A printf conversion glued to a name, a name with spaces cut to its last
word, an extension list, and an identifier in code were reported as new
files.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 16:21:04 +02:00
1 parent 5fba72f1a3
commit 97c960484c
2 files changed
+118 -6

No files matched your search

+80 -6
View File
@@ -2300,23 +2300,95 @@ def declared_hashes(profile: dict) -> set[str]:
return hashes
# Sources where a filename is a string. Outside a literal or a comment,
# `EE.bios` is a config key and `psm_s.pal` a struct field (pcee2).
CODE_SUFFIXES = (
".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx", ".inc", ".inl",
".m", ".mm", ".rs", ".java", ".kt", ".cs", ".swift",
)
_PRINTF_SPEC_RE = re.compile(
r"[-+ #0]*(?:\d+|\*)?(?:\.\d+)?(?:hh|h|ll|l|z|j|t|L)?[diouxXsScpfFeEgGaA]"
)
_TITLE_WORD_RE = re.compile(r"[A-Z0-9][\w.()&'+-]*")
def _string_literal(line: str, index: int) -> tuple[int, int] | None:
"""Bounds of the double-quoted literal holding index, None outside one."""
start = None
position = 0
while position < len(line):
char = line[position]
if char == "\\" and start is not None:
position += 2
continue
if char == '"':
if start is None:
start = position + 1
else:
if start <= index < position:
return start, position
start = None
position += 1
return None
def _in_comment(line: str, index: int) -> bool:
before = line[:index]
return "//" in before or "/*" in before or line.lstrip().startswith(("*", "#"))
def file_literal(line: str, start: int, end: int, code: bool) -> str | None:
"""The filename a match stands for, None when it names no file.
A printf conversion glued to the name is not part of it: sdl2trs writes
`"%slevel2.rom"` with ROM_PATH in front. A name with spaces is read
whole: virtualjaguar opens `"Jaguar CD BIOS.rom"`, and the match alone
was `BIOS.rom`. Such a name is written as a title, so `"Please insert
bios.bin"` and `"Packing patches.zip"` still yield the file alone. In
a code file a match outside any string or comment is an identifier and
names nothing.
"""
name = line[start:end]
if start and line[start - 1] == "%":
spec = _PRINTF_SPEC_RE.match(name)
if spec:
name = name[spec.end():]
literal = _string_literal(line, start)
if literal is None:
if code and not _in_comment(line, start):
return None
elif literal[1] == end and line[start - 1:start] == " ":
words = line[literal[0]:start].split() + [name]
if all(_TITLE_WORD_RE.fullmatch(word) for word in words):
return line[literal[0]:end]
base = os.path.basename(name)
# `".bin.hex.iii.rom"` (sdl2trs) lists extensions, it names no file.
if base.startswith(".") or not _FILE_RE.fullmatch(base):
return None
return base
def detect_new_files(
head_lines: list[str], known: set[str], cap: int = 25
head_lines: list[str], known: set[str], cap: int = 25, path: str = ""
) -> tuple[list[tuple[int, str]], int]:
"""Filename literals at HEAD the profile does not declare."""
found: list[tuple[int, str]] = []
seen: set[str] = set()
elided = 0
code = path.lower().endswith(CODE_SUFFIXES)
for number, line in enumerate(head_lines, start=1):
for match in _FILE_RE.findall(line):
name = os.path.basename(match).casefold()
for match in _FILE_RE.finditer(line):
literal = file_literal(line, match.start(), match.end(), code)
if literal is None:
continue
name = literal.casefold()
if name in known or name in seen:
continue
seen.add(name)
if len(found) >= cap:
elided += 1
continue
found.append((number, os.path.basename(match)))
found.append((number, literal))
return found, elided
@@ -4050,10 +4122,12 @@ def _detect(
or []
)
if args.detect_new_files:
found, elided = detect_new_files(head_lines, names)
found, elided = detect_new_files(head_lines, names, path=path)
at_pin = {
name.casefold()
for _, name in detect_new_files(pin_lines, names, cap=sys.maxsize)[0]
for _, name in detect_new_files(
pin_lines, names, cap=sys.maxsize, path=path
)[0]
}
new_files.extend(
{"path": path, "line": number, "name": name,
+38
View File
@@ -3310,6 +3310,44 @@ class TestDetectNewFiles(unittest.TestCase):
found, _ = detect_new_files(['#include "driver.cpp"'], set())
self.assertEqual(found, [])
def test_a_printf_conversion_is_not_part_of_the_name(self):
# sdl2trs src/trs_options.c:864-866 builds "%slevel2.rom" and the
# report named slevel2.rom.
lines = [
'snprintf(romfile1, sizeof(romfile1), "%slevel2.rom", ROM_PATH);',
'snprintf(name, 64, "%02dcart.bin", slot);',
]
found, _ = detect_new_files(lines, set(), path="src/trs_options.c")
self.assertEqual(found, [(1, "level2.rom"), (2, "cart.bin")])
def test_a_quoted_name_with_spaces_is_read_whole(self):
# virtualjaguar libretro.c:5815-5816 opens "Jaguar CD BIOS.rom",
# reported as BIOS.rom. A sentence still yields the name it holds.
lines = [
'snprintf(path, n, "%s%c%s", dir, slash, "Jaguar CD BIOS.rom");',
'log("Please insert bios.bin first");',
'message(STATUS "Packing patches.zip")',
'#define ROM ".bin.hex.iii.rom"',
]
found, _ = detect_new_files(lines, set(), path="libretro.c")
self.assertEqual(
found,
[(1, "Jaguar CD BIOS.rom"), (2, "bios.bin"), (3, "patches.zip")],
)
def test_an_identifier_in_code_is_not_a_file(self):
# pcee2 reads config keys EE.bios and IOP.bios and a field psm_s.pal.
lines = [
"cfg.EE.bios = load(path);",
"if (psm_s.pal) {",
"// loads boot.rom from the system directory",
'open("ipl.rom");',
]
found, _ = detect_new_files(lines, set(), path="src/core/config.cpp")
self.assertEqual(found, [(3, "boot.rom"), (4, "ipl.rom")])
found, _ = detect_new_files(["Copy EE.bios here"], set(), path="README.md")
self.assertEqual(found, [(1, "EE.bios")])
class TestWatchHashes(unittest.TestCase):
def test_finds_new_sha1(self):