slopless 0.2.28 → 0.2.30

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.
@@ -42,6 +42,7 @@ export const everything = {
42
42
  "perception-verb-density": true,
43
43
  "prohibited-phrases": true,
44
44
  "prohibited-words": true,
45
+ "quietly-filler": true,
45
46
  "quietly-overuse": true,
46
47
  "recommended-terms": true,
47
48
  redundancy: true,
@@ -3,6 +3,7 @@ import hedgeStacking from "../rules/words/hedge-stacking.js";
3
3
  import llmVocabularyDensity from "../rules/words/llm-vocabulary-density.js";
4
4
  import llmVocabulary from "../rules/words/llm-vocabulary.js";
5
5
  import prohibitedWords from "../rules/words/prohibited-words.js";
6
+ import quietlyFiller from "../rules/words/quietly-filler.js";
6
7
  import quietlyOveruse from "../rules/words/quietly-overuse.js";
7
8
  import simplicity from "../rules/words/simplicity.js";
8
9
  export const wordRules = {
@@ -11,6 +12,7 @@ export const wordRules = {
11
12
  "llm-vocabulary-density": llmVocabularyDensity,
12
13
  "llm-vocabulary": llmVocabulary,
13
14
  "prohibited-words": prohibitedWords,
15
+ "quietly-filler": quietlyFiller,
14
16
  "quietly-overuse": quietlyOveruse,
15
17
  simplicity
16
18
  };
@@ -133,6 +133,9 @@ export function reportsForPolicy(units, detections, policy, formatMessage) {
133
133
  message: "",
134
134
  range: unit?.sourceRangeFor(detection.range) ?? detection.range,
135
135
  ruleId: detection.ruleId,
136
+ ...(policy.severity === undefined
137
+ ? {}
138
+ : { severity: policy.severity }),
136
139
  unitId: detection.unitId
137
140
  };
138
141
  });
@@ -18,7 +18,10 @@ export function oneToOneRule(input) {
18
18
  return defineTextlintRule({
19
19
  detector: detector(input),
20
20
  formatMessage: input.formatMessage,
21
- reportPolicy: { kind: "one-to-one" },
21
+ reportPolicy: {
22
+ kind: "one-to-one",
23
+ ...(input.severity === undefined ? {} : { severity: input.severity })
24
+ },
22
25
  units: (document) => {
23
26
  switch (input.unitKind) {
24
27
  case "document":
@@ -0,0 +1,694 @@
1
+ {
2
+ "detachedCompanionWords": ["slowly"],
3
+ "detachedExplanationWords": ["ways"],
4
+ "auxiliaries": [
5
+ "am",
6
+ "are",
7
+ "be",
8
+ "been",
9
+ "being",
10
+ "can",
11
+ "could",
12
+ "did",
13
+ "do",
14
+ "does",
15
+ "had",
16
+ "has",
17
+ "have",
18
+ "is",
19
+ "may",
20
+ "might",
21
+ "must",
22
+ "should",
23
+ "was",
24
+ "were",
25
+ "will",
26
+ "would"
27
+ ],
28
+ "evaluativeWords": [
29
+ "consequential",
30
+ "dynamic",
31
+ "effective",
32
+ "heartbreaking",
33
+ "important",
34
+ "impressive",
35
+ "influential",
36
+ "powerful",
37
+ "remarkable",
38
+ "revolutionary",
39
+ "significant",
40
+ "stylish",
41
+ "transformative"
42
+ ],
43
+ "abstractChangeWords": [
44
+ "abandon",
45
+ "abandoned",
46
+ "abandons",
47
+ "alter",
48
+ "altered",
49
+ "alters",
50
+ "arrive",
51
+ "arrived",
52
+ "arrives",
53
+ "assemble",
54
+ "assembled",
55
+ "assembles",
56
+ "assembling",
57
+ "atrophied",
58
+ "atrophies",
59
+ "atrophy",
60
+ "balloon",
61
+ "ballooned",
62
+ "balloons",
63
+ "became",
64
+ "become",
65
+ "becomes",
66
+ "changed",
67
+ "changes",
68
+ "changing",
69
+ "climbed",
70
+ "climbs",
71
+ "compounded",
72
+ "compounds",
73
+ "degraded",
74
+ "degrades",
75
+ "degrading",
76
+ "disappear",
77
+ "disappeared",
78
+ "disappearing",
79
+ "disappears",
80
+ "discard",
81
+ "discarded",
82
+ "discarding",
83
+ "discards",
84
+ "doubled",
85
+ "doubles",
86
+ "ended",
87
+ "ends",
88
+ "erode",
89
+ "eroded",
90
+ "erodes",
91
+ "escalated",
92
+ "escalates",
93
+ "evolved",
94
+ "evolves",
95
+ "lose",
96
+ "loses",
97
+ "lost",
98
+ "moved",
99
+ "moves",
100
+ "redefined",
101
+ "redefines",
102
+ "reshaped",
103
+ "reshapes",
104
+ "reshaping",
105
+ "revolutionized",
106
+ "revolutionizes",
107
+ "revolutionizing",
108
+ "shift",
109
+ "shifted",
110
+ "shifting",
111
+ "shifts",
112
+ "stopped",
113
+ "stops",
114
+ "survive",
115
+ "survived",
116
+ "survives",
117
+ "surviving",
118
+ "transformed",
119
+ "transforms",
120
+ "trend",
121
+ "trended",
122
+ "trending",
123
+ "trends",
124
+ "widened",
125
+ "widens",
126
+ "wither",
127
+ "withered",
128
+ "withering",
129
+ "withers"
130
+ ],
131
+ "strongAbstractChangeWords": [
132
+ "atrophied",
133
+ "atrophies",
134
+ "atrophy",
135
+ "balloon",
136
+ "ballooned",
137
+ "balloons",
138
+ "compounded",
139
+ "compounds",
140
+ "degraded",
141
+ "degrades",
142
+ "degrading",
143
+ "disappear",
144
+ "disappeared",
145
+ "disappearing",
146
+ "disappears",
147
+ "eroded",
148
+ "erodes",
149
+ "redefined",
150
+ "redefines",
151
+ "reshaped",
152
+ "reshapes",
153
+ "reshaping",
154
+ "revolutionized",
155
+ "revolutionizes",
156
+ "revolutionizing",
157
+ "transformed",
158
+ "transforms",
159
+ "widened",
160
+ "widens",
161
+ "wither",
162
+ "withered",
163
+ "withering",
164
+ "withers"
165
+ ],
166
+ "abstractSubjectWords": [
167
+ "assumption",
168
+ "assumptions",
169
+ "baseline",
170
+ "build",
171
+ "capacity",
172
+ "category",
173
+ "change",
174
+ "charge",
175
+ "communities",
176
+ "constraint",
177
+ "conversation",
178
+ "cost",
179
+ "curiosity",
180
+ "data",
181
+ "era",
182
+ "expectations",
183
+ "gap",
184
+ "gate",
185
+ "health",
186
+ "improvement",
187
+ "improvements",
188
+ "inconsistency",
189
+ "industry",
190
+ "infrastructure",
191
+ "landscape",
192
+ "market",
193
+ "matrix",
194
+ "model",
195
+ "partnership",
196
+ "performance",
197
+ "pieces",
198
+ "platform",
199
+ "principle",
200
+ "process",
201
+ "product",
202
+ "products",
203
+ "profession",
204
+ "project",
205
+ "projects",
206
+ "quality",
207
+ "review",
208
+ "search",
209
+ "shift",
210
+ "signal",
211
+ "space",
212
+ "spend",
213
+ "standard",
214
+ "strategy",
215
+ "subscriber",
216
+ "subscribers",
217
+ "system",
218
+ "systems",
219
+ "technology",
220
+ "times",
221
+ "trust",
222
+ "volume",
223
+ "web",
224
+ "work"
225
+ ],
226
+ "hiddenHarmWords": [
227
+ "bleed",
228
+ "bleeding",
229
+ "block",
230
+ "blocked",
231
+ "blocking",
232
+ "break",
233
+ "breaking",
234
+ "breaks",
235
+ "destroy",
236
+ "destroyed",
237
+ "destroying",
238
+ "destroys",
239
+ "drown",
240
+ "drowned",
241
+ "drowning",
242
+ "drowns",
243
+ "fail",
244
+ "failed",
245
+ "failing",
246
+ "fails",
247
+ "fix",
248
+ "fixed",
249
+ "fixes",
250
+ "hurt",
251
+ "hurting",
252
+ "hurts",
253
+ "outsource",
254
+ "outsourced",
255
+ "outsources",
256
+ "outsourcing",
257
+ "poison",
258
+ "poisoned",
259
+ "poisoning",
260
+ "poisons",
261
+ "rot",
262
+ "rots",
263
+ "rotting",
264
+ "ruin",
265
+ "ruined",
266
+ "ruining",
267
+ "ruins",
268
+ "sabotage",
269
+ "sabotaged",
270
+ "sabotages",
271
+ "sabotaging",
272
+ "steer",
273
+ "steered",
274
+ "steering",
275
+ "steers",
276
+ "stop",
277
+ "stopped",
278
+ "stopping",
279
+ "stops",
280
+ "tank",
281
+ "tanked",
282
+ "tanking",
283
+ "tanks",
284
+ "throttle",
285
+ "throttled",
286
+ "throttles",
287
+ "throttling",
288
+ "violate",
289
+ "violates",
290
+ "violating",
291
+ "wrong"
292
+ ],
293
+ "backgroundActionWords": [
294
+ "carve",
295
+ "carved",
296
+ "carves",
297
+ "carving",
298
+ "did",
299
+ "do",
300
+ "does",
301
+ "doing",
302
+ "embedded",
303
+ "everywhere",
304
+ "humming",
305
+ "live",
306
+ "lived",
307
+ "lives",
308
+ "living",
309
+ "made",
310
+ "make",
311
+ "makes",
312
+ "making",
313
+ "manage",
314
+ "managed",
315
+ "manages",
316
+ "managing",
317
+ "operate",
318
+ "operated",
319
+ "operates",
320
+ "operating",
321
+ "pay",
322
+ "paying",
323
+ "pays",
324
+ "perform",
325
+ "performed",
326
+ "performing",
327
+ "performs",
328
+ "power",
329
+ "powered",
330
+ "powering",
331
+ "powers",
332
+ "probe",
333
+ "probed",
334
+ "probes",
335
+ "probing",
336
+ "process",
337
+ "processed",
338
+ "processes",
339
+ "processing",
340
+ "run",
341
+ "running",
342
+ "runs",
343
+ "solve",
344
+ "solved",
345
+ "solves",
346
+ "solving",
347
+ "use",
348
+ "used",
349
+ "uses",
350
+ "using",
351
+ "work",
352
+ "worked",
353
+ "working",
354
+ "works"
355
+ ],
356
+ "backgroundSignificanceWords": [
357
+ "around",
358
+ "background",
359
+ "behalf",
360
+ "behind",
361
+ "bill",
362
+ "easier",
363
+ "entire",
364
+ "everything",
365
+ "everywhere",
366
+ "half",
367
+ "hardest",
368
+ "important",
369
+ "incredible",
370
+ "industries",
371
+ "infrastructure",
372
+ "internet",
373
+ "life",
374
+ "many",
375
+ "model",
376
+ "most",
377
+ "readable",
378
+ "real",
379
+ "scenes",
380
+ "sideways",
381
+ "time",
382
+ "trillion",
383
+ "trillions",
384
+ "whole",
385
+ "wisdom",
386
+ "wrong"
387
+ ],
388
+ "trendActionWords": [
389
+ "adopt",
390
+ "adopted",
391
+ "adopting",
392
+ "adopts",
393
+ "build",
394
+ "building",
395
+ "builds",
396
+ "built",
397
+ "create",
398
+ "created",
399
+ "creates",
400
+ "creating",
401
+ "drop",
402
+ "dropped",
403
+ "dropping",
404
+ "drops",
405
+ "integrate",
406
+ "integrated",
407
+ "integrates",
408
+ "integrating",
409
+ "kill",
410
+ "killed",
411
+ "killing",
412
+ "kills",
413
+ "replace",
414
+ "replaced",
415
+ "replaces",
416
+ "replacing",
417
+ "stop",
418
+ "stopped",
419
+ "stopping",
420
+ "stops",
421
+ "take",
422
+ "takes",
423
+ "taking",
424
+ "took",
425
+ "use",
426
+ "used",
427
+ "uses",
428
+ "using"
429
+ ],
430
+ "trendSubjectWords": [
431
+ "agents",
432
+ "companies",
433
+ "developers",
434
+ "group",
435
+ "groups",
436
+ "industry",
437
+ "models",
438
+ "organizations",
439
+ "platform",
440
+ "platforms",
441
+ "products",
442
+ "publisher",
443
+ "publishers",
444
+ "site",
445
+ "sites",
446
+ "systems",
447
+ "team",
448
+ "teams",
449
+ "technologies",
450
+ "technology",
451
+ "tools",
452
+ "web"
453
+ ],
454
+ "titleContextWords": [
455
+ "circled",
456
+ "includes",
457
+ "lists",
458
+ "named",
459
+ "performed",
460
+ "phrase",
461
+ "quoted",
462
+ "titled"
463
+ ],
464
+ "normalMannerWords": [
465
+ "applaud",
466
+ "applauded",
467
+ "applauding",
468
+ "ask",
469
+ "asked",
470
+ "asking",
471
+ "breathe",
472
+ "breathed",
473
+ "breathing",
474
+ "close",
475
+ "closed",
476
+ "closing",
477
+ "enter",
478
+ "entered",
479
+ "entering",
480
+ "fall",
481
+ "falling",
482
+ "falls",
483
+ "flew",
484
+ "fly",
485
+ "flying",
486
+ "lay",
487
+ "leave",
488
+ "leaves",
489
+ "leaving",
490
+ "lie",
491
+ "look",
492
+ "looked",
493
+ "looking",
494
+ "move",
495
+ "moved",
496
+ "moving",
497
+ "open",
498
+ "opened",
499
+ "opening",
500
+ "play",
501
+ "played",
502
+ "playing",
503
+ "read",
504
+ "rest",
505
+ "rested",
506
+ "resting",
507
+ "said",
508
+ "sat",
509
+ "say",
510
+ "saying",
511
+ "sing",
512
+ "singing",
513
+ "sit",
514
+ "sitting",
515
+ "speak",
516
+ "speaking",
517
+ "spoke",
518
+ "stand",
519
+ "standing",
520
+ "stood",
521
+ "wait",
522
+ "waited",
523
+ "waiting",
524
+ "walk",
525
+ "walked",
526
+ "walking",
527
+ "watch",
528
+ "watched",
529
+ "watching"
530
+ ],
531
+ "privateActionWords": [
532
+ "approved",
533
+ "approving",
534
+ "assume",
535
+ "assumed",
536
+ "assuming",
537
+ "counted",
538
+ "counting",
539
+ "donated",
540
+ "edited",
541
+ "editing",
542
+ "investigated",
543
+ "investigating",
544
+ "interviewed",
545
+ "interviewing",
546
+ "launched",
547
+ "launching",
548
+ "negotiated",
549
+ "negotiating",
550
+ "order",
551
+ "ordered",
552
+ "ordering",
553
+ "relied",
554
+ "relying",
555
+ "removed",
556
+ "removing",
557
+ "resigned",
558
+ "settled",
559
+ "settling"
560
+ ],
561
+ "specifiedTechnicalWords": [
562
+ "desync",
563
+ "desynced",
564
+ "desyncs",
565
+ "disagreed",
566
+ "disagrees",
567
+ "drop",
568
+ "dropped",
569
+ "dropping",
570
+ "drops",
571
+ "fail",
572
+ "failed",
573
+ "failing",
574
+ "fails",
575
+ "fix",
576
+ "fixed",
577
+ "fixes",
578
+ "fixing",
579
+ "ignored",
580
+ "ignores",
581
+ "misalign",
582
+ "misaligned",
583
+ "misaligns",
584
+ "miscounted",
585
+ "miscounts",
586
+ "passed",
587
+ "passes",
588
+ "stop",
589
+ "stopped",
590
+ "stopping",
591
+ "stops",
592
+ "sync",
593
+ "synced",
594
+ "syncing",
595
+ "truncated",
596
+ "truncates",
597
+ "truncating",
598
+ "wrong"
599
+ ],
600
+ "technicalEvidenceWords": [
601
+ "alarm",
602
+ "application",
603
+ "audit",
604
+ "because",
605
+ "canonical",
606
+ "channel",
607
+ "code",
608
+ "controller",
609
+ "database",
610
+ "dba",
611
+ "error",
612
+ "events",
613
+ "field",
614
+ "index",
615
+ "logged",
616
+ "maintenance",
617
+ "malformed",
618
+ "mechanism",
619
+ "mode",
620
+ "multiprocessing",
621
+ "process",
622
+ "rate",
623
+ "records",
624
+ "rows",
625
+ "setting",
626
+ "snapshot",
627
+ "strict",
628
+ "string",
629
+ "windows"
630
+ ],
631
+ "hiddenHarmPairGroups": [
632
+ {
633
+ "actions": ["accept", "accepted", "accepting", "accepts"],
634
+ "markers": ["just", "way"]
635
+ },
636
+ {
637
+ "actions": ["fall", "falling", "falls", "fell"],
638
+ "markers": ["apart"]
639
+ },
640
+ {
641
+ "actions": ["go", "goes", "going", "gone", "went"],
642
+ "markers": ["wrong"]
643
+ },
644
+ {
645
+ "actions": ["inherit", "inherited", "inheriting", "inherits"],
646
+ "markers": [
647
+ "code",
648
+ "file",
649
+ "instruction",
650
+ "instructions",
651
+ "problem",
652
+ "problems",
653
+ "added"
654
+ ]
655
+ },
656
+ {
657
+ "actions": ["take", "takes", "taking", "took"],
658
+ "markers": ["money", "most", "time", "cost", "costs"]
659
+ },
660
+ {
661
+ "actions": ["cost", "costing", "costs"],
662
+ "markers": ["money", "most", "time"]
663
+ }
664
+ ],
665
+ "detachedResultWords": [
666
+ "arrive",
667
+ "arrived",
668
+ "arrives",
669
+ "complete",
670
+ "completed",
671
+ "completes",
672
+ "finish",
673
+ "finished",
674
+ "finishes"
675
+ ],
676
+ "abstractChangePairGroups": [
677
+ {
678
+ "actions": ["continue", "continued", "continues", "continuing"],
679
+ "markers": ["exist", "not"]
680
+ }
681
+ ],
682
+ "trendPhraseGroups": [
683
+ {
684
+ "actions": ["build", "building", "builds", "built"],
685
+ "markers": ["different", "something"]
686
+ }
687
+ ],
688
+ "backgroundPhraseGroups": [
689
+ {
690
+ "actions": ["sit", "sits", "sitting", "sat"],
691
+ "markers": ["suddenly", "until"]
692
+ }
693
+ ]
694
+ }
@@ -0,0 +1,60 @@
1
+ const BOUNDARY_WORDS = new Set([
2
+ "after",
3
+ "although",
4
+ "because",
5
+ "before",
6
+ "but",
7
+ "if",
8
+ "once",
9
+ "since",
10
+ "so",
11
+ "that",
12
+ "though",
13
+ "unless",
14
+ "when",
15
+ "whenever",
16
+ "where",
17
+ "whereas",
18
+ "while",
19
+ "which",
20
+ "who"
21
+ ]);
22
+ function hasBoundaryPunctuation(text, left, right) {
23
+ const between = text.slice(left.end, right.start);
24
+ return (between.includes(";") ||
25
+ between.includes(":") ||
26
+ between.includes(".") ||
27
+ between.includes("!") ||
28
+ between.includes("?"));
29
+ }
30
+ export function quietlyClauseFor(text, tokens, index) {
31
+ let start = 0;
32
+ for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
33
+ const token = tokens[cursor];
34
+ const next = tokens[cursor + 1];
35
+ if (token === undefined ||
36
+ next === undefined ||
37
+ BOUNDARY_WORDS.has(token.normalized) ||
38
+ hasBoundaryPunctuation(text, token, next)) {
39
+ start = cursor + 1;
40
+ break;
41
+ }
42
+ }
43
+ let end = tokens.length;
44
+ for (let cursor = index + 1; cursor < tokens.length; cursor += 1) {
45
+ const previous = tokens[cursor - 1];
46
+ const token = tokens[cursor];
47
+ if (previous === undefined ||
48
+ token === undefined ||
49
+ BOUNDARY_WORDS.has(token.normalized) ||
50
+ hasBoundaryPunctuation(text, previous, token)) {
51
+ end = cursor;
52
+ break;
53
+ }
54
+ }
55
+ return {
56
+ index: index - start,
57
+ tokens: tokens.slice(start, end)
58
+ };
59
+ }
60
+ //# sourceMappingURL=quietly-clause.js.map
@@ -0,0 +1,163 @@
1
+ import { wordTokens } from "../../../shared/text/tokens.js";
2
+ import quietlyContextVocabulary from "../data/quietly-context.json" with { type: "json" };
3
+ import { quietlyClauseFor } from "./quietly-clause.js";
4
+ import { hasDetachedCompanion, hasInformativeChangeEvidence, hasLinkedTechnicalEvidence, hasSpecifiedTechnicalEvidence } from "./quietly-evidence.js";
5
+ const TARGET = "quietly";
6
+ const DETACHED_EXPLANATION_WORDS = new Set(quietlyContextVocabulary.detachedExplanationWords);
7
+ const DETACHED_RESULT_WORDS = new Set(quietlyContextVocabulary.detachedResultWords);
8
+ const AUXILIARIES = new Set(quietlyContextVocabulary.auxiliaries);
9
+ const EVALUATIVE_WORDS = new Set(quietlyContextVocabulary.evaluativeWords);
10
+ const ABSTRACT_CHANGE_WORDS = new Set(quietlyContextVocabulary.abstractChangeWords);
11
+ const STRONG_ABSTRACT_CHANGE_WORDS = new Set(quietlyContextVocabulary.strongAbstractChangeWords);
12
+ const ABSTRACT_SUBJECT_WORDS = new Set(quietlyContextVocabulary.abstractSubjectWords);
13
+ const HIDDEN_HARM_WORDS = new Set(quietlyContextVocabulary.hiddenHarmWords);
14
+ const BACKGROUND_ACTION_WORDS = new Set(quietlyContextVocabulary.backgroundActionWords);
15
+ const BACKGROUND_SIGNIFICANCE_WORDS = new Set(quietlyContextVocabulary.backgroundSignificanceWords);
16
+ const TREND_ACTION_WORDS = new Set(quietlyContextVocabulary.trendActionWords);
17
+ const TREND_SUBJECT_WORDS = new Set(quietlyContextVocabulary.trendSubjectWords);
18
+ const TITLE_CONTEXT_WORDS = new Set(quietlyContextVocabulary.titleContextWords);
19
+ const NORMAL_MANNER_WORDS = new Set(quietlyContextVocabulary.normalMannerWords);
20
+ const PRIVATE_ACTION_WORDS = new Set(quietlyContextVocabulary.privateActionWords);
21
+ const SPECIFIED_TECHNICAL_WORDS = new Set(quietlyContextVocabulary.specifiedTechnicalWords);
22
+ function containsAny(words, candidates) {
23
+ return words.some((word) => candidates.has(word));
24
+ }
25
+ function governingWords(tokens, index) {
26
+ const words = [];
27
+ const after = nextMeaningfulWord(tokens, index);
28
+ if (after !== undefined) {
29
+ words.push(after);
30
+ }
31
+ for (let cursor = index - 1; cursor >= Math.max(0, index - 3); cursor -= 1) {
32
+ const word = tokens[cursor]?.normalized;
33
+ if (word === undefined || AUXILIARIES.has(word)) {
34
+ break;
35
+ }
36
+ words.push(word);
37
+ }
38
+ return words;
39
+ }
40
+ function wordsNear(tokens, index, radius) {
41
+ return tokens
42
+ .slice(Math.max(0, index - radius), index + radius + 1)
43
+ .map((token) => token.normalized);
44
+ }
45
+ function matchesPairGroup(localWords, allWords, groups) {
46
+ return groups.some(({ actions, markers }) => actions.some((word) => localWords.includes(word)) &&
47
+ markers.some((word) => allWords.includes(word)));
48
+ }
49
+ function matchesRequiredPairGroup(localWords, allWords, groups) {
50
+ return groups.some(({ actions, markers }) => actions.some((word) => localWords.includes(word)) &&
51
+ markers.every((word) => allWords.includes(word)));
52
+ }
53
+ function isFirstPersonBuild(tokens, index, localWords, sentenceWords) {
54
+ const before = tokens.slice(0, index).map((token) => token.normalized);
55
+ return (containsAny(localWords, TREND_ACTION_WORDS) &&
56
+ containsAny(sentenceWords, TREND_SUBJECT_WORDS) &&
57
+ (before.includes("i") ||
58
+ before.includes("i've") ||
59
+ before.includes("my") ||
60
+ before.includes("we") ||
61
+ before.includes("we've")));
62
+ }
63
+ function nextMeaningfulWord(tokens, index) {
64
+ for (let cursor = index + 1; cursor < tokens.length; cursor += 1) {
65
+ const word = tokens[cursor]?.normalized;
66
+ if (word !== undefined && !AUXILIARIES.has(word)) {
67
+ return word;
68
+ }
69
+ }
70
+ return undefined;
71
+ }
72
+ function isAbstractChangeContext(tokens, index, localWords, contextWords, sentenceWords, sentenceTokens, sentenceIndex) {
73
+ const hasChangePattern = containsAny(localWords, STRONG_ABSTRACT_CHANGE_WORDS) ||
74
+ (containsAny(localWords, ABSTRACT_CHANGE_WORDS) &&
75
+ containsAny(contextWords, ABSTRACT_SUBJECT_WORDS)) ||
76
+ matchesRequiredPairGroup(localWords, sentenceWords, quietlyContextVocabulary.abstractChangePairGroups);
77
+ return (hasChangePattern &&
78
+ !hasInformativeChangeEvidence(tokens, index, sentenceTokens, sentenceIndex));
79
+ }
80
+ function isNormalUse(text, tokens, index, label, sentenceTokens, sentenceIndex) {
81
+ const allWords = tokens.map((token) => token.normalized);
82
+ if (containsAny(wordsNear(tokens, index, 5), TITLE_CONTEXT_WORDS)) {
83
+ return true;
84
+ }
85
+ const localWords = governingWords(tokens, index);
86
+ if ((label === undefined && containsAny(localWords, NORMAL_MANNER_WORDS)) ||
87
+ containsAny(localWords, PRIVATE_ACTION_WORDS)) {
88
+ return true;
89
+ }
90
+ return (containsAny(localWords, SPECIFIED_TECHNICAL_WORDS) &&
91
+ (hasSpecifiedTechnicalEvidence(allWords) ||
92
+ hasLinkedTechnicalEvidence(text, sentenceTokens, sentenceIndex)));
93
+ }
94
+ function contextClassFor(text, tokens, index, sentenceTokens, sentenceIndex) {
95
+ const words = tokens.map((token) => token.normalized);
96
+ const sentenceWords = sentenceTokens.map((token) => token.normalized);
97
+ if (sentenceTokens.length === 1) {
98
+ return "detached-emphasis";
99
+ }
100
+ if (hasDetachedCompanion(text, tokens, index) &&
101
+ (tokens.length <= 8 || containsAny(words, DETACHED_EXPLANATION_WORDS))) {
102
+ return "detached-emphasis";
103
+ }
104
+ const localWords = governingWords(tokens, index);
105
+ const contextWords = wordsNear(tokens, index, 5);
106
+ const token = tokens[index];
107
+ const isTrailingResult = token !== undefined &&
108
+ containsAny(localWords, DETACHED_RESULT_WORDS) &&
109
+ (index === tokens.length - 1 ||
110
+ text.slice(token.end).trimStart().startsWith(":"));
111
+ if (isTrailingResult) {
112
+ return "detached-emphasis";
113
+ }
114
+ const nextWord = nextMeaningfulWord(tokens, index);
115
+ if (nextWord !== undefined && EVALUATIVE_WORDS.has(nextWord)) {
116
+ return "evaluative-intensifier";
117
+ }
118
+ if (containsAny(localWords, HIDDEN_HARM_WORDS) ||
119
+ matchesPairGroup(localWords, wordsNear(tokens, index, 8), quietlyContextVocabulary.hiddenHarmPairGroups)) {
120
+ return "hidden-harm";
121
+ }
122
+ if (isAbstractChangeContext(tokens, index, localWords, contextWords, words, sentenceTokens, sentenceIndex)) {
123
+ return "abstract-change";
124
+ }
125
+ if (containsAny(localWords, BACKGROUND_ACTION_WORDS) &&
126
+ containsAny(contextWords, BACKGROUND_SIGNIFICANCE_WORDS)) {
127
+ return "background-significance";
128
+ }
129
+ if (matchesRequiredPairGroup(localWords, words, quietlyContextVocabulary.backgroundPhraseGroups)) {
130
+ return "background-significance";
131
+ }
132
+ if ((containsAny(words, TREND_SUBJECT_WORDS) &&
133
+ (containsAny(localWords, TREND_ACTION_WORDS) ||
134
+ (index <= 2 && containsAny(words, TREND_ACTION_WORDS)))) ||
135
+ isFirstPersonBuild(tokens, index, localWords, sentenceWords) ||
136
+ matchesRequiredPairGroup(localWords, words, quietlyContextVocabulary.trendPhraseGroups)) {
137
+ return "unannounced-trend";
138
+ }
139
+ return undefined;
140
+ }
141
+ export function findQuietlyContextMatches(text) {
142
+ const tokens = wordTokens(text);
143
+ const matches = [];
144
+ for (let index = 0; index < tokens.length; index += 1) {
145
+ const token = tokens[index];
146
+ if (token?.normalized !== TARGET) {
147
+ continue;
148
+ }
149
+ const clause = quietlyClauseFor(text, tokens, index);
150
+ const label = contextClassFor(text, clause.tokens, clause.index, tokens, index);
151
+ if (label === undefined ||
152
+ isNormalUse(text, clause.tokens, clause.index, label, tokens, index)) {
153
+ continue;
154
+ }
155
+ matches.push({
156
+ evidence: token.text,
157
+ label,
158
+ range: { end: token.end, start: token.start }
159
+ });
160
+ }
161
+ return matches;
162
+ }
163
+ //# sourceMappingURL=quietly-context.js.map
@@ -0,0 +1,68 @@
1
+ import quietlyContextVocabulary from "../data/quietly-context.json" with { type: "json" };
2
+ const ABSTRACT_CHANGE_WORDS = new Set(quietlyContextVocabulary.abstractChangeWords);
3
+ const STRONG_ABSTRACT_CHANGE_WORDS = new Set(quietlyContextVocabulary.strongAbstractChangeWords);
4
+ const DETACHED_COMPANION_WORDS = new Set(quietlyContextVocabulary.detachedCompanionWords);
5
+ const TECHNICAL_EVIDENCE_WORDS = new Set(quietlyContextVocabulary.technicalEvidenceWords);
6
+ function startsWithDigit(word) {
7
+ const first = word.at(0);
8
+ return first !== undefined && first >= "0" && first <= "9";
9
+ }
10
+ export function hasSpecifiedTechnicalEvidence(words) {
11
+ const evidenceCount = words.filter((word) => TECHNICAL_EVIDENCE_WORDS.has(word)).length;
12
+ return (words.some(startsWithDigit) ||
13
+ words.includes("because") ||
14
+ evidenceCount >= 2);
15
+ }
16
+ export function hasLinkedTechnicalEvidence(text, tokens, index) {
17
+ const following = tokens.slice(index + 1);
18
+ const boundaryIndex = following.findIndex((token) => ["after", "because", "when"].includes(token.normalized));
19
+ const quietToken = tokens[index];
20
+ const colonIndex = quietToken === undefined ? -1 : text.indexOf(":", quietToken.end);
21
+ const evidence = boundaryIndex >= 0
22
+ ? following.slice(boundaryIndex + 1)
23
+ : colonIndex >= 0
24
+ ? following.filter((token) => token.start > colonIndex)
25
+ : [];
26
+ return ((following[boundaryIndex]?.normalized === "because" &&
27
+ evidence.length >= 3) ||
28
+ hasSpecifiedTechnicalEvidence(evidence.map((token) => token.normalized)));
29
+ }
30
+ export function hasDetachedCompanion(text, tokens, index) {
31
+ const companion = tokens
32
+ .slice(0, index)
33
+ .findLast((token) => DETACHED_COMPANION_WORDS.has(token.normalized));
34
+ const target = tokens[index];
35
+ return (companion !== undefined &&
36
+ target !== undefined &&
37
+ text.slice(companion.end, target.start).includes(","));
38
+ }
39
+ export function hasInformativeChangeEvidence(clauseTokens, clauseIndex, sentenceTokens, sentenceIndex) {
40
+ const clauseWords = clauseTokens.map((token) => token.normalized);
41
+ if (clauseWords.includes("from") && clauseWords.includes("to")) {
42
+ return true;
43
+ }
44
+ const actionIndices = clauseWords.flatMap((word, index) => ABSTRACT_CHANGE_WORDS.has(word) || STRONG_ABSTRACT_CHANGE_WORDS.has(word)
45
+ ? [index]
46
+ : []);
47
+ const actionIndex = actionIndices.reduce((nearest, candidate) => nearest === undefined ||
48
+ Math.abs(candidate - clauseIndex) < Math.abs(nearest - clauseIndex)
49
+ ? candidate
50
+ : nearest, undefined);
51
+ if (actionIndex !== undefined &&
52
+ clauseWords.slice(actionIndex + 1, actionIndex + 7).some(startsWithDigit)) {
53
+ return true;
54
+ }
55
+ if (actionIndex === undefined) {
56
+ return false;
57
+ }
58
+ const sentenceActionIndex = sentenceTokens.findIndex((token) => token.start === clauseTokens[actionIndex]?.start);
59
+ if (sentenceActionIndex < 0) {
60
+ return false;
61
+ }
62
+ const explanation = sentenceTokens.slice(sentenceActionIndex + 1);
63
+ const boundaryIndex = explanation.findIndex((token) => token.normalized === "after" || token.normalized === "because");
64
+ return (sentenceActionIndex <= sentenceIndex &&
65
+ boundaryIndex >= 0 &&
66
+ explanation.length - boundaryIndex > 4);
67
+ }
68
+ //# sourceMappingURL=quietly-evidence.js.map
@@ -0,0 +1,21 @@
1
+ import { oneToOneRule } from "../private/textlint-rule-builders.js";
2
+ import { findQuietlyContextMatches } from "./private/quietly-context.js";
3
+ const RULE_ID = "words:quietly-filler";
4
+ const rule = oneToOneRule({
5
+ detect: (unit) => findQuietlyContextMatches(unit.text).map((match) => ({
6
+ data: { contextClass: match.label },
7
+ evidence: match.evidence,
8
+ label: match.label,
9
+ range: match.range
10
+ })),
11
+ family: "words",
12
+ formatMessage: (report) => {
13
+ const contextClass = report.detections[0]?.data?.["contextClass"];
14
+ return `"quietly" adds vague hidden significance (${contextClass}). Name the concrete change or remove the adverb.`;
15
+ },
16
+ ruleId: RULE_ID,
17
+ severity: 1,
18
+ unitKind: "sentence"
19
+ });
20
+ export default rule;
21
+ //# sourceMappingURL=quietly-filler.js.map
@@ -1,4 +1,4 @@
1
- import { allParagraphs } from "./sections.js";
1
+ import { allDocumentParagraphs } from "./sections.js";
2
2
  function trimStartLength(text) {
3
3
  return text.length - text.trimStart().length;
4
4
  }
@@ -6,7 +6,7 @@ export function documentSourceText(document) {
6
6
  const paragraphs = [];
7
7
  const textParts = [];
8
8
  let outputStart = 0;
9
- for (const paragraph of allParagraphs(document)) {
9
+ for (const paragraph of allDocumentParagraphs(document)) {
10
10
  const text = paragraph.text.trim();
11
11
  if (text.length === 0) {
12
12
  continue;
@@ -1,12 +1,24 @@
1
1
  import { splitSentences } from "./sentences.js";
2
- import { proseSourceText, sourceText } from "./traverse.js";
2
+ import { proseSourceText } from "./traverse.js";
3
3
  function isParagraphNode(node) {
4
4
  return node.type === "Paragraph";
5
5
  }
6
6
  function isParentNode(node) {
7
7
  return "children" in node;
8
8
  }
9
- function collectParagraphs(node, paragraphs) {
9
+ function collectRuleParagraphs(node, paragraphs) {
10
+ if (isParagraphNode(node)) {
11
+ paragraphs.push(node);
12
+ return;
13
+ }
14
+ if (!isParentNode(node)) {
15
+ return;
16
+ }
17
+ for (const child of node.children) {
18
+ collectRuleParagraphs(child, paragraphs);
19
+ }
20
+ }
21
+ function collectDocumentParagraphs(node, paragraphs) {
10
22
  if (isParagraphNode(node)) {
11
23
  paragraphs.push(node);
12
24
  return;
@@ -15,13 +27,20 @@ function collectParagraphs(node, paragraphs) {
15
27
  return;
16
28
  }
17
29
  for (const child of node.children) {
18
- collectParagraphs(child, paragraphs);
30
+ collectDocumentParagraphs(child, paragraphs);
19
31
  }
20
32
  }
21
- function sectionParagraphs(section) {
33
+ function sectionRuleParagraphs(section) {
22
34
  const paragraphs = [];
23
35
  for (const node of section) {
24
- collectParagraphs(node, paragraphs);
36
+ collectRuleParagraphs(node, paragraphs);
37
+ }
38
+ return paragraphs;
39
+ }
40
+ function sectionDocumentParagraphs(section) {
41
+ const paragraphs = [];
42
+ for (const node of section) {
43
+ collectDocumentParagraphs(node, paragraphs);
25
44
  }
26
45
  return paragraphs;
27
46
  }
@@ -44,7 +63,7 @@ function documentSections(document) {
44
63
  return sections;
45
64
  }
46
65
  function paragraphSentences(paragraph) {
47
- const source = sourceText(paragraph);
66
+ const source = proseSourceText(paragraph);
48
67
  return splitSentences(source.text).map((sentence) => ({
49
68
  paragraph,
50
69
  sentence,
@@ -54,16 +73,16 @@ function paragraphSentences(paragraph) {
54
73
  export function allParagraphSentences(document) {
55
74
  const sentences = [];
56
75
  for (const section of documentSections(document)) {
57
- for (const paragraph of sectionParagraphs(section)) {
76
+ for (const paragraph of sectionRuleParagraphs(section)) {
58
77
  sentences.push(...paragraphSentences(paragraph));
59
78
  }
60
79
  }
61
80
  return sentences;
62
81
  }
63
- export function allParagraphs(document) {
82
+ function mappedParagraphs(document, select) {
64
83
  const paragraphs = [];
65
84
  for (const section of documentSections(document)) {
66
- for (const paragraph of sectionParagraphs(section)) {
85
+ for (const paragraph of select(section)) {
67
86
  const source = proseSourceText(paragraph);
68
87
  paragraphs.push({
69
88
  paragraph,
@@ -74,10 +93,16 @@ export function allParagraphs(document) {
74
93
  }
75
94
  return paragraphs;
76
95
  }
96
+ export function allDocumentParagraphs(document) {
97
+ return mappedParagraphs(document, sectionDocumentParagraphs);
98
+ }
99
+ export function allParagraphs(document) {
100
+ return mappedParagraphs(document, sectionRuleParagraphs);
101
+ }
77
102
  export function sectionFirstSentences(document) {
78
103
  const sentences = [];
79
104
  for (const section of documentSections(document)) {
80
- const firstParagraph = sectionParagraphs(section).at(0);
105
+ const firstParagraph = sectionRuleParagraphs(section).at(0);
81
106
  if (firstParagraph === undefined) {
82
107
  continue;
83
108
  }
@@ -91,7 +116,7 @@ export function sectionFirstSentences(document) {
91
116
  export function sectionLastSentences(document) {
92
117
  const sentences = [];
93
118
  for (const section of documentSections(document)) {
94
- const lastParagraph = sectionParagraphs(section).at(-1);
119
+ const lastParagraph = sectionRuleParagraphs(section).at(-1);
95
120
  if (lastParagraph === undefined) {
96
121
  continue;
97
122
  }
@@ -15,10 +15,19 @@ function normalizeNode(node, includeImageAlt) {
15
15
  }
16
16
  if ((node.type === "Image" || node.type === "ImageReference") &&
17
17
  !includeImageAlt) {
18
+ const alt = "alt" in node && typeof node.alt === "string" ? node.alt : "";
18
19
  return {
19
20
  ...node,
20
21
  type: "Str",
21
- value: ""
22
+ value: " ".repeat(alt.length)
23
+ };
24
+ }
25
+ if (node.type === "Code" && !includeImageAlt) {
26
+ const value = "value" in node && typeof node.value === "string" ? node.value : "";
27
+ return {
28
+ ...node,
29
+ type: "Str",
30
+ value: " ".repeat(value.length)
22
31
  };
23
32
  }
24
33
  if (isHtmlNode(node) && !includeImageAlt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopless",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "Deterministic textlint rules and CLI for catching prose slop in English Markdown.",
5
5
  "keywords": [
6
6
  "textlint",
@@ -112,6 +112,7 @@
112
112
  "./rules/words/llm-vocabulary": "./dist/rules/words/llm-vocabulary.js",
113
113
  "./rules/words/simplicity": "./dist/rules/words/simplicity.js",
114
114
  "./rules/words/prohibited-words": "./dist/rules/words/prohibited-words.js",
115
+ "./rules/words/quietly-filler": "./dist/rules/words/quietly-filler.js",
115
116
  "./rules/words/quietly-overuse": "./dist/rules/words/quietly-overuse.js"
116
117
  },
117
118
  "dependencies": {