@faircopy/rules-nlp 1.15.0 → 1.16.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.
- package/README.md +198 -0
- package/dist/index.d.ts +77 -1
- package/dist/index.js +635 -44
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,171 @@
|
|
|
1
|
+
// src/no-absolute-intensifiers.ts
|
|
2
|
+
var DEFAULT_INTENSIFIERS = [
|
|
3
|
+
"very",
|
|
4
|
+
"really",
|
|
5
|
+
"completely",
|
|
6
|
+
"totally",
|
|
7
|
+
"absolutely",
|
|
8
|
+
"utterly",
|
|
9
|
+
"quite",
|
|
10
|
+
"extremely",
|
|
11
|
+
"perfectly",
|
|
12
|
+
"entirely"
|
|
13
|
+
];
|
|
14
|
+
var DEFAULT_ABSOLUTES = [
|
|
15
|
+
"unique",
|
|
16
|
+
"finished",
|
|
17
|
+
"destroyed",
|
|
18
|
+
"essential",
|
|
19
|
+
"perfect",
|
|
20
|
+
"impossible",
|
|
21
|
+
"dead",
|
|
22
|
+
"empty",
|
|
23
|
+
"full",
|
|
24
|
+
"silent",
|
|
25
|
+
"unanimous",
|
|
26
|
+
"infinite",
|
|
27
|
+
"eternal",
|
|
28
|
+
"flawless",
|
|
29
|
+
"ultimate",
|
|
30
|
+
"final",
|
|
31
|
+
"complete",
|
|
32
|
+
"total",
|
|
33
|
+
"absolute"
|
|
34
|
+
];
|
|
35
|
+
function escapeRegExp(value) {
|
|
36
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
37
|
+
}
|
|
38
|
+
function longestFirst(values) {
|
|
39
|
+
return [...values].sort((left, right) => right.length - left.length);
|
|
40
|
+
}
|
|
41
|
+
function buildPattern(intensifiers, absolutes) {
|
|
42
|
+
const intensifierPattern = longestFirst(intensifiers).map((phrase) => escapeRegExp(phrase).replace(/\\s+/g, "\\s+")).join("|");
|
|
43
|
+
const absolutePattern = longestFirst(absolutes).map((phrase) => escapeRegExp(phrase).replace(/\\s+/g, "\\s+")).join("|");
|
|
44
|
+
return new RegExp(
|
|
45
|
+
`\\b(${intensifierPattern})\\s+\\b(${absolutePattern})(?![\\w'-])`,
|
|
46
|
+
"gi"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
var noAbsoluteIntensifiers = {
|
|
50
|
+
id: "no-absolute-intensifiers",
|
|
51
|
+
description: "Flag intensifiers before absolute adjectives",
|
|
52
|
+
defaults: {
|
|
53
|
+
intensifiers: DEFAULT_INTENSIFIERS,
|
|
54
|
+
absolutes: DEFAULT_ABSOLUTES
|
|
55
|
+
},
|
|
56
|
+
help: 'Absolute adjectives already express an extreme. Intensifiers like "very" before them are redundant and weaken the claim. Remove the intensifier or replace the phrase with concrete evidence.',
|
|
57
|
+
check({ text, sourceMap, options }) {
|
|
58
|
+
const diagnostics = [];
|
|
59
|
+
const intensifiers = options.intensifiers?.length ? options.intensifiers : DEFAULT_INTENSIFIERS;
|
|
60
|
+
const absolutes = options.absolutes?.length ? options.absolutes : DEFAULT_ABSOLUTES;
|
|
61
|
+
if (!intensifiers.length || !absolutes.length) return diagnostics;
|
|
62
|
+
const re = buildPattern(intensifiers, absolutes);
|
|
63
|
+
let match;
|
|
64
|
+
while ((match = re.exec(text)) !== null) {
|
|
65
|
+
const matchedText = match[0];
|
|
66
|
+
const intensifier = match[1];
|
|
67
|
+
const absolute = match[2];
|
|
68
|
+
const matchStart = match.index;
|
|
69
|
+
const matchEnd = matchStart + matchedText.length;
|
|
70
|
+
const start = sourceMap[matchStart];
|
|
71
|
+
const end = sourceMap[matchEnd - 1];
|
|
72
|
+
if (start === void 0 || end === void 0) continue;
|
|
73
|
+
const suggest = {
|
|
74
|
+
description: `remove "${intensifier}" before "${absolute}"`,
|
|
75
|
+
edits: [{ range: { start, end: end + 1 }, replacement: absolute }]
|
|
76
|
+
};
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
ruleId: "no-absolute-intensifiers",
|
|
79
|
+
severity: "warn",
|
|
80
|
+
message: `"${matchedText}" is redundant \u2014 "${absolute}" is already absolute`,
|
|
81
|
+
range: { start, end: end + 1 },
|
|
82
|
+
help: noAbsoluteIntensifiers.help,
|
|
83
|
+
suggest
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/utils.ts
|
|
91
|
+
import nlp from "compromise";
|
|
92
|
+
function createDoc(text) {
|
|
93
|
+
return nlp(text);
|
|
94
|
+
}
|
|
95
|
+
function getMatchOccurrences(text, matches) {
|
|
96
|
+
const json = matches.json({ offset: true, text: true, terms: { offset: true } });
|
|
97
|
+
return json.flatMap((entry) => {
|
|
98
|
+
const start = entry.offset?.start ?? entry.terms?.[0]?.offset?.start;
|
|
99
|
+
const length = entry.offset?.length ?? sumTermLengths(entry.terms);
|
|
100
|
+
if (typeof start !== "number" || typeof length !== "number" || length <= 0) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
return [{
|
|
104
|
+
text: entry.text ?? text.slice(start, start + length),
|
|
105
|
+
start,
|
|
106
|
+
end: start + length
|
|
107
|
+
}];
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function getOccurrenceRange(sourceMap, occurrence) {
|
|
111
|
+
const start = sourceMap[occurrence.start];
|
|
112
|
+
const end = sourceMap[occurrence.end - 1];
|
|
113
|
+
if (start === void 0 || end === void 0) return null;
|
|
114
|
+
return { start, end: end + 1 };
|
|
115
|
+
}
|
|
116
|
+
function sumTermLengths(terms) {
|
|
117
|
+
if (!terms?.length) return void 0;
|
|
118
|
+
let total = 0;
|
|
119
|
+
for (const term of terms) {
|
|
120
|
+
const length = term.offset?.length;
|
|
121
|
+
if (typeof length !== "number") return void 0;
|
|
122
|
+
total += length;
|
|
123
|
+
}
|
|
124
|
+
return total;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/no-adverb-overuse.ts
|
|
128
|
+
var DEFAULT_MAX_ADVERBS = 2;
|
|
129
|
+
var DEFAULT_ALLOWED_ADVERBS = ["only"];
|
|
130
|
+
var noAdverbOveruse = {
|
|
131
|
+
id: "no-adverb-overuse",
|
|
132
|
+
description: "Flag sentences with more than two -ly adverbs",
|
|
133
|
+
defaults: { maxAdverbs: DEFAULT_MAX_ADVERBS, allowedAdverbs: DEFAULT_ALLOWED_ADVERBS },
|
|
134
|
+
help: "Too many -ly adverbs in one sentence make copy feel padded. Remove the adverb, replace it with a stronger verb or adjective, or add it to allowedAdverbs if it is essential.",
|
|
135
|
+
check({ text, sourceMap, options }) {
|
|
136
|
+
const maxAdverbs = options.maxAdverbs ?? DEFAULT_MAX_ADVERBS;
|
|
137
|
+
const allowed = new Set((options.allowedAdverbs ?? DEFAULT_ALLOWED_ADVERBS).map((value) => value.toLowerCase()));
|
|
138
|
+
const doc = createDoc(text);
|
|
139
|
+
const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } });
|
|
140
|
+
const diagnostics = [];
|
|
141
|
+
for (const sentence of sentences) {
|
|
142
|
+
if (!sentence.terms) continue;
|
|
143
|
+
const occurrences = sentence.terms.filter((term) => term.tags?.includes("Adverb")).map((term) => {
|
|
144
|
+
const start = term.offset?.start;
|
|
145
|
+
const length = term.offset?.length;
|
|
146
|
+
return {
|
|
147
|
+
text: term.text?.replace(/[^a-zA-Z]+$/, "") ?? "",
|
|
148
|
+
start: typeof start === "number" ? start : 0,
|
|
149
|
+
end: typeof start === "number" && typeof length === "number" ? start + length : 0
|
|
150
|
+
};
|
|
151
|
+
}).filter((occurrence) => /ly$/i.test(occurrence.text)).filter((occurrence) => !allowed.has(occurrence.text.toLowerCase())).sort((left, right) => left.start - right.start);
|
|
152
|
+
for (let index = maxAdverbs; index < occurrences.length; index += 1) {
|
|
153
|
+
const occurrence = occurrences[index];
|
|
154
|
+
const range = getOccurrenceRange(sourceMap, occurrence);
|
|
155
|
+
if (!range) continue;
|
|
156
|
+
diagnostics.push({
|
|
157
|
+
ruleId: "no-adverb-overuse",
|
|
158
|
+
severity: "warn",
|
|
159
|
+
message: `reduce adverb overuse: "${occurrence.text}" exceeds the limit of ${maxAdverbs} -ly adverb${maxAdverbs === 1 ? "" : "s"} per sentence`,
|
|
160
|
+
range,
|
|
161
|
+
help: noAdverbOveruse.help
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return diagnostics;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
1
169
|
// src/no-buzzword-stacks.ts
|
|
2
170
|
var DEFAULT_TERMS = [
|
|
3
171
|
"alignment",
|
|
@@ -77,41 +245,74 @@ function escapeRegex(value) {
|
|
|
77
245
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
78
246
|
}
|
|
79
247
|
|
|
80
|
-
// src/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
248
|
+
// src/no-complex-readability.ts
|
|
249
|
+
var DEFAULT_OPTIONS = {
|
|
250
|
+
maxGradeLevel: 12,
|
|
251
|
+
minSentences: 3,
|
|
252
|
+
minWords: 30
|
|
253
|
+
};
|
|
254
|
+
var noComplexReadability = {
|
|
255
|
+
id: "no-complex-readability",
|
|
256
|
+
description: "Flag prose whose Flesch-Kincaid grade level exceeds a target",
|
|
257
|
+
defaults: { ...DEFAULT_OPTIONS },
|
|
258
|
+
help: "Landing-page copy should be readable by a broad audience. Break long sentences, replace jargon, and front-load the point until the grade level drops.",
|
|
259
|
+
check({ text, sourceMap, options }) {
|
|
260
|
+
const maxGradeLevel = options.maxGradeLevel ?? DEFAULT_OPTIONS.maxGradeLevel;
|
|
261
|
+
const minSentences = options.minSentences ?? DEFAULT_OPTIONS.minSentences;
|
|
262
|
+
const minWords = options.minWords ?? DEFAULT_OPTIONS.minWords;
|
|
263
|
+
const sentences = getSentences(text);
|
|
264
|
+
const words = getWords(text);
|
|
265
|
+
if (sentences.length < minSentences || words.length < minWords) {
|
|
91
266
|
return [];
|
|
92
267
|
}
|
|
268
|
+
const syllables = words.reduce((sum, word) => sum + countSyllables(word), 0);
|
|
269
|
+
const grade = fleschKincaidGrade(words.length, sentences.length, syllables);
|
|
270
|
+
if (grade <= maxGradeLevel) {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
const start = sourceMap[0];
|
|
274
|
+
const end = sourceMap[sourceMap.length - 1];
|
|
275
|
+
if (start === void 0 || end === void 0) return [];
|
|
93
276
|
return [{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
277
|
+
ruleId: "no-complex-readability",
|
|
278
|
+
severity: "warn",
|
|
279
|
+
message: `readability is grade ${grade.toFixed(1)} \u2014 simplify to ${maxGradeLevel} or below`,
|
|
280
|
+
range: { start, end: end + 1 },
|
|
281
|
+
help: noComplexReadability.help
|
|
97
282
|
}];
|
|
98
|
-
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
function getSentences(text) {
|
|
286
|
+
const parts = text.split(/([.!?]+)/);
|
|
287
|
+
const sentences = [];
|
|
288
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
289
|
+
const sentence = parts[i];
|
|
290
|
+
const terminator = parts[i + 1] ?? "";
|
|
291
|
+
const combined = (sentence ?? "") + terminator;
|
|
292
|
+
const trimmed = combined.trim();
|
|
293
|
+
if (trimmed) sentences.push(trimmed);
|
|
294
|
+
}
|
|
295
|
+
return sentences;
|
|
99
296
|
}
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
const end = sourceMap[occurrence.end - 1];
|
|
103
|
-
if (start === void 0 || end === void 0) return null;
|
|
104
|
-
return { start, end: end + 1 };
|
|
297
|
+
function getWords(text) {
|
|
298
|
+
return text.toLowerCase().replace(/[^a-z0-9\s'-]/g, " ").split(/\s+/).filter((word) => word.length > 0 && /[a-z0-9]/.test(word));
|
|
105
299
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
300
|
+
function countSyllables(word) {
|
|
301
|
+
const cleaned = word.toLowerCase().replace(/[^a-z]/g, "");
|
|
302
|
+
if (!cleaned) return 0;
|
|
303
|
+
if (cleaned.length <= 3) return 1;
|
|
304
|
+
const vowels = cleaned.match(/[aeiouy]+/g);
|
|
305
|
+
if (!vowels) return 1;
|
|
306
|
+
let count = vowels.length;
|
|
307
|
+
if (cleaned.endsWith("e")) count--;
|
|
308
|
+
if (cleaned.endsWith("le") && cleaned.length > 2 && !/[aeiouy]$/.test(cleaned[cleaned.length - 3] ?? "")) {
|
|
309
|
+
count++;
|
|
113
310
|
}
|
|
114
|
-
return
|
|
311
|
+
return Math.max(1, count);
|
|
312
|
+
}
|
|
313
|
+
function fleschKincaidGrade(words, sentences, syllables) {
|
|
314
|
+
if (sentences === 0 || words === 0) return 0;
|
|
315
|
+
return 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59;
|
|
115
316
|
}
|
|
116
317
|
|
|
117
318
|
// src/no-expletive-openers.ts
|
|
@@ -255,7 +456,7 @@ var noFuturePromises = {
|
|
|
255
456
|
const diagnostics = [];
|
|
256
457
|
const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES3;
|
|
257
458
|
for (const phrase of phrases) {
|
|
258
|
-
const re = new RegExp(`\\b${
|
|
459
|
+
const re = new RegExp(`\\b${escapeRegExp2(phrase).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
259
460
|
let match;
|
|
260
461
|
while ((match = re.exec(text)) !== null) {
|
|
261
462
|
const matchedPhrase = match[0];
|
|
@@ -274,7 +475,7 @@ var noFuturePromises = {
|
|
|
274
475
|
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
275
476
|
}
|
|
276
477
|
};
|
|
277
|
-
function
|
|
478
|
+
function escapeRegExp2(value) {
|
|
278
479
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
279
480
|
}
|
|
280
481
|
|
|
@@ -300,7 +501,7 @@ var noHedgeWords = {
|
|
|
300
501
|
const diagnostics = [];
|
|
301
502
|
const hedges = options.hedges?.length ? options.hedges : DEFAULT_HEDGES;
|
|
302
503
|
for (const hedge of hedges) {
|
|
303
|
-
const re = new RegExp(`\\b${
|
|
504
|
+
const re = new RegExp(`\\b${escapeRegExp3(hedge).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
304
505
|
let match;
|
|
305
506
|
while ((match = re.exec(text)) !== null) {
|
|
306
507
|
const phrase = match[0];
|
|
@@ -319,7 +520,7 @@ var noHedgeWords = {
|
|
|
319
520
|
return diagnostics;
|
|
320
521
|
}
|
|
321
522
|
};
|
|
322
|
-
function
|
|
523
|
+
function escapeRegExp3(value) {
|
|
323
524
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
324
525
|
}
|
|
325
526
|
|
|
@@ -344,7 +545,7 @@ var noJargon = {
|
|
|
344
545
|
const diagnostics = [];
|
|
345
546
|
const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES4;
|
|
346
547
|
for (const phrase of phrases) {
|
|
347
|
-
const re = new RegExp(`\\b${
|
|
548
|
+
const re = new RegExp(`\\b${escapeRegExp4(phrase).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
348
549
|
let match;
|
|
349
550
|
while ((match = re.exec(text)) !== null) {
|
|
350
551
|
const matchedPhrase = match[0];
|
|
@@ -363,7 +564,7 @@ var noJargon = {
|
|
|
363
564
|
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
364
565
|
}
|
|
365
566
|
};
|
|
366
|
-
function
|
|
567
|
+
function escapeRegExp4(value) {
|
|
367
568
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
368
569
|
}
|
|
369
570
|
|
|
@@ -389,7 +590,7 @@ var noMeaninglessModifiers = {
|
|
|
389
590
|
const diagnostics = [];
|
|
390
591
|
const modifiers = options.modifiers?.length ? options.modifiers : DEFAULT_MODIFIERS;
|
|
391
592
|
for (const modifier of modifiers) {
|
|
392
|
-
const re = new RegExp(`\\b${
|
|
593
|
+
const re = new RegExp(`\\b${escapeRegExp5(modifier).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
393
594
|
let match;
|
|
394
595
|
while ((match = re.exec(text)) !== null) {
|
|
395
596
|
const matchedModifier = match[0];
|
|
@@ -408,7 +609,7 @@ var noMeaninglessModifiers = {
|
|
|
408
609
|
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
409
610
|
}
|
|
410
611
|
};
|
|
411
|
-
function
|
|
612
|
+
function escapeRegExp5(value) {
|
|
412
613
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
413
614
|
}
|
|
414
615
|
|
|
@@ -459,6 +660,192 @@ function escapeRegex2(value) {
|
|
|
459
660
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
460
661
|
}
|
|
461
662
|
|
|
663
|
+
// src/no-overly-complex-sentences.ts
|
|
664
|
+
var DEFAULT_MAX_CONJUNCTIONS = 4;
|
|
665
|
+
var DEFAULT_MAX_COORDINATING = 3;
|
|
666
|
+
var DEFAULT_MAX_SUBORDINATING = 2;
|
|
667
|
+
var DEFAULT_COORDINATING = ["and", "but", "or", "nor", "yet", "so"];
|
|
668
|
+
var DEFAULT_SUBORDINATING = [
|
|
669
|
+
"because",
|
|
670
|
+
"although",
|
|
671
|
+
"though",
|
|
672
|
+
"while",
|
|
673
|
+
"since",
|
|
674
|
+
"unless",
|
|
675
|
+
"if",
|
|
676
|
+
"when",
|
|
677
|
+
"after",
|
|
678
|
+
"before",
|
|
679
|
+
"until",
|
|
680
|
+
"whether",
|
|
681
|
+
"once"
|
|
682
|
+
];
|
|
683
|
+
var noOverlyComplexSentences = {
|
|
684
|
+
id: "no-overly-complex-sentences",
|
|
685
|
+
description: "Flag sentences with too many coordinating or subordinating conjunctions",
|
|
686
|
+
defaults: {
|
|
687
|
+
maxConjunctions: DEFAULT_MAX_CONJUNCTIONS,
|
|
688
|
+
maxCoordinating: DEFAULT_MAX_COORDINATING,
|
|
689
|
+
maxSubordinating: DEFAULT_MAX_SUBORDINATING,
|
|
690
|
+
coordinating: DEFAULT_COORDINATING,
|
|
691
|
+
subordinating: DEFAULT_SUBORDINATING,
|
|
692
|
+
allowList: []
|
|
693
|
+
},
|
|
694
|
+
help: "Sentences packed with conjunctions are often run-ons or nested too deeply. Break them into shorter sentences so each point stands on its own.",
|
|
695
|
+
check({ text, sourceMap, options }) {
|
|
696
|
+
const maxConjunctions = options.maxConjunctions ?? DEFAULT_MAX_CONJUNCTIONS;
|
|
697
|
+
const maxCoordinating = options.maxCoordinating ?? DEFAULT_MAX_COORDINATING;
|
|
698
|
+
const maxSubordinating = options.maxSubordinating ?? DEFAULT_MAX_SUBORDINATING;
|
|
699
|
+
const coordinating = (options.coordinating ?? DEFAULT_COORDINATING).map(
|
|
700
|
+
(value) => value.toLowerCase().trim()
|
|
701
|
+
);
|
|
702
|
+
const subordinating = (options.subordinating ?? DEFAULT_SUBORDINATING).map(
|
|
703
|
+
(value) => value.toLowerCase().trim()
|
|
704
|
+
);
|
|
705
|
+
const allowList = new Set(
|
|
706
|
+
(options.allowList ?? []).map((value) => value.toLowerCase().trim())
|
|
707
|
+
);
|
|
708
|
+
const patterns = [
|
|
709
|
+
...coordinating.map((phrase) => ({
|
|
710
|
+
phrase,
|
|
711
|
+
category: "coordinating",
|
|
712
|
+
regex: phraseToRegex(phrase)
|
|
713
|
+
})),
|
|
714
|
+
...subordinating.map((phrase) => ({
|
|
715
|
+
phrase,
|
|
716
|
+
category: "subordinating",
|
|
717
|
+
regex: phraseToRegex(phrase)
|
|
718
|
+
}))
|
|
719
|
+
].sort((a, b) => b.phrase.length - a.phrase.length);
|
|
720
|
+
const doc = createDoc(text);
|
|
721
|
+
const sentences = doc.sentences().json({ offset: true, terms: { offset: true } });
|
|
722
|
+
const diagnostics = [];
|
|
723
|
+
for (const sentence of sentences) {
|
|
724
|
+
if (!sentence.offset || !sentence.terms) continue;
|
|
725
|
+
const sentenceStart = sentence.offset.start ?? 0;
|
|
726
|
+
const sentenceText = sentence.text ?? "";
|
|
727
|
+
const matchedRanges = [];
|
|
728
|
+
let coordinatingCount = 0;
|
|
729
|
+
let subordinatingCount = 0;
|
|
730
|
+
for (const { phrase, category, regex } of patterns) {
|
|
731
|
+
if (allowList.has(phrase)) continue;
|
|
732
|
+
let match;
|
|
733
|
+
while ((match = regex.exec(sentenceText)) !== null) {
|
|
734
|
+
const matchStart = match.index;
|
|
735
|
+
const matchEnd = match.index + match[0].length;
|
|
736
|
+
if (matchedRanges.some((range) => matchStart < range.end && matchEnd > range.start)) {
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
matchedRanges.push({ start: matchStart, end: matchEnd });
|
|
740
|
+
if (category === "coordinating") {
|
|
741
|
+
coordinatingCount += 1;
|
|
742
|
+
} else {
|
|
743
|
+
subordinatingCount += 1;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
const total = coordinatingCount + subordinatingCount;
|
|
748
|
+
if (total <= maxConjunctions && coordinatingCount <= maxCoordinating && subordinatingCount <= maxSubordinating) {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const length = sentence.offset.length ?? 0;
|
|
752
|
+
const sentenceEnd = sentenceStart + length;
|
|
753
|
+
const sourceStart = sourceMap[sentenceStart];
|
|
754
|
+
const sourceEnd = sourceMap[sentenceEnd - 1];
|
|
755
|
+
if (sourceStart === void 0 || sourceEnd === void 0) continue;
|
|
756
|
+
const reasons = [];
|
|
757
|
+
if (total > maxConjunctions) {
|
|
758
|
+
reasons.push(`${total} conjunctions (max ${maxConjunctions})`);
|
|
759
|
+
}
|
|
760
|
+
if (coordinatingCount > maxCoordinating) {
|
|
761
|
+
reasons.push(`${coordinatingCount} coordinating (max ${maxCoordinating})`);
|
|
762
|
+
}
|
|
763
|
+
if (subordinatingCount > maxSubordinating) {
|
|
764
|
+
reasons.push(`${subordinatingCount} subordinating (max ${maxSubordinating})`);
|
|
765
|
+
}
|
|
766
|
+
diagnostics.push({
|
|
767
|
+
ruleId: "no-overly-complex-sentences",
|
|
768
|
+
severity: "warn",
|
|
769
|
+
message: `sentence is overly complex: ${reasons.join(", ")} \u2014 consider splitting it`,
|
|
770
|
+
range: { start: sourceStart, end: sourceEnd + 1 },
|
|
771
|
+
help: noOverlyComplexSentences.help,
|
|
772
|
+
suggest: {
|
|
773
|
+
description: "Split this sentence into shorter sentences, one idea each.",
|
|
774
|
+
edits: []
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return diagnostics;
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
function phraseToRegex(phrase) {
|
|
782
|
+
const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
783
|
+
const spaced = escaped.replace(/\s+/g, "\\s+");
|
|
784
|
+
const source = spaced.includes("\\s+") ? spaced : `\\b${spaced}\\b`;
|
|
785
|
+
return new RegExp(source, "gi");
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/no-overused-adverbs.ts
|
|
789
|
+
var DEFAULT_THRESHOLD = 3;
|
|
790
|
+
var DEFAULT_MIN_LENGTH = 3;
|
|
791
|
+
var DEFAULT_ALLOWED_ADVERBS2 = ["only", "not"];
|
|
792
|
+
var noOverusedAdverbs = {
|
|
793
|
+
id: "no-overused-adverbs",
|
|
794
|
+
description: "Flag adverbs that appear excessively across the text",
|
|
795
|
+
defaults: {
|
|
796
|
+
threshold: DEFAULT_THRESHOLD,
|
|
797
|
+
minLength: DEFAULT_MIN_LENGTH,
|
|
798
|
+
allowedAdverbs: DEFAULT_ALLOWED_ADVERBS2
|
|
799
|
+
},
|
|
800
|
+
help: "Repeating the same adverb throughout a passage weakens copy and reads as filler. Replace the adverb with a stronger verb or adjective, cut it, or add it to allowedAdverbs if it is essential.",
|
|
801
|
+
check({ text, sourceMap, options }) {
|
|
802
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
803
|
+
const minLength = options.minLength ?? DEFAULT_MIN_LENGTH;
|
|
804
|
+
const allowed = new Set((options.allowedAdverbs ?? DEFAULT_ALLOWED_ADVERBS2).map((value) => value.toLowerCase()));
|
|
805
|
+
const adverbFilter = options.adverbs ? new Set(options.adverbs.map((value) => value.toLowerCase())) : null;
|
|
806
|
+
const doc = createDoc(text);
|
|
807
|
+
const sentences = doc.json({ offset: true, terms: { offset: true, tags: true } });
|
|
808
|
+
const occurrencesByAdverb = /* @__PURE__ */ new Map();
|
|
809
|
+
for (const sentence of sentences) {
|
|
810
|
+
for (const term of sentence.terms ?? []) {
|
|
811
|
+
if (!term.tags?.includes("Adverb")) continue;
|
|
812
|
+
const rawText = term.text?.replace(/[^a-zA-Z]+$/, "") ?? "";
|
|
813
|
+
const normalized = rawText.toLowerCase();
|
|
814
|
+
if (!normalized || normalized.length < minLength) continue;
|
|
815
|
+
if (allowed.has(normalized)) continue;
|
|
816
|
+
if (adverbFilter && !adverbFilter.has(normalized)) continue;
|
|
817
|
+
const start = term.offset?.start;
|
|
818
|
+
const length = term.offset?.length;
|
|
819
|
+
if (typeof start !== "number" || typeof length !== "number") continue;
|
|
820
|
+
const occurrence = { text: rawText, start, end: start + length };
|
|
821
|
+
const existing = occurrencesByAdverb.get(normalized);
|
|
822
|
+
if (existing) {
|
|
823
|
+
existing.push(occurrence);
|
|
824
|
+
} else {
|
|
825
|
+
occurrencesByAdverb.set(normalized, [occurrence]);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const diagnostics = [];
|
|
830
|
+
for (const [, occurrences] of occurrencesByAdverb) {
|
|
831
|
+
if (occurrences.length <= threshold) continue;
|
|
832
|
+
for (let index = threshold; index < occurrences.length; index += 1) {
|
|
833
|
+
const occurrence = occurrences[index];
|
|
834
|
+
const range = getOccurrenceRange(sourceMap, occurrence);
|
|
835
|
+
if (!range) continue;
|
|
836
|
+
diagnostics.push({
|
|
837
|
+
ruleId: "no-overused-adverbs",
|
|
838
|
+
severity: "warn",
|
|
839
|
+
message: `reduce overused adverb: "${occurrence.text}" appears ${occurrences.length} times`,
|
|
840
|
+
range,
|
|
841
|
+
help: noOverusedAdverbs.help
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
|
|
462
849
|
// src/no-passive-voice.ts
|
|
463
850
|
var DEFAULT_ALLOWED_AUXILIARIES = ["is", "are", "was", "were", "be", "been", "being"];
|
|
464
851
|
var noPassiveVoice = {
|
|
@@ -549,6 +936,65 @@ function escapeRegex3(value) {
|
|
|
549
936
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
550
937
|
}
|
|
551
938
|
|
|
939
|
+
// src/no-qualifier-creep.ts
|
|
940
|
+
var DEFAULT_QUALIFIERS = [
|
|
941
|
+
"very",
|
|
942
|
+
"really",
|
|
943
|
+
"quite",
|
|
944
|
+
"fairly",
|
|
945
|
+
"somewhat",
|
|
946
|
+
"rather",
|
|
947
|
+
"pretty",
|
|
948
|
+
"basically",
|
|
949
|
+
"actually",
|
|
950
|
+
"literally",
|
|
951
|
+
"essentially",
|
|
952
|
+
"truly",
|
|
953
|
+
"definitely",
|
|
954
|
+
"clearly",
|
|
955
|
+
"obviously",
|
|
956
|
+
"absolutely",
|
|
957
|
+
"completely",
|
|
958
|
+
"totally",
|
|
959
|
+
"utterly"
|
|
960
|
+
];
|
|
961
|
+
var DEFAULT_MAX_QUALIFIERS = 1;
|
|
962
|
+
var noQualifierCreep = {
|
|
963
|
+
id: "no-qualifier-creep",
|
|
964
|
+
description: "Flag stacked qualifiers or intensifiers before an adjective or adverb",
|
|
965
|
+
defaults: { qualifiers: DEFAULT_QUALIFIERS, maxQualifiers: DEFAULT_MAX_QUALIFIERS },
|
|
966
|
+
help: "Stacked qualifiers dilute the claim and feel hedged. Keep the strongest qualifier or replace the phrase with concrete evidence.",
|
|
967
|
+
check({ text, sourceMap, options }) {
|
|
968
|
+
const qualifiers = new Set((options.qualifiers?.length ? options.qualifiers : DEFAULT_QUALIFIERS).map((value) => value.toLowerCase()));
|
|
969
|
+
const maxQualifiers = options.maxQualifiers ?? DEFAULT_MAX_QUALIFIERS;
|
|
970
|
+
if (maxQualifiers < 1) return [];
|
|
971
|
+
const doc = createDoc(text);
|
|
972
|
+
const diagnostics = [];
|
|
973
|
+
const seenRanges = [];
|
|
974
|
+
const patterns = ["#Adverb #Adverb+ #Adjective", "#Adverb #Adverb+ #Adverb"];
|
|
975
|
+
for (const pattern of patterns) {
|
|
976
|
+
const matches = doc.match(pattern);
|
|
977
|
+
for (const occurrence of getMatchOccurrences(text, matches)) {
|
|
978
|
+
const words = occurrence.text.trim().split(/\s+/);
|
|
979
|
+
const qualifierWords = words.filter((word) => qualifiers.has(word.toLowerCase().replace(/[^a-zA-Z]+$/, "")));
|
|
980
|
+
if (qualifierWords.length <= maxQualifiers) continue;
|
|
981
|
+
if (seenRanges.some((range2) => occurrence.start < range2.end && occurrence.end > range2.start)) continue;
|
|
982
|
+
seenRanges.push({ start: occurrence.start, end: occurrence.end });
|
|
983
|
+
const range = getOccurrenceRange(sourceMap, occurrence);
|
|
984
|
+
if (!range) continue;
|
|
985
|
+
diagnostics.push({
|
|
986
|
+
ruleId: "no-qualifier-creep",
|
|
987
|
+
severity: "warn",
|
|
988
|
+
message: `remove stacked qualifiers in "${occurrence.text}" \u2014 use one strong word or a concrete detail`,
|
|
989
|
+
range,
|
|
990
|
+
help: noQualifierCreep.help
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
|
|
552
998
|
// src/no-redundant-pairs.ts
|
|
553
999
|
var DEFAULT_PHRASES5 = [
|
|
554
1000
|
"first and foremost",
|
|
@@ -570,7 +1016,7 @@ var noRedundantPairs = {
|
|
|
570
1016
|
const diagnostics = [];
|
|
571
1017
|
const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES5;
|
|
572
1018
|
for (const phrase of phrases) {
|
|
573
|
-
const re = new RegExp(`\\b${
|
|
1019
|
+
const re = new RegExp(`\\b${escapeRegExp6(phrase).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
574
1020
|
let match;
|
|
575
1021
|
while ((match = re.exec(text)) !== null) {
|
|
576
1022
|
const matchedPhrase = match[0];
|
|
@@ -589,7 +1035,7 @@ var noRedundantPairs = {
|
|
|
589
1035
|
return diagnostics;
|
|
590
1036
|
}
|
|
591
1037
|
};
|
|
592
|
-
function
|
|
1038
|
+
function escapeRegExp6(value) {
|
|
593
1039
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
594
1040
|
}
|
|
595
1041
|
|
|
@@ -644,7 +1090,7 @@ var noSuperlativeClaims = {
|
|
|
644
1090
|
);
|
|
645
1091
|
const claimedRanges = [];
|
|
646
1092
|
for (const phrase of phrases) {
|
|
647
|
-
const re = new RegExp(`\\b${
|
|
1093
|
+
const re = new RegExp(`\\b${escapeRegExp7(phrase).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
648
1094
|
let match;
|
|
649
1095
|
while ((match = re.exec(text)) !== null) {
|
|
650
1096
|
const matchedPhrase = match[0];
|
|
@@ -673,10 +1119,89 @@ function getLongestPhrasesFirst(phrases) {
|
|
|
673
1119
|
function rangesOverlap(range, start, end) {
|
|
674
1120
|
return start < range.end && end > range.start;
|
|
675
1121
|
}
|
|
676
|
-
function
|
|
1122
|
+
function escapeRegExp7(value) {
|
|
677
1123
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
678
1124
|
}
|
|
679
1125
|
|
|
1126
|
+
// src/no-vague-comparatives.ts
|
|
1127
|
+
import nlp2 from "compromise";
|
|
1128
|
+
var DEFAULT_OPTIONS2 = {
|
|
1129
|
+
comparatives: [
|
|
1130
|
+
"better",
|
|
1131
|
+
"worse",
|
|
1132
|
+
"more",
|
|
1133
|
+
"less",
|
|
1134
|
+
"faster",
|
|
1135
|
+
"slower",
|
|
1136
|
+
"easier",
|
|
1137
|
+
"harder",
|
|
1138
|
+
"stronger",
|
|
1139
|
+
"weaker",
|
|
1140
|
+
"higher",
|
|
1141
|
+
"lower",
|
|
1142
|
+
"bigger",
|
|
1143
|
+
"smaller",
|
|
1144
|
+
"greater"
|
|
1145
|
+
],
|
|
1146
|
+
requireThan: true
|
|
1147
|
+
};
|
|
1148
|
+
var noVagueComparatives = {
|
|
1149
|
+
id: "no-vague-comparatives",
|
|
1150
|
+
description: "Flag comparative claims that omit a clear baseline",
|
|
1151
|
+
defaults: { ...DEFAULT_OPTIONS2 },
|
|
1152
|
+
help: 'Comparative words like "better" or "faster" only persuade when the reader knows what is being compared. Add "than" and a concrete baseline, or rephrase with a specific metric.',
|
|
1153
|
+
check({ text, sourceMap, options }) {
|
|
1154
|
+
const comparatives = new Set(
|
|
1155
|
+
(options.comparatives ?? DEFAULT_OPTIONS2.comparatives).map((word) => word.toLowerCase())
|
|
1156
|
+
);
|
|
1157
|
+
const requireThan = options.requireThan ?? DEFAULT_OPTIONS2.requireThan;
|
|
1158
|
+
if (comparatives.size === 0) return [];
|
|
1159
|
+
const doc = nlp2(text);
|
|
1160
|
+
const diagnostics = [];
|
|
1161
|
+
const seenRanges = [];
|
|
1162
|
+
const sentences = doc.sentences().json({ offset: true, terms: { offset: true, text: true } });
|
|
1163
|
+
for (const sentence of sentences) {
|
|
1164
|
+
const sentenceStart = sentence.offset?.start ?? sentence.terms?.[0]?.offset?.start;
|
|
1165
|
+
const sentenceLength = sentence.offset?.length ?? sentence.terms?.reduce((sum, term) => sum + (term.offset?.length ?? 0), 0);
|
|
1166
|
+
if (typeof sentenceStart !== "number" || typeof sentenceLength !== "number") continue;
|
|
1167
|
+
const sentenceEnd = sentenceStart + sentenceLength;
|
|
1168
|
+
const sentenceText = sentence.text ?? text.slice(sentenceStart, sentenceEnd);
|
|
1169
|
+
const sentenceDoc = nlp2(sentenceText);
|
|
1170
|
+
const explicitComparatives = Array.from(comparatives).join("|");
|
|
1171
|
+
const patterns = [
|
|
1172
|
+
"#Comparative",
|
|
1173
|
+
`(more|less) #Adjective`,
|
|
1174
|
+
`(more|less) #Adverb`,
|
|
1175
|
+
`(${explicitComparatives})`
|
|
1176
|
+
];
|
|
1177
|
+
for (const pattern of patterns) {
|
|
1178
|
+
const matches = sentenceDoc.match(pattern);
|
|
1179
|
+
for (const occurrence of getMatchOccurrences(sentenceText, matches)) {
|
|
1180
|
+
const words = occurrence.text.trim().toLowerCase().split(/\s+/);
|
|
1181
|
+
const matchedComparative = words.find((word) => comparatives.has(word.replace(/[^a-zA-Z]+$/, "")));
|
|
1182
|
+
if (!matchedComparative) continue;
|
|
1183
|
+
const trailingPunctuation = occurrence.text.match(/[^a-zA-Z\s]+$/)?.[0].length ?? 0;
|
|
1184
|
+
const occurrenceStart = sentenceStart + occurrence.start;
|
|
1185
|
+
const occurrenceEnd = sentenceStart + occurrence.end - trailingPunctuation;
|
|
1186
|
+
if (seenRanges.some((range2) => occurrenceStart < range2.end && occurrenceEnd > range2.start)) continue;
|
|
1187
|
+
seenRanges.push({ start: occurrenceStart, end: occurrenceEnd });
|
|
1188
|
+
if (requireThan && sentenceDoc.has("than")) continue;
|
|
1189
|
+
const range = getOccurrenceRange(sourceMap, { text: occurrence.text, start: occurrenceStart, end: occurrenceEnd });
|
|
1190
|
+
if (!range) continue;
|
|
1191
|
+
diagnostics.push({
|
|
1192
|
+
ruleId: "no-vague-comparatives",
|
|
1193
|
+
severity: "warn",
|
|
1194
|
+
message: `comparative "${occurrence.text.trim().replace(/[^a-zA-Z\s]+$/, "")}" needs a baseline \u2014 add "than" or a concrete comparison`,
|
|
1195
|
+
range,
|
|
1196
|
+
help: noVagueComparatives.help
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
|
|
680
1205
|
// src/no-vague-quantifiers.ts
|
|
681
1206
|
var DEFAULT_QUANTIFIERS = [
|
|
682
1207
|
"many",
|
|
@@ -703,7 +1228,7 @@ var noVagueQuantifiers = {
|
|
|
703
1228
|
const quantifiers = options.quantifiers?.length ? options.quantifiers : DEFAULT_QUANTIFIERS;
|
|
704
1229
|
for (const quantifier of quantifiers) {
|
|
705
1230
|
const re = new RegExp(
|
|
706
|
-
`\\b${
|
|
1231
|
+
`\\b${escapeRegExp8(quantifier).replace(/\\s+/g, "\\s+")}\\b`,
|
|
707
1232
|
"gi"
|
|
708
1233
|
);
|
|
709
1234
|
let match;
|
|
@@ -724,7 +1249,7 @@ var noVagueQuantifiers = {
|
|
|
724
1249
|
return diagnostics;
|
|
725
1250
|
}
|
|
726
1251
|
};
|
|
727
|
-
function
|
|
1252
|
+
function escapeRegExp8(value) {
|
|
728
1253
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
729
1254
|
}
|
|
730
1255
|
|
|
@@ -773,9 +1298,62 @@ var noWeakModals = {
|
|
|
773
1298
|
}
|
|
774
1299
|
};
|
|
775
1300
|
|
|
1301
|
+
// src/sentence-complexity.ts
|
|
1302
|
+
var DEFAULT_OPTIONS3 = {
|
|
1303
|
+
maxWordCount: 25,
|
|
1304
|
+
maxClauseCount: 3
|
|
1305
|
+
};
|
|
1306
|
+
var sentenceComplexity = {
|
|
1307
|
+
id: "sentence-complexity",
|
|
1308
|
+
description: "Flag sentences that exceed a word or clause threshold",
|
|
1309
|
+
defaults: { ...DEFAULT_OPTIONS3 },
|
|
1310
|
+
help: "Long, clause-heavy sentences are harder to read. Split them into shorter sentences that each make one point.",
|
|
1311
|
+
check({ text, sourceMap, options }) {
|
|
1312
|
+
const maxWordCount = options.maxWordCount ?? DEFAULT_OPTIONS3.maxWordCount;
|
|
1313
|
+
const maxClauseCount = options.maxClauseCount ?? DEFAULT_OPTIONS3.maxClauseCount;
|
|
1314
|
+
const doc = createDoc(text);
|
|
1315
|
+
const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } });
|
|
1316
|
+
const diagnostics = [];
|
|
1317
|
+
for (const sentence of sentences) {
|
|
1318
|
+
if (!sentence.offset || !sentence.terms) continue;
|
|
1319
|
+
const terms = sentence.terms.filter((term) => /[a-zA-Z0-9]/.test(term.text ?? ""));
|
|
1320
|
+
const wordCount = terms.length;
|
|
1321
|
+
const clauseCount = terms.filter((term) => {
|
|
1322
|
+
const tags = term.tags ?? [];
|
|
1323
|
+
return tags.includes("Verb") && !tags.includes("Gerund") && !tags.includes("Infinitive") && !tags.includes("Particle");
|
|
1324
|
+
}).length;
|
|
1325
|
+
if (wordCount <= maxWordCount && clauseCount <= maxClauseCount) continue;
|
|
1326
|
+
const start = sentence.offset.start ?? 0;
|
|
1327
|
+
const length = sentence.offset.length ?? 0;
|
|
1328
|
+
const end = start + length;
|
|
1329
|
+
const sourceStart = sourceMap[start];
|
|
1330
|
+
const sourceEnd = sourceMap[end - 1];
|
|
1331
|
+
if (sourceStart === void 0 || sourceEnd === void 0) continue;
|
|
1332
|
+
const reasons = [];
|
|
1333
|
+
if (wordCount > maxWordCount) reasons.push(`${wordCount} words (max ${maxWordCount})`);
|
|
1334
|
+
if (clauseCount > maxClauseCount) reasons.push(`${clauseCount} clauses (max ${maxClauseCount})`);
|
|
1335
|
+
diagnostics.push({
|
|
1336
|
+
ruleId: "sentence-complexity",
|
|
1337
|
+
severity: "warn",
|
|
1338
|
+
message: `sentence is too complex: ${reasons.join(", ")} \u2014 consider splitting it`,
|
|
1339
|
+
range: { start: sourceStart, end: sourceEnd + 1 },
|
|
1340
|
+
help: sentenceComplexity.help,
|
|
1341
|
+
suggest: {
|
|
1342
|
+
description: "Split this sentence into shorter sentences, one idea each.",
|
|
1343
|
+
edits: []
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
return diagnostics;
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
|
|
776
1351
|
// src/index.ts
|
|
777
1352
|
var ruleRegistry = /* @__PURE__ */ new Map([
|
|
1353
|
+
["no-absolute-intensifiers", noAbsoluteIntensifiers],
|
|
1354
|
+
["no-adverb-overuse", noAdverbOveruse],
|
|
778
1355
|
["no-buzzword-stacks", noBuzzwordStacks],
|
|
1356
|
+
["no-complex-readability", noComplexReadability],
|
|
779
1357
|
["no-empty-transformation-claims", noEmptyTransformationClaims],
|
|
780
1358
|
["no-expletive-openers", noExpletiveOpeners],
|
|
781
1359
|
["no-filter-words", noFilterWords],
|
|
@@ -784,16 +1362,24 @@ var ruleRegistry = /* @__PURE__ */ new Map([
|
|
|
784
1362
|
["no-jargon", noJargon],
|
|
785
1363
|
["no-meaningless-modifiers", noMeaninglessModifiers],
|
|
786
1364
|
["no-nominalized-phrases", noNominalizedPhrases],
|
|
1365
|
+
["no-overly-complex-sentences", noOverlyComplexSentences],
|
|
1366
|
+
["no-overused-adverbs", noOverusedAdverbs],
|
|
787
1367
|
["no-passive-voice", noPassiveVoice],
|
|
788
1368
|
["no-pronoun-led-claims", noPronounLedClaims],
|
|
1369
|
+
["no-qualifier-creep", noQualifierCreep],
|
|
789
1370
|
["no-redundant-pairs", noRedundantPairs],
|
|
790
1371
|
["no-stacked-adjectives", noStackedAdjectives],
|
|
791
1372
|
["no-superlative-claims", noSuperlativeClaims],
|
|
1373
|
+
["no-vague-comparatives", noVagueComparatives],
|
|
792
1374
|
["no-vague-quantifiers", noVagueQuantifiers],
|
|
793
|
-
["no-weak-modals", noWeakModals]
|
|
1375
|
+
["no-weak-modals", noWeakModals],
|
|
1376
|
+
["sentence-complexity", sentenceComplexity]
|
|
794
1377
|
]);
|
|
795
1378
|
export {
|
|
1379
|
+
noAbsoluteIntensifiers,
|
|
1380
|
+
noAdverbOveruse,
|
|
796
1381
|
noBuzzwordStacks,
|
|
1382
|
+
noComplexReadability,
|
|
797
1383
|
noEmptyTransformationClaims,
|
|
798
1384
|
noExpletiveOpeners,
|
|
799
1385
|
noFilterWords,
|
|
@@ -802,13 +1388,18 @@ export {
|
|
|
802
1388
|
noJargon,
|
|
803
1389
|
noMeaninglessModifiers,
|
|
804
1390
|
noNominalizedPhrases,
|
|
1391
|
+
noOverlyComplexSentences,
|
|
1392
|
+
noOverusedAdverbs,
|
|
805
1393
|
noPassiveVoice,
|
|
806
1394
|
noPronounLedClaims,
|
|
1395
|
+
noQualifierCreep,
|
|
807
1396
|
noRedundantPairs,
|
|
808
1397
|
noStackedAdjectives,
|
|
809
1398
|
noSuperlativeClaims,
|
|
1399
|
+
noVagueComparatives,
|
|
810
1400
|
noVagueQuantifiers,
|
|
811
1401
|
noWeakModals,
|
|
812
|
-
ruleRegistry
|
|
1402
|
+
ruleRegistry,
|
|
1403
|
+
sentenceComplexity
|
|
813
1404
|
};
|
|
814
1405
|
//# sourceMappingURL=index.js.map
|