Skip to content

.pr_agent_auto_best_practices

qodo-merge-bot edited this page Mar 25, 2026 · 5 revisions

Pattern 1: In unit tests, assert exact expected values (or the full structured output) instead of weak predicate checks like contains(...), substring matches, or isPresent() followed by get(); prefer direct equality/Optional assertions that would fail on near-miss regressions.

Example code before:

Optional<String> url = service.findPdfUrl();
assertTrue(url.isPresent());
assertTrue(url.get().contains("pdf"));

Example code after:

Optional<String> url = service.findPdfUrl();
assertEquals(Optional.of("https://example.org/scidir-pdf/123"), url);
Relevant past accepted suggestions:
Suggestion 1: [correctness] Weak assertion on PDF URL
Weak assertion on PDF URL The new unit test asserts only that the result URL `contains("pdfft")`, which can pass for incorrect URLs and weakens regression protection. JabRef JUnit conventions prefer direct content assertions over indirect `assertTrue` predicate checks.

Issue description

The test findsPdfDirectlyWhenSciDirPdfPresent uses weak predicate assertions (isPresent() and contains(&amp;amp;quot;pdfft&amp;amp;quot;)) instead of verifying the exact URL returned.

Issue Context

This behavior change is intended to return the scidir-pdf link directly. The test should assert the full expected URL (or at minimum equality to the mocked @href value) to reliably prevent regressions.

Fix Focus Areas

  • jablib/src/test/java/org/jabref/logic/importer/fetcher/ScienceDirectTest.java[91-115]

Suggestion 2: [correctness] Optional asserted via get()
Optional asserted via get() The new test asserts `Optional` presence via `isPresent()` and then calls `get()`, which is a discouraged Optional usage and makes assertions less direct. Use an equality assertion against the expected `Optional` value instead.

Issue description

The test uses assertTrue(result.isPresent()) followed by result.get(), which is less direct and relies on unsafe access patterns.

Issue Context

JUnit assertions should prefer direct value/content comparisons, and Optional usage should avoid isPresent()/get() when an equality assertion is clearer.

Fix Focus Areas

  • jablib/src/test/java/org/jabref/logic/importer/fetcher/citation/AllCitationFetcherTest.java[61-71]

Suggestion 3: [correctness] Test uses `orElse("")`
Test uses `orElse("")` The new tests assert citation keys via `Optional.orElse("")`, which converts absence into a sentinel value and is discouraged by the Optional usage guideline. This can hide intent and makes assertions less precise than asserting on the Optional directly.

Issue description

The new tests use Optional.orElse(&amp;quot;&amp;quot;) when asserting citation keys. This introduces a sentinel default and is discouraged by the project Optional guideline.

Issue Context

BibEntry.getCitationKey() returns an Optional, so the tests can assert Optional.of(&amp;quot;...&amp;quot;) for present values and Optional.empty() for absent values.

Fix Focus Areas

  • jablib/src/test/java/org/jabref/logic/importer/fileformat/CitaviXmlImporterTest.java[59-61]
  • jablib/src/test/java/org/jabref/logic/importer/fileformat/CitaviXmlImporterTest.java[101-103]

Suggestion 4: [reliability] Weak month assertion
Weak month assertion The added test checks only for substring "jan", which is also present in the original value "January". This can let failures in `normalize_month` slip through without being detected.

Issue description

The month normalization test can pass even if the month was not normalized, because it only checks for the substring jan.

Issue Context

normalize_month produces JabRef month format like #jan#, while the original input January also contains jan.

Fix Focus Areas

  • jabkit/src/test/java/org/jabref/toolkit/commands/ConvertTest.java[144-147]

Suggested change

Replace the substring assertion with something that distinguishes normalized vs unnormalized, e.g.:

  • assertTrue(outputContent.contains(&amp;quot;#jan#&amp;quot;)); (or assert on the exact month field line if stable).

Suggestion 5: [correctness] Missing unicode dash testcases
Missing unicode dash testcases Behavior in `FirstPage`/`LastPage` was changed to handle en-dash/em-dash page ranges, but the existing unit tests were not updated to cover these new inputs. This risks regressions and leaves the reported bug scenario unverified by automated tests.

Issue description

FirstPage and LastPage were updated to split page ranges on Unicode en-dash/em-dash, but the existing unit tests only cover ASCII hyphen variants. Add/extend tests so the new behavior is verified and regressions are prevented.

Issue Context

The production code now uses a split regex including \u2013 and \u2014, which changes parsing behavior in org.jabref.logic.

Fix Focus Areas

  • jablib/src/test/java/org/jabref/logic/layout/format/FirstPageTest.java[23-30]
  • jablib/src/test/java/org/jabref/logic/layout/format/LastPageTest.java[23-31]

Pattern 2: Avoid hard-coded upstream identifiers and magic strings in workflows/tests; derive repository owner/name/URLs from runtime context (e.g., GitHub Actions variables) and reuse shared constants to prevent drift and fork-incompatibilities.

Example code before:

String image = "ghcr.io/UpstreamOrg/" + component;
when(prefs.getApiKey("Scopus")).thenReturn("key");

Example code after:

String image = "ghcr.io/" + repoOwner + "/" + component;
when(prefs.getApiKey(Scopus.FETCHER_NAME)).thenReturn("key");
Relevant past accepted suggestions:
Suggestion 1: [correctness] Fork images use upstream names
Fork images use upstream names After removing the job-level repository guard, the workflow runs in forks but still generates tags for `ghcr.io/JabRef/...` and labels pointing to `https://github.com/JabRef/jabref`, so fork builds produce incorrect image metadata and cannot publish correctly-namespaced images on fork `push`/`tag` events.

Issue description

The Docker Images workflow now runs in forks (job-level guard removed), but still hard-codes the upstream GHCR namespace (ghcr.io/JabRef/...) and upstream org.opencontainers.image.source label. This causes fork builds to generate incorrect tags/labels and prevents forks from producing correctly-namespaced images on fork push/tag events.

Issue Context

The workflow already gates login/push to upstream-only, but the metadata generation is still upstream-specific.

Fix Focus Areas

  • .github/workflows/dockerimages.yml[31-45]
  • Change images: to use the current repo owner dynamically (e.g., ghcr.io/${{ github.repository_owner }}/${{ matrix.component }}).
  • Change org.opencontainers.image.source to use the current repo (e.g., https://github.com/${{ github.repository }}).
  • .github/workflows/dockerimages.yml[46-88]
  • Revisit login/push gating if the intent is to allow forks to publish on their own push/tag events (otherwise keep upstream-only, but ensure tags/labels are still fork-correct for non-push builds).

Suggestion 2: [maintainability] Magic string `"Scopus"` in test
Magic string `"Scopus"` in test The modified test stubs `getApiKey` using the literal `"Scopus"` instead of reusing the existing fetcher constant, increasing risk of typos and drift if the name changes. This reduces maintainability and consistency across the codebase.

Issue description

ScienceDirectTest hardcodes the fetcher key name as &amp;amp;quot;Scopus&amp;amp;quot;, duplicating the value instead of using the existing shared constant.

Issue Context

A constant already exists (Scopus.FETCHER_NAME). Using it reduces the risk of typos and keeps tests aligned if the fetcher name ever changes.

Fix Focus Areas

  • jablib/src/test/java/org/jabref/logic/importer/fetcher/ScienceDirectTest.java[42-46]
  • jablib/src/main/java/org/jabref/logic/importer/fetcher/Scopus.java[32-33]

Suggestion 3: [correctness] Wrong default database mode
Wrong default database mode In JabRefFrameViewModel.addParserResult, when no library is open it creates a new BibDatabaseContext without setting the mode from preferences, so it defaults to BibLaTeX even if the user’s default is BibTeX. This causes ImportCleanup (and the subsequent import dialog flow) to operate under an unintended mode, impacting imported field/entry handling.

Issue description

When importing into the “current library” with no library tab open, addParserResult creates a new BibDatabaseContext() but does not set its mode from user preferences. BibDatabaseContext then defaults to BIBLATEX, which can make ImportCleanup and the import flow behave incorrectly for users whose default mode is BibTeX.

Issue Context

Other new-library creation paths (e.g., NewDatabaseAction) explicitly set the mode via preferences.getLibraryPreferences().getDefaultBibDatabaseMode(). The import code immediately uses libraryTab.getBibDatabaseContext().getMode() to drive cleanup.

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java[379-383]

Suggested change

Set the mode right after creating the context:


Pattern 3: Never mutate lists/collections that may be immutable (e.g., List.of(), preference getters, or observable wrappers); defensively copy into a mutable collection before calling add, addAll, or wrapping as an observable list.

Example code before:

List<String> cycle = prefs.getStringList("previewCycle"); // may be List.of()
if (cycle.isEmpty()) {
  cycle.addAll(List.of("Preview", "BibTeX"));
}

Example code after:

List<String> cycle = new ArrayList<>(prefs.getStringList("previewCycle"));
if (cycle.isEmpty()) {
  cycle.addAll(List.of("Preview", "BibTeX"));
}
Relevant past accepted suggestions:
Suggestion 1: [reliability] Preview cycle addAll crash
Preview cycle addAll crash JabRefGuiPreferences#getPreviewLayouts mutates the List returned by getStringList(PREVIEW_CYCLE); when the stored value is blank, getStringList returns an immutable List.of(), so cycle.addAll(...) throws UnsupportedOperationException and preview preferences fail to load.

Issue description

JabRefGuiPreferences#getPreviewLayouts calls cycle.addAll(...) when the cycle is empty. When the stored preference is blank, getStringList(...) returns an immutable List.of(); mutating it throws UnsupportedOperationException and prevents preview preferences from loading.

Issue Context

Reset currently writes an empty cycle to the backing store, making it likely that PREVIEW_CYCLE becomes blank and triggers this path.

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[822-869]
  • jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[856-862]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[284-306]

Suggestion 2: [reliability] Immutable BST paths list
Immutable BST paths list PreviewPreferences wraps bstPreviewLayoutPaths with FXCollections.observableList; when constructed from PreviewPreferences.getDefault() (List.of()), later mutations like setBstPreviewLayoutPaths can throw UnsupportedOperationException on fresh profiles where PREVIEW_BST_LAYOUT_PATHS is absent.

Issue description

bstPreviewLayoutPaths is currently an observable view over the passed-in list. When that passed-in list is immutable (e.g., List.of() from defaults), later mutations (setAll, setBstPreviewLayoutPaths) can throw UnsupportedOperationException.

Issue Context

Fresh profiles without PREVIEW_BST_LAYOUT_PATHS use defaults.getBstPreviewLayoutPaths(), which originates from PreviewPreferences.getDefault() and currently uses List.of().

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[30-56]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[829-836]
  • jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[146-152]

Suggestion 3: [reliability] Preview cycle addAll crash
Preview cycle addAll crash JabRefGuiPreferences#getPreviewLayouts mutates the List returned by getStringList(PREVIEW_CYCLE); when the stored value is blank, getStringList returns an immutable List.of(), so cycle.addAll(...) throws UnsupportedOperationException and preview preferences fail to load.

Issue description

JabRefGuiPreferences#getPreviewLayouts calls cycle.addAll(...) when the cycle is empty. When the stored preference is blank, getStringList(...) returns an immutable List.of(); mutating it throws UnsupportedOperationException and prevents preview preferences from loading.

Issue Context

Reset currently writes an empty cycle to the backing store, making it likely that PREVIEW_CYCLE becomes blank and triggers this path.

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[822-869]
  • jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[856-862]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[284-306]

Suggestion 4: [reliability] Immutable BST paths list
Immutable BST paths list PreviewPreferences wraps bstPreviewLayoutPaths with FXCollections.observableList; when constructed from PreviewPreferences.getDefault() (List.of()), later mutations like setBstPreviewLayoutPaths can throw UnsupportedOperationException on fresh profiles where PREVIEW_BST_LAYOUT_PATHS is absent.

Issue description

bstPreviewLayoutPaths is currently an observable view over the passed-in list. When that passed-in list is immutable (e.g., List.of() from defaults), later mutations (setAll, setBstPreviewLayoutPaths) can throw UnsupportedOperationException.

Issue Context

Fresh profiles without PREVIEW_BST_LAYOUT_PATHS use defaults.getBstPreviewLayoutPaths(), which originates from PreviewPreferences.getDefault() and currently uses List.of().

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[30-56]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[829-836]
  • jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[146-152]

Suggestion 5: [reliability] Preview cycle addAll crash
Preview cycle addAll crash JabRefGuiPreferences#getPreviewLayouts mutates the List returned by getStringList(PREVIEW_CYCLE); when the stored value is blank, getStringList returns an immutable List.of(), so cycle.addAll(...) throws UnsupportedOperationException and preview preferences fail to load.

Issue description

JabRefGuiPreferences#getPreviewLayouts calls cycle.addAll(...) when the cycle is empty. When the stored preference is blank, getStringList(...) returns an immutable List.of(); mutating it throws UnsupportedOperationException and prevents preview preferences from loading.

Issue Context

Reset currently writes an empty cycle to the backing store, making it likely that PREVIEW_CYCLE becomes blank and triggers this path.

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[822-869]
  • jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[856-862]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[284-306]

Suggestion 6: [reliability] Immutable BST paths list
Immutable BST paths list PreviewPreferences wraps bstPreviewLayoutPaths with FXCollections.observableList; when constructed from PreviewPreferences.getDefault() (List.of()), later mutations like setBstPreviewLayoutPaths can throw UnsupportedOperationException on fresh profiles where PREVIEW_BST_LAYOUT_PATHS is absent.

Issue description

bstPreviewLayoutPaths is currently an observable view over the passed-in list. When that passed-in list is immutable (e.g., List.of() from defaults), later mutations (setAll, setBstPreviewLayoutPaths) can throw UnsupportedOperationException.

Issue Context

Fresh profiles without PREVIEW_BST_LAYOUT_PATHS use defaults.getBstPreviewLayoutPaths(), which originates from PreviewPreferences.getDefault() and currently uses List.of().

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[30-56]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[829-836]
  • jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java[146-152]

Suggestion 7: [reliability] Preview cycle addAll crash
Preview cycle addAll crash JabRefGuiPreferences#getPreviewLayouts mutates the List returned by getStringList(PREVIEW_CYCLE); when the stored value is blank, getStringList returns an immutable List.of(), so cycle.addAll(...) throws UnsupportedOperationException and preview preferences fail to load.

Issue description

JabRefGuiPreferences#getPreviewLayouts calls cycle.addAll(...) when the cycle is empty. When the stored preference is blank, getStringList(...) returns an immutable List.of(); mutating it throws UnsupportedOperationException and prevents preview preferences from loading.

Issue Context

Reset currently writes an empty cycle to the backing store, making it likely that PREVIEW_CYCLE becomes blank and triggers this path.

Fix Focus Areas

  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[822-869]
  • jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[856-862]
  • jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java[284-306]

Pattern 4: Keep user-facing documentation and release notes professional, precise, and consistently formatted: fix grammar/typos, avoid overly technical wording, ensure headings/sections are correct, and keep issue/PR references and Markdown links valid and consistent.

Example code before:

### Added
-Added fix for NPE warnings [#15127][https://github.com/org/repo/issues/15283]

Example code after:

### Fixed
- Fixed a crash when opening the notification dialog. [#15127](https://github.com/org/repo/issues/15127)
Relevant past accepted suggestions:
Suggestion 1: [maintainability] Slang term `AI slop`
Slang term `AI slop` The Code of Conduct includes the slang term `AI slop`, which is potentially unprofessional for user-facing documentation. This may reduce clarity and tone consistency in official community guidelines.

Issue description

The Code of Conduct uses the slang phrase AI slop, which can be perceived as unprofessional or unclear in official documentation.

Issue Context

User-facing documentation should use professional, precise wording.

Fix Focus Areas

  • CODE_OF_CONDUCT.md[53-56]

Suggestion 2: [maintainability] FAQ Gradle section grammar issues
FAQ Gradle section grammar issues New documentation text contains grammar and capitalization issues (e.g., `Gradle builds files`, `gradle run`, and `development versions of gradle`). This violates the requirement that user-facing text be professional and free of typos/grammar errors.

Issue description

The newly added Gradle FAQ text has grammar/capitalization issues (e.g., Gradle builds files, gradle run, and development versions of gradle), which makes the documentation look unpolished.

Issue Context

This is user-facing documentation and must be professional and free of typos/grammar errors.

Fix Focus Areas

  • docs/code-howtos/faq.md[20-35]

Suggestion 3: [maintainability] Empty compilation section
Empty compilation section In docs/code-howtos/faq.md, the new "Compilation" heading has no content because the next heading is also level-2, so the intended subsection is not nested and the section hierarchy/TOC becomes misleading. This appears to be an oversight introduced when adding the new parent "Compilation" section around the existing entry.

Issue description

## Compilation is immediately followed by another ## heading, which makes the Compilation section empty and breaks the document hierarchy.

Issue Context

This section appears to be a new parent grouping for an existing FAQ entry; the child heading should be nested.

Fix Focus Areas

  • docs/code-howtos/faq.md[193-199]

Suggested change

Change ## \java: package org.jabref.logic.journals does not exist`to### ...(and optionally add a short intro line under## Compilation`).


Suggestion 4: [correctness] Changelog misses DOI fix
Changelog misses DOI fix The changelog addition documents an entry-editor closing fix, but it does not document the DOI case-insensitive duplicate-detection fix introduced in this PR. This can lead to incomplete or misleading release notes for users.

Issue description

The changelog update in this PR documents an entry-editor closing fix, but does not document the DOI case-insensitivity duplicate-detection fix that this PR introduces.

Issue Context

Release notes should reflect user-visible bug fixes; this PR changes duplicate detection behavior for DOI matching.

Fix Focus Areas

  • CHANGELOG.md[32-37]
  • jablib/src/main/java/org/jabref/logic/database/DuplicateCheck.java[75-79]

Suggestion 5: [correctness] Changelog entry lacks issue link
Changelog entry lacks issue link The new `CHANGELOG.md` entry omits the issue/PR reference and uses awkward phrasing (`quick setting`, `cover images download`) compared to surrounding entries. This reduces professionalism and traceability of user-facing release notes.

Issue description

The added CHANGELOG.md entry is missing an issue/PR reference and the wording is imprecise (quick setting, cover images download).

Issue Context

Other entries in the same section include issue references (e.g., [#15134], [#13821]). This entry should match that convention and be proofread.

Fix Focus Areas

  • CHANGELOG.md[16-19]

Suggestion 6: [correctness] Changelog too technical
Changelog too technical The new `CHANGELOG.md` entry includes highly technical/internal terms (`index exceeds maxCellCount`, `StackOverflowError`) and may be imprecise about the log level/wording (“warnings”), reducing clarity for end users. This risks violating the requirement that release notes be end-user focused and precisely worded.

Issue description

The new CHANGELOG.md entry is likely too technical for end users (mentions internal JavaFX message text and StackOverflowError) and may be imprecise about what was suppressed.

Issue Context

Changelog entries should be understandable by average users and use precise wording.

Fix Focus Areas

  • CHANGELOG.md[19-19]

Suggestion 7: [correctness] Changelog entry lacks issue link
Changelog entry lacks issue link The new CHANGELOG entry has no issue reference and contains slightly unpolished wording (missing article and inconsistent provider name). This reduces traceability and professionalism of user-facing release notes.

Issue description

The added CHANGELOG.md entry is user-facing text but is missing an issue reference and has minor grammar/wording issues.

Issue Context

Other entries in the same section include an issue link (e.g., [#15134](...)). The added entry should follow the same traceability and wording quality.

Fix Focus Areas

  • CHANGELOG.md[16-16]

Suggestion 8: [correctness] Changelog link/wording incorrect
Changelog link/wording incorrect The new CHANGELOG entry is developer-focused/grammatically incorrect and references `#15127` but links to a different issue (`/issues/15283`), which can mislead users. This does not meet the project requirements for user-facing, professional CHANGELOG text with correct references.

Issue description

The new CHANGELOG.md entry is not user-focused (mentions NPE/TaskNotification), contains grammatical issues, and the displayed issue reference #15127 links to /issues/15283.

Issue Context

Compliance requires CHANGELOG entries to be understandable to average users and user-facing text to be precise and correctly referenced.

Fix Focus Areas

  • CHANGELOG.md[31-31]

Suggestion 9: [correctness] Changelog bullet missing space
Changelog bullet missing space The new changelog bullet is formatted as `-Added ...` without a space after `-`, which breaks the project’s changelog formatting conventions. This is user-facing text and should be cleanly formatted.

Issue description

The added changelog entry is missing a space after the list marker (-).

Issue Context

The changelog is user-facing and should follow consistent Markdown list formatting.

Fix Focus Areas

  • CHANGELOG.md[15-15]

Suggestion 10: [correctness] Changelog misstates ESC behavior
Changelog misstates ESC behavior The new CHANGELOG entry claims that pressing ESC closes the global search dialog, but the updated implementation consumes the CLEAR_SEARCH/ESC event when the field is non-empty (clearing it instead). This can mislead users about the actual two-step ESC behavior (first clears, then closes).

Issue description

The CHANGELOG entry says pressing ESC closes the global search dialog, but the implementation clears (and consumes) the event when the input is non-empty, so ESC does not close the dialog on the first press in that case.

Issue Context

This is user-facing release-note text and should precisely match actual behavior.

Fix Focus Areas

  • CHANGELOG.md[14-14]

Suggestion 11: [correctness] Changelog entry miscategorized
Changelog entry miscategorized The changelog entry describes a bug fix but is placed under the "Added" section instead of "Fixed", reducing release note consistency.

Issue description

A bug-fix changelog entry is currently under the “### Added” section.

Issue Context

The line begins with “We fixed an issue…”, which belongs under “### Fixed” per Keep a Changelog conventions used in this file.

Fix Focus Areas

  • CHANGELOG.md[10-35]

Suggestion 12: [correctness] Awkward CHANGELOG wording
Awkward CHANGELOG wording The new CHANGELOG entry contains grammatically incorrect/awkward phrasing (`will show up`) and imprecise wording (`side panels down/up`) in user-facing text. This reduces clarity and professionalism of release notes.

Issue description

The CHANGELOG entry added in this PR is user-facing but has awkward/incorrect grammar (e.g., will show up) and imprecise phrasing (down/up, side panels).

Issue Context

Compliance requires user-facing text (including CHANGELOG entries) to be typo-free, grammatically correct, and precise.

Fix Focus Areas

  • CHANGELOG.md[29-29]

Suggestion 13: [correctness] Broken `#15195` changelog link
Broken `#15195` changelog link The new changelog entry uses invalid Markdown link syntax for the referenced issue. This makes the issue reference non-clickable and reduces the quality of user-facing release notes.

Issue description

The CHANGELOG entry uses incorrect Markdown link syntax: [#15195][https://...].

Issue Context

Changelog entries are user-facing text and should be correctly formatted and professional.

Fix Focus Areas

  • CHANGELOG.md[16-16]

Suggestion 14: [correctness] Changelog uses `Latex` spelling
Changelog uses `Latex` spelling The new changelog entry uses `Latex` instead of the standard `LaTeX` spelling, which is a typographical/style mistake in user-facing documentation. This reduces professionalism and consistency of written content.

Issue description

The changelog entry contains a typographical/style issue: Latex should be LaTeX.

Issue Context

This is user-facing documentation and should match established terminology/capitalization used in the project.

Fix Focus Areas

  • CHANGELOG.md[45-45]

Suggestion 15: [correctness] Changelog link format
Changelog link format The new CHANGELOG entry uses inconsistent issue link formatting and inconsistent wording/capitalization compared to adjacent entries and the UI tab name, reducing documentation consistency.

Issue description

The new CHANGELOG entry is inconsistent with surrounding entries: it uses [15117](...) instead of [#15117](...), and it refers to “External File Type” (singular, title case) while the UI uses “External file types”.

Issue Context

This is a documentation-only consistency fix; no runtime behavior changes required.

Fix Focus Areas

  • CHANGELOG.md[49-49]
  • jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java[35-38]

Pattern 5: Sanitize and correctly escape external/user-controlled text when generating filenames, paths, CLI parsing, or serialized output (YAML/Lucene/etc.); prevent path traversal/collisions and prefer robust escaping/serialization helpers over ad-hoc string concatenation.

Example code before:

Path md = outputDir.resolve(citeKey + ".md");          // citeKey may contain "../"
String yaml = "title: '" + title.replace("'", "\\'") + "'\n";
String query = term.replace("/", "/");                // incomplete escaping

Example code after:

String safeKey = FileNameCleaner.cleanFileName(citeKey).replace("..", "").replace("/", "_");
Path md = outputDir.resolve(safeKey + ".md").normalize();
if (!md.startsWith(outputDir)) {
  throw new IllegalArgumentException("Invalid output path");
}
Map<String, Object> fm = Map.of("title", title, "permalink", "/" + safeKey + "/");
String yaml = new Yaml().dump(fm);
Relevant past accepted suggestions:
Suggestion 1: [security] Path traversal via citekey
Path traversal via citekey Raw citation keys are used to form .md/.bib/.pdf filenames and resolved paths. Since citation keys are not restricted from containing path separators, a crafted/dirty key can escape the export directory or create invalid paths, resulting in arbitrary file write outside the target folder or export failures.

Issue description

AcademicPagesExporter builds output file paths from citationKey without sanitization. Citation keys can contain path separators (e.g., /) or traversal segments (..), so outputDir.resolve(citationKey + &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.pdf&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) can write outside files/.

Issue Context

JabRef’s citation key validation does not disallow / (see DISALLOWED_CHARACTERS), so keys are not guaranteed safe as filenames.

Fix Focus Areas

  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[102-105]
  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[206-224]
  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[232-240]
  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[176-191]

Suggested approach

  1. Create a helper like sanitizeForFileName(String) using existing utilities (e.g., FileNameCleaner.cleanFileName / FileUtil.getValidFileName) and additionally strip path separators and ...
  2. When resolving, normalize and assert containment:
  • Path target = outputDir.resolve(safeName).normalize();
  • if (!target.startsWith(outputDir)) { /* skip or fail */ }
  1. Use the sanitized key for .md/.bib/.pdf filenames; consider a separate URL slug (or encode) for permalink: and paperurl/bibtexurl.

Suggestion 2: [correctness] Invalid YAML quoting
Invalid YAML quoting YAML front matter is hand-built and uses `\'` to escape apostrophes inside single-quoted scalars. This is not valid YAML single-quote escaping (should be doubled), and unquoted permalink values can also break YAML if the key contains special characters (e.g., ':' or '#').

Issue description

Front matter YAML is built via string concatenation and uses incorrect escaping for apostrophes in single-quoted scalars, and permalink is emitted unquoted while embedding the citation key.

Issue Context

The project already uses SnakeYAML in CffExporter, which avoids these escaping pitfalls.

Fix Focus Areas

  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[171-198]
  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[265-271]

Suggested approach

  • Preferred: build a Map&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;String, Object&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; of front-matter fields and dump it via SnakeYAML (then wrap with --- lines).
  • If staying with strings: change single-quote escaping to YAML-compliant value.replace(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;#x27;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;#x27;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;#x27;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;) and quote permalink: (and possibly collection/category) as needed.

Suggestion 3: [reliability] Output overwrites on collisions
Output overwrites on collisions Entries missing citation keys (or with duplicate keys) will overwrite each other’s markdown/PDF/BibTeX outputs because filenames are derived solely from date + key (or "unknown") and PDFs/BIBs are just `key.pdf` / `key.bib`.

Issue description

Filename scheme can collide (&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;unknown&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; fallback; duplicate keys), causing silent overwrites of .md/.bib/.pdf outputs.

Issue Context

JabRef supports duplicate detection for citation keys (see getNumberOfCitationKeyOccurrences), so collisions are possible.

Fix Focus Areas

  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[102-105]
  • jablib/src/main/java/org/jabref/logic/exporter/AcademicPagesExporter.java[221-244]

Suggested approach

  • If citation key is missing, generate a stable unique suffix (e.g., entry ID) for filenames.
  • If a target path already exists, append -a, -b, ... (similar to citation key generator) instead of overwriting.
  • Consider keeping REPLACE_EXISTING only if user explicitly opts in.

Suggestion 4: [correctness] Incomplete key sanitization
Incomplete key sanitization CitaviXmlImporter strips whitespace from but leaves other BibTeX-disallowed characters (e.g., comma, braces) intact. Since BibEntryWriter writes the citation key verbatim into the entry header, such keys can produce malformed .bib output (e.g., "@article{Smith,2020,"), and will also be flagged by JabRef’s citation-key validity checker.

Issue description

CitaviXmlImporter currently strips whitespace from &amp;lt;CitationKey&amp;gt; but does not remove BibTeX-disallowed characters (e.g., ,, {, }), which can lead to malformed BibTeX output because the key is written verbatim in @type{&amp;lt;key&amp;gt;,.

Issue Context

JabRef already centralizes citation key cleaning rules in CitationKeyGenerator and uses them elsewhere (e.g., EndnoteImporter). BibEntryWriter writes the citation key without escaping.

Fix Focus Areas

  • jablib/src/main/java/org/jabref/logic/importer/fileformat/CitaviXmlImporter.java[522-528]
  • jablib/src/main/java/org/jabref/logic/citationkeypattern/CitationKeyGenerator.java[33-38]
  • jablib/src/main/java/org/jabref/logic/citationkeypattern/CitationKeyGenerator.java[94-114]
  • jablib/src/main/java/org/jabref/logic/importer/fileformat/EndnoteImporter.java[238-240]

Suggested change

Replace the replaceAll(&amp;quot;\\s+&amp;quot;, &amp;quot;&amp;quot;) mapping with a call to CitationKeyGenerator.removeUnwantedCharactersWithKeepDiacritics(...) (and optionally DEFAULT_UNWANTED_CHARACTERS) so that whitespace and disallowed characters are removed. After cleaning, re-check blankness before setting the key.

Tests

Add a unit test covering e.g. &amp;lt;CitationKey&amp;gt;Smith, 2020&amp;lt;/CitationKey&amp;gt; to ensure the imported key becomes Smith2020 (or whatever policy you decide) and does not produce an invalid key per ValidCitationKeyChecker.


Suggestion 5: [correctness] Incomplete Lucene escaping
Incomplete Lucene escaping The new escapeForLucene() does not escape some characters that are valid in Search.g4 TERM (e.g., '"' and '/'), so valid user input can be converted into invalid/altered Lucene syntax. This can cause ParseException and return empty results, or accidentally form a regex-delimited query because this code itself uses /.../ to represent regex queries.

Issue description

escapeForLucene is intended to replace QueryParser.escape while preserving * and ? wildcards, but it currently escapes an incomplete set of Lucene-special characters. Because the search grammar allows these characters inside TERM, some valid user searches can be converted to invalid/altered Lucene syntax, causing ParseException and silently returning empty results.

Issue Context

  • TERM allows many characters (only excludes whitespace and =!~()), so users can enter quotes or slashes without quoting.
  • SearchToLuceneVisitor itself uses /.../ as a regex delimiter, so leaving / unescaped in non-regex terms can create ambiguous output.
  • LinkedFilesSearcher swallows parse errors and returns empty results.

Fix Focus Areas

  • jablib/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java[61-120]
  • jablib/src/main/antlr/org/jabref/search/Search.g4[42-45]
  • jablib/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java[15-60]
  • jablib/src/main/java/org/jabref/logic/search/retrieval/LinkedFilesSearcher.java[84-91]

Suggested approach

  • Update escapeForLucene to escape the full Lucene reserved set (including at least &amp;amp;amp;quot; and /), while deliberately NOT escaping * and ?.
  • Consider implementing as: QueryParser.escape(term) then selectively unescape \* -&amp;amp;gt; * and \? -&amp;amp;gt; ?.
  • Add unit tests for unquoted terms containing &amp;amp;amp;quot; and / (and ensure they don’t cause parse failures).

Suggestion 6: [correctness] `convert` formatter parsing broken
`convert` formatter parsing broken The new `--field-formatters` parsing replaces all commas, which breaks specifying multiple formatters within a single field (commas inside `[...]`). This can silently apply the wrong formatter(s) or none at all, producing incorrect export output.

Issue description

--field-formatters currently does fieldFormatters.replace(&amp;quot;,&amp;quot;, &amp;quot;\n&amp;quot;), which corrupts valid formatter specs that contain comma-separated formatter lists inside brackets (e.g., title[escapeAmpersands,latex_cleanup]). This can lead to silently wrong/no cleanup actions.

Issue Context

This CLI argument is external input and should be parsed robustly. The current approach cannot distinguish commas separating field blocks from commas separating formatters within a field.

Fix Focus Areas

  • jabkit/src/main/java/org/jabref/toolkit/commands/Convert.java[72-80]
  • jabkit/src/main/java/org/jabref/toolkit/commands/GenerateBibFromAux.java[86-94]

[Auto-generated best practices - 2026-03-25]

Clone this wiki locally