paperlint 8.0.0 → 9.0.0

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 (356) hide show
  1. package/CLAUDE.md +9 -25
  2. package/CONTRIBUTING.md +5 -4
  3. package/README.md +4 -3
  4. package/dist/adapters/bibtex/index.d.ts +3 -0
  5. package/dist/adapters/bibtex/index.d.ts.map +1 -0
  6. package/dist/adapters/bibtex/index.js +103 -0
  7. package/dist/adapters/bibtex/index.js.map +1 -0
  8. package/dist/adapters/latex/filecontents.d.ts +33 -0
  9. package/dist/adapters/latex/filecontents.d.ts.map +1 -0
  10. package/dist/adapters/latex/filecontents.js +65 -0
  11. package/dist/adapters/latex/filecontents.js.map +1 -0
  12. package/dist/adapters/latex/includes.d.ts +3 -3
  13. package/dist/adapters/latex/includes.d.ts.map +1 -1
  14. package/dist/adapters/latex/includes.js +18 -10
  15. package/dist/adapters/latex/includes.js.map +1 -1
  16. package/dist/adapters/latex/index.d.ts.map +1 -1
  17. package/dist/adapters/latex/index.js +2 -0
  18. package/dist/adapters/latex/index.js.map +1 -1
  19. package/dist/adapters/latex/nodes.d.ts +12 -0
  20. package/dist/adapters/latex/nodes.d.ts.map +1 -1
  21. package/dist/adapters/latex/nodes.js +39 -0
  22. package/dist/adapters/latex/nodes.js.map +1 -1
  23. package/dist/adapters/latex/parse.d.ts +7 -1
  24. package/dist/adapters/latex/parse.d.ts.map +1 -1
  25. package/dist/adapters/latex/parse.js +55 -6
  26. package/dist/adapters/latex/parse.js.map +1 -1
  27. package/dist/adapters/memory/index.d.ts +3 -0
  28. package/dist/adapters/memory/index.d.ts.map +1 -1
  29. package/dist/adapters/memory/index.js +9 -0
  30. package/dist/adapters/memory/index.js.map +1 -1
  31. package/dist/adapters/references/index.d.ts.map +1 -1
  32. package/dist/adapters/references/index.js +27 -24
  33. package/dist/adapters/references/index.js.map +1 -1
  34. package/dist/adapters/sources-record/index.d.ts +9 -0
  35. package/dist/adapters/sources-record/index.d.ts.map +1 -0
  36. package/dist/adapters/sources-record/index.js +68 -0
  37. package/dist/adapters/sources-record/index.js.map +1 -0
  38. package/dist/adapters/tex-output/blg.d.ts +9 -0
  39. package/dist/adapters/tex-output/blg.d.ts.map +1 -0
  40. package/dist/adapters/tex-output/blg.js +20 -0
  41. package/dist/adapters/tex-output/blg.js.map +1 -0
  42. package/dist/adapters/tex-output/fls.d.ts +8 -0
  43. package/dist/adapters/tex-output/fls.d.ts.map +1 -0
  44. package/dist/adapters/tex-output/fls.js +16 -0
  45. package/dist/adapters/tex-output/fls.js.map +1 -0
  46. package/dist/adapters/tex-output/index.d.ts +7 -0
  47. package/dist/adapters/tex-output/index.d.ts.map +1 -0
  48. package/dist/adapters/tex-output/index.js +7 -0
  49. package/dist/adapters/tex-output/index.js.map +1 -0
  50. package/dist/bib-rules.d.ts +47 -0
  51. package/dist/bib-rules.d.ts.map +1 -0
  52. package/dist/bib-rules.js +127 -0
  53. package/dist/bib-rules.js.map +1 -0
  54. package/dist/build.d.ts +29 -1
  55. package/dist/build.d.ts.map +1 -1
  56. package/dist/build.js +40 -24
  57. package/dist/build.js.map +1 -1
  58. package/dist/claim-provenance.d.ts +2 -2
  59. package/dist/cli.d.ts +6 -1
  60. package/dist/cli.d.ts.map +1 -1
  61. package/dist/cli.js +30 -26
  62. package/dist/cli.js.map +1 -1
  63. package/dist/domain/paper-source.d.ts +1 -10
  64. package/dist/domain/paper-source.d.ts.map +1 -1
  65. package/dist/domain/paper-source.js +34 -24
  66. package/dist/domain/paper-source.js.map +1 -1
  67. package/dist/domain/paper-sources.d.ts +72 -0
  68. package/dist/domain/paper-sources.d.ts.map +1 -0
  69. package/dist/domain/paper-sources.js +18 -0
  70. package/dist/domain/paper-sources.js.map +1 -0
  71. package/dist/domain/sources-record.d.ts +102 -0
  72. package/dist/domain/sources-record.d.ts.map +1 -0
  73. package/dist/domain/sources-record.js +38 -0
  74. package/dist/domain/sources-record.js.map +1 -0
  75. package/dist/domain/tex-run.d.ts +25 -0
  76. package/dist/domain/tex-run.d.ts.map +1 -0
  77. package/dist/domain/tex-run.js +79 -0
  78. package/dist/domain/tex-run.js.map +1 -0
  79. package/dist/eslint-rules/paper-context.d.ts.map +1 -1
  80. package/dist/eslint-rules/paper-context.js +4 -3
  81. package/dist/eslint-rules/paper-context.js.map +1 -1
  82. package/dist/eslint-rules/paper-typography.d.ts +0 -9
  83. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  84. package/dist/eslint-rules/paper-typography.js +12 -5
  85. package/dist/eslint-rules/paper-typography.js.map +1 -1
  86. package/dist/latex-log.d.ts +10 -0
  87. package/dist/latex-log.d.ts.map +1 -1
  88. package/dist/latex-log.js +10 -0
  89. package/dist/latex-log.js.map +1 -1
  90. package/dist/latex-loop.d.ts +2 -0
  91. package/dist/latex-loop.d.ts.map +1 -1
  92. package/dist/latex-loop.js.map +1 -1
  93. package/dist/package-dirs.d.ts.map +1 -1
  94. package/dist/package-dirs.js +3 -2
  95. package/dist/package-dirs.js.map +1 -1
  96. package/dist/paper-includes.d.ts +4 -7
  97. package/dist/paper-includes.d.ts.map +1 -1
  98. package/dist/paper-includes.js +29 -21
  99. package/dist/paper-includes.js.map +1 -1
  100. package/dist/paper-prose.d.ts +2 -2
  101. package/dist/paper-record.d.ts +25 -0
  102. package/dist/paper-record.d.ts.map +1 -0
  103. package/dist/paper-record.js +39 -0
  104. package/dist/paper-record.js.map +1 -0
  105. package/dist/paper-sources.d.ts +66 -0
  106. package/dist/paper-sources.d.ts.map +1 -0
  107. package/dist/paper-sources.js +92 -0
  108. package/dist/paper-sources.js.map +1 -0
  109. package/dist/ports/bib-reader.d.ts +18 -0
  110. package/dist/ports/bib-reader.d.ts.map +1 -0
  111. package/dist/ports/bib-reader.js +2 -0
  112. package/dist/ports/bib-reader.js.map +1 -0
  113. package/dist/ports/check-references.d.ts +9 -4
  114. package/dist/ports/check-references.d.ts.map +1 -1
  115. package/dist/ports/latex.d.ts +15 -0
  116. package/dist/ports/latex.d.ts.map +1 -1
  117. package/dist/ports/sources-codec.d.ts +11 -0
  118. package/dist/ports/sources-codec.d.ts.map +1 -0
  119. package/dist/ports/sources-codec.js +2 -0
  120. package/dist/ports/sources-codec.js.map +1 -0
  121. package/dist/ports/tex-output.d.ts +17 -0
  122. package/dist/ports/tex-output.d.ts.map +1 -0
  123. package/dist/ports/tex-output.js +2 -0
  124. package/dist/ports/tex-output.js.map +1 -0
  125. package/dist/recorded-bibliography.d.ts +89 -0
  126. package/dist/recorded-bibliography.d.ts.map +1 -0
  127. package/dist/recorded-bibliography.js +136 -0
  128. package/dist/recorded-bibliography.js.map +1 -0
  129. package/dist/reference-rules.d.ts +5 -5
  130. package/dist/reference-rules.d.ts.map +1 -1
  131. package/dist/reference-rules.js +150 -77
  132. package/dist/reference-rules.js.map +1 -1
  133. package/dist/references.d.ts +71 -16
  134. package/dist/references.d.ts.map +1 -1
  135. package/dist/references.js +110 -26
  136. package/dist/references.js.map +1 -1
  137. package/dist/register-bands.d.ts +3 -3
  138. package/dist/register-bands.d.ts.map +1 -1
  139. package/dist/register-bands.js.map +1 -1
  140. package/dist/register.d.ts +2 -2
  141. package/dist/sources-record.d.ts +38 -0
  142. package/dist/sources-record.d.ts.map +1 -0
  143. package/dist/sources-record.js +123 -0
  144. package/dist/sources-record.js.map +1 -0
  145. package/dist/sources-rules.d.ts +44 -0
  146. package/dist/sources-rules.d.ts.map +1 -0
  147. package/dist/sources-rules.js +76 -0
  148. package/dist/sources-rules.js.map +1 -0
  149. package/dist/tex-paper.d.ts +10 -15
  150. package/dist/tex-paper.d.ts.map +1 -1
  151. package/dist/tex-paper.js +24 -40
  152. package/dist/tex-paper.js.map +1 -1
  153. package/dist/tex-venue-rules.d.ts +3 -3
  154. package/dist/tex-venue-rules.d.ts.map +1 -1
  155. package/dist/tex-venue-rules.js.map +1 -1
  156. package/docs/configuration.md +12 -4
  157. package/docs/design/paper-sources.md +197 -0
  158. package/docs/package-shape-options.md +1 -1
  159. package/docs/references.md +34 -8
  160. package/docs/rules/bib/reachable-entry.md +64 -0
  161. package/docs/rules/paper/sources-fresh.md +81 -0
  162. package/docs/rules.md +62 -57
  163. package/eslint-rules/paper-context.ts +4 -3
  164. package/eslint-rules/paper-typography.test.mjs +12 -72
  165. package/eslint-rules/paper-typography.ts +13 -6
  166. package/eslint.config.mjs +22 -0
  167. package/fixtures/build-e2e/aidc/paper.tex +1 -1
  168. package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
  169. package/fixtures/build-e2e/anon-author/paper.tex +1 -1
  170. package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
  171. package/fixtures/build-e2e/anon-href/paper.tex +1 -1
  172. package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
  173. package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
  174. package/fixtures/paper-sources/p1/paper.tex +30 -0
  175. package/fixtures/paper-sources/p1/refs.bib +6 -0
  176. package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
  177. package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
  178. package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
  179. package/fixtures/paper-sources/p1/tex-truth.json +37 -0
  180. package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
  181. package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
  182. package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
  183. package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
  184. package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
  185. package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
  186. package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
  187. package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
  188. package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
  189. package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
  190. package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
  191. package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
  192. package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
  193. package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
  194. package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
  195. package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
  196. package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
  197. package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
  198. package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
  199. package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
  200. package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
  201. package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
  202. package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
  203. package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
  204. package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
  205. package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
  206. package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
  207. package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
  208. package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
  209. package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
  210. package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
  211. package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
  212. package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
  213. package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
  214. package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
  215. package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
  216. package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
  217. package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
  218. package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
  219. package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
  220. package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
  221. package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
  222. package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
  223. package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
  224. package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
  225. package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
  226. package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
  227. package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
  228. package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
  229. package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
  230. package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
  231. package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
  232. package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
  233. package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
  234. package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
  235. package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
  236. package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
  237. package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
  238. package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
  239. package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
  240. package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
  241. package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
  242. package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
  243. package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
  244. package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
  245. package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
  246. package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
  247. package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
  248. package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
  249. package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
  250. package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
  251. package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
  252. package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
  253. package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
  254. package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
  255. package/package.json +2 -10
  256. package/scripts/check.test.ts +23 -1
  257. package/scripts/check.ts +7 -3
  258. package/scripts/coverage-config.test.ts +19 -10
  259. package/scripts/layer-legacy-frozen.test.ts +41 -2
  260. package/scripts/layer-legacy-frozen.ts +31 -2
  261. package/skills/grade-paper-writing/SKILL.md +4 -3
  262. package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
  263. package/skills/grade-paper-writing/prose-lint.mjs +11 -2
  264. package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
  265. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
  266. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
  267. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
  268. package/skills/verify-citations/SKILL.md +7 -4
  269. package/skills/verify-citations/SKILL.md.spec.ts +6 -3
  270. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -19
  271. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
  272. package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
  273. package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
  274. package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
  275. package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
  276. package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
  277. package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
  278. package/src/adapters/bibtex/index.test.ts +163 -0
  279. package/src/adapters/bibtex/index.ts +155 -0
  280. package/src/adapters/latex/filecontents.ts +114 -0
  281. package/src/adapters/latex/includes.ts +18 -10
  282. package/src/adapters/latex/index.test.ts +73 -0
  283. package/src/adapters/latex/index.ts +2 -0
  284. package/src/adapters/latex/nodes.ts +64 -0
  285. package/src/adapters/latex/parse.test.ts +38 -0
  286. package/src/adapters/latex/parse.ts +67 -6
  287. package/src/adapters/memory/index.ts +17 -0
  288. package/src/adapters/references/index.test.ts +75 -21
  289. package/src/adapters/references/index.ts +37 -32
  290. package/src/adapters/sources-record/index.test.ts +87 -0
  291. package/src/adapters/sources-record/index.ts +82 -0
  292. package/src/adapters/tex-output/blg.ts +27 -0
  293. package/src/adapters/tex-output/fls.ts +24 -0
  294. package/src/adapters/tex-output/index.test.ts +171 -0
  295. package/src/adapters/tex-output/index.ts +12 -0
  296. package/src/bib-rules.test.ts +356 -0
  297. package/src/bib-rules.ts +228 -0
  298. package/src/bibliography-at.test.ts +286 -0
  299. package/src/build.test.ts +210 -0
  300. package/src/build.ts +100 -35
  301. package/src/claim-provenance.test.ts +33 -7
  302. package/src/claim-provenance.ts +2 -2
  303. package/src/cli.harness.mjs +20 -10
  304. package/src/cli.test.ts +14 -10
  305. package/src/cli.ts +37 -28
  306. package/src/domain/paper-source.test.ts +58 -20
  307. package/src/domain/paper-source.ts +38 -42
  308. package/src/domain/paper-sources.ts +94 -0
  309. package/src/domain/sources-record.test.ts +71 -0
  310. package/src/domain/sources-record.ts +136 -0
  311. package/src/domain/tex-run.test.ts +228 -0
  312. package/src/domain/tex-run.ts +139 -0
  313. package/src/heading-case.test.ts +24 -10
  314. package/src/latex-log.ts +20 -0
  315. package/src/latex-loop.harness.ts +1 -0
  316. package/src/latex-loop.ts +2 -0
  317. package/src/linted-set.test.ts +9 -1
  318. package/src/new-paper.harness.mjs +3 -2
  319. package/src/new-paper.test.ts +9 -5
  320. package/src/package-dirs.ts +3 -2
  321. package/src/paper-includes.test.ts +157 -38
  322. package/src/paper-includes.ts +39 -35
  323. package/src/paper-prose.test.ts +34 -5
  324. package/src/paper-prose.ts +2 -2
  325. package/src/paper-record.test.ts +173 -0
  326. package/src/paper-record.ts +66 -0
  327. package/src/paper-settings-commands.test.ts +5 -2
  328. package/src/paper-sources.ts +151 -0
  329. package/src/ports/bib-reader.ts +27 -0
  330. package/src/ports/check-references.ts +7 -3
  331. package/src/ports/latex.ts +19 -0
  332. package/src/ports/sources-codec.ts +11 -0
  333. package/src/ports/tex-output.ts +17 -0
  334. package/src/recorded-bibliography.ts +297 -0
  335. package/src/reference-rules.ts +217 -104
  336. package/src/references.test.ts +520 -177
  337. package/src/references.ts +185 -37
  338. package/src/register-bands.test.ts +5 -1
  339. package/src/register-bands.ts +3 -5
  340. package/src/register.test.ts +6 -1
  341. package/src/register.ts +2 -2
  342. package/src/rule-cases.test.ts +68 -12
  343. package/src/rule-docs.test.ts +0 -1
  344. package/src/sources-record.test.ts +308 -0
  345. package/src/sources-record.ts +231 -0
  346. package/src/sources-rules.test.ts +152 -0
  347. package/src/sources-rules.ts +117 -0
  348. package/src/tex-paper.test.ts +148 -78
  349. package/src/tex-paper.ts +31 -61
  350. package/src/tex-venue-rules.test.ts +36 -8
  351. package/src/tex-venue-rules.ts +3 -5
  352. package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
  353. package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
  354. package/dist/eslint-rules/bib-reachable-entry.js +0 -75
  355. package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
  356. package/eslint-rules/bib-reachable-entry.ts +0 -76
@@ -0,0 +1,117 @@
1
+ /**
2
+ * `paper/sources-fresh` — the one finding for a paper whose build record cannot be used
3
+ * (docs/design/paper-sources.md §1, docs/rules/paper/sources-fresh.md). Every rule that needs to know
4
+ * which files make up the paper, or which bibliography TeX reads, reads `_build/sources.json`
5
+ * (`paperRecord`, `src/paper-record.ts`); when there is none, or the paper has changed since, they
6
+ * are silent and this rule speaks once instead:
7
+ *
8
+ * no record "not built" — run `npx paperlint build`
9
+ * a record this paperlint cannot read the reason, and the same command
10
+ * a file changed since the build which files, and the same command
11
+ *
12
+ * A warning, not an error, for the reason `pdf/measured` is one: lint often runs where nothing is
13
+ * built (the CI action only lints). Each message says what stays unchecked until a build: the files
14
+ * `paper.tex` includes are not linted, and the rules over the bibliography say nothing.
15
+ */
16
+ import { basename, dirname } from "node:path";
17
+ import { callerPath } from "./caller-path.ts";
18
+ import { describeChanges } from "./domain/sources-record.ts";
19
+ import { MAIN_FILE } from "./domain/paper-sources.ts";
20
+ import { paperRecord, type RecordReadDeps } from "./paper-record.ts";
21
+
22
+ /** One finding: its message, and the values the message names. */
23
+ interface Finding {
24
+ readonly messageId: string;
25
+ readonly data?: Readonly<Record<string, string>>;
26
+ }
27
+
28
+ interface Loc {
29
+ readonly line: number;
30
+ readonly column: number;
31
+ }
32
+ interface RuleContext {
33
+ readonly filename: string;
34
+ readonly sourceCode: { getLocFromIndex(i: number): Loc };
35
+ report(
36
+ d: Finding & { readonly loc: Readonly<{ start: Loc; end: Loc }> },
37
+ ): void;
38
+ }
39
+ export interface SourcesRuleModule {
40
+ readonly meta: {
41
+ readonly type: "problem" | "suggestion";
42
+ readonly docs: {
43
+ readonly description: string;
44
+ readonly url: string;
45
+ };
46
+ readonly schema: readonly object[];
47
+ readonly messages: Readonly<Record<string, string>>;
48
+ };
49
+ create(context: RuleContext): { "root:exit"?: () => void };
50
+ }
51
+
52
+ const BUILD = "`npx paperlint build`";
53
+ const UNTIL =
54
+ "until then the files `paper.tex` includes are not linted, and the rules that read the bibliography say nothing";
55
+
56
+ const META: SourcesRuleModule["meta"] = {
57
+ type: "problem",
58
+ docs: {
59
+ description:
60
+ "the build recorded which files TeX read, and the paper has not changed since (_build/sources.json)",
61
+ url: "https://github.com/zernie/paperlint/blob/main/docs/rules/paper/sources-fresh.md",
62
+ },
63
+ schema: [],
64
+ messages: {
65
+ notBuilt: `the paper has not been built — run ${BUILD}, which records the files TeX reads. Until it has, the files \`paper.tex\` includes are not linted, and the rules that read the bibliography say nothing`,
66
+ unreadable: `the last build's record cannot be used ({{why}}) — run ${BUILD}; ${UNTIL}`,
67
+ stale: `the paper changed since the last build ({{changed}}) — run ${BUILD}; ${UNTIL}`,
68
+ },
69
+ };
70
+
71
+ /** The finding for a paper's record, or none when it is current. */
72
+ function findingFor(dir: string, deps: RecordReadDeps): Finding | null {
73
+ const r = paperRecord(dir, deps);
74
+ switch (r.kind) {
75
+ case "none":
76
+ return r.unreadable === null
77
+ ? { messageId: "notBuilt" }
78
+ : { messageId: "unreadable", data: { why: r.unreadable } };
79
+ case "stale":
80
+ return {
81
+ messageId: "stale",
82
+ data: { changed: describeChanges(r.changed) },
83
+ };
84
+ case "fresh":
85
+ return null;
86
+ }
87
+ }
88
+
89
+ /** `paper/sources-fresh`, reading the paper through `deps`. It acts on `paper.tex` only. */
90
+ export function sourcesRules(
91
+ deps: RecordReadDeps,
92
+ ): Readonly<Record<"sources-fresh", SourcesRuleModule>> {
93
+ return {
94
+ "sources-fresh": {
95
+ meta: META,
96
+ create: (context) =>
97
+ basename(context.filename) !== MAIN_FILE
98
+ ? {}
99
+ : {
100
+ "root:exit": () => {
101
+ const f = findingFor(
102
+ dirname(callerPath(context.filename)),
103
+ deps,
104
+ );
105
+ if (f === null) return;
106
+ const at = context.sourceCode.getLocFromIndex(0);
107
+ context.report({ loc: { start: at, end: at }, ...f });
108
+ },
109
+ },
110
+ },
111
+ };
112
+ }
113
+
114
+ /** The level it is on at for every `paper.tex` in paperlint's own config. */
115
+ export const SOURCES_RULE_LEVELS = {
116
+ "paper/sources-fresh": "warn",
117
+ } as const;
@@ -1,106 +1,162 @@
1
1
  /**
2
- * The reading every parse-tree rule shares (`readPaper`), the files of the paper's body that lint
3
- * reads on their own (`bodyFiles`), and where a finding of the assembled text is reported
4
- * (`reportInPaper`).
2
+ * The reading every parse-tree rule shares (`readPaper`): the paper's text with the files TeX read
3
+ * spliced in — the files the build's record lists, and no others — and where a finding of that text
4
+ * is reported (`reportInPaper`). The record fed to it is TeX's own answer for a planted paper
5
+ * (`fixtures/paper-sources/<name>/tex-truth.json`).
5
6
  */
6
7
  import { describe, expect, it } from "vitest";
7
8
  import { latexReader } from "./adapters/latex/index.ts";
8
9
  import { memoryFiles } from "./adapters/memory/index.ts";
10
+ import { sourcesCodec } from "./adapters/sources-record/index.ts";
9
11
  import { join } from "node:path";
10
12
  import { texInputsDir } from "./package-dirs.ts";
11
- import { bodyFiles, readPaper, reportInPaper } from "./tex-paper.ts";
12
-
13
- describe("readPaper — the paper as the rules read it", () => {
14
- it("reads includes from the paper's own directory", () => {
15
- const p = readPaper("/p/paper.tex", "x\\input{s/a}y", {
16
- files: memoryFiles({ "/p/s/a.tex": "AAA" }),
17
- latex: latexReader,
18
- });
19
- expect(p.text).toBe("xAAAy");
13
+ import { readPaper, reportInPaper } from "./tex-paper.ts";
14
+ import {
15
+ builtFixture,
16
+ builtPaper,
17
+ fixtureFiles,
18
+ } from "../test/recorded-fixture.ts";
19
+
20
+ /** What `readPaper` reads with: the files held in memory, the LaTeX reader, the record's schema. */
21
+ const proseDeps = (files: Record<string, string>) => ({
22
+ files: memoryFiles(files),
23
+ latex: latexReader,
24
+ codec: sourcesCodec,
25
+ });
26
+
27
+ const MAIN = "/p/paper.tex";
28
+ const textOf = (files: Record<string, string>): string => files[MAIN] ?? "";
29
+
30
+ describe("readPaper — the paper as the rules read it, from the files TeX read", () => {
31
+ it("🔴 splices a file the record lists where its include stands, and TeX's end of line after it", () => {
32
+ // p1: TeX read sections/intro.tex, and the file's last line ends with a newline.
33
+ const files = builtFixture("p1", "/p");
34
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
35
+ const main = textOf(files);
36
+ const intro = files["/p/sections/intro.tex"] ?? "";
37
+ expect(p.text).toBe(main.replace("\\input{sections/intro}", intro));
20
38
  expect(p.main).toBe("paper.tex");
21
39
  });
22
40
 
23
- it("an include the paper's directory lacks is found in paperlint's own inputs, as the build finds it", () => {
24
- const p = readPaper("/p/paper.tex", "x\\input{guards}y", {
25
- files: memoryFiles({ [join(texInputsDir(), "guards.tex")]: "G" }),
26
- latex: latexReader,
27
- });
28
- expect({ text: p.text, missing: p.missing }).toEqual({
29
- text: "xGy",
30
- missing: [],
31
- });
41
+ it("an include behind a comment or `\\iffalse` is not spliced: the record does not list it", () => {
42
+ const files = builtFixture("p1", "/p");
43
+ const { text } = readPaper(MAIN, textOf(files), proseDeps(files));
44
+ expect(text).not.toContain("Dead section");
45
+ expect(text).not.toContain("Parked section");
46
+ });
47
+
48
+ it("🔴 a file the text names after \\end{document} is not spliced: TeX never read it", () => {
49
+ // v23-parked-include: `\input{parked}` stands past the end of the document; parked.tex is on
50
+ // disk, and the record lists paper.tex alone.
51
+ const files = builtFixture("v23-parked-include", "/p");
52
+ expect(files["/p/parked.tex"]).toBeDefined();
53
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
54
+ expect(p.text).toBe(textOf(files));
55
+ expect(p.text).not.toContain("\\bibliography{old}");
56
+ });
57
+
58
+ it("🔴 a .tex a filecontents block wrote is spliced where the body \\input's it: TeX typeset its text", () => {
59
+ // v29-generated-input: the record lists body.tex, which the block in paper.tex wrote.
60
+ const files = builtFixture("v29-generated-input", "/p");
61
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
62
+ expect(p.text).toBe(
63
+ textOf(files).replace("\\input{body}", files["/p/body.tex"] ?? ""),
64
+ );
65
+ expect([...new Set(p.segments.map((s) => s.file))]).toEqual([
66
+ "paper.tex",
67
+ "body.tex",
68
+ ]);
32
69
  });
33
70
 
34
- it("the paper's directory comes first: its file wins over paperlint's of the same name", () => {
35
- const p = readPaper("/p/paper.tex", "\\input{guards}", {
36
- files: memoryFiles({
37
- "/p/guards.tex": "mine",
38
- [join(texInputsDir(), "guards.tex")]: "package",
39
- }),
40
- latex: latexReader,
41
- });
42
- expect(p.text).toBe("mine");
71
+ it("🔴 with no record, the paper is paper.tex alone: its includes are not read", () => {
72
+ const files = fixtureFiles("p1", "/p");
73
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
74
+ expect(p.text).toBe(textOf(files));
75
+ expect(p.segments).toHaveLength(1);
76
+ });
77
+
78
+ it("🔴 with a stale record, the paper is paper.tex alone", () => {
79
+ const files = {
80
+ ...builtFixture("p1", "/p"),
81
+ "/p/sections/intro.tex": "\\section{Changed}\n",
82
+ };
83
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
84
+ expect(p.text).toBe(textOf(files));
43
85
  });
44
86
  });
45
87
 
46
- describe("bodyFiles — the files of the body lint reads on their own", () => {
47
- const doc = (preamble: string, body: string): string =>
48
- `\\documentclass{article}\n${preamble}\n\\begin{document}\n${body}\n\\end{document}\n`;
49
- const files = (extra: Record<string, string> = {}) =>
50
- memoryFiles({
51
- "/p/macros.tex": "\\newcommand{\\x}{y}",
52
- "/p/sections/a.tex": "A\n\\input{sections/b}\n",
53
- "/p/sections/b.tex": "B",
54
- [join(texInputsDir(), "guards.tex")]: "G",
55
- ...extra,
56
- });
57
-
58
- it("every file an include in the document body brings in, nested ones too, by absolute path", () => {
59
- expect(
60
- bodyFiles("/p/paper.tex", doc("", "\\input{sections/a}"), {
61
- files: files(),
62
- latex: latexReader,
63
- }),
64
- ).toEqual({
65
- files: ["/p/sections/a.tex", "/p/sections/b.tex"],
66
- missing: [],
67
- });
88
+ describe("readPaper — which files, in which order", () => {
89
+ it("a file the record lists that is not on disk (it was absent when recorded and still is) contributes nothing", () => {
90
+ const files = builtPaper("/p", { "paper.tex": "x\\input{gone}y" }, [
91
+ { path: "paper.tex", role: "body" },
92
+ { path: "gone.tex", role: "body" },
93
+ ]);
94
+ expect(readPaper(MAIN, textOf(files), proseDeps(files)).text).toBe("xy");
68
95
  });
69
96
 
70
- it("not a preamble include (macros), and not a file found only in paperlint's inputs", () => {
71
- expect(
72
- bodyFiles(
73
- "/p/paper.tex",
74
- doc("\\input{macros}", "\\input{guards}\nText."),
75
- { files: files(), latex: latexReader },
76
- ),
77
- ).toEqual({ files: [], missing: [] });
97
+ it("files are found in the paper's directory only: paperlint's inputs are not the paper's", () => {
98
+ const files = {
99
+ ...builtPaper("/p", { "paper.tex": "x\\input{guards}y" }, [
100
+ { path: "paper.tex", role: "body" },
101
+ ]),
102
+ [join(texInputsDir(), "guards.tex")]: "G",
103
+ };
104
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
105
+ expect(p.text).toBe("xy");
78
106
  });
79
107
 
80
- it("an include that resolves nowhere is named, with the file that wrote it", () => {
81
- expect(
82
- bodyFiles("/p/paper.tex", doc("", "\\input{gone}"), {
83
- files: files(),
84
- latex: latexReader,
85
- }),
86
- ).toEqual({ files: [], missing: [{ file: "paper.tex", target: "gone" }] });
108
+ it("nested includes are spliced in the order TeX read them, each once", () => {
109
+ const files = builtPaper(
110
+ "/p",
111
+ {
112
+ "paper.tex": "A\n\\input{sections/a}\nZ\n",
113
+ "sections/a.tex": "a1\n\\input{sections/b}\na2\n",
114
+ "sections/b.tex": "b1",
115
+ },
116
+ [
117
+ { path: "paper.tex", role: "body" },
118
+ { path: "sections/a.tex", role: "body" },
119
+ { path: "sections/b.tex", role: "body" },
120
+ ],
121
+ );
122
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
123
+ expect(p.text).toBe("A\na1\nb1\n\na2\n\nZ\n");
124
+ expect([...new Set(p.segments.map((s) => s.file))]).toEqual([
125
+ "paper.tex",
126
+ "sections/a.tex",
127
+ "sections/b.tex",
128
+ ]);
87
129
  });
88
130
 
89
- it("a main file with no document environment: every include is the body", () => {
90
- expect(
91
- bodyFiles("/p/paper.tex", "\\input{sections/b}", {
92
- files: files(),
93
- latex: latexReader,
94
- }),
95
- ).toEqual({ files: ["/p/sections/b.tex"], missing: [] });
131
+ it("a preamble file the record lists is spliced too: it is a file of the paper", () => {
132
+ const files = builtPaper(
133
+ "/p",
134
+ {
135
+ "paper.tex": "\\input{macros}\n\\begin{document}\nText.\n",
136
+ "macros.tex": "\\newcommand{\\x}{y}\n",
137
+ },
138
+ [
139
+ { path: "paper.tex", role: "body" },
140
+ { path: "macros.tex", role: "preamble" },
141
+ ],
142
+ );
143
+ const p = readPaper(MAIN, textOf(files), proseDeps(files));
144
+ expect(p.text).toBe("\\newcommand{\\x}{y}\n\n\\begin{document}\nText.\n");
96
145
  });
97
146
  });
98
147
 
99
148
  describe("reportInPaper — where a finding of the assembled text is reported", () => {
100
- const paper = readPaper("/p/paper.tex", "x\n\\input{a}\ny", {
101
- files: memoryFiles({ "/p/a.tex": "one\ntwo" }),
102
- latex: latexReader,
103
- });
149
+ const main = "x\n\\input{a}\ny";
150
+ const paper = readPaper(
151
+ "/p/paper.tex",
152
+ main,
153
+ proseDeps(
154
+ builtPaper("/p", { "paper.tex": main, "a.tex": "one\ntwo" }, [
155
+ { path: "paper.tex", role: "body" },
156
+ { path: "a.tex", role: "body" },
157
+ ]),
158
+ ),
159
+ );
104
160
  const report = (
105
161
  at: { start: number; end: number } | null,
106
162
  messageId = "m",
@@ -146,3 +202,17 @@ describe("reportInPaper — where a finding of the assembled text is reported",
146
202
  ]);
147
203
  });
148
204
  });
205
+
206
+ describe("readPaper — how an include names its file", () => {
207
+ it("🔴 an include spelled otherwise than the disk is the file the record names: TeX opened it by either spelling", () => {
208
+ const files = builtPaper(
209
+ "/p",
210
+ { "paper.tex": "x\\input{Sections/Intro}y", "sections/intro.tex": "I" },
211
+ [
212
+ { path: "paper.tex", role: "body" },
213
+ { path: "sections/intro.tex", role: "body" },
214
+ ],
215
+ );
216
+ expect(readPaper(MAIN, textOf(files), proseDeps(files)).text).toBe("xI\ny");
217
+ });
218
+ });
package/src/tex-paper.ts CHANGED
@@ -7,9 +7,13 @@
7
7
  * file at the include in `paper.tex` that brought it in — the one place ESLint can point to in this
8
8
  * file — with the included file, line and column at the front of the message.
9
9
  *
10
- * An include is looked for where the build tells TeX to look (`texSearchPath`): the paper's own
11
- * directory, then paperlint's inputs. `bodyFiles` names the files of the paper's BODY the author
12
- * wrote — what the rules over ESLint's own LaTeX text read one by one, each at its own path.
10
+ * 🔴 WHICH FILES ARE THE PAPER'S IS TEX'S ANSWER, NOT THIS MODULE'S (docs/design/paper-sources.md §1).
11
+ * `readPaper` splices only the files the build's record lists (`_build/sources.json`, `paperRecord`):
12
+ * the files of the paper directory TeX read. An include of a file the record does not list — behind
13
+ * `\iffalse`, never reached — contributes nothing, as it did to TeX. With no
14
+ * record, or a stale one, the paper is `paper.tex` alone, and `paper/sources-fresh` says the files it
15
+ * includes went unlinted. Where an include stands in the text is the only thing read from the text:
16
+ * whether the file it names is part of the paper is the record's to say, never the disk's.
13
17
  */
14
18
  import { basename, dirname, join } from "node:path";
15
19
  import {
@@ -21,8 +25,8 @@ import type { Span } from "./domain/tex-document.ts";
21
25
  import type { LatexReader } from "./ports/latex.ts";
22
26
  import type { Files } from "./ports/files.ts";
23
27
  import { callerPath } from "./caller-path.ts";
24
- import type { AbsolutePath } from "./domain/paths.ts";
25
- import { texSearchPath } from "./package-dirs.ts";
28
+ import { paperRecord, type RecordReadDeps } from "./paper-record.ts";
29
+ import { spelledIn } from "./domain/sources-record.ts";
26
30
  import type { Located, TexRuleContext } from "./tex-venue-rules.ts";
27
31
 
28
32
  /** What reading a paper needs: the disk, and the LaTeX reader. */
@@ -31,75 +35,41 @@ export interface PaperDeps {
31
35
  readonly latex: LatexReader;
32
36
  }
33
37
 
34
- /** The paper whose main file is `filename` with the text `src`, its includes spliced. */
38
+ /** What `readPaper` needs: the disk, the LaTeX reader (where an include stands), and the record's schema. */
39
+ export interface ProseDeps extends PaperDeps, RecordReadDeps {}
40
+
41
+ /** The text of a file read from the disk, or null when there was none. */
42
+ export const decoded = (b: Uint8Array | null): string | null =>
43
+ b === null ? null : new TextDecoder().decode(b);
44
+
45
+ /**
46
+ * The paper whose main file is `filename` with the text `src`: the files the last build's record lists
47
+ * spliced where their includes stand. No record, or one the paper has outgrown, is `src` alone.
48
+ */
35
49
  export function readPaper(
36
50
  filename: string,
37
51
  src: string,
38
- deps: PaperDeps,
52
+ deps: ProseDeps,
39
53
  ): PaperSource {
40
54
  const dir = dirname(filename);
55
+ const record = paperRecord(dir, deps);
56
+ const listed =
57
+ record.kind === "fresh" ? record.record.inputs.map((i) => i.path) : [];
41
58
  return assemblePaper(basename(filename), src, {
42
- includes: deps.latex.includes,
59
+ // No record: no include is looked at, so there is nothing to splice.
60
+ includes: record.kind === "fresh" ? deps.latex.includes : () => [],
43
61
  documentBody: deps.latex.documentBody,
44
62
  read: (rel) => {
45
- const at = located(dir, rel, deps.files);
46
- const b = at === null ? null : deps.files.readBytes(at);
47
- return b === null ? null : new TextDecoder().decode(b);
63
+ const recorded = spelledIn(listed, rel);
64
+ return recorded === null
65
+ ? null
66
+ : decoded(deps.files.readBytes(callerPath(join(dir, recorded))));
48
67
  },
49
68
  });
50
69
  }
51
70
 
52
- /** `rel` in the first directory of the paper's TeX search path that holds it, or null. */
53
- const located = (dir: string, rel: string, files: Files): AbsolutePath | null =>
54
- texSearchPath(dir)
55
- .map((d) => callerPath(join(d, rel)))
56
- .find((p) => files.isFile(p)) ?? null;
57
-
58
- /** An include that resolved nowhere: the file that wrote it, and the path as written. */
59
- export interface Unread {
60
- readonly file: string;
61
- readonly target: string;
62
- }
63
-
64
- /**
65
- * The files of the body of the paper whose main file is `filename`: every file an include inside its
66
- * `document` environment brings in, nested ones too, found in the paper's own directory. Not a
67
- * preamble include (macros are not the body), and not a file found only in paperlint's inputs (not
68
- * the author's text). `missing` is every include, anywhere, that resolved nowhere.
69
- */
70
- export function bodyFiles(
71
- filename: string,
72
- src: string,
73
- deps: PaperDeps,
74
- ): {
75
- readonly files: readonly AbsolutePath[];
76
- readonly missing: readonly Unread[];
77
- } {
78
- const dir = dirname(filename);
79
- const paper = readPaper(filename, src, deps);
80
- const body = deps.latex.documentBody(src);
81
- const inBody = (via: Span | null): boolean =>
82
- via !== null &&
83
- (body === null || (via.start >= body.start && via.end <= body.end));
84
- const own = (rel: string): AbsolutePath | null => {
85
- const at = located(dir, rel, deps.files);
86
- return at === callerPath(join(dir, rel)) ? at : null;
87
- };
88
- return {
89
- files: [
90
- ...new Set(
91
- paper.segments
92
- .filter((s) => s.file !== paper.main && inBody(s.via))
93
- .map((s) => own(s.file))
94
- .filter((f): f is AbsolutePath => f !== null),
95
- ),
96
- ],
97
- missing: paper.missing.map(({ file, target }) => ({ file, target })),
98
- };
99
- }
100
-
101
71
  /** Line and column (1-based) of an offset in a text. */
102
- function lineColumn(text: string, at: number): string {
72
+ export function lineColumn(text: string, at: number): string {
103
73
  const line = text.slice(0, at).split("\n").length;
104
74
  const column = at - text.lastIndexOf("\n", at - 1);
105
75
  return `${String(line)}:${String(column)}`;
@@ -7,6 +7,7 @@ import { readdirSync, readFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { describe, expect, it } from "vitest";
9
9
  import { memoryFiles } from "./adapters/memory/index.ts";
10
+ import { sourcesCodec } from "./adapters/sources-record/index.ts";
10
11
  import { presetsDir } from "./package-dirs.ts";
11
12
  import { latexReader } from "./adapters/latex/index.ts";
12
13
  import {
@@ -21,10 +22,13 @@ import {
21
22
  } from "./tex-venue-rules.ts";
22
23
  import { resolvePreset } from "./presets.ts";
23
24
  import { buildConfig, SHIPPED_RULES } from "./cli.ts";
25
+ import { builtPaper } from "../test/recorded-fixture.ts";
24
26
  import { venuePreset } from "../test/support.ts";
25
27
 
26
28
  const VENUES = presetsDir();
27
29
  const PAPER = "/work/papers/p";
30
+ /** What the rules read with besides the files: the presets, the LaTeX reader, the record's schema. */
31
+ const READERS = { venuesDir: VENUES, latex: latexReader, codec: sourcesCodec };
28
32
  const shipped = Object.fromEntries(
29
33
  readdirSync(VENUES)
30
34
  .filter((f) => f.endsWith(".jsonc") || f.endsWith(".json"))
@@ -70,7 +74,7 @@ function lint(
70
74
  ...extra,
71
75
  });
72
76
  const out: Finding[] = [];
73
- const deps = { files, venuesDir: VENUES, latex: latexReader };
77
+ const deps = { files, ...READERS };
74
78
  for (const [id, rule] of [
75
79
  ...Object.entries(texVenueRules(deps)).map(
76
80
  ([n, r]) => [`tex/${n}`, r] as const,
@@ -207,13 +211,29 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
207
211
  "\\documentclass[conference,compsoc]{IEEEtran}",
208
212
  "Text.\n\\input{sections/closing}",
209
213
  );
210
- const extra = {
211
- [`${PAPER}/sections/closing.tex`]:
212
- "\\section*{LLM Usage Statement}\nNone.\n",
213
- };
214
+ // The build recorded TeX reading both files.
215
+ const extra = builtPaper(
216
+ PAPER,
217
+ {
218
+ "paper.tex": tex,
219
+ "sections/closing.tex": "\\section*{LLM Usage Statement}\nNone.\n",
220
+ },
221
+ [
222
+ { path: "paper.tex", role: "body" },
223
+ { path: "sections/closing.tex", role: "body" },
224
+ ],
225
+ );
214
226
  expect(lint(tex, AIDC, { extra })).toEqual([]);
215
227
  // Without the file, the section is missing.
216
228
  expect(ids(lint(tex, AIDC))).toEqual(["tex/required-section:missing"]);
229
+ // 🔴 The file on disk and no record of the build: the paper is paper.tex, and the section is missing.
230
+ const unbuilt = {
231
+ [`${PAPER}/sections/closing.tex`]:
232
+ "\\section*{LLM Usage Statement}\nNone.\n",
233
+ };
234
+ expect(ids(lint(tex, AIDC, { extra: unbuilt }))).toEqual([
235
+ "tex/required-section:missing",
236
+ ]);
217
237
  });
218
238
 
219
239
  it("a leftover venue name in an included file is reported at the \\input, naming the file and line", () => {
@@ -222,9 +242,17 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
222
242
  "Text.\n\\input{intro}\n\\section*{LLM Usage Statement}\nNone.",
223
243
  );
224
244
  const fs = lint(tex, AIDC, {
225
- extra: {
226
- [`${PAPER}/intro.tex`]: "One line.\nFirst written for AgenticDev.\n",
227
- },
245
+ extra: builtPaper(
246
+ PAPER,
247
+ {
248
+ "paper.tex": tex,
249
+ "intro.tex": "One line.\nFirst written for AgenticDev.\n",
250
+ },
251
+ [
252
+ { path: "paper.tex", role: "body" },
253
+ { path: "intro.tex", role: "body" },
254
+ ],
255
+ ),
228
256
  });
229
257
  expect(fs.map((f) => [f.line, f.message.split(": ")[0]])).toEqual([
230
258
  [5, "intro.tex:2:19"],
@@ -49,7 +49,7 @@ import type { LatexReader } from "./ports/latex.ts";
49
49
  import { paperPreset, shippedVenueNames, type Preset } from "./presets.ts";
50
50
  import type { NamedVenue } from "./domain/venue-name.ts";
51
51
  import type { PaperSource } from "./domain/paper-source.ts";
52
- import { readPaper, reportInPaper } from "./tex-paper.ts";
52
+ import { readPaper, reportInPaper, type ProseDeps } from "./tex-paper.ts";
53
53
  import type { RequiredSection } from "./tex-requirements.ts";
54
54
  import type { Finding, VenueRuleDeps } from "./venue-rules.ts";
55
55
  import { isPorting } from "./domain/cycle.ts";
@@ -476,10 +476,8 @@ export function otherVenues(
476
476
  .map(({ label, aliases, mentions }) => ({ label, aliases, mentions }));
477
477
  }
478
478
 
479
- /** What the venue-conformance rules are built with: the preset store, and the LaTeX reader. */
480
- export interface TexVenueRuleDeps extends VenueRuleDeps {
481
- readonly latex: LatexReader;
482
- }
479
+ /** What the venue-conformance rules are built with: the preset store, the LaTeX reader, and the paper's files as the build recorded them. */
480
+ export interface TexVenueRuleDeps extends VenueRuleDeps, ProseDeps {}
483
481
 
484
482
  /**
485
483
  * What a judge reads for the paper whose `paper.tex` is `filename` — the paper with its includes
@@ -1,22 +0,0 @@
1
- import type { RuleContext } from "./rule-context.ts";
2
- declare const _default: {
3
- rules: {
4
- "reachable-entry": {
5
- meta: {
6
- type: "suggestion";
7
- docs: {
8
- description: string;
9
- };
10
- schema: never[];
11
- messages: {
12
- unreachable: string;
13
- };
14
- };
15
- create(context: RuleContext): {
16
- "root:exit"(): void;
17
- };
18
- };
19
- };
20
- };
21
- export default _default;
22
- //# sourceMappingURL=bib-reachable-entry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bib-reachable-entry.d.ts","sourceRoot":"","sources":["../../eslint-rules/bib-reachable-entry.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;;;;;;;;;;;;;;4BAsB/B,WAAW;;;;;;AAfjC,wBA4CE"}