Checking a Paper with jsslintr

jsslintr checks LaTeX/BibTeX manuscripts against the Journal of Statistical Software (JSS) style guide and can apply many of the fixes automatically. This vignette walks through the typical workflow: lint, inspect, fix, re-lint.

A small example paper

Let’s write a deliberately sloppy two-file manuscript to work with:

writeLines(c(
  "\\documentclass[nojss]{jss}",
  "\\author{Jane Doe\\\\University of Somewhere}",
  "\\title{A great package for doing things in R}",
  "\\Abstract{We present a package for R, e.g. for plotting.}",
  "\\Keywords{stuff, things}",
  "\\begin{document}",
  "\\section{introduction}",
  "We use R and the ggplot2 package for our p-values.",
  "\\end{document}"
), "paper.tex")

writeLines(c(
  "@article{smith2020,",
  "  author = {John Smith},",
  "  title = {Some Methods for Data},",
  "  journal = {Journal of Statistical Software},",
  "  year = {2020},",
  "  volume = {95},",
  "  pages = {1--20}",
  "}"
), "refs.bib")

Lint

jsslint() takes the file paths, reads them, and returns a check result. Printing it gives a quick overview; each line is one issue in file:line:column form. (Called with no arguments, jsslint() checks every .tex/.ltx/.bib file in the working directory.)

library("jsslintr")
res <- jsslint(c("paper.tex", "refs.bib"))
res
#> JSS style check (journal "jss"): paper.tex, refs.bib
#> Compliance: 75.0%
#> 
#> 6 issues (6 warnings; 5 auto-fixable)
#> 
#> paper.tex:3:1  [warning] JSS-CAP-001    \title{} is in title style (principal words capitalised)
#> paper.tex:4:36 [warning] JSS-MARKUP-001 Programming-language names in prose are wrapped in \proglang{}
#> paper.tex:4:39 [warning] JSS-HOUSE-001  "e.g." and "i.e." are followed by a comma so LaTeX does not treat the period as a sentence end
#> paper.tex:8:8  [warning] JSS-MARKUP-001 Programming-language names in prose are wrapped in \proglang{}
#> paper.tex:8:18 [warning] JSS-MARKUP-002 Software-package names in prose are wrapped in \pkg{}
#> paper.tex:8:42 [warning] JSS-OPER-001   Symbol-plus-noun constructs like p-value and t-statistic are typeset as $p$~value and $t$~statistic (tie, no hyphen)
#> 
#> Use summary() for a breakdown by rule and category, and jssfix() to apply the 5 automatic fixes.

Inspect

summary() breaks the issues down by rule, file, and style-guide category:

summary(res)
#> JSS style check (journal "jss"): paper.tex, refs.bib
#> Compliance: 75.0% -- 6 issues (6 warnings; 5 auto-fixable)
#> 
#> Files:
#>       file error warning info total
#>  paper.tex     0       6    0     6
#>   refs.bib     0       0    0     0
#> 
#> Issues by rule:
#>         rule_id severity issues fixable              guide_section
#>  JSS-MARKUP-001  warning      2       2                §4.1 Markup
#>     JSS-CAP-001  warning      1       0        §6.2 Capitalisation
#>   JSS-HOUSE-001  warning      1       1           §8.1 House style
#>  JSS-MARKUP-002  warning      1       1                §4.1 Markup
#>    JSS-OPER-001  warning      1       1 §4.4 Mathematical notation
#> 
#> Categories:
#>          category status rules_passed issues
#>          Preamble   PASS          8/8      0
#>         Structure   PASS          6/6      0
#>            Markup   FAIL          2/4      3
#>         Citations   PASS          3/3      0
#>        References   PASS          6/6      0
#>            BibTeX   PASS          5/5      0
#>            Naming   PASS          2/2      0
#>    Capitalization   FAIL          2/3      1
#>        Typography   PASS          4/4      0
#>     Abbreviations   PASS          1/1      0
#>        Code style   PASS          3/3      0
#>        Code width   PASS          1/1      0
#>         Operators   FAIL          3/4      1
#>  Cross-references   PASS          7/7      0
#>       House style   FAIL          2/3      1
#>           Project   PASS          2/2      0

For programmatic access — filtering, counting, or feeding issues into your own tooling — as.data.frame() gives one row per issue, including the suggested rewrite and whether an automatic fix exists:

df <- as.data.frame(res)
df[df$fixable, c("file", "line", "rule_id", "suggestion")]
#>        file line        rule_id
#> 2 paper.tex    4 JSS-MARKUP-001
#> 3 paper.tex    4  JSS-HOUSE-001
#> 4 paper.tex    8 JSS-MARKUP-001
#> 5 paper.tex    8 JSS-MARKUP-002
#> 6 paper.tex    8   JSS-OPER-001
#>                                                       suggestion
#> 2                                     Wrap 'R' in \\proglang{R}.
#> 3                             Add a comma after 'e.g.': 'e.g.,'.
#> 4                                     Wrap 'R' in \\proglang{R}.
#> 5                              Wrap 'ggplot2' in \\pkg{ggplot2}.
#> 6 Replace 'p-values' with '$p$~values' (italicized symbol, tie).

Fix

jssfix() applies the automatic fixes in place. Pass the check result (its files and options are reused) or file paths directly. With dry_run = TRUE it only shows the diff of what would change:

jssfix(res, dry_run = TRUE)
#> --- paper.tex
#> +++ paper.tex
#> @@ -1,9 +1,9 @@
#>  \documentclass[nojss]{jss}
#>  \author{Jane Doe\\University of Somewhere}
#>  \title{A great package for doing things in R}
#> -\Abstract{We present a package for R, e.g. for plotting.}
#> +\Abstract{We present a package for \proglang{R}, e.g., for plotting.}
#>  \Keywords{stuff, things}
#>  \begin{document}
#>  \section{introduction}
#> -We use R and the ggplot2 package for our p-values.
#> +We use \proglang{R} and the \pkg{ggplot2} package for our $p$~values.
#>  \end{document}
#> 
#> Dry run: 5 fixes would be applied to 1 file. Re-run with dry_run = FALSE to write.

That looks right, so let it write the files:

jssfix(res)
#> Applied 5 fixes to 1 file:
#>   paper.tex: 5 fixes
#> Re-run jsslint() to review the remaining issues.

Fixes are applied atomically, and a fix that would reintroduce its own violation is rolled back. Overlapping fixes are resolved to one winner per span; if any were skipped as conflicting, running jssfix() again picks them up. A rules argument restricts fixing to selected rules, e.g., jssfix(res, rules = "JSS-HOUSE-001").

Re-lint

The remaining issues need manual attention — the printed suggestions say what to do:

jsslint(c("paper.tex", "refs.bib"))
#> JSS style check (journal "jss"): paper.tex, refs.bib
#> Compliance: 87.5%
#> 
#> 2 issues (2 warnings; 0 auto-fixable)
#> 
#> paper.tex:3:1  [warning] JSS-CAP-001  \title{} is in title style (principal words capitalised)
#> paper.tex:8:29 [warning] JSS-CITE-002 First occurrence of a software package has a citation within the same paragraph
#> 
#> Use summary() for a breakdown by rule and category.

Lower-level access

render() is the bare in-memory binding underneath jsslint(): it takes a named character vector (names = paths, values = contents) and returns a report rendered as "terminal", "json", "sarif", or "html" text — useful when the files never touch disk or you want machine-readable output:

cat(substr(render(c("paper.tex" = "\\documentclass{article}"),
  output = "json"
), 1, 300), "...\n")
#> {
#>   "categories": [
#>     {
#>       "category_id": "preamble",
#>       "rules_applied": 8,
#>       "rules_passed": 7,
#>       "status": "FAIL",
#>       "title": "Preamble"
#>     },
#>     {
#>       "category_id": "structure",
#>       "rules_applied": 6,
#>       "rules_passed": 6,
#>       "status": "PASS",
#>       "title": "St ...