paperlint 10.1.0 → 10.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "10.1.0",
3
+ "version": "10.1.1",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -184,10 +184,19 @@ if [ -f "$BLG" ]; then
184
184
  grep -E "^(You're|I'm skipping|Repeated entry|I couldn't open)" "$BLG" | sed 's/^/ /' | head -12
185
185
  fi
186
186
  # A cited key that never made it into the .bbl is the same failure seen from the other side.
187
+ # An IEEEtran BST control entry (`@IEEEtranBSTCTL`, activated by `\bstctlcite{key}`) is cited
188
+ # in the .aux like any key, but IEEEtran reads it as configuration and never prints it as a
189
+ # \bibitem — so it is not a missing reference. A key is exempt only when IEEEtran itself reports
190
+ # reading it as a control entry: the .blg line `** IEEEtran BST control entry "key" detected.`,
191
+ # which every IEEEtran*.bst prints. BibTeX wrote that line after parsing the build's own databases,
192
+ # so a record in a .bib the build does not read, inside `@comment{…}`, or misspelled exempts
193
+ # nothing, and every layout BibTeX accepts is recognised without parsing .bib files here.
187
194
  if [ -f "$BASE.aux" ] && [ -f "$BASE.bbl" ]; then
188
195
  missing=0
196
+ ctl=$(sed -n 's/^\*\* IEEEtran BST control entry "\(.*\)" detected\.$/\1/p' "$BASE.blg" 2>/dev/null | sort -u)
189
197
  while read -r key; do
190
198
  [ -z "$key" ] && continue
199
+ printf '%s\n' "$ctl" | grep -qxF -- "$key" && continue
191
200
  grep -qF "{$key}" "$BASE.bbl" || { echo " cited but NOT in the bibliography: $key"; missing=$((missing+1)); }
192
201
  done < <(grep -oE '\\citation\{[^}]*\}' "$BASE.aux" | sed 's/.*{//;s/}//' | tr ',' '\n' | sort -u)
193
202
  [ "$missing" -gt 0 ] && { echo " CITED-BUT-ABSENT references: $missing"; blgerr=$((blgerr+missing)); }
@@ -295,3 +295,105 @@ await checkSkill("render-paper");
295
295
  : ` under ${root}/`),
296
296
  );
297
297
  }
298
+
299
+ // ── AN IEEEtran BST CONTROL ENTRY IS NOT A MISSING REFERENCE ─────────────────
300
+ // `\\bstctlcite{key}` cites an `@IEEEtranBSTCTL{key, …}` entry, so the key lands in the .aux like any
301
+ // citation, but IEEEtran reads it as configuration and never prints a \\bibitem for it. The
302
+ // "cited but NOT in the bibliography" check failed a real IEEE paper on exactly that key (an
303
+ // "et al. after six authors" setting). The exemption comes only from IEEEtran's own .blg line
304
+ // `** IEEEtran BST control entry "key" detected.`: BibTeX wrote it after parsing the build's
305
+ // databases, so a record BibTeX never read (a .bib the build does not use, a record inside
306
+ // `@comment{…}`) or a misspelled key exempts nothing, and a genuinely missing key is still reported.
307
+ {
308
+ const HERE = dirname(fileURLToPath(import.meta.url));
309
+ const CHECK = join(HERE, "check-render.sh");
310
+ // `detected` = the keys IEEEtran reported in the .blg as control entries it read.
311
+ const paper = (citations, detected = ["BSTcontrol"]) => {
312
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "bstctl-")));
313
+ writeFileSync(
314
+ join(dir, "paper.tex"),
315
+ "\\documentclass{IEEEtran}\n\\begin{document}\n\\bstctlcite{BSTcontrol}\nText \\cite{real}.\n\\bibliographystyle{IEEEtran}\n\\bibliography{refs}\n\\end{document}\n",
316
+ );
317
+ writeFileSync(
318
+ join(dir, "paper.aux"),
319
+ citations.map((k) => `\\citation{${k}}`).join("\n") +
320
+ "\n\\bibstyle{IEEEtran}\n\\bibdata{refs}\n",
321
+ );
322
+ writeFileSync(
323
+ join(dir, "paper.bbl"),
324
+ "\\begin{thebibliography}{1}\n\\bibitem{real}\nA real reference, 2026.\n\\end{thebibliography}\n",
325
+ );
326
+ writeFileSync(
327
+ join(dir, "paper.blg"),
328
+ "This is BibTeX, Version 0.99e\nDatabase file #1: refs.bib\n-- IEEEtran.bst version 1.14 (2015/08/26) by Michael Shell.\n" +
329
+ detected
330
+ .map((k) => `** IEEEtran BST control entry "${k}" detected.\n`)
331
+ .join("") +
332
+ "\nDone.\n",
333
+ );
334
+ writeFileSync(
335
+ join(dir, "paper.log"),
336
+ "This is pdfTeX\nOutput written on paper.pdf (1 page, 1000 bytes).\n",
337
+ );
338
+ const r = spawnSync("bash", [CHECK, dir, "paper"], {
339
+ encoding: "utf8",
340
+ env: { ...process.env, NO_COMPILE: "1" },
341
+ });
342
+ // The script must run to its verdict: a run that stopped early prints no complaint either, and
343
+ // "no complaint" is what the exemption case asserts. stderr and the bash version name the cause.
344
+ assert.match(
345
+ r.stdout,
346
+ /^(PASS|FAIL)\b/m,
347
+ `check-render.sh stopped before its verdict (exit ${r.status}, ${BASH_VERSION}):\n` +
348
+ `--- stdout\n${r.stdout}--- stderr\n${r.stderr}`,
349
+ );
350
+ return r;
351
+ };
352
+ const BASH_VERSION = spawnSync("bash", ["--version"], {
353
+ encoding: "utf8",
354
+ }).stdout.split("\n")[0];
355
+
356
+ const ctlOnly = paper(["BSTcontrol", "real"]);
357
+ assert.doesNotMatch(
358
+ ctlOnly.stdout,
359
+ /CITED-BUT-ABSENT/,
360
+ `a control key IEEEtran reported reading was flagged as a missing reference:\n${ctlOnly.stdout}`,
361
+ );
362
+
363
+ // A key that starts with "-" is still a key, not a grep option.
364
+ const dashKey = paper(["-ctl", "real"], ["-ctl"]);
365
+ assert.doesNotMatch(
366
+ dashKey.stdout,
367
+ /CITED-BUT-ABSENT/,
368
+ `a control key starting with "-" was flagged:\n${dashKey.stdout}`,
369
+ );
370
+
371
+ // A misspelled \\bstctlcite key: IEEEtran detected a different key, so this one is not exempt.
372
+ const typo = paper(["BSTcontrl", "real"]);
373
+ assert.match(
374
+ typo.stdout,
375
+ /cited but NOT in the bibliography: BSTcontrl/,
376
+ `a \\bstctlcite key IEEEtran did not report must be flagged:\n${typo.stdout}`,
377
+ );
378
+
379
+ // A record BibTeX never read — in a .bib the build does not use, or inside @comment{…} — leaves
380
+ // no "detected" line, so the key is not exempt.
381
+ const unread = paper(["BSTcontrol", "real"], []);
382
+ assert.match(
383
+ unread.stdout,
384
+ /cited but NOT in the bibliography: BSTcontrol/,
385
+ `a control key IEEEtran never reported reading must be flagged:\n${unread.stdout}`,
386
+ );
387
+
388
+ const withGap = paper(["BSTcontrol", "real", "gone"]);
389
+ assert.match(
390
+ withGap.stdout,
391
+ /cited but NOT in the bibliography: gone/,
392
+ `a key with no \\bibitem must still be reported:\n${withGap.stdout}`,
393
+ );
394
+ assert.doesNotMatch(
395
+ withGap.stdout,
396
+ /bibliography: BSTcontrol/,
397
+ withGap.stdout,
398
+ );
399
+ }