paperlint 8.0.0 → 9.0.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.
Files changed (393) hide show
  1. package/.github/CLAUDE.md +22 -0
  2. package/.github/CLAUDE.md.spec.ts +20 -0
  3. package/CLAUDE.md +59 -531
  4. package/CONTRIBUTING.md +7 -6
  5. package/README.md +4 -3
  6. package/dist/CLAUDE.md.spec.d.ts +3 -0
  7. package/dist/CLAUDE.md.spec.d.ts.map +1 -0
  8. package/dist/CLAUDE.md.spec.js +62 -0
  9. package/dist/CLAUDE.md.spec.js.map +1 -0
  10. package/dist/adapters/bibtex/index.d.ts +3 -0
  11. package/dist/adapters/bibtex/index.d.ts.map +1 -0
  12. package/dist/adapters/bibtex/index.js +103 -0
  13. package/dist/adapters/bibtex/index.js.map +1 -0
  14. package/dist/adapters/latex/filecontents.d.ts +33 -0
  15. package/dist/adapters/latex/filecontents.d.ts.map +1 -0
  16. package/dist/adapters/latex/filecontents.js +65 -0
  17. package/dist/adapters/latex/filecontents.js.map +1 -0
  18. package/dist/adapters/latex/includes.d.ts +3 -3
  19. package/dist/adapters/latex/includes.d.ts.map +1 -1
  20. package/dist/adapters/latex/includes.js +18 -10
  21. package/dist/adapters/latex/includes.js.map +1 -1
  22. package/dist/adapters/latex/index.d.ts.map +1 -1
  23. package/dist/adapters/latex/index.js +2 -0
  24. package/dist/adapters/latex/index.js.map +1 -1
  25. package/dist/adapters/latex/nodes.d.ts +12 -0
  26. package/dist/adapters/latex/nodes.d.ts.map +1 -1
  27. package/dist/adapters/latex/nodes.js +39 -0
  28. package/dist/adapters/latex/nodes.js.map +1 -1
  29. package/dist/adapters/latex/parse.d.ts +7 -1
  30. package/dist/adapters/latex/parse.d.ts.map +1 -1
  31. package/dist/adapters/latex/parse.js +55 -6
  32. package/dist/adapters/latex/parse.js.map +1 -1
  33. package/dist/adapters/memory/index.d.ts +3 -0
  34. package/dist/adapters/memory/index.d.ts.map +1 -1
  35. package/dist/adapters/memory/index.js +9 -0
  36. package/dist/adapters/memory/index.js.map +1 -1
  37. package/dist/adapters/references/index.d.ts.map +1 -1
  38. package/dist/adapters/references/index.js +27 -24
  39. package/dist/adapters/references/index.js.map +1 -1
  40. package/dist/adapters/sources-record/index.d.ts +9 -0
  41. package/dist/adapters/sources-record/index.d.ts.map +1 -0
  42. package/dist/adapters/sources-record/index.js +68 -0
  43. package/dist/adapters/sources-record/index.js.map +1 -0
  44. package/dist/adapters/tex-output/blg.d.ts +9 -0
  45. package/dist/adapters/tex-output/blg.d.ts.map +1 -0
  46. package/dist/adapters/tex-output/blg.js +20 -0
  47. package/dist/adapters/tex-output/blg.js.map +1 -0
  48. package/dist/adapters/tex-output/fls.d.ts +8 -0
  49. package/dist/adapters/tex-output/fls.d.ts.map +1 -0
  50. package/dist/adapters/tex-output/fls.js +16 -0
  51. package/dist/adapters/tex-output/fls.js.map +1 -0
  52. package/dist/adapters/tex-output/index.d.ts +7 -0
  53. package/dist/adapters/tex-output/index.d.ts.map +1 -0
  54. package/dist/adapters/tex-output/index.js +7 -0
  55. package/dist/adapters/tex-output/index.js.map +1 -0
  56. package/dist/bib-rules.d.ts +47 -0
  57. package/dist/bib-rules.d.ts.map +1 -0
  58. package/dist/bib-rules.js +127 -0
  59. package/dist/bib-rules.js.map +1 -0
  60. package/dist/build.d.ts +29 -1
  61. package/dist/build.d.ts.map +1 -1
  62. package/dist/build.js +41 -25
  63. package/dist/build.js.map +1 -1
  64. package/dist/claim-provenance.d.ts +2 -2
  65. package/dist/cli.d.ts +6 -1
  66. package/dist/cli.d.ts.map +1 -1
  67. package/dist/cli.js +30 -26
  68. package/dist/cli.js.map +1 -1
  69. package/dist/domain/paper-source.d.ts +1 -10
  70. package/dist/domain/paper-source.d.ts.map +1 -1
  71. package/dist/domain/paper-source.js +34 -24
  72. package/dist/domain/paper-source.js.map +1 -1
  73. package/dist/domain/paper-sources.d.ts +72 -0
  74. package/dist/domain/paper-sources.d.ts.map +1 -0
  75. package/dist/domain/paper-sources.js +18 -0
  76. package/dist/domain/paper-sources.js.map +1 -0
  77. package/dist/domain/sources-record.d.ts +102 -0
  78. package/dist/domain/sources-record.d.ts.map +1 -0
  79. package/dist/domain/sources-record.js +38 -0
  80. package/dist/domain/sources-record.js.map +1 -0
  81. package/dist/domain/tex-run.d.ts +25 -0
  82. package/dist/domain/tex-run.d.ts.map +1 -0
  83. package/dist/domain/tex-run.js +79 -0
  84. package/dist/domain/tex-run.js.map +1 -0
  85. package/dist/eslint-rules/CLAUDE.md.spec.d.ts +3 -0
  86. package/dist/eslint-rules/CLAUDE.md.spec.d.ts.map +1 -0
  87. package/dist/eslint-rules/CLAUDE.md.spec.js +39 -0
  88. package/dist/eslint-rules/CLAUDE.md.spec.js.map +1 -0
  89. package/dist/eslint-rules/install-path-literals.d.ts +1 -1
  90. package/dist/eslint-rules/install-path-literals.js +4 -4
  91. package/dist/eslint-rules/install-path-literals.js.map +1 -1
  92. package/dist/eslint-rules/paper-context.d.ts.map +1 -1
  93. package/dist/eslint-rules/paper-context.js +4 -3
  94. package/dist/eslint-rules/paper-context.js.map +1 -1
  95. package/dist/eslint-rules/paper-typography.d.ts +0 -9
  96. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  97. package/dist/eslint-rules/paper-typography.js +12 -5
  98. package/dist/eslint-rules/paper-typography.js.map +1 -1
  99. package/dist/eslint-rules/pdf-last-page-balance.js +1 -1
  100. package/dist/latex-log.d.ts +10 -0
  101. package/dist/latex-log.d.ts.map +1 -1
  102. package/dist/latex-log.js +10 -0
  103. package/dist/latex-log.js.map +1 -1
  104. package/dist/latex-loop.d.ts +2 -0
  105. package/dist/latex-loop.d.ts.map +1 -1
  106. package/dist/latex-loop.js.map +1 -1
  107. package/dist/lib/agent-cli-version.js +1 -1
  108. package/dist/lib/agent-cli-version.js.map +1 -1
  109. package/dist/package-dirs.d.ts.map +1 -1
  110. package/dist/package-dirs.js +3 -2
  111. package/dist/package-dirs.js.map +1 -1
  112. package/dist/paper-includes.d.ts +4 -7
  113. package/dist/paper-includes.d.ts.map +1 -1
  114. package/dist/paper-includes.js +29 -21
  115. package/dist/paper-includes.js.map +1 -1
  116. package/dist/paper-prose.d.ts +2 -2
  117. package/dist/paper-record.d.ts +25 -0
  118. package/dist/paper-record.d.ts.map +1 -0
  119. package/dist/paper-record.js +39 -0
  120. package/dist/paper-record.js.map +1 -0
  121. package/dist/paper-sources.d.ts +66 -0
  122. package/dist/paper-sources.d.ts.map +1 -0
  123. package/dist/paper-sources.js +92 -0
  124. package/dist/paper-sources.js.map +1 -0
  125. package/dist/ports/bib-reader.d.ts +18 -0
  126. package/dist/ports/bib-reader.d.ts.map +1 -0
  127. package/dist/ports/bib-reader.js +2 -0
  128. package/dist/ports/bib-reader.js.map +1 -0
  129. package/dist/ports/check-references.d.ts +9 -4
  130. package/dist/ports/check-references.d.ts.map +1 -1
  131. package/dist/ports/latex.d.ts +15 -0
  132. package/dist/ports/latex.d.ts.map +1 -1
  133. package/dist/ports/sources-codec.d.ts +11 -0
  134. package/dist/ports/sources-codec.d.ts.map +1 -0
  135. package/dist/ports/sources-codec.js +2 -0
  136. package/dist/ports/sources-codec.js.map +1 -0
  137. package/dist/ports/tex-output.d.ts +17 -0
  138. package/dist/ports/tex-output.d.ts.map +1 -0
  139. package/dist/ports/tex-output.js +2 -0
  140. package/dist/ports/tex-output.js.map +1 -0
  141. package/dist/recorded-bibliography.d.ts +89 -0
  142. package/dist/recorded-bibliography.d.ts.map +1 -0
  143. package/dist/recorded-bibliography.js +136 -0
  144. package/dist/recorded-bibliography.js.map +1 -0
  145. package/dist/reference-rules.d.ts +5 -5
  146. package/dist/reference-rules.d.ts.map +1 -1
  147. package/dist/reference-rules.js +150 -77
  148. package/dist/reference-rules.js.map +1 -1
  149. package/dist/references.d.ts +71 -16
  150. package/dist/references.d.ts.map +1 -1
  151. package/dist/references.js +110 -26
  152. package/dist/references.js.map +1 -1
  153. package/dist/register-bands.d.ts +3 -3
  154. package/dist/register-bands.d.ts.map +1 -1
  155. package/dist/register-bands.js.map +1 -1
  156. package/dist/register.d.ts +2 -2
  157. package/dist/sources-record.d.ts +38 -0
  158. package/dist/sources-record.d.ts.map +1 -0
  159. package/dist/sources-record.js +123 -0
  160. package/dist/sources-record.js.map +1 -0
  161. package/dist/sources-rules.d.ts +44 -0
  162. package/dist/sources-rules.d.ts.map +1 -0
  163. package/dist/sources-rules.js +76 -0
  164. package/dist/sources-rules.js.map +1 -0
  165. package/dist/tex-paper.d.ts +10 -15
  166. package/dist/tex-paper.d.ts.map +1 -1
  167. package/dist/tex-paper.js +24 -40
  168. package/dist/tex-paper.js.map +1 -1
  169. package/dist/tex-requirements.js +1 -1
  170. package/dist/tex-venue-rules.d.ts +3 -3
  171. package/dist/tex-venue-rules.d.ts.map +1 -1
  172. package/dist/tex-venue-rules.js.map +1 -1
  173. package/dist/toolchain.js +1 -1
  174. package/docs/configuration.md +12 -4
  175. package/docs/design/paper-sources.md +197 -0
  176. package/docs/e2e.md +1 -1
  177. package/docs/incidents.md +5 -5
  178. package/docs/package-shape-options.md +1 -1
  179. package/docs/references.md +34 -8
  180. package/docs/rules/bib/reachable-entry.md +64 -0
  181. package/docs/rules/paper/sources-fresh.md +81 -0
  182. package/docs/rules.md +62 -57
  183. package/eslint-rules/CLAUDE.md +44 -0
  184. package/eslint-rules/CLAUDE.md.spec.ts +43 -0
  185. package/eslint-rules/install-path-literals.harness.mjs +1 -1
  186. package/eslint-rules/install-path-literals.ts +4 -4
  187. package/eslint-rules/paper-context.ts +4 -3
  188. package/eslint-rules/paper-typography.test.mjs +12 -72
  189. package/eslint-rules/paper-typography.ts +13 -6
  190. package/eslint-rules/pdf-last-page-balance.ts +1 -1
  191. package/eslint.config.mjs +28 -3
  192. package/fixtures/build-e2e/aidc/paper.tex +1 -1
  193. package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
  194. package/fixtures/build-e2e/anon-author/paper.tex +1 -1
  195. package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
  196. package/fixtures/build-e2e/anon-href/paper.tex +1 -1
  197. package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
  198. package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
  199. package/fixtures/paper-sources/p1/paper.tex +30 -0
  200. package/fixtures/paper-sources/p1/refs.bib +6 -0
  201. package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
  202. package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
  203. package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
  204. package/fixtures/paper-sources/p1/tex-truth.json +37 -0
  205. package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
  206. package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
  207. package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
  208. package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
  209. package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
  210. package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
  211. package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
  212. package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
  213. package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
  214. package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
  215. package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
  216. package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
  217. package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
  218. package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
  219. package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
  220. package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
  221. package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
  222. package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
  223. package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
  224. package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
  225. package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
  226. package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
  227. package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
  228. package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
  229. package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
  230. package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
  231. package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
  232. package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
  233. package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
  234. package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
  235. package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
  236. package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
  237. package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
  238. package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
  239. package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
  240. package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
  241. package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
  242. package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
  243. package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
  244. package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
  245. package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
  246. package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
  247. package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
  248. package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
  249. package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
  250. package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
  251. package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
  252. package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
  253. package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
  254. package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
  255. package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
  256. package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
  257. package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
  258. package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
  259. package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
  260. package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
  261. package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
  262. package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
  263. package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
  264. package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
  265. package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
  266. package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
  267. package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
  268. package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
  269. package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
  270. package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
  271. package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
  272. package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
  273. package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
  274. package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
  275. package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
  276. package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
  277. package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
  278. package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
  279. package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
  280. package/hooks/CLAUDE.md +34 -0
  281. package/hooks/CLAUDE.md.spec.ts +30 -0
  282. package/lib/agent-cli-version.ts +1 -1
  283. package/package.json +3 -11
  284. package/plugin/CLAUDE.md +22 -0
  285. package/plugin/CLAUDE.md.spec.ts +22 -0
  286. package/scripts/check.test.ts +23 -1
  287. package/scripts/check.ts +7 -3
  288. package/scripts/coverage-config.test.ts +21 -11
  289. package/scripts/layer-legacy-frozen.test.ts +41 -2
  290. package/scripts/layer-legacy-frozen.ts +31 -2
  291. package/skills/CLAUDE.md +19 -0
  292. package/skills/CLAUDE.md.spec.ts +18 -0
  293. package/skills/grade-paper-writing/SKILL.md +4 -3
  294. package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
  295. package/skills/grade-paper-writing/prose-lint.mjs +11 -2
  296. package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
  297. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
  298. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
  299. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
  300. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +21 -102
  301. package/skills/verify-citations/SKILL.md +7 -4
  302. package/skills/verify-citations/SKILL.md.spec.ts +6 -3
  303. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -19
  304. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
  305. package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
  306. package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
  307. package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
  308. package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
  309. package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
  310. package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
  311. package/src/CLAUDE.md +23 -1
  312. package/src/CLAUDE.md.spec.ts +72 -0
  313. package/src/adapters/bibtex/index.test.ts +163 -0
  314. package/src/adapters/bibtex/index.ts +155 -0
  315. package/src/adapters/latex/filecontents.ts +114 -0
  316. package/src/adapters/latex/includes.ts +18 -10
  317. package/src/adapters/latex/index.test.ts +73 -0
  318. package/src/adapters/latex/index.ts +2 -0
  319. package/src/adapters/latex/nodes.ts +64 -0
  320. package/src/adapters/latex/parse.test.ts +38 -0
  321. package/src/adapters/latex/parse.ts +67 -6
  322. package/src/adapters/memory/index.ts +17 -0
  323. package/src/adapters/references/index.test.ts +75 -21
  324. package/src/adapters/references/index.ts +37 -32
  325. package/src/adapters/sources-record/index.test.ts +87 -0
  326. package/src/adapters/sources-record/index.ts +82 -0
  327. package/src/adapters/tex-output/blg.ts +27 -0
  328. package/src/adapters/tex-output/fls.ts +24 -0
  329. package/src/adapters/tex-output/index.test.ts +171 -0
  330. package/src/adapters/tex-output/index.ts +12 -0
  331. package/src/bib-rules.test.ts +356 -0
  332. package/src/bib-rules.ts +228 -0
  333. package/src/bibliography-at.test.ts +286 -0
  334. package/src/build.test.ts +210 -0
  335. package/src/build.ts +101 -36
  336. package/src/claim-provenance.test.ts +33 -7
  337. package/src/claim-provenance.ts +2 -2
  338. package/src/cli.harness.mjs +20 -10
  339. package/src/cli.test.ts +14 -10
  340. package/src/cli.ts +37 -28
  341. package/src/domain/paper-source.test.ts +58 -20
  342. package/src/domain/paper-source.ts +38 -42
  343. package/src/domain/paper-sources.ts +94 -0
  344. package/src/domain/sources-record.test.ts +71 -0
  345. package/src/domain/sources-record.ts +136 -0
  346. package/src/domain/tex-run.test.ts +228 -0
  347. package/src/domain/tex-run.ts +139 -0
  348. package/src/heading-case.test.ts +24 -10
  349. package/src/latex-log.ts +20 -0
  350. package/src/latex-loop.harness.ts +1 -0
  351. package/src/latex-loop.ts +2 -0
  352. package/src/linted-set.test.ts +9 -1
  353. package/src/new-paper.harness.mjs +3 -2
  354. package/src/new-paper.test.ts +9 -5
  355. package/src/package-dirs.ts +3 -2
  356. package/src/paper-includes.test.ts +157 -38
  357. package/src/paper-includes.ts +39 -35
  358. package/src/paper-prose.test.ts +34 -5
  359. package/src/paper-prose.ts +2 -2
  360. package/src/paper-record.test.ts +173 -0
  361. package/src/paper-record.ts +66 -0
  362. package/src/paper-settings-commands.test.ts +5 -2
  363. package/src/paper-sources.ts +151 -0
  364. package/src/ports/bib-reader.ts +27 -0
  365. package/src/ports/check-references.ts +7 -3
  366. package/src/ports/latex.ts +19 -0
  367. package/src/ports/sources-codec.ts +11 -0
  368. package/src/ports/tex-output.ts +17 -0
  369. package/src/recorded-bibliography.ts +297 -0
  370. package/src/reference-rules.ts +217 -104
  371. package/src/references.test.ts +520 -177
  372. package/src/references.ts +185 -37
  373. package/src/register-bands.test.ts +5 -1
  374. package/src/register-bands.ts +3 -5
  375. package/src/register.test.ts +6 -1
  376. package/src/register.ts +2 -2
  377. package/src/rule-cases.test.ts +68 -12
  378. package/src/rule-docs.test.ts +0 -1
  379. package/src/sources-record.test.ts +308 -0
  380. package/src/sources-record.ts +231 -0
  381. package/src/sources-rules.test.ts +152 -0
  382. package/src/sources-rules.ts +117 -0
  383. package/src/tex-paper.test.ts +148 -78
  384. package/src/tex-paper.ts +31 -61
  385. package/src/tex-requirements.ts +1 -1
  386. package/src/tex-venue-rules.test.ts +36 -8
  387. package/src/tex-venue-rules.ts +3 -5
  388. package/src/toolchain.ts +1 -1
  389. package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
  390. package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
  391. package/dist/eslint-rules/bib-reachable-entry.js +0 -75
  392. package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
  393. package/eslint-rules/bib-reachable-entry.ts +0 -76
@@ -0,0 +1,297 @@
1
+ /**
2
+ * THE BIBLIOGRAPHY THE LAST BUILD READ — the entries of the databases bibtex opened, from the build's
3
+ * record (`_build/sources.json`, docs/design/paper-sources.md §1), through the one bibtex reader. Nothing
4
+ * here reads TeX source to decide which bibliography a paper has: TeX answered, the build wrote it
5
+ * down, and `paperRecord` says whether the paper is still the one it was written for.
6
+ *
7
+ * unrecorded no record, or a stale one — the rules that need it are silent (`paper/sources-fresh`
8
+ * speaks), the scripts refuse ("run `npx paperlint build` first")
9
+ * recorded a current record, and the databases bibtex opened that are on disk now (none when
10
+ * bibtex ran none), and the names of those that are not
11
+ *
12
+ * The databases are read in the order bibtex opened them, each with the `@string`s the ones before it
13
+ * left in force — bibtex keeps one table of them across its databases, so `\\bibliography{abbrev,refs}`
14
+ * expands an entry of `refs.bib` with a `@string` of `abbrev.bib`. A database that is not on disk adds
15
+ * none: its `@string`s are bibtex's alone.
16
+ *
17
+ * WHERE A FINDING ABOUT AN ENTRY GOES (`entryReports`). ESLint lints `paper.tex` only, so every finding
18
+ * is a place in it. An entry of a `.bib` TeX wrote (a `filecontents` block) is reported on its own line
19
+ * in the block of `paper.tex` whose body bibtex takes as the same database (`sameDatabase`) — a lookup
20
+ * of where the text stands, which decides nothing: the record said what TeX read. When no block of
21
+ * `paper.tex` is that database — the block is in an included file, or `paper.tex` was edited after the
22
+ * build — and for the entry of every other `.bib`, the finding is at the top of `paper.tex`, the file,
23
+ * line and column of the entry at the front of the message (`refs.bib:12:1:`).
24
+ */
25
+ import { extname, posix, relative, resolve } from "node:path";
26
+ import { callerPath } from "./caller-path.ts";
27
+ import {
28
+ sameDatabase,
29
+ type BibEntry,
30
+ type BibStrings,
31
+ type BibText,
32
+ } from "./domain/paper-sources.ts";
33
+ import type { AbsolutePath } from "./domain/paths.ts";
34
+ import type { Span } from "./domain/tex-document.ts";
35
+ import {
36
+ JOBNAME,
37
+ spelledIn,
38
+ type SourcesRecord,
39
+ } from "./domain/sources-record.ts";
40
+ import {
41
+ paperRecord,
42
+ type PaperRecord,
43
+ type RecordReadDeps,
44
+ } from "./paper-record.ts";
45
+ import type { BibReader } from "./ports/bib-reader.ts";
46
+ import type { LatexReader } from "./ports/latex.ts";
47
+ import { decoded, lineColumn } from "./tex-paper.ts";
48
+
49
+ /** What reading the recorded bibliography needs: the disk, the record's schema, the bibtex reader. */
50
+ export interface RecordedReadDeps extends RecordReadDeps {
51
+ readonly bib: BibReader;
52
+ }
53
+
54
+ /** And what finding where a `.bib` TeX wrote stands needs besides: the reader of `filecontents` blocks. */
55
+ export interface RecordedDeps extends RecordedReadDeps {
56
+ readonly latex: Pick<LatexReader, "filecontents">;
57
+ }
58
+
59
+ /** A database bibtex opened that is on disk now, as the bibtex reader reads it. */
60
+ export interface ReadDatabase {
61
+ /** As bibtex names it: `refs.bib`, `bibs/x.bib`. */
62
+ readonly name: string;
63
+ /** A `.bib` TeX wrote (a `filecontents` block), not one the author keeps. */
64
+ readonly written: boolean;
65
+ readonly bib: BibText;
66
+ }
67
+
68
+ export type RecordedBibliography =
69
+ | {
70
+ readonly kind: "unrecorded";
71
+ readonly record: Exclude<PaperRecord, { readonly kind: "fresh" }>;
72
+ }
73
+ | {
74
+ readonly kind: "recorded";
75
+ readonly record: SourcesRecord;
76
+ /** In the order bibtex opened them, each once. */
77
+ readonly databases: readonly ReadDatabase[];
78
+ /**
79
+ * As bibtex names them: the databases it opened that the paper directory does not hold — found on
80
+ * its search path (BIBINPUTS). One that was there when the build recorded it and is gone now made
81
+ * the record stale instead: every database bibtex opened is hashed.
82
+ */
83
+ readonly unread: readonly string[];
84
+ };
85
+
86
+ /** A database bibtex opened, read as it read it — or null when it is not on disk. */
87
+ interface Opened {
88
+ readonly name: string;
89
+ readonly bib: BibText | null;
90
+ }
91
+
92
+ /**
93
+ * The databases `names`, in this order, each read with the `@string`s the ones before it left in force,
94
+ * as bibtex reads them. One that is not on disk adds none.
95
+ */
96
+ const readInSequence = (
97
+ dir: string,
98
+ names: readonly string[],
99
+ deps: RecordedReadDeps,
100
+ ): readonly Opened[] =>
101
+ names.reduce<{
102
+ readonly inForce: BibStrings;
103
+ readonly read: readonly Opened[];
104
+ }>(
105
+ (acc, name) => {
106
+ const path = callerPath(resolve(dir, name));
107
+ const text = decoded(deps.files.readBytes(path));
108
+ const bib =
109
+ text === null ? null : deps.bib.readFile(path, text, acc.inForce);
110
+ return {
111
+ inForce: bib?.strings ?? acc.inForce,
112
+ read: [...acc.read, { name, bib }],
113
+ };
114
+ },
115
+ { inForce: {}, read: [] },
116
+ ).read;
117
+
118
+ /** The bibliography the last build of the paper in `dir` read. */
119
+ export function recordedBibliography(
120
+ dir: string,
121
+ deps: RecordedReadDeps,
122
+ ): RecordedBibliography {
123
+ const r = paperRecord(dir, deps);
124
+ if (r.kind !== "fresh") return { kind: "unrecorded", record: r };
125
+ const { bibtex, written } = r.record;
126
+ const opened = readInSequence(
127
+ dir,
128
+ (bibtex.ran ? bibtex.databases : []).filter(
129
+ (d, i, all) =>
130
+ all.findIndex((o) => resolve(dir, o) === resolve(dir, d)) === i,
131
+ ),
132
+ deps,
133
+ );
134
+ return {
135
+ kind: "recorded",
136
+ record: r.record,
137
+ databases: opened.flatMap(({ name, bib }): readonly ReadDatabase[] =>
138
+ bib === null ? [] : [{ name, written: written.includes(name), bib }],
139
+ ),
140
+ unread: opened.filter((d) => d.bib === null).map((d) => d.name),
141
+ };
142
+ }
143
+
144
+ /** An entry of a database bibtex opened, and where it stands in that database. */
145
+ export interface RecordedEntry {
146
+ readonly database: ReadDatabase;
147
+ readonly entry: BibEntry;
148
+ /** Its place among the database's entries: what pairs it with the same entry of a block. */
149
+ readonly index: number;
150
+ }
151
+
152
+ /** Every entry of the databases, in the order bibtex opened them and the entries stand. */
153
+ export const entriesOfDatabases = (
154
+ databases: readonly ReadDatabase[],
155
+ ): readonly RecordedEntry[] =>
156
+ databases.flatMap((database) =>
157
+ database.bib.entries.map((entry, index) => ({ database, entry, index })),
158
+ );
159
+
160
+ // ── where a finding about an entry goes ─────────────────────────────────────────────────────
161
+
162
+ /** Where a finding about an entry is reported in paper.tex. */
163
+ export type EntryReport =
164
+ | { readonly kind: "here"; readonly span: Span }
165
+ /** At the top of paper.tex; `where` names the entry (`refs.bib:12:1`). */
166
+ | { readonly kind: "elsewhere"; readonly span: Span; readonly where: string };
167
+
168
+ /** The first line of an entry: `@misc{key,` — what a finding underlines. */
169
+ const headOf = (bib: BibText, e: BibEntry): Span => {
170
+ const first = bib.text
171
+ .slice(e.span.start, e.span.end)
172
+ .split("\n", 1)
173
+ .join("");
174
+ return { start: e.span.start, end: e.span.start + first.length };
175
+ };
176
+
177
+ const TOP: Span = { start: 0, end: 0 };
178
+
179
+ /** A text of the paper that may hold a block: a file of the record, the main file as the editor has it. */
180
+ interface Holder {
181
+ readonly path: AbsolutePath;
182
+ readonly text: string;
183
+ }
184
+
185
+ /**
186
+ * The text of every `.tex` the record lists, in the order TeX first read them: from disk, except
187
+ * `override` (the main file as the editor holds it), which stands for its own path. A record is only
188
+ * current while every file it lists is on disk, so none is skipped for being absent.
189
+ */
190
+ function holders(
191
+ dir: string,
192
+ record: SourcesRecord,
193
+ override: Holder | null,
194
+ deps: RecordedDeps,
195
+ ): readonly Holder[] {
196
+ return record.inputs
197
+ .map((i) => callerPath(resolve(dir, i.path)))
198
+ .filter((p) => extname(p) === ".tex")
199
+ .map((path) =>
200
+ override !== null && override.path === path
201
+ ? override
202
+ : { path, text: decoded(deps.files.readBytes(path)) },
203
+ )
204
+ .filter((h): h is Holder => h.text !== null);
205
+ }
206
+
207
+ /** The block, in some text of the paper, that holds what bibtex read of `db`. */
208
+ const blockHolding = (
209
+ db: ReadDatabase,
210
+ texts: readonly Holder[],
211
+ deps: RecordedDeps,
212
+ ): { readonly holder: Holder; readonly bib: BibText } | null =>
213
+ texts
214
+ .flatMap((holder) =>
215
+ deps.latex
216
+ .filecontents(holder.text, JOBNAME)
217
+ // The block that wrote this database: its file, then its text.
218
+ .filter((b) => spelledIn([posix.normalize(b.writes)], db.name) !== null)
219
+ // Read as bibtex read the database: with the `@string`s in force before it.
220
+ .map((b) => ({
221
+ holder,
222
+ bib: deps.bib.read(
223
+ holder.path,
224
+ holder.text,
225
+ b.body,
226
+ db.bib.inherited,
227
+ ),
228
+ })),
229
+ )
230
+ .find((b) => sameDatabase(b.bib, db.bib)) ?? null;
231
+
232
+ /** The paper being linted: where it is, the record of its build, and its main file as the editor holds it. */
233
+ export interface LintedPaper {
234
+ readonly dir: string;
235
+ readonly record: SourcesRecord;
236
+ readonly main: Holder;
237
+ }
238
+
239
+ /** Where a finding about `f` is reported, given the block of a text of the paper that holds its database (or none). */
240
+ function reportOf(
241
+ paper: LintedPaper,
242
+ block: { readonly holder: Holder; readonly bib: BibText } | null,
243
+ f: RecordedEntry,
244
+ ): EntryReport {
245
+ const there = block?.bib.entries[f.index];
246
+ if (block === null || there?.key !== f.entry.key)
247
+ return {
248
+ kind: "elsewhere",
249
+ span: TOP,
250
+ where: `${f.database.name}:${lineColumn(f.database.bib.text, headOf(f.database.bib, f.entry).start)}`,
251
+ };
252
+ const span = headOf(block.bib, there);
253
+ return block.holder.path === paper.main.path
254
+ ? { kind: "here", span }
255
+ : {
256
+ kind: "elsewhere",
257
+ span: TOP,
258
+ where: `${relative(paper.dir, block.holder.path)}:${lineColumn(block.holder.text, span.start)}`,
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Where each finding about `found` is reported, in the same order. The main file is read as the editor
264
+ * holds it; the record's other `.tex` files are read from disk.
265
+ */
266
+ export function entryReports(
267
+ paper: LintedPaper,
268
+ found: readonly RecordedEntry[],
269
+ deps: RecordedDeps,
270
+ ): readonly EntryReport[] {
271
+ const texts = holders(paper.dir, paper.record, paper.main, deps);
272
+ // Only the written databases that have a finding are searched for their block.
273
+ const blocks = new Map(
274
+ found
275
+ .map((f) => f.database)
276
+ .filter((d, i, all) => d.written && all.indexOf(d) === i)
277
+ .map((d) => [d, blockHolding(d, texts, deps)] as const),
278
+ );
279
+ return found.map((f) => reportOf(paper, blocks.get(f.database) ?? null, f));
280
+ }
281
+
282
+ /**
283
+ * The text the author edits for each database bibtex opened, in the order it opened them: for a `.bib`
284
+ * TeX wrote, the block of `paper.tex` or of an included file that holds what bibtex read; for any other
285
+ * database, and for a written one no block holds, the file itself. What a script reads, so its entries
286
+ * stand where the author finds them.
287
+ */
288
+ export function authoredTexts(
289
+ dir: string,
290
+ recorded: Extract<RecordedBibliography, { readonly kind: "recorded" }>,
291
+ deps: RecordedDeps,
292
+ ): readonly BibText[] {
293
+ const texts = holders(dir, recorded.record, null, deps);
294
+ return recorded.databases.map(
295
+ (d) => (d.written ? blockHolding(d, texts, deps)?.bib : undefined) ?? d.bib,
296
+ );
297
+ }