mdrefine 0.2.0-rc.2 → 0.2.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 (130) hide show
  1. package/CHANGELOG.md +577 -0
  2. package/CODE_OF_CONDUCT.md +30 -0
  3. package/CONTRIBUTING.md +189 -16
  4. package/README.md +100 -154
  5. package/SECURITY.md +15 -13
  6. package/config.schema.json +91 -0
  7. package/dist/cli/diff.d.ts +10 -0
  8. package/dist/cli/diff.js +247 -0
  9. package/dist/cli/diff.js.map +1 -0
  10. package/dist/cli/main.js +270 -150
  11. package/dist/cli/main.js.map +1 -1
  12. package/dist/config/load.js +67 -6
  13. package/dist/config/load.js.map +1 -1
  14. package/dist/config/problems.d.ts +7 -0
  15. package/dist/config/problems.js +208 -0
  16. package/dist/config/problems.js.map +1 -0
  17. package/dist/config/resolve.d.ts +42 -109
  18. package/dist/config/resolve.js +193 -52
  19. package/dist/config/resolve.js.map +1 -1
  20. package/dist/core/directives.d.ts +7 -0
  21. package/dist/core/directives.js +25 -13
  22. package/dist/core/directives.js.map +1 -1
  23. package/dist/core/engine.d.ts +42 -4
  24. package/dist/core/engine.js +518 -79
  25. package/dist/core/engine.js.map +1 -1
  26. package/dist/core/frozen.d.ts +6 -0
  27. package/dist/core/frozen.js +15 -0
  28. package/dist/core/frozen.js.map +1 -0
  29. package/dist/core/scope.d.ts +30 -0
  30. package/dist/core/scope.js +122 -0
  31. package/dist/core/scope.js.map +1 -0
  32. package/dist/core/types.d.ts +202 -23
  33. package/dist/core/types.js.map +1 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js.map +1 -1
  36. package/dist/node.d.ts +3 -0
  37. package/dist/node.js +4 -0
  38. package/dist/node.js.map +1 -0
  39. package/dist/rules/blocks.d.ts +34 -0
  40. package/dist/rules/blocks.js +550 -0
  41. package/dist/rules/blocks.js.map +1 -0
  42. package/dist/rules/content.d.ts +2 -0
  43. package/dist/rules/content.js +85 -0
  44. package/dist/rules/content.js.map +1 -0
  45. package/dist/rules/dialects.js +10 -6
  46. package/dist/rules/dialects.js.map +1 -1
  47. package/dist/rules/links.js +302 -6
  48. package/dist/rules/links.js.map +1 -1
  49. package/dist/rules/lists.d.ts +2 -0
  50. package/dist/rules/lists.js +924 -0
  51. package/dist/rules/lists.js.map +1 -0
  52. package/dist/rules/structure.js +127 -1
  53. package/dist/rules/structure.js.map +1 -1
  54. package/dist/rules/style.d.ts +8 -2
  55. package/dist/rules/style.js +1137 -165
  56. package/dist/rules/style.js.map +1 -1
  57. package/dist/syntax/block-identifier.d.ts +18 -0
  58. package/dist/syntax/block-identifier.js +24 -0
  59. package/dist/syntax/block-identifier.js.map +1 -0
  60. package/dist/syntax/obsidian.d.ts +29 -11
  61. package/dist/syntax/obsidian.js +9 -10
  62. package/dist/syntax/obsidian.js.map +1 -1
  63. package/dist/syntax/parse.d.ts +87 -3
  64. package/dist/syntax/parse.js +247 -19
  65. package/dist/syntax/parse.js.map +1 -1
  66. package/dist/workspace/access.d.ts +12 -3
  67. package/dist/workspace/access.js +33 -5
  68. package/dist/workspace/access.js.map +1 -1
  69. package/dist/workspace/discovery.d.ts +85 -0
  70. package/dist/workspace/discovery.js +155 -0
  71. package/dist/workspace/discovery.js.map +1 -0
  72. package/dist/workspace/encoding.d.ts +24 -0
  73. package/dist/workspace/encoding.js +113 -0
  74. package/dist/workspace/encoding.js.map +1 -0
  75. package/dist/workspace/files.d.ts +92 -6
  76. package/dist/workspace/files.js +715 -59
  77. package/dist/workspace/files.js.map +1 -1
  78. package/dist/workspace/git.d.ts +54 -0
  79. package/dist/workspace/git.js +198 -0
  80. package/dist/workspace/git.js.map +1 -0
  81. package/dist/workspace/index.d.ts +58 -3
  82. package/dist/workspace/index.js +136 -37
  83. package/dist/workspace/index.js.map +1 -1
  84. package/dist/workspace/selection.d.ts +2 -2
  85. package/dist/workspace/selection.js +3 -3
  86. package/dist/workspace/selection.js.map +1 -1
  87. package/dist/workspace/slug.js +8 -1
  88. package/dist/workspace/slug.js.map +1 -1
  89. package/dist/workspace/vault.js +3 -1
  90. package/dist/workspace/vault.js.map +1 -1
  91. package/docs/api.md +239 -0
  92. package/docs/benchmarks.md +140 -200
  93. package/docs/changelog.md +1 -0
  94. package/docs/cli.md +281 -89
  95. package/docs/configuration.md +193 -480
  96. package/docs/design.md +163 -284
  97. package/docs/dialects.md +169 -0
  98. package/docs/examples.md +151 -109
  99. package/docs/index.md +21 -37
  100. package/docs/integrations.md +144 -0
  101. package/docs/obsidian.md +91 -115
  102. package/docs/plugins.md +147 -212
  103. package/docs/quick-start.md +68 -74
  104. package/docs/rules.md +1237 -0
  105. package/docs/versions.md +56 -0
  106. package/examples/README.md +17 -14
  107. package/examples/automation/summarize-report.mjs +5 -2
  108. package/examples/library/lint-folder.mjs +5 -4
  109. package/examples/library/lint-workspace.mjs +32 -0
  110. package/examples/plugin/notes/release-checklist.md +2 -2
  111. package/examples/plugin/rules.mjs +17 -23
  112. package/examples/workspaces/obsidian-vault/after/mdtools.config.jsonc +2 -2
  113. package/examples/workspaces/obsidian-vault/before/mdtools.config.jsonc +2 -2
  114. package/examples/workspaces/repository-docs/after/README.md +2 -2
  115. package/examples/workspaces/repository-docs/after/mdtools.config.jsonc +0 -2
  116. package/examples/workspaces/repository-docs/before/README.md +2 -2
  117. package/examples/workspaces/repository-docs/before/docs/guide.md +7 -7
  118. package/examples/workspaces/repository-docs/before/docs/reference.md +3 -1
  119. package/examples/workspaces/repository-docs/before/mdtools.config.jsonc +0 -2
  120. package/package.json +38 -12
  121. package/docs/contributing.md +0 -218
  122. package/docs/releases/0.1.0-beta.1.md +0 -39
  123. package/docs/releases/0.1.0-beta.2.md +0 -83
  124. package/docs/releases/0.1.0-beta.3.md +0 -61
  125. package/docs/releases/0.1.0-beta.4.md +0 -50
  126. package/docs/releases/0.1.0-rc.1.md +0 -99
  127. package/docs/releases/0.2.0-rc.1.md +0 -141
  128. package/docs/releases/0.2.0-rc.2.md +0 -225
  129. package/docs/releases.md +0 -144
  130. package/docs/testing.md +0 -93
@@ -1,63 +1,57 @@
1
1
  # Benchmarks
2
2
 
3
- `npm run benchmark` times the tool on synthetic documents. This page explains
4
- what is measured, records reference results, and says how good they are.
3
+ `npm run benchmark`, in a checkout of the repository, times the tool on
4
+ synthetic documents. This page explains what is measured, records reference
5
+ results, and says how good they are.
5
6
 
6
7
  ## In short
7
8
 
8
9
  - **Checking a whole workspace takes seconds.** From the command line, 500 pages
9
- of documentation (1.4 MB) are linted in about 2.9 s and checked for formatting
10
- in about 3.2 s. That is fast enough for a CI job or a check before every push.
11
- - **A single file is mostly start-up.** Every command needs about 0.25 s before
12
- it reads a document, nearly all of it Node.js loading the tool's code. Linting
13
- one file of a 500-file workspace costs little more than that. Pass many files
14
- to one command rather than running one command per file.
15
- - **Formatting is in the same range as Prettier.** Rewriting files takes
16
- about 1.6 times as long as Prettier needs for the same files, and checking
17
- files that are already formatted takes slightly less time than Prettier does.
18
- The two do different work: this tool also validates links, and re-reads every
19
- document it changed to confirm that it still means the same.
10
+ of documentation (1.4 MB) are linted in about 2.1 s and checked for formatting
11
+ in about 2.4 s, fast enough for CI or a check before every push.
12
+ - **A single file is mostly start-up.** Every command needs about 0.17 s before
13
+ it reads a document, nearly all of it Node.js loading the tool's code. Pass
14
+ many files to one command rather than running one command per file.
15
+ - **Formatting is in the same range as Prettier.** Rewriting files takes about
16
+ 1.8 times as long as Prettier, and checking formatted files slightly less,
17
+ although this tool also checks links and parses every changed document again
18
+ to confirm its meaning.
20
19
  - **Cost grows in step with size.** Sixteen times as many files take about
21
- sixteen times as long. One very large document costs somewhat more per
22
- kilobyte than a small one, about a quarter more at a megabyte, but nothing
23
- becomes disproportionately slow.
20
+ sixteen times as long, and a document of a megabyte costs about two thirds
21
+ more per kilobyte than a short one.
24
22
  - **Where it is weak.** The tool uses one CPU core and remembers nothing between
25
- runs, so every run repeats all the work, and formatting several thousand files
26
- takes tens of seconds. Its start-up is slow beside the 26 ms that Node.js
27
- itself needs. Memory grows with the workspace, though more slowly than the
28
- number of files: linting peaks near 165 MB for 126 pages and near 400 MB for
29
- 2,000. One very large document needs a multiple of its own size while it is
30
- parsed: about 70 times for prose and about 130 times for a long list of links.
23
+ runs, so formatting several thousand files takes tens of seconds every time.
24
+ Memory grows with the workspace, though more slowly than the number of files:
25
+ linting peaks near 170 MB for 126 pages and near 425 MB for 2,000. While one
26
+ document is parsed, it needs about 70 times its size for prose and about 130
27
+ times for a long list of links.
31
28
 
32
29
  ## What is measured
33
30
 
34
- The documents are generated, not collected: the same command produces the same
35
- bytes on every machine, and nothing private is involved. There are two sets.
31
+ The documents are generated, so the same command produces the same bytes on
32
+ every machine, and nothing private is involved:
36
33
 
37
- - **Repository documentation** for the GitHub profile: pages of about 2.7 kB
34
+ - **Repository documentation** for the GitHub dialect: pages of about 2.7 kB
38
35
  with prose, lists, a table, a code block, an alert, and links to headings on
39
36
  other pages.
40
37
  - **An Obsidian vault**: notes of about 1.2 kB with wikilinks, an embed, a
41
38
  callout, a task list, and a block reference.
42
39
 
43
- Every generated file needs formatting: its prose is on long lines, its emphasis
44
- uses the markers the default rules replace, and its tables are unaligned. That
45
- is the worst case for a formatter. Formatting the result a second time is the
46
- best case, and both are measured.
47
-
48
- Each time is the median of five runs that follow one untimed warm-up run. The
49
- library measurements call the package directly and touch no files. The
50
- command-line measurements start `mdtools` as a separate process on files on
51
- disk, so they include start-up, finding and reading files, and printing
52
- diagnostics.
40
+ Every generated file needs formatting: long prose lines, the emphasis markers
41
+ that the default rules replace, and unaligned tables. That is the worst case;
42
+ formatting the result again is the best case, and both are measured. Each time
43
+ is the median of five runs after one untimed warm-up run. The library
44
+ measurements call the package and touch no files; the command-line measurements
45
+ start `mdtools` as a process on files on disk, so they include start-up, finding
46
+ and reading files, and printing.
53
47
 
54
48
  ## Reference results
55
49
 
56
- - Node.js v24.21.0 on Linux x64; AMD EPYC 7763 64-Core Processor, 4 logical
50
+ - Node.js v24.21.0 on Linux x64; AMD EPYC 9V45 96-Core Processor, 4 logical
57
51
  CPUs.
58
52
  - Each time is the median of 5 runs after one warm-up run.
59
- - Prettier 3.9.9 is the comparison formatter.
60
- - Output hash: `d99a546f0f09ad93`.
53
+ - Prettier 3.9.10 is the comparison formatter.
54
+ - Output hash: `aea576e325adf396`.
61
55
 
62
56
  These results come from the repository's Benchmark workflow on a GitHub-hosted
63
57
  runner with four CPU cores, of which the tool uses one. Read them as an order of
@@ -69,26 +63,25 @@ magnitude and as ratios between rows, not as a promise for another machine.
69
63
 
70
64
  | Step | Time | Fastest to slowest run | Per file | Per second | Compared with parsing |
71
65
  | ------------------------------------------ | ------ | ---------------------- | -------- | ---------- | --------------------- |
72
- | Parse only | 1.01 s | 1.00 s to 1.04 s | 2.02 ms | 1.34 MB | 1.0× |
73
- | Lint | 2.10 s | 2.09 s to 2.14 s | 4.19 ms | 0.65 MB | 2.1× |
74
- | Format, every file needs changes | 4.83 s | 4.81 s to 4.88 s | 9.63 ms | 0.28 MB | 4.8× |
75
- | Format, every file already formatted | 2.56 s | 2.52 s to 2.58 s | 5.11 ms | 0.60 MB | 2.5× |
76
- | Prettier on the same files, for comparison | 3.04 s | 3.01 s to 3.07 s | 6.06 ms | 0.45 MB | 3.0× |
77
-
78
- - **Parse only** turns each file into a syntax tree and does nothing else. No
79
- rule can run without it, so it is the floor that the other rows are compared
80
- with.
66
+ | Parse only | 595 ms | 583 ms to 622 ms | 1.19 ms | 2.28 MB | 1.0× |
67
+ | Lint | 1.44 s | 1.43 s to 1.48 s | 2.87 ms | 0.94 MB | 2.4× |
68
+ | Format, every file needs changes | 3.40 s | 3.31 s to 3.42 s | 6.79 ms | 0.40 MB | 5.7× |
69
+ | Format, every file already formatted | 1.78 s | 1.72 s to 1.83 s | 3.54 ms | 0.86 MB | 3.0× |
70
+ | Prettier on the same files, for comparison | 1.90 s | 1.85 s to 1.97 s | 3.79 ms | 0.72 MB | 3.2× |
71
+
72
+ - **Parse only** turns each file into a syntax tree. No rule can run without it,
73
+ so it is the floor for the other rows.
81
74
  - **Lint** runs every enabled rule and checks every link, which includes reading
82
75
  the headings of each linked page.
83
- - **Format, every file needs changes** is the worst case. Formatting works in
84
- phases, and after each phase that changes a document the tool parses the
85
- result again and compares its meaning with the original. That safety check is
86
- why this row costs several times the parsing floor.
76
+ - **Format, every file needs changes** is the worst case. After each formatting
77
+ phase that changes a document, the tool parses the result again and compares
78
+ its meaning with the original, which is why this row costs several times the
79
+ floor.
87
80
  - **Format, every file already formatted** is the usual case, and what
88
- `format --check` costs in CI. Nothing changes, so nothing needs to be verified
81
+ `format --check` costs in CI: nothing changes, so nothing needs to be verified
89
82
  again.
90
83
  - **Prettier on the same files** formats each file with Prettier's Markdown
91
- formatter at the same width. It is here to give the numbers a familiar scale.
84
+ formatter at the same width, to give the numbers a familiar scale.
92
85
 
93
86
  ### Library: Obsidian vault
94
87
 
@@ -96,15 +89,13 @@ magnitude and as ratios between rows, not as a promise for another machine.
96
89
 
97
90
  | Step | Time | Fastest to slowest run | Per file | Per second | Compared with parsing |
98
91
  | ------------------------------------ | ------ | ---------------------- | -------- | ---------- | --------------------- |
99
- | Parse only | 479 ms | 475 ms to 503 ms | 0.96 ms | 1.29 MB | 1.0× |
100
- | Lint | 1.04 s | 1.02 s to 1.05 s | 2.07 ms | 0.59 MB | 2.2× |
101
- | Format, every file needs changes | 2.63 s | 2.61 s to 2.66 s | 5.26 ms | 0.23 MB | 5.5× |
102
- | Format, every file already formatted | 1.16 s | 1.15 s to 1.17 s | 2.31 ms | 0.54 MB | 2.4× |
92
+ | Parse only | 278 ms | 266 ms to 281 ms | 0.56 ms | 2.22 MB | 1.0× |
93
+ | Lint | 702 ms | 681 ms to 740 ms | 1.40 ms | 0.88 MB | 2.5× |
94
+ | Format, every file needs changes | 1.75 s | 1.71 s to 1.85 s | 3.50 ms | 0.35 MB | 6.3× |
95
+ | Format, every file already formatted | 793 ms | 771 ms to 820 ms | 1.59 ms | 0.78 MB | 2.9× |
103
96
 
104
- The vault's notes are less than half the size of the documentation pages, so
105
- each file is cheaper while the cost per megabyte is similar. The same pattern
106
- holds: linting costs about twice the parsing floor, formatting already formatted
107
- notes a little more, and rewriting every note about five times.
97
+ The notes are less than half the size of the documentation pages, so each file
98
+ is cheaper while the cost per megabyte is similar.
108
99
 
109
100
  ### Command line
110
101
 
@@ -112,22 +103,18 @@ The repository documentation above, as 501 files on disk.
112
103
 
113
104
  | Command | Time | Fastest to slowest run | Peak memory |
114
105
  | ---------------------------------------------- | ------ | ---------------------- | ----------- |
115
- | Start Node.js and exit, for comparison | 26 ms | 24 ms to 27 ms | 51.6 MB |
116
- | Start the CLI and print its version | 249 ms | 248 ms to 259 ms | 84.4 MB |
117
- | Lint the workspace | 2.91 s | 2.76 s to 2.93 s | 259.4 MB |
118
- | Lint one file of the workspace | 342 ms | 335 ms to 347 ms | 94.3 MB |
119
- | Format and write, every file needs changes | 6.30 s | 6.20 s to 6.33 s | 345.8 MB |
120
- | Check formatting, every file already formatted | 3.23 s | 3.20 s to 3.32 s | 342.5 MB |
121
-
122
- - **Start Node.js and exit** is the cost of the runtime alone,
123
- and **start the CLI and print its version** adds loading the tool. The
124
- difference is paid by every command, however small its input.
125
- - **Lint one file of the workspace** shows that the other files cost almost
126
- nothing when they are not selected: they are listed so that links can be
127
- checked, but only read when a link points at them.
128
- - The whole-workspace rows are close to the library rows above plus start-up.
129
- Finding, reading, and writing files is a small part of the total.
130
- - **Peak memory** is the most memory the process held, including Node.js itself.
106
+ | Start Node.js and exit, for comparison | 22 ms | 20 ms to 23 ms | 51.7 MB |
107
+ | Start the CLI and print its version | 165 ms | 161 ms to 166 ms | 84.4 MB |
108
+ | Lint the workspace | 2.11 s | 2.06 s to 2.18 s | 270.5 MB |
109
+ | Lint one file of the workspace | 238 ms | 234 ms to 240 ms | 101.6 MB |
110
+ | Format and write, every file needs changes | 4.53 s | 4.45 s to 4.68 s | 377.1 MB |
111
+ | Check formatting, every file already formatted | 2.43 s | 2.39 s to 2.48 s | 359.4 MB |
112
+
113
+ The difference between the first two rows, loading the tool, is paid by every
114
+ command. Linting one file shows that unselected files cost almost nothing: they
115
+ are listed so that links can be checked, and read only when a link points at
116
+ them. The whole-workspace rows are close to the library rows plus start-up. Peak
117
+ memory includes Node.js itself.
131
118
 
132
119
  ### More files
133
120
 
@@ -136,50 +123,38 @@ the median of 3 runs without a warm-up run.
136
123
 
137
124
  | Files | Size | Lint | Lint per file | Format with changes | Format per file | Peak memory of a command-line lint |
138
125
  | ----- | ------- | ------ | ------------- | ------------------- | --------------- | ---------------------------------- |
139
- | 126 | 0.34 MB | 533 ms | 4.23 ms | 1.23 s | 9.73 ms | 163.8 MB |
140
- | 501 | 1.36 MB | 2.10 s | 4.19 ms | 4.83 s | 9.63 ms | 259.4 MB |
141
- | 2,001 | 5.44 MB | 8.51 s | 4.25 ms | 19.66 s | 9.82 ms | 402.0 MB |
142
-
143
- If the tool slowed down as a workspace grows, the per-file columns would rise
144
- from row to row. They stay level, so the time for a larger workspace can be
145
- estimated by multiplying.
146
-
147
- Memory is different. The last column is the most memory a `mdtools lint` process
148
- held for a workspace of that size, including the roughly 85 MB that the tool
149
- needs before it reads a document. It grows with the workspace, though far more
150
- slowly than the number of files: sixteen times as many files take about 2.5
151
- times the memory. Until a run finishes, the tool keeps every file's text and
152
- diagnostics, and the anchors of every document that a link to a heading or block
153
- points to. Versions up to `0.2.0-rc.1` also kept the parsed form of each of
154
- those documents, and the previous reference run, made with that version, peaked
155
- at 496 MB for the 2,001 files. The figure also depends on when Node.js reclaims
156
- memory, so it is an upper bound on what the run needed, not an exact amount.
157
-
158
- These rows use the GitHub profile. For the Obsidian dialect the tool also keeps
159
- a table for finding a note by its name, with one entry for each file of the
160
- workspace, attachments included: about 70 bytes per file. Versions up to
161
- `0.2.0-rc.1` kept two tables that together held about 1.6 kB per file, 160 MB
162
- for a vault with 100,000 attachments, once a link had been searched for by name.
126
+ | 126 | 0.34 MB | 380 ms | 3.02 ms | 884 ms | 7.02 ms | 172.0 MB |
127
+ | 501 | 1.36 MB | 1.44 s | 2.87 ms | 3.40 s | 6.79 ms | 270.5 MB |
128
+ | 2,001 | 5.44 MB | 5.94 s | 2.97 ms | 14.70 s | 7.35 ms | 426.8 MB |
129
+
130
+ The per-file columns stay level, so the time for a larger workspace can be
131
+ estimated by multiplying. Memory grows far more slowly: sixteen times as many
132
+ files take about 2.5 times the memory, including about 85 MB that the tool needs
133
+ before it reads a document. Until a run finishes, the tool keeps every file's
134
+ text and diagnostics, and the anchors of every document that a link points into;
135
+ in the Obsidian dialect also about 70 bytes per file of the workspace,
136
+ attachments included, for finding notes by name. The figure depends on when
137
+ Node.js reclaims memory, so it is an upper bound.
163
138
 
164
139
  ### Larger documents
165
140
 
166
141
  | Document size | Format with changes | Per kilobyte |
167
142
  | ------------- | ------------------- | ------------ |
168
- | 17 kB | 41 ms | 2.42 ms |
169
- | 129 kB | 377 ms | 2.93 ms |
170
- | 1,024 kB | 3.09 s | 3.02 ms |
171
-
172
- The same check for one document instead of many files. The cost per kilobyte
173
- rises by about a quarter from a short document to one of a megabyte, which is
174
- far larger than documents usually are. The growth is mild, but it is growth: a
175
- very large document is slower than its size alone suggests.
176
-
177
- Memory for one document is a different matter. While a document is parsed, the
178
- parser holds a list of every token it found, and that list is many times larger
179
- than the text. These figures are the smallest heap limit
143
+ | 17 kB | 29 ms | 1.76 ms |
144
+ | 129 kB | 315 ms | 2.44 ms |
145
+ | 1,024 kB | 3.04 s | 2.97 ms |
146
+
147
+ The cost per kilobyte rises by about two thirds from a short document to one of
148
+ a megabyte, far larger than documents usually are. Earlier reference results, on
149
+ another processor, showed a rise of two fifths and of a quarter, so the size of
150
+ the rise depends on the runner. It is not explained yet; on Windows the cost per
151
+ kilobyte stays level ([issue
152
+ #300](https://github.com/viell-dev/slop-markdown-tools/issues/300)).
153
+
154
+ While a document is parsed, the parser holds a list of every token it found,
155
+ many times larger than the text. These are the smallest heap limits
180
156
  (`--max-old-space-size`) under which one generated document could be parsed,
181
- found by trying limits 8 MB apart on one machine. They depend on the document,
182
- not on the processor:
157
+ found in steps of 8 MB; they depend on the document, not on the processor:
183
158
 
184
159
  | Document | Size | Heap needed to parse |
185
160
  | ----------------------------------------------- | ------ | -------------------- |
@@ -188,78 +163,57 @@ not on the processor:
188
163
  | The same | 2.0 MB | 142 MB |
189
164
  | An index note: 13,400 list items, each one link | 2.2 MB | 291 MB |
190
165
 
191
- That is about 70 times the size of prose and about 130 times the size of the
192
- index note. For the index note, the parser's token list alone holds 240 MB once
193
- it is complete, about 18 kB for each list item; the syntax tree that the tool
194
- keeps afterwards is 40 MB. The list belongs to the parser the tool builds on,
195
- and no rule or setting changes it. Each document is parsed by itself and its
196
- token list is released before the next one, so a run needs the memory of its
197
- largest document, not the sum. Node.js allows a few gigabytes by default, which
198
- is enough for documents of several megabytes; under a tighter limit, such as
199
- `--max-old-space-size=256`, a document like the index note is what fails.
166
+ For the index note, the token list alone holds 240 MB, and the syntax tree that
167
+ the tool keeps afterwards 40 MB. No rule or setting changes this. Each document
168
+ is parsed by itself, so a run needs the memory of its largest document, not the
169
+ sum; Node.js allows a few gigabytes by default.
200
170
 
201
171
  ## Is it any good?
202
172
 
203
173
  | Situation | Reference result | Verdict |
204
174
  | --------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
205
- | A hook or an agent lints one file | 0.34 s, most of it start-up | Fine, but not instant. Batch files into one command. |
206
- | CI lints and checks 500 pages | 2.9 s and 3.2 s | Good. Shorter than installing the dependencies. |
207
- | Formatting 500 pages for the first time | 6.3 s | Good for a one-time job. |
208
- | A workspace of 2,000 pages | About 8.5 s to lint, 20 s to format everything | Acceptable. Tens of seconds, on one core, every time. |
209
- | Compared with Prettier | 1.6 times its time when rewriting, slightly less than its time when checking | Comparable, while also validating links and meaning. |
210
- | Memory | About 260 MB to lint and 350 MB to format 500 pages; about 400 MB to lint 2,000 | Fine at these sizes. At this rate, tens of thousands of files could still need a gigabyte or more. |
211
-
212
- The design accepts some slowness on purpose. Parsing every changed document
213
- again is what lets the tool refuse an edit that would alter a document's
214
- meaning, and that guarantee matters more to this project than speed. The real
215
- limits are start-up time, memory, and the lack of any reuse between runs. None
216
- is a problem at the sizes measured here; they would be the place to start if the
217
- tool had to serve an editor, or workspaces of tens of thousands of files.
218
- Start-up time was examined in
219
- [issue 116](https://github.com/viell-dev/slop-markdown-tools/issues/116), which
220
- declined packing the tool into one file for a gain of about 75 ms. Dropping a
221
- dependency that the tool never used has since taken 15 to 20 ms off it
222
- ([issue 140](https://github.com/viell-dev/slop-markdown-tools/issues/140)). The
223
- largest part of the memory that versions up to `0.2.0-rc.1` held was removed for
224
- [issue 114](https://github.com/viell-dev/slop-markdown-tools/issues/114); what a
225
- run still holds is every file's text and diagnostics.
175
+ | A hook or an agent lints one file | 0.24 s, most of it start-up | Fine, but not instant. Batch files into one command. |
176
+ | CI lints and checks 500 pages | 2.1 s and 2.4 s | Good. Shorter than installing the dependencies. |
177
+ | Formatting 500 pages for the first time | 4.5 s | Good for a one-time job. |
178
+ | A workspace of 2,000 pages | About 6 s to lint, 15 s to format everything | Acceptable. Seconds to tens of seconds, on one core, every time. |
179
+ | Compared with Prettier | 1.8 times its time when rewriting, slightly less than its time when checking | Comparable, while also validating links and meaning. |
180
+ | Memory | About 270 MB to lint and 375 MB to format 500 pages; about 425 MB to lint 2,000 | Fine at these sizes. At this rate, tens of thousands of files could still need a gigabyte or more. |
181
+
182
+ The design accepts some slowness on purpose: parsing every changed document
183
+ again is what lets the tool refuse an edit that would change a document's
184
+ meaning. The real limits are start-up time, memory, and the lack of reuse
185
+ between runs, which would matter for an editor or for workspaces of tens of
186
+ thousands of files.
226
187
 
227
188
  ## Limits of these numbers
228
189
 
229
- - The documents are synthetic. Real documents have a different mix of syntax,
230
- and a document with many links to check or many long tables costs more per
231
- kilobyte.
232
- - The synthetic workspaces are small. A check against a large real-world vault
233
- of several thousand notes, about 40 MB of Markdown, matched the synthetic
234
- throughput per megabyte within about 15% on the same machine, which supports
235
- estimating time by multiplying. It also showed what small sets cannot: peak
236
- memory of about 1.2 GB, half of what `0.2.0-rc.1` needed, and a few seconds
237
- spent finding files before the first document was read, because that workspace
238
- also held well over 100,000 files that are not Markdown. How much heap such a
239
- run needs at the least is set by its largest document, not by the number of
240
- files: a generated index note of 2.2 MB, a list of 13,400 links, needs about
241
- 290 MB to parse. In a simple tree, finding files costs about a third of a
242
- second per 100,000 files.
243
- - One paragraph is not one document. The times above grow in step with size
244
- because long documents are made of many paragraphs, and each is parsed by
245
- itself. A single paragraph of 100 kB or more that is full of inline code,
246
- links, or emphasis takes much longer than its size suggests: measured once,
247
- doubling such a paragraph from 96 kB to 192 kB tripled the time, and it took
248
- about twenty times as long as the same text read as CommonMark. The cause is
249
- in the parser, which merges the pieces of text between the inline constructs
250
- one by one, and it shows with GitHub's literal autolinks, which every dialect
251
- but CommonMark includes. A list or a table of that size is not affected.
252
- - Only the built-in rules run. Plugins add their own time.
253
- - The runner is shared. GitHub assigns runs to machines with different
254
- processors: two runs of this benchmark on the same day differed by a factor of
255
- about 1.8 in every absolute time, while the "compared with parsing" ratios and
256
- the comparison with Prettier stayed within a tenth. Compare absolute times
257
- only between reports whose header names the same processor.
190
+ - The documents are synthetic. A document with many links to check or many long
191
+ tables costs more per kilobyte.
192
+ - The synthetic workspaces are small. On a large real-world vault of several
193
+ thousand notes, about 40 MB of Markdown, the throughput per megabyte matched
194
+ within about 15% on the same machine. That run peaked at about 1.2 GB of
195
+ memory and spent a few seconds finding files, because the vault also held well
196
+ over 100,000 other files; finding files costs about a third of a second per
197
+ 100,000 files in a simple tree.
198
+ - One paragraph is not one document. A single paragraph of 100 kB or more that
199
+ is full of inline code, links, or emphasis is parsed much more slowly than its
200
+ size suggests: doubling one from 96 kB to 192 kB tripled the time, about
201
+ twenty times as long as the same text read as CommonMark. The cause is in the
202
+ parser, and GitHub's literal autolinks, which every dialect but CommonMark
203
+ reads, bring it out. Lists and tables of that size are not affected. Square
204
+ brackets are the worst case, in every dialect: a single paragraph of 70 kB
205
+ made only of bracketed references such as `[a][b]` took from 1 to more than 30
206
+ seconds to parse, while one of 35 kB took a fraction of a second ([issue
207
+ #240](https://github.com/viell-dev/slop-markdown-tools/issues/240)).
208
+ - Only the built-in rules run; plugins add their own time.
209
+ - The runner is shared. Two runs on the same day differed by a factor of about
210
+ 1.8 in every absolute time, while the "compared with parsing" ratios and the
211
+ comparison with Prettier stayed within a tenth. Compare absolute times only
212
+ between reports whose header names the same processor.
258
213
  - Only Linux is measured. Starting a process is slower on Windows, which adds to
259
214
  every command-line row.
260
215
  - The Prettier comparison uses its default Markdown settings with
261
- `proseWrap: "always"` at 80 columns, through its API. It says nothing about
262
- Prettier's command line or other configurations.
216
+ `proseWrap: "always"` at 80 columns, through its API.
263
217
 
264
218
  ## Run it and compare
265
219
 
@@ -269,31 +223,17 @@ npm run benchmark -- --json # the same measurements as JSON
269
223
  npm run benchmark -- --quick # tiny inputs, to check that it runs
270
224
  ```
271
225
 
272
- A full run takes a few minutes. Run it on a machine that is doing nothing else.
273
-
274
- To judge a change, run the benchmark before and after it on the same machine,
275
- and compare like with like:
226
+ A full run takes a few minutes; run it on a machine that is doing nothing else.
227
+ To judge a change, run it before and after on the same machine:
276
228
 
277
- - A step's time should not grow by more than run-to-run variation, which the
278
- "fastest to slowest run" column shows.
229
+ - A step's time should not grow by more than the spread in the "fastest to
230
+ slowest run" column.
279
231
  - The "compared with parsing" column should not grow. It depends little on the
280
- machine's speed, so it can also be compared with the tables above.
281
- - The per-file columns of the "More files" table should stay level from row to
282
- row, and the per-kilobyte column of the last table should not rise more
283
- steeply than it does above. A steeper rise means the change made something
284
- more than proportionally slower.
285
- - The output hash in the report's header identifies the formatted output. It
286
- stays the same when a change does not alter what the formatter produces, and
287
- it must change when the change is meant to.
288
-
289
- The Benchmark workflow runs the same command on a GitHub-hosted runner, for pull
290
- requests that change the benchmark and on request. Its report replaces the
291
- tables on this page when they are updated, so that the published results come
292
- from a machine anyone can use and not from one contributor's computer. Because
293
- runners differ in speed, a new report's absolute times can differ from the old
294
- one's without any change in the tool; judge by the ratios, or by
295
- before-and-after runs on one machine.
232
+ machine, so it can also be compared with the tables above.
233
+ - The per-file columns of "More files" should stay level, and the per-kilobyte
234
+ column of "Larger documents" should not rise more steeply than above.
235
+ - The output hash identifies the formatted output: it stays the same unless the
236
+ change is meant to alter what the formatter produces.
296
237
 
297
238
  The benchmark never fails on a slow result, because a shared machine's speed
298
- varies too much for a limit to be reliable. A few [unit tests](testing.md#speed)
299
- separately guard against algorithms whose cost grows faster than their input.
239
+ varies too much for a limit to be reliable.
@@ -0,0 +1 @@
1
+ <!--@include: ../CHANGELOG.md-->