slopless 0.1.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 (193) hide show
  1. package/README.md +64 -0
  2. package/dist/families/metrics/avg-sentence-length.js +22 -0
  3. package/dist/families/metrics/avg-sentence-length.js.map +1 -0
  4. package/dist/families/metrics/coleman-liau.js +27 -0
  5. package/dist/families/metrics/coleman-liau.js.map +1 -0
  6. package/dist/families/metrics/flesch-kincaid.js +27 -0
  7. package/dist/families/metrics/flesch-kincaid.js.map +1 -0
  8. package/dist/families/metrics/gunning-fog.js +27 -0
  9. package/dist/families/metrics/gunning-fog.js.map +1 -0
  10. package/dist/families/metrics/paragraph-length.js +21 -0
  11. package/dist/families/metrics/paragraph-length.js.map +1 -0
  12. package/dist/families/metrics/word-repetition.js +46 -0
  13. package/dist/families/metrics/word-repetition.js.map +1 -0
  14. package/dist/families/orthography/colon-dramatic.js +117 -0
  15. package/dist/families/orthography/colon-dramatic.js.map +1 -0
  16. package/dist/families/orthography/em-dashes.js +41 -0
  17. package/dist/families/orthography/em-dashes.js.map +1 -0
  18. package/dist/families/orthography/exclamation-density.js +51 -0
  19. package/dist/families/orthography/exclamation-density.js.map +1 -0
  20. package/dist/families/orthography/fake-timestamps.js +103 -0
  21. package/dist/families/orthography/fake-timestamps.js.map +1 -0
  22. package/dist/families/orthography/sentence-case.js +66 -0
  23. package/dist/families/orthography/sentence-case.js.map +1 -0
  24. package/dist/families/orthography/smart-quotes.js +53 -0
  25. package/dist/families/orthography/smart-quotes.js.map +1 -0
  26. package/dist/families/phrases/cliches.js +17 -0
  27. package/dist/families/phrases/cliches.js.map +1 -0
  28. package/dist/families/phrases/corporate-speak.js +23 -0
  29. package/dist/families/phrases/corporate-speak.js.map +1 -0
  30. package/dist/families/phrases/data/cliches.json +699 -0
  31. package/dist/families/phrases/data/corporate-speak.json +27 -0
  32. package/dist/families/phrases/data/prohibited-phrases.json +8 -0
  33. package/dist/families/phrases/data/skunked-terms.json +10 -0
  34. package/dist/families/phrases/data/uncomparables.json +57 -0
  35. package/dist/families/phrases/humble-bragger.js +26 -0
  36. package/dist/families/phrases/humble-bragger.js.map +1 -0
  37. package/dist/families/phrases/jargon-faker.js +84 -0
  38. package/dist/families/phrases/jargon-faker.js.map +1 -0
  39. package/dist/families/phrases/llm-disclaimer.js +54 -0
  40. package/dist/families/phrases/llm-disclaimer.js.map +1 -0
  41. package/dist/families/phrases/prohibited-phrases.js +17 -0
  42. package/dist/families/phrases/prohibited-phrases.js.map +1 -0
  43. package/dist/families/phrases/skunked-terms.js +23 -0
  44. package/dist/families/phrases/skunked-terms.js.map +1 -0
  45. package/dist/families/phrases/uncomparables.js +78 -0
  46. package/dist/families/phrases/uncomparables.js.map +1 -0
  47. package/dist/families/semantic-thinness/patterns/abstract-contrast.json +85 -0
  48. package/dist/families/semantic-thinness/patterns/abstract-metaphor-claim.json +112 -0
  49. package/dist/families/semantic-thinness/patterns/body-emotion-shorthand.json +77 -0
  50. package/dist/families/semantic-thinness/patterns/body-knows.json +80 -0
  51. package/dist/families/semantic-thinness/patterns/deictic-summary.json +109 -0
  52. package/dist/families/semantic-thinness/patterns/empty-atmosphere-shift.json +70 -0
  53. package/dist/families/semantic-thinness/patterns/empty-emotional-weather.json +104 -0
  54. package/dist/families/semantic-thinness/patterns/empty-scene-state.json +117 -0
  55. package/dist/families/semantic-thinness/patterns/empty-scene-transition.json +98 -0
  56. package/dist/families/semantic-thinness/patterns/gaze-choreography.json +87 -0
  57. package/dist/families/semantic-thinness/patterns/generic-lesson-extraction.json +82 -0
  58. package/dist/families/semantic-thinness/patterns/generic-pressure-or-stakes.json +107 -0
  59. package/dist/families/semantic-thinness/patterns/generic-realization.json +113 -0
  60. package/dist/families/semantic-thinness/patterns/hollow-significance.json +103 -0
  61. package/dist/families/semantic-thinness/patterns/low-information-physical-blocking.json +113 -0
  62. package/dist/families/semantic-thinness/patterns/point-is-frame.json +81 -0
  63. package/dist/families/semantic-thinness/patterns/real-work-begins.json +66 -0
  64. package/dist/families/semantic-thinness/patterns/silence-as-actor.json +69 -0
  65. package/dist/families/semantic-thinness/patterns/something-shifted.json +73 -0
  66. package/dist/families/semantic-thinness/patterns/truth-answer-moves.json +75 -0
  67. package/dist/families/semantic-thinness/patterns/vague-connective-payoff.json +90 -0
  68. package/dist/families/semantic-thinness/patterns/vague-threshold-change.json +98 -0
  69. package/dist/families/semantic-thinness/private/pattern-data-a.js +15 -0
  70. package/dist/families/semantic-thinness/private/pattern-data-a.js.map +1 -0
  71. package/dist/families/semantic-thinness/private/pattern-data-b.js +15 -0
  72. package/dist/families/semantic-thinness/private/pattern-data-b.js.map +1 -0
  73. package/dist/families/semantic-thinness/private/pattern-data-c.js +9 -0
  74. package/dist/families/semantic-thinness/private/pattern-data-c.js.map +1 -0
  75. package/dist/families/semantic-thinness/private/pattern-data-d.js +17 -0
  76. package/dist/families/semantic-thinness/private/pattern-data-d.js.map +1 -0
  77. package/dist/families/semantic-thinness/private/pattern-data.js +11 -0
  78. package/dist/families/semantic-thinness/private/pattern-data.js.map +1 -0
  79. package/dist/families/semantic-thinness/private/pattern-matcher.js +172 -0
  80. package/dist/families/semantic-thinness/private/pattern-matcher.js.map +1 -0
  81. package/dist/families/semantic-thinness/semantic-thinness.js +25 -0
  82. package/dist/families/semantic-thinness/semantic-thinness.js.map +1 -0
  83. package/dist/families/syntactic-patterns/authority/authority-padding.js +81 -0
  84. package/dist/families/syntactic-patterns/authority/authority-padding.js.map +1 -0
  85. package/dist/families/syntactic-patterns/closers/affirmation-closers.js +48 -0
  86. package/dist/families/syntactic-patterns/closers/affirmation-closers.js.map +1 -0
  87. package/dist/families/syntactic-patterns/closers/boilerplate-conclusion.js +131 -0
  88. package/dist/families/syntactic-patterns/closers/boilerplate-conclusion.js.map +1 -0
  89. package/dist/families/syntactic-patterns/closers/false-question.js +30 -0
  90. package/dist/families/syntactic-patterns/closers/false-question.js.map +1 -0
  91. package/dist/families/syntactic-patterns/closers/summative-closer.js +33 -0
  92. package/dist/families/syntactic-patterns/closers/summative-closer.js.map +1 -0
  93. package/dist/families/syntactic-patterns/contrast/blame-reframe.js +59 -0
  94. package/dist/families/syntactic-patterns/contrast/blame-reframe.js.map +1 -0
  95. package/dist/families/syntactic-patterns/contrast/contrastive-aphorism.js +203 -0
  96. package/dist/families/syntactic-patterns/contrast/contrastive-aphorism.js.map +1 -0
  97. package/dist/families/syntactic-patterns/contrast/negation-reframe.js +20 -0
  98. package/dist/families/syntactic-patterns/contrast/negation-reframe.js.map +1 -0
  99. package/dist/families/syntactic-patterns/contrast/private/negation-reframe-matcher.js +218 -0
  100. package/dist/families/syntactic-patterns/contrast/private/negation-reframe-matcher.js.map +1 -0
  101. package/dist/families/syntactic-patterns/contrast/private/negation-reframe-parts.js +136 -0
  102. package/dist/families/syntactic-patterns/contrast/private/negation-reframe-parts.js.map +1 -0
  103. package/dist/families/syntactic-patterns/generalization/softening-language.js +143 -0
  104. package/dist/families/syntactic-patterns/generalization/softening-language.js.map +1 -0
  105. package/dist/families/syntactic-patterns/generalization/universalizing-claims.js +137 -0
  106. package/dist/families/syntactic-patterns/generalization/universalizing-claims.js.map +1 -0
  107. package/dist/families/syntactic-patterns/lead-ins/boilerplate-framing.js +104 -0
  108. package/dist/families/syntactic-patterns/lead-ins/boilerplate-framing.js.map +1 -0
  109. package/dist/families/syntactic-patterns/lead-ins/generic-signposting.js +230 -0
  110. package/dist/families/syntactic-patterns/lead-ins/generic-signposting.js.map +1 -0
  111. package/dist/families/syntactic-patterns/lead-ins/lesson-framing.js +76 -0
  112. package/dist/families/syntactic-patterns/lead-ins/lesson-framing.js.map +1 -0
  113. package/dist/families/syntactic-patterns/lead-ins/llm-openers.js +24 -0
  114. package/dist/families/syntactic-patterns/lead-ins/llm-openers.js.map +1 -0
  115. package/dist/families/syntactic-patterns/lead-ins/observer-guidance.js +95 -0
  116. package/dist/families/syntactic-patterns/lead-ins/observer-guidance.js.map +1 -0
  117. package/dist/families/syntactic-patterns/llm-artifacts/response-wrapper.js +106 -0
  118. package/dist/families/syntactic-patterns/llm-artifacts/response-wrapper.js.map +1 -0
  119. package/dist/families/syntactic-patterns/repetition/demonstrative-emphasis.js +251 -0
  120. package/dist/families/syntactic-patterns/repetition/demonstrative-emphasis.js.map +1 -0
  121. package/dist/families/syntactic-patterns/repetition/empty-emphasis.js +108 -0
  122. package/dist/families/syntactic-patterns/repetition/empty-emphasis.js.map +1 -0
  123. package/dist/families/syntactic-patterns/repetition/fragment-stacking.js +306 -0
  124. package/dist/families/syntactic-patterns/repetition/fragment-stacking.js.map +1 -0
  125. package/dist/families/syntactic-patterns/repetition/triple-repeat.js +149 -0
  126. package/dist/families/syntactic-patterns/repetition/triple-repeat.js.map +1 -0
  127. package/dist/families/term-policy/recommended-terms.js +87 -0
  128. package/dist/families/term-policy/recommended-terms.js.map +1 -0
  129. package/dist/families/term-policy/required-terms.js +32 -0
  130. package/dist/families/term-policy/required-terms.js.map +1 -0
  131. package/dist/families/words/data/prohibited-words.json +12 -0
  132. package/dist/families/words/data/simplicity-pairs.json +7 -0
  133. package/dist/families/words/hedge-stacking.js +43 -0
  134. package/dist/families/words/hedge-stacking.js.map +1 -0
  135. package/dist/families/words/llm-vocabulary.js +33 -0
  136. package/dist/families/words/llm-vocabulary.js.map +1 -0
  137. package/dist/families/words/prohibited-words.js +17 -0
  138. package/dist/families/words/prohibited-words.js.map +1 -0
  139. package/dist/families/words/simplicity.js +27 -0
  140. package/dist/families/words/simplicity.js.map +1 -0
  141. package/dist/index.js +26 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/presets/everything.js +52 -0
  144. package/dist/presets/everything.js.map +1 -0
  145. package/dist/registries/metrics.js +15 -0
  146. package/dist/registries/metrics.js.map +1 -0
  147. package/dist/registries/orthography.js +15 -0
  148. package/dist/registries/orthography.js.map +1 -0
  149. package/dist/registries/phrases.js +19 -0
  150. package/dist/registries/phrases.js.map +1 -0
  151. package/dist/registries/semantic-thinness.js +5 -0
  152. package/dist/registries/semantic-thinness.js.map +1 -0
  153. package/dist/registries/syntactic-patterns/authority.js +5 -0
  154. package/dist/registries/syntactic-patterns/authority.js.map +1 -0
  155. package/dist/registries/syntactic-patterns/closers.js +11 -0
  156. package/dist/registries/syntactic-patterns/closers.js.map +1 -0
  157. package/dist/registries/syntactic-patterns/contrast.js +9 -0
  158. package/dist/registries/syntactic-patterns/contrast.js.map +1 -0
  159. package/dist/registries/syntactic-patterns/generalization.js +7 -0
  160. package/dist/registries/syntactic-patterns/generalization.js.map +1 -0
  161. package/dist/registries/syntactic-patterns/lead-ins.js +13 -0
  162. package/dist/registries/syntactic-patterns/lead-ins.js.map +1 -0
  163. package/dist/registries/syntactic-patterns/llm-artifacts.js +5 -0
  164. package/dist/registries/syntactic-patterns/llm-artifacts.js.map +1 -0
  165. package/dist/registries/syntactic-patterns/repetition.js +11 -0
  166. package/dist/registries/syntactic-patterns/repetition.js.map +1 -0
  167. package/dist/registries/syntactic-patterns.js +17 -0
  168. package/dist/registries/syntactic-patterns.js.map +1 -0
  169. package/dist/registries/term-policy.js +7 -0
  170. package/dist/registries/term-policy.js.map +1 -0
  171. package/dist/registries/words.js +11 -0
  172. package/dist/registries/words.js.map +1 -0
  173. package/dist/shared/matchers/phrases.js +71 -0
  174. package/dist/shared/matchers/phrases.js.map +1 -0
  175. package/dist/shared/matchers/prose-patterns.js +104 -0
  176. package/dist/shared/matchers/prose-patterns.js.map +1 -0
  177. package/dist/shared/text/document.js +18 -0
  178. package/dist/shared/text/document.js.map +1 -0
  179. package/dist/shared/text/normalize.js +30 -0
  180. package/dist/shared/text/normalize.js.map +1 -0
  181. package/dist/shared/text/quotes.js +20 -0
  182. package/dist/shared/text/quotes.js.map +1 -0
  183. package/dist/shared/text/sections.js +119 -0
  184. package/dist/shared/text/sections.js.map +1 -0
  185. package/dist/shared/text/sentences.js +79 -0
  186. package/dist/shared/text/sentences.js.map +1 -0
  187. package/dist/shared/text/tokens.js +18 -0
  188. package/dist/shared/text/tokens.js.map +1 -0
  189. package/dist/shared/text/traverse.js +10 -0
  190. package/dist/shared/text/traverse.js.map +1 -0
  191. package/dist/shared/text/whitespace.js +25 -0
  192. package/dist/shared/text/whitespace.js.map +1 -0
  193. package/package.json +115 -0
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # slopless
2
+
3
+ Deterministic textlint rules for catching slop in prose.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -D textlint slopless
9
+ ```
10
+
11
+ ## Configure
12
+
13
+ Create `.textlintrc.json`:
14
+
15
+ ```json
16
+ {
17
+ "rules": {
18
+ "preset-slopless": true
19
+ }
20
+ }
21
+ ```
22
+
23
+ ## Run
24
+
25
+ Add an npm script:
26
+
27
+ ```json
28
+ {
29
+ "scripts": {
30
+ "lint:prose": "textlint \"**/*.md\""
31
+ }
32
+ }
33
+ ```
34
+
35
+ Run it:
36
+
37
+ ```bash
38
+ npm run lint:prose
39
+ ```
40
+
41
+ ## CI Output
42
+
43
+ Use standard textlint formatters:
44
+
45
+ ```bash
46
+ npx textlint "docs/**/*.md" --format stylish
47
+ npx textlint "docs/**/*.md" --format json
48
+ ```
49
+
50
+ ## What It Checks
51
+
52
+ - stock AI-style phrasing
53
+ - empty or generic prose patterns
54
+ - rhetorical filler
55
+ - weak closers and lead-ins
56
+ - hedge stacking
57
+ - prohibited vocabulary
58
+ - readability and sentence metrics
59
+ - Markdown style signals
60
+
61
+ ## Requirements
62
+
63
+ - Node.js 20 or newer
64
+ - textlint 15
@@ -0,0 +1,22 @@
1
+ import { documentMetrics } from "../../shared/text/document.js";
2
+ const MAX_WORDS = 24;
3
+ const rule = (context) => {
4
+ const { Syntax, RuleError, locator, report } = context;
5
+ return {
6
+ [Syntax.Document](node) {
7
+ const metrics = documentMetrics(node);
8
+ if (metrics.sentenceCount === 0) {
9
+ return;
10
+ }
11
+ const average = Math.floor(metrics.wordCount / metrics.sentenceCount);
12
+ if (average <= MAX_WORDS) {
13
+ return;
14
+ }
15
+ report(node, new RuleError(`Average sentence length is ${average} words. Keep it at ${MAX_WORDS} or fewer.`, {
16
+ padding: locator.at(0)
17
+ }));
18
+ }
19
+ };
20
+ };
21
+ export default rule;
22
+ //# sourceMappingURL=avg-sentence-length.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avg-sentence-length.js","sourceRoot":"","sources":["../../../src/families/metrics/avg-sentence-length.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEvD,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAqB;YACrC,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,OAAO,CAAC,aAAa,KAAK,CAAC,EAAE,CAAC;gBAChC,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;YACtE,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;gBACzB,OAAO;YACT,CAAC;YAED,MAAM,CACJ,IAAI,EACJ,IAAI,SAAS,CACX,8BAA8B,OAAO,sBAAsB,SAAS,YAAY,EAChF;gBACE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;aACvB,CACF,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { TextReadability } from "@lunarisapp/readability";
2
+ import { documentText } from "../../shared/text/document.js";
3
+ const MAX_SCORE = 12;
4
+ function rounded(score) {
5
+ return Math.round(score * 100) / 100;
6
+ }
7
+ const rule = (context) => {
8
+ const { Syntax, RuleError, locator, report } = context;
9
+ const readability = new TextReadability({ lang: "en_US" });
10
+ return {
11
+ [Syntax.Document](node) {
12
+ const text = documentText(node);
13
+ if (text.length === 0) {
14
+ return;
15
+ }
16
+ const score = readability.colemanLiauIndex(text);
17
+ if (score <= MAX_SCORE) {
18
+ return;
19
+ }
20
+ report(node, new RuleError(`Coleman-Liau is ${rounded(score)}. Keep it at ${MAX_SCORE} or lower.`, {
21
+ padding: locator.at(0)
22
+ }));
23
+ }
24
+ };
25
+ };
26
+ export default rule;
27
+ //# sourceMappingURL=coleman-liau.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coleman-liau.js","sourceRoot":"","sources":["../../../src/families/metrics/coleman-liau.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAE3D,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAqB;YACrC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO;YACT,CAAC;YAED,MAAM,KAAK,GAAG,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBACvB,OAAO;YACT,CAAC;YAED,MAAM,CACJ,IAAI,EACJ,IAAI,SAAS,CACX,mBAAmB,OAAO,CAAC,KAAK,CAAC,gBAAgB,SAAS,YAAY,EACtE;gBACE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;aACvB,CACF,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { TextReadability } from "@lunarisapp/readability";
2
+ import { documentText } from "../../shared/text/document.js";
3
+ const MIN_SCORE = 61;
4
+ function rounded(score) {
5
+ return Math.round(score * 100) / 100;
6
+ }
7
+ const rule = (context) => {
8
+ const { Syntax, RuleError, locator, report } = context;
9
+ const readability = new TextReadability({ lang: "en_US" });
10
+ return {
11
+ [Syntax.Document](node) {
12
+ const text = documentText(node);
13
+ if (text.length === 0) {
14
+ return;
15
+ }
16
+ const score = readability.fleschReadingEase(text);
17
+ if (score >= MIN_SCORE) {
18
+ return;
19
+ }
20
+ report(node, new RuleError(`Flesch Reading Ease is ${rounded(score)}. Keep it at ${MIN_SCORE} or higher.`, {
21
+ padding: locator.at(0)
22
+ }));
23
+ }
24
+ };
25
+ };
26
+ export default rule;
27
+ //# sourceMappingURL=flesch-kincaid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flesch-kincaid.js","sourceRoot":"","sources":["../../../src/families/metrics/flesch-kincaid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAE3D,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAqB;YACrC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO;YACT,CAAC;YAED,MAAM,KAAK,GAAG,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAClD,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBACvB,OAAO;YACT,CAAC;YAED,MAAM,CACJ,IAAI,EACJ,IAAI,SAAS,CACX,0BAA0B,OAAO,CAAC,KAAK,CAAC,gBAAgB,SAAS,aAAa,EAC9E;gBACE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;aACvB,CACF,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { TextReadability } from "@lunarisapp/readability";
2
+ import { documentText } from "../../shared/text/document.js";
3
+ const MAX_SCORE = 12;
4
+ function rounded(score) {
5
+ return Math.round(score * 100) / 100;
6
+ }
7
+ const rule = (context) => {
8
+ const { Syntax, RuleError, locator, report } = context;
9
+ const readability = new TextReadability({ lang: "en_US" });
10
+ return {
11
+ [Syntax.Document](node) {
12
+ const text = documentText(node);
13
+ if (text.length === 0) {
14
+ return;
15
+ }
16
+ const score = readability.gunningFog(text);
17
+ if (score <= MAX_SCORE) {
18
+ return;
19
+ }
20
+ report(node, new RuleError(`Gunning Fog is ${rounded(score)}. Keep it at ${MAX_SCORE} or lower.`, {
21
+ padding: locator.at(0)
22
+ }));
23
+ }
24
+ };
25
+ };
26
+ export default rule;
27
+ //# sourceMappingURL=gunning-fog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gunning-fog.js","sourceRoot":"","sources":["../../../src/families/metrics/gunning-fog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAE3D,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAqB;YACrC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO;YACT,CAAC;YAED,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC3C,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBACvB,OAAO;YACT,CAAC;YAED,MAAM,CACJ,IAAI,EACJ,IAAI,SAAS,CACX,kBAAkB,OAAO,CAAC,KAAK,CAAC,gBAAgB,SAAS,YAAY,EACrE;gBACE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;aACvB,CACF,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { allParagraphs } from "../../shared/text/sections.js";
2
+ import { splitSentences } from "../../shared/text/sentences.js";
3
+ const MAX_SENTENCES = 6;
4
+ const rule = (context) => {
5
+ const { Syntax, RuleError, locator, report } = context;
6
+ return {
7
+ [Syntax.Document](node) {
8
+ for (const item of allParagraphs(node)) {
9
+ const sentenceCount = splitSentences(item.text).length;
10
+ if (sentenceCount <= MAX_SENTENCES) {
11
+ continue;
12
+ }
13
+ report(item.paragraph, new RuleError(`Paragraph has ${sentenceCount} sentences. Keep paragraphs to ${MAX_SENTENCES} sentences or fewer.`, {
14
+ padding: locator.range([0, item.source.text.length])
15
+ }));
16
+ }
17
+ }
18
+ };
19
+ };
20
+ export default rule;
21
+ //# sourceMappingURL=paragraph-length.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paragraph-length.js","sourceRoot":"","sources":["../../../src/families/metrics/paragraph-length.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEvD,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAqB;YACrC,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;gBAEvD,IAAI,aAAa,IAAI,aAAa,EAAE,CAAC;oBACnC,SAAS;gBACX,CAAC;gBAED,MAAM,CACJ,IAAI,CAAC,SAAS,EACd,IAAI,SAAS,CACX,iBAAiB,aAAa,kCAAkC,aAAa,sBAAsB,EACnG;oBACE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;iBACrD,CACF,CACF,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { allParagraphs } from "../../shared/text/sections.js";
2
+ import { wordTokens } from "../../shared/text/tokens.js";
3
+ const MAX_REPETITION = 5;
4
+ const EXCLUDED_WORDS = new Set([
5
+ "the",
6
+ "a",
7
+ "an",
8
+ "is",
9
+ "in",
10
+ "to",
11
+ "and",
12
+ "of",
13
+ "for",
14
+ "that"
15
+ ]);
16
+ function repeatedWords(text) {
17
+ const counts = new Map();
18
+ for (const token of wordTokens(text)) {
19
+ const word = token.normalized;
20
+ counts.set(word, (counts.get(word) ?? 0) + 1);
21
+ }
22
+ return [...counts]
23
+ .filter(([word, count]) => {
24
+ return (word.length >= 4 && !EXCLUDED_WORDS.has(word) && count > MAX_REPETITION);
25
+ })
26
+ .sort(([left], [right]) => left.localeCompare(right));
27
+ }
28
+ const rule = (context) => {
29
+ const { Syntax, RuleError, locator, report } = context;
30
+ return {
31
+ [Syntax.Document](node) {
32
+ for (const item of allParagraphs(node)) {
33
+ if (item.source.text.trimStart().startsWith("<")) {
34
+ continue;
35
+ }
36
+ for (const [word, count] of repeatedWords(item.text)) {
37
+ report(item.paragraph, new RuleError(`Word repeated ${count} times in one paragraph: "${word}". Keep repeated words to ${MAX_REPETITION} or fewer.`, {
38
+ padding: locator.range([0, item.source.text.length])
39
+ }));
40
+ }
41
+ }
42
+ }
43
+ };
44
+ };
45
+ export default rule;
46
+ //# sourceMappingURL=word-repetition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"word-repetition.js","sourceRoot":"","sources":["../../../src/families/metrics/word-repetition.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAEzD,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,KAAK;IACL,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,KAAK;IACL,MAAM;CACP,CAAC,CAAC;AAEH,SAAS,aAAa,CACpB,IAAY;IAEZ,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC;SACf,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;QACxB,OAAO,CACL,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,cAAc,CACxE,CAAC;IACJ,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEvD,OAAO;QACL,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAqB;YACrC,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACjD,SAAS;gBACX,CAAC;gBAED,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrD,MAAM,CACJ,IAAI,CAAC,SAAS,EACd,IAAI,SAAS,CACX,iBAAiB,KAAK,6BAA6B,IAAI,6BAA6B,cAAc,YAAY,EAC9G;wBACE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;qBACrD,CACF,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { RuleHelper } from "textlint-rule-helper";
2
+ import { sourceText } from "../../shared/text/traverse.js";
3
+ import { countWhitespaceSeparatedWords } from "../../shared/text/whitespace.js";
4
+ const MAX_DRAMATIC_AFTER_COLON_WORDS = 5;
5
+ function firstNonWhitespaceIndex(text) {
6
+ for (let index = 0; index < text.length; index += 1) {
7
+ if (text[index]?.trim() !== "") {
8
+ return index;
9
+ }
10
+ }
11
+ return undefined;
12
+ }
13
+ function trimEndIndex(text) {
14
+ for (let index = text.length; index > 0; index -= 1) {
15
+ if (text[index - 1]?.trim() !== "") {
16
+ return index;
17
+ }
18
+ }
19
+ return 0;
20
+ }
21
+ function containsComma(text) {
22
+ for (const character of text) {
23
+ if (character === ",") {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
29
+ function isMeridiemInnerPeriod(text, index) {
30
+ const previous = text[index - 1]?.toLocaleLowerCase();
31
+ const next = text[index + 1]?.toLocaleLowerCase();
32
+ return (previous === "a" || previous === "p") && next === "m";
33
+ }
34
+ function sentenceEndWithClosingQuotes(text, index) {
35
+ let end = index + 1;
36
+ while (text[end] === '"' ||
37
+ text[end] === "\u201D" ||
38
+ text[end] === "\u2019") {
39
+ end += 1;
40
+ }
41
+ return end;
42
+ }
43
+ function sentenceEndAfter(text, start) {
44
+ for (let index = start; index < text.length; index += 1) {
45
+ const character = text[index];
46
+ if (character === "." && isMeridiemInnerPeriod(text, index)) {
47
+ continue;
48
+ }
49
+ if (character === "." || character === "?" || character === "!") {
50
+ return sentenceEndWithClosingQuotes(text, index);
51
+ }
52
+ }
53
+ return text.length;
54
+ }
55
+ function dramaticColonAt(text, colonIndex) {
56
+ const end = sentenceEndAfter(text, colonIndex + 1);
57
+ const afterColon = text.slice(colonIndex + 1, end);
58
+ const trimStart = firstNonWhitespaceIndex(afterColon);
59
+ if (trimStart === undefined) {
60
+ return undefined;
61
+ }
62
+ const trimEnd = trimEndIndex(afterColon);
63
+ const trimmed = afterColon.slice(trimStart, trimEnd);
64
+ if (containsComma(trimmed)) {
65
+ return undefined;
66
+ }
67
+ if (countWhitespaceSeparatedWords(trimmed) > MAX_DRAMATIC_AFTER_COLON_WORDS) {
68
+ return undefined;
69
+ }
70
+ return {
71
+ end: colonIndex + 1 + trimEnd,
72
+ start: colonIndex + 1 + trimStart,
73
+ text: trimmed
74
+ };
75
+ }
76
+ function findDramaticColonMatches(text) {
77
+ const matches = [];
78
+ for (let index = 0; index < text.length; index += 1) {
79
+ if (text[index] !== ":") {
80
+ continue;
81
+ }
82
+ const match = dramaticColonAt(text, index);
83
+ if (match === undefined) {
84
+ continue;
85
+ }
86
+ matches.push(match);
87
+ }
88
+ return matches;
89
+ }
90
+ const rule = (context) => {
91
+ const { Syntax, RuleError, locator, report } = context;
92
+ const helper = new RuleHelper(context);
93
+ const ignoredParents = [
94
+ Syntax.List,
95
+ Syntax.ListItem,
96
+ Syntax.Table,
97
+ Syntax.TableCell
98
+ ];
99
+ return {
100
+ [Syntax.Paragraph](node) {
101
+ if (helper.isChildNode(node, ignoredParents)) {
102
+ return;
103
+ }
104
+ const source = sourceText(node);
105
+ for (const match of findDramaticColonMatches(source.text)) {
106
+ report(node, new RuleError(`Dramatic colon found: "${match.text}". Rewrite without the short reveal after the colon.`, {
107
+ padding: locator.range([
108
+ source.originalStartFor(match.start),
109
+ source.originalEndFor(match.end)
110
+ ])
111
+ }));
112
+ }
113
+ }
114
+ };
115
+ };
116
+ export default rule;
117
+ //# sourceMappingURL=colon-dramatic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colon-dramatic.js","sourceRoot":"","sources":["../../../src/families/orthography/colon-dramatic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { RuleHelper } from "textlint-rule-helper";
2
+ import { sourceText } from "../../shared/text/traverse.js";
3
+ import { isWhitespace } from "../../shared/text/whitespace.js";
4
+ const CLOSED_EM_DASH = "\u2014";
5
+ function isClosedEmDash(text, index) {
6
+ if (text[index] !== CLOSED_EM_DASH) {
7
+ return false;
8
+ }
9
+ return !isWhitespace(text[index - 1]) && !isWhitespace(text[index + 1]);
10
+ }
11
+ const rule = (context) => {
12
+ const { Syntax, RuleError, locator, report } = context;
13
+ const helper = new RuleHelper(context);
14
+ const ignoredParents = [
15
+ Syntax.List,
16
+ Syntax.ListItem,
17
+ Syntax.Table,
18
+ Syntax.TableCell
19
+ ];
20
+ return {
21
+ [Syntax.Paragraph](node) {
22
+ if (helper.isChildNode(node, ignoredParents)) {
23
+ return;
24
+ }
25
+ const source = sourceText(node);
26
+ for (let index = 0; index < source.text.length; index += 1) {
27
+ if (!isClosedEmDash(source.text, index)) {
28
+ continue;
29
+ }
30
+ report(node, new RuleError("Closed em dash found. Replace it with a comma, colon, parenthesis, or spaced dash.", {
31
+ padding: locator.range([
32
+ source.originalStartFor(index),
33
+ source.originalEndFor(index + CLOSED_EM_DASH.length)
34
+ ])
35
+ }));
36
+ }
37
+ }
38
+ };
39
+ };
40
+ export default rule;
41
+ //# sourceMappingURL=em-dashes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"em-dashes.js","sourceRoot":"","sources":["../../../src/families/orthography/em-dashes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEhC,SAAS,cAAc,CAAC,IAAY,EAAE,KAAa;IACjD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,cAAc,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,cAAc,GAAG;QACrB,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,SAAS;KACjB,CAAC;IAEF,OAAO;QACL,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAmB;YACpC,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC;gBAC7C,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAEhC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC3D,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;oBACxC,SAAS;gBACX,CAAC;gBAED,MAAM,CACJ,IAAI,EACJ,IAAI,SAAS,CACX,oFAAoF,EACpF;oBACE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC;wBACrB,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC;wBAC9B,MAAM,CAAC,cAAc,CAAC,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC;qBACrD,CAAC;iBACH,CACF,CACF,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { RuleHelper } from "textlint-rule-helper";
2
+ import { sourceText } from "../../shared/text/traverse.js";
3
+ const MAX_EXCLAMATIONS_PER_PARAGRAPH = 1;
4
+ function countExclamations(text) {
5
+ let count = 0;
6
+ for (const character of text) {
7
+ if (character === "!") {
8
+ count += 1;
9
+ }
10
+ }
11
+ return count;
12
+ }
13
+ function firstExclamationIndex(text) {
14
+ for (let index = 0; index < text.length; index += 1) {
15
+ if (text[index] === "!") {
16
+ return index;
17
+ }
18
+ }
19
+ return undefined;
20
+ }
21
+ const rule = (context) => {
22
+ const { Syntax, RuleError, locator, report } = context;
23
+ const helper = new RuleHelper(context);
24
+ const ignoredParents = [
25
+ Syntax.List,
26
+ Syntax.ListItem,
27
+ Syntax.Table,
28
+ Syntax.TableCell
29
+ ];
30
+ return {
31
+ [Syntax.Paragraph](node) {
32
+ if (helper.isChildNode(node, ignoredParents)) {
33
+ return;
34
+ }
35
+ const source = sourceText(node);
36
+ const count = countExclamations(source.text);
37
+ if (count <= MAX_EXCLAMATIONS_PER_PARAGRAPH) {
38
+ return;
39
+ }
40
+ const index = firstExclamationIndex(source.text) ?? 0;
41
+ report(node, new RuleError(`Paragraph has ${count} exclamation marks. Keep at most ${MAX_EXCLAMATIONS_PER_PARAGRAPH}.`, {
42
+ padding: locator.range([
43
+ source.originalStartFor(index),
44
+ source.originalEndFor(index + 1)
45
+ ])
46
+ }));
47
+ }
48
+ };
49
+ };
50
+ export default rule;
51
+ //# sourceMappingURL=exclamation-density.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclamation-density.js","sourceRoot":"","sources":["../../../src/families/orthography/exclamation-density.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAEzC,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY;IACzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,IAAI,GAAuB,CAAC,OAAO,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,cAAc,GAAG;QACrB,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,SAAS;KACjB,CAAC;IAEF,OAAO;QACL,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAmB;YACpC,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC;gBAC7C,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAE7C,IAAI,KAAK,IAAI,8BAA8B,EAAE,CAAC;gBAC5C,OAAO;YACT,CAAC;YAED,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEtD,MAAM,CACJ,IAAI,EACJ,IAAI,SAAS,CACX,iBAAiB,KAAK,oCAAoC,8BAA8B,GAAG,EAC3F;gBACE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC;oBACrB,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC;oBAC9B,MAAM,CAAC,cAAc,CAAC,KAAK,GAAG,CAAC,CAAC;iBACjC,CAAC;aACH,CACF,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,103 @@
1
+ import { RuleHelper } from "textlint-rule-helper";
2
+ import { splitSentences } from "../../shared/text/sentences.js";
3
+ import { sourceText } from "../../shared/text/traverse.js";
4
+ function isAsciiDigit(character) {
5
+ return character !== undefined && character >= "0" && character <= "9";
6
+ }
7
+ function isPeriodMarker(first, second) {
8
+ if (first === undefined || second === undefined) {
9
+ return false;
10
+ }
11
+ const normalizedFirst = first.toUpperCase();
12
+ return ((normalizedFirst === "A" || normalizedFirst === "P") &&
13
+ second.toUpperCase() === "M");
14
+ }
15
+ function findTimestampMatches(text) {
16
+ const matches = [];
17
+ let index = 0;
18
+ while (index < text.length) {
19
+ if (!isAsciiDigit(text[index])) {
20
+ index += 1;
21
+ continue;
22
+ }
23
+ const start = index;
24
+ while (isAsciiDigit(text[index])) {
25
+ index += 1;
26
+ }
27
+ if (text[index] !== ":") {
28
+ index = start + 1;
29
+ continue;
30
+ }
31
+ index += 1;
32
+ const minuteStart = index;
33
+ while (isAsciiDigit(text[index])) {
34
+ index += 1;
35
+ }
36
+ if (index === minuteStart) {
37
+ index = start + 1;
38
+ continue;
39
+ }
40
+ if (text[index] === " ") {
41
+ index += 1;
42
+ }
43
+ if (!isPeriodMarker(text[index], text[index + 1])) {
44
+ index = start + 1;
45
+ continue;
46
+ }
47
+ const end = index + 2;
48
+ matches.push({
49
+ end,
50
+ start,
51
+ text: text.slice(start, end)
52
+ });
53
+ index = end;
54
+ }
55
+ return matches;
56
+ }
57
+ function findSentenceTimestampMatches(text) {
58
+ const matches = [];
59
+ for (const sentence of splitSentences(text)) {
60
+ const timestampMatches = findTimestampMatches(sentence.text);
61
+ if (timestampMatches.length === 0) {
62
+ continue;
63
+ }
64
+ const first = timestampMatches[0];
65
+ if (first === undefined) {
66
+ continue;
67
+ }
68
+ matches.push({
69
+ end: sentence.start + first.end,
70
+ start: sentence.start + first.start,
71
+ text: first.text
72
+ });
73
+ }
74
+ return matches;
75
+ }
76
+ const rule = (context) => {
77
+ const { Syntax, RuleError, locator, report } = context;
78
+ const helper = new RuleHelper(context);
79
+ const ignoredParents = [
80
+ Syntax.List,
81
+ Syntax.ListItem,
82
+ Syntax.Table,
83
+ Syntax.TableCell
84
+ ];
85
+ return {
86
+ [Syntax.Paragraph](node) {
87
+ if (helper.isChildNode(node, ignoredParents)) {
88
+ return;
89
+ }
90
+ const source = sourceText(node);
91
+ for (const match of findSentenceTimestampMatches(source.text)) {
92
+ report(node, new RuleError(`Fake timestamp found: "${match.text}". Remove fabricated clock specificity.`, {
93
+ padding: locator.range([
94
+ source.originalStartFor(match.start),
95
+ source.originalEndFor(match.end)
96
+ ])
97
+ }));
98
+ }
99
+ }
100
+ };
101
+ };
102
+ export default rule;
103
+ //# sourceMappingURL=fake-timestamps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-timestamps.js","sourceRoot":"","sources":["../../../src/families/orthography/fake-timestamps.ts"],"names":[],"mappings":"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"}