slopless 0.2.22 → 0.2.23
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
CHANGED
|
@@ -160,10 +160,6 @@ npm audit signatures slopless
|
|
|
160
160
|
|
|
161
161
|
This confirms the tarball you installed was built in the published source repository's Actions environment, from the commit the GitHub Release points at.
|
|
162
162
|
|
|
163
|
-
## Star history
|
|
164
|
-
|
|
165
|
-
[](https://star-history.com/#seochecks-ai/slopless&Date)
|
|
166
|
-
|
|
167
163
|
## Credits
|
|
168
164
|
|
|
169
165
|
[Graham Rowe](https://github.com/grahamrowe82/antislop), thanks for giving me new ideas for classes of slop to detect. Your lib is now fully incorporated with your permission.
|
|
@@ -1,285 +1,7 @@
|
|
|
1
|
-
import { hasConcreteInventorySubjects } from "../../../shared/matchers/concrete-evidence.js";
|
|
2
|
-
import { splitSentences } from "../../../shared/text/sentences.js";
|
|
3
|
-
import { splitWhitespace } from "../../../shared/text/whitespace.js";
|
|
4
1
|
import { oneToOneRule } from "../../private/textlint-rule-builders.js";
|
|
5
|
-
|
|
6
|
-
const MAX_PAYOFF_WORDS = 28;
|
|
7
|
-
const SUBJECT_WORDS = new Set([
|
|
8
|
-
"i",
|
|
9
|
-
"you",
|
|
10
|
-
"we",
|
|
11
|
-
"they",
|
|
12
|
-
"he",
|
|
13
|
-
"she",
|
|
14
|
-
"it",
|
|
15
|
-
"this",
|
|
16
|
-
"that",
|
|
17
|
-
"these",
|
|
18
|
-
"those"
|
|
19
|
-
]);
|
|
20
|
-
const OBJECT_WORDS = new Set([
|
|
21
|
-
"me",
|
|
22
|
-
"him",
|
|
23
|
-
"her",
|
|
24
|
-
"us",
|
|
25
|
-
"them",
|
|
26
|
-
"it",
|
|
27
|
-
"the",
|
|
28
|
-
"a",
|
|
29
|
-
"an",
|
|
30
|
-
"my",
|
|
31
|
-
"your",
|
|
32
|
-
"our",
|
|
33
|
-
"their"
|
|
34
|
-
]);
|
|
35
|
-
const FINITE_VERBS = new Set([
|
|
36
|
-
"is",
|
|
37
|
-
"are",
|
|
38
|
-
"was",
|
|
39
|
-
"were",
|
|
40
|
-
"am",
|
|
41
|
-
"be",
|
|
42
|
-
"been",
|
|
43
|
-
"being",
|
|
44
|
-
"have",
|
|
45
|
-
"has",
|
|
46
|
-
"had",
|
|
47
|
-
"do",
|
|
48
|
-
"does",
|
|
49
|
-
"did",
|
|
50
|
-
"can",
|
|
51
|
-
"could",
|
|
52
|
-
"will",
|
|
53
|
-
"would",
|
|
54
|
-
"should",
|
|
55
|
-
"may",
|
|
56
|
-
"might",
|
|
57
|
-
"must",
|
|
58
|
-
"shall"
|
|
59
|
-
]);
|
|
60
|
-
const FRAGMENT_LEADS = new Set([
|
|
61
|
-
"too",
|
|
62
|
-
"most",
|
|
63
|
-
"more",
|
|
64
|
-
"less",
|
|
65
|
-
"deeply",
|
|
66
|
-
"completely",
|
|
67
|
-
"possibly",
|
|
68
|
-
"probably",
|
|
69
|
-
"maybe",
|
|
70
|
-
"weird",
|
|
71
|
-
"strange",
|
|
72
|
-
"odd",
|
|
73
|
-
"pure",
|
|
74
|
-
"total",
|
|
75
|
-
"incredibly"
|
|
76
|
-
]);
|
|
77
|
-
const PAYOFF_STARTS = ["more like ", "most ", "then ", "instead "];
|
|
78
|
-
const IMPERATIVE_STARTS = new Set([
|
|
79
|
-
"feed",
|
|
80
|
-
"leave",
|
|
81
|
-
"notice",
|
|
82
|
-
"stop",
|
|
83
|
-
"start",
|
|
84
|
-
"take",
|
|
85
|
-
"keep",
|
|
86
|
-
"get",
|
|
87
|
-
"look",
|
|
88
|
-
"think",
|
|
89
|
-
"try",
|
|
90
|
-
"make",
|
|
91
|
-
"let",
|
|
92
|
-
"give",
|
|
93
|
-
"accept",
|
|
94
|
-
"hold",
|
|
95
|
-
"reduce"
|
|
96
|
-
]);
|
|
97
|
-
const SIMPLE_PAST_VERBS = new Set([
|
|
98
|
-
"ran",
|
|
99
|
-
"went",
|
|
100
|
-
"came",
|
|
101
|
-
"felt",
|
|
102
|
-
"heard",
|
|
103
|
-
"found",
|
|
104
|
-
"made",
|
|
105
|
-
"took",
|
|
106
|
-
"kept",
|
|
107
|
-
"left",
|
|
108
|
-
"thought",
|
|
109
|
-
"knew",
|
|
110
|
-
"got",
|
|
111
|
-
"put",
|
|
112
|
-
"said",
|
|
113
|
-
"told",
|
|
114
|
-
"held",
|
|
115
|
-
"stood",
|
|
116
|
-
"sat",
|
|
117
|
-
"became",
|
|
118
|
-
"wrote",
|
|
119
|
-
"spoke",
|
|
120
|
-
"won",
|
|
121
|
-
"lost",
|
|
122
|
-
"paid",
|
|
123
|
-
"met",
|
|
124
|
-
"read",
|
|
125
|
-
"saw",
|
|
126
|
-
"grew",
|
|
127
|
-
"fell",
|
|
128
|
-
"broke"
|
|
129
|
-
]);
|
|
130
|
-
function isAlphanumeric(character) {
|
|
131
|
-
const lower = character.toLocaleLowerCase("en");
|
|
132
|
-
const upper = character.toLocaleUpperCase("en");
|
|
133
|
-
return (character >= "0" && character <= "9") || lower !== upper;
|
|
134
|
-
}
|
|
135
|
-
function cleanWord(word) {
|
|
136
|
-
const normalized = word.toLocaleLowerCase("en").replaceAll("\u2019", "'");
|
|
137
|
-
let start = 0;
|
|
138
|
-
let end = normalized.length;
|
|
139
|
-
while (start < end &&
|
|
140
|
-
normalized[start] !== "'" &&
|
|
141
|
-
!isAlphanumeric(normalized[start] ?? "")) {
|
|
142
|
-
start += 1;
|
|
143
|
-
}
|
|
144
|
-
while (end > start &&
|
|
145
|
-
normalized[end - 1] !== "'" &&
|
|
146
|
-
!isAlphanumeric(normalized[end - 1] ?? "")) {
|
|
147
|
-
end -= 1;
|
|
148
|
-
}
|
|
149
|
-
return normalized.slice(start, end);
|
|
150
|
-
}
|
|
151
|
-
function sentenceWords(sentence) {
|
|
152
|
-
return splitWhitespace(sentence).map(cleanWord);
|
|
153
|
-
}
|
|
154
|
-
function isFunctionWord(word) {
|
|
155
|
-
return (word === "the" ||
|
|
156
|
-
word === "a" ||
|
|
157
|
-
word === "an" ||
|
|
158
|
-
word === "and" ||
|
|
159
|
-
word === "or" ||
|
|
160
|
-
word === "but" ||
|
|
161
|
-
word === "to" ||
|
|
162
|
-
word === "of" ||
|
|
163
|
-
word === "in" ||
|
|
164
|
-
word === "on" ||
|
|
165
|
-
word === "at" ||
|
|
166
|
-
word === "for");
|
|
167
|
-
}
|
|
168
|
-
function looksLikeModifierPhrase(first, second) {
|
|
169
|
-
return (first.endsWith("ly") ||
|
|
170
|
-
second.endsWith("ive") ||
|
|
171
|
-
second.endsWith("ous") ||
|
|
172
|
-
second.endsWith("al") ||
|
|
173
|
-
second.endsWith("ful") ||
|
|
174
|
-
second.endsWith("less"));
|
|
175
|
-
}
|
|
176
|
-
function looksLikeSubjectDrop(first, second) {
|
|
177
|
-
return (((first.endsWith("ed") && !first.endsWith("eed")) ||
|
|
178
|
-
first.endsWith("ing")) &&
|
|
179
|
-
!isFunctionWord(second));
|
|
180
|
-
}
|
|
181
|
-
function looksLikeSimpleClause(first, second) {
|
|
182
|
-
return (!isFunctionWord(first) &&
|
|
183
|
-
(second.endsWith("ed") || SIMPLE_PAST_VERBS.has(second)));
|
|
184
|
-
}
|
|
185
|
-
function looksLikeBriefImperative(first, second) {
|
|
186
|
-
return (IMPERATIVE_STARTS.has(first) &&
|
|
187
|
-
(OBJECT_WORDS.has(second) || second.endsWith("er") || second.endsWith("ly")));
|
|
188
|
-
}
|
|
189
|
-
function classifyFragment(sentence) {
|
|
190
|
-
const words = sentenceWords(sentence.text);
|
|
191
|
-
if (words.length < 2 || words.length > MAX_FRAGMENT_WORDS) {
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
if (words.some((word) => word === "")) {
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
if (words.some((word) => SUBJECT_WORDS.has(word))) {
|
|
198
|
-
return undefined;
|
|
199
|
-
}
|
|
200
|
-
if (words.some((word) => FINITE_VERBS.has(word))) {
|
|
201
|
-
return undefined;
|
|
202
|
-
}
|
|
203
|
-
const first = words[0];
|
|
204
|
-
const second = words[1];
|
|
205
|
-
if (first === undefined || second === undefined) {
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
if (FRAGMENT_LEADS.has(first) || looksLikeModifierPhrase(first, second)) {
|
|
209
|
-
return "modifier-fragment";
|
|
210
|
-
}
|
|
211
|
-
if (looksLikeSimpleClause(first, second) ||
|
|
212
|
-
looksLikeBriefImperative(first, second)) {
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
if (looksLikeSubjectDrop(first, second)) {
|
|
216
|
-
return "subject-drop";
|
|
217
|
-
}
|
|
218
|
-
return "noun-fragment";
|
|
219
|
-
}
|
|
220
|
-
function looksLikePayoffSentence(sentence) {
|
|
221
|
-
const words = sentenceWords(sentence.text);
|
|
222
|
-
if (words.length <= 2 || words.length > MAX_PAYOFF_WORDS) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
const lowered = sentence.text.toLocaleLowerCase("en");
|
|
226
|
-
if (!PAYOFF_STARTS.some((start) => lowered.startsWith(start))) {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
return !words.some((word) => SUBJECT_WORDS.has(word));
|
|
230
|
-
}
|
|
231
|
-
function findFragmentStacks(text) {
|
|
232
|
-
const sentences = splitSentences(text);
|
|
233
|
-
const classifications = sentences.map(classifyFragment);
|
|
234
|
-
const matches = [];
|
|
235
|
-
let sentenceIndex = 0;
|
|
236
|
-
while (sentenceIndex < sentences.length) {
|
|
237
|
-
const firstType = classifications[sentenceIndex];
|
|
238
|
-
const firstSentence = sentences[sentenceIndex];
|
|
239
|
-
if (firstType === undefined || firstSentence === undefined) {
|
|
240
|
-
sentenceIndex += 1;
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
const runSentences = [firstSentence];
|
|
244
|
-
const fragmentTypes = [firstType];
|
|
245
|
-
let cursor = sentenceIndex + 1;
|
|
246
|
-
while (cursor < sentences.length) {
|
|
247
|
-
const sentence = sentences[cursor];
|
|
248
|
-
const nextType = classifications[cursor];
|
|
249
|
-
if (sentence === undefined) {
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
if (nextType !== undefined) {
|
|
253
|
-
runSentences.push(sentence);
|
|
254
|
-
fragmentTypes.push(nextType);
|
|
255
|
-
cursor += 1;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (looksLikePayoffSentence(sentence)) {
|
|
259
|
-
runSentences.push(sentence);
|
|
260
|
-
cursor += 1;
|
|
261
|
-
}
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
if (fragmentTypes.length >= 2 &&
|
|
265
|
-
runSentences.length >= 3 &&
|
|
266
|
-
!hasConcreteInventorySubjects(runSentences.map((sentence) => sentence.text))) {
|
|
267
|
-
const last = runSentences[runSentences.length - 1];
|
|
268
|
-
if (last !== undefined) {
|
|
269
|
-
matches.push({
|
|
270
|
-
end: last.end,
|
|
271
|
-
fragmentTypes,
|
|
272
|
-
sentences: runSentences.map((sentence) => sentence.text),
|
|
273
|
-
start: firstSentence.start
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
sentenceIndex = Math.max(cursor, sentenceIndex + 1);
|
|
278
|
-
}
|
|
279
|
-
return matches;
|
|
280
|
-
}
|
|
2
|
+
import { findFragmentMatches } from "./private/fragment-stack-detector.js";
|
|
281
3
|
const rule = oneToOneRule({
|
|
282
|
-
detect: (unit) =>
|
|
4
|
+
detect: (unit) => findFragmentMatches(unit.text).map((match) => ({
|
|
283
5
|
evidence: match.sentences.join(" "),
|
|
284
6
|
label: match.fragmentTypes.join(","),
|
|
285
7
|
range: { start: match.start, end: match.end }
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { hasConcreteInventorySubjects } from "../../../../shared/matchers/concrete-evidence.js";
|
|
2
|
+
import { splitSentences } from "../../../../shared/text/sentences.js";
|
|
3
|
+
import { splitWhitespace } from "../../../../shared/text/whitespace.js";
|
|
4
|
+
const MAX_FRAGMENT_WORDS = 6;
|
|
5
|
+
const MAX_NO_FRAGMENT_WORDS = 5;
|
|
6
|
+
const MAX_PAYOFF_WORDS = 28;
|
|
7
|
+
const SUBJECT_WORDS = new Set([
|
|
8
|
+
"i",
|
|
9
|
+
"you",
|
|
10
|
+
"we",
|
|
11
|
+
"they",
|
|
12
|
+
"he",
|
|
13
|
+
"she",
|
|
14
|
+
"it",
|
|
15
|
+
"this",
|
|
16
|
+
"that",
|
|
17
|
+
"these",
|
|
18
|
+
"those"
|
|
19
|
+
]);
|
|
20
|
+
const OBJECT_WORDS = new Set([
|
|
21
|
+
"me",
|
|
22
|
+
"him",
|
|
23
|
+
"her",
|
|
24
|
+
"us",
|
|
25
|
+
"them",
|
|
26
|
+
"it",
|
|
27
|
+
"the",
|
|
28
|
+
"a",
|
|
29
|
+
"an",
|
|
30
|
+
"my",
|
|
31
|
+
"your",
|
|
32
|
+
"our",
|
|
33
|
+
"their"
|
|
34
|
+
]);
|
|
35
|
+
const FINITE_VERBS = new Set([
|
|
36
|
+
"is",
|
|
37
|
+
"are",
|
|
38
|
+
"was",
|
|
39
|
+
"were",
|
|
40
|
+
"am",
|
|
41
|
+
"be",
|
|
42
|
+
"been",
|
|
43
|
+
"being",
|
|
44
|
+
"have",
|
|
45
|
+
"has",
|
|
46
|
+
"had",
|
|
47
|
+
"do",
|
|
48
|
+
"does",
|
|
49
|
+
"did",
|
|
50
|
+
"can",
|
|
51
|
+
"could",
|
|
52
|
+
"will",
|
|
53
|
+
"would",
|
|
54
|
+
"should",
|
|
55
|
+
"may",
|
|
56
|
+
"might",
|
|
57
|
+
"must",
|
|
58
|
+
"shall"
|
|
59
|
+
]);
|
|
60
|
+
const FRAGMENT_LEADS = new Set([
|
|
61
|
+
"too",
|
|
62
|
+
"most",
|
|
63
|
+
"more",
|
|
64
|
+
"less",
|
|
65
|
+
"deeply",
|
|
66
|
+
"completely",
|
|
67
|
+
"possibly",
|
|
68
|
+
"probably",
|
|
69
|
+
"maybe",
|
|
70
|
+
"weird",
|
|
71
|
+
"strange",
|
|
72
|
+
"odd",
|
|
73
|
+
"pure",
|
|
74
|
+
"total",
|
|
75
|
+
"incredibly"
|
|
76
|
+
]);
|
|
77
|
+
const PAYOFF_STARTS = ["more like ", "most ", "then ", "instead "];
|
|
78
|
+
const IMPERATIVE_STARTS = new Set([
|
|
79
|
+
"feed",
|
|
80
|
+
"leave",
|
|
81
|
+
"notice",
|
|
82
|
+
"stop",
|
|
83
|
+
"start",
|
|
84
|
+
"take",
|
|
85
|
+
"keep",
|
|
86
|
+
"get",
|
|
87
|
+
"look",
|
|
88
|
+
"think",
|
|
89
|
+
"try",
|
|
90
|
+
"make",
|
|
91
|
+
"let",
|
|
92
|
+
"give",
|
|
93
|
+
"accept",
|
|
94
|
+
"hold",
|
|
95
|
+
"reduce"
|
|
96
|
+
]);
|
|
97
|
+
const SIMPLE_PAST_VERBS = new Set([
|
|
98
|
+
"ran",
|
|
99
|
+
"went",
|
|
100
|
+
"came",
|
|
101
|
+
"felt",
|
|
102
|
+
"heard",
|
|
103
|
+
"found",
|
|
104
|
+
"made",
|
|
105
|
+
"took",
|
|
106
|
+
"kept",
|
|
107
|
+
"left",
|
|
108
|
+
"thought",
|
|
109
|
+
"knew",
|
|
110
|
+
"got",
|
|
111
|
+
"put",
|
|
112
|
+
"said",
|
|
113
|
+
"told",
|
|
114
|
+
"held",
|
|
115
|
+
"stood",
|
|
116
|
+
"sat",
|
|
117
|
+
"became",
|
|
118
|
+
"wrote",
|
|
119
|
+
"spoke",
|
|
120
|
+
"won",
|
|
121
|
+
"lost",
|
|
122
|
+
"paid",
|
|
123
|
+
"met",
|
|
124
|
+
"read",
|
|
125
|
+
"saw",
|
|
126
|
+
"grew",
|
|
127
|
+
"fell",
|
|
128
|
+
"broke"
|
|
129
|
+
]);
|
|
130
|
+
function isAlphanumeric(character) {
|
|
131
|
+
const lower = character.toLocaleLowerCase("en");
|
|
132
|
+
const upper = character.toLocaleUpperCase("en");
|
|
133
|
+
return (character >= "0" && character <= "9") || lower !== upper;
|
|
134
|
+
}
|
|
135
|
+
function cleanWord(word) {
|
|
136
|
+
const normalized = word.toLocaleLowerCase("en").replaceAll("\u2019", "'");
|
|
137
|
+
let start = 0;
|
|
138
|
+
let end = normalized.length;
|
|
139
|
+
while (start < end &&
|
|
140
|
+
normalized[start] !== "'" &&
|
|
141
|
+
!isAlphanumeric(normalized[start] ?? "")) {
|
|
142
|
+
start += 1;
|
|
143
|
+
}
|
|
144
|
+
while (end > start &&
|
|
145
|
+
normalized[end - 1] !== "'" &&
|
|
146
|
+
!isAlphanumeric(normalized[end - 1] ?? "")) {
|
|
147
|
+
end -= 1;
|
|
148
|
+
}
|
|
149
|
+
return normalized.slice(start, end);
|
|
150
|
+
}
|
|
151
|
+
function sentenceWords(sentence) {
|
|
152
|
+
return splitWhitespace(sentence).map(cleanWord);
|
|
153
|
+
}
|
|
154
|
+
function isFunctionWord(word) {
|
|
155
|
+
return (word === "the" ||
|
|
156
|
+
word === "a" ||
|
|
157
|
+
word === "an" ||
|
|
158
|
+
word === "and" ||
|
|
159
|
+
word === "or" ||
|
|
160
|
+
word === "but" ||
|
|
161
|
+
word === "to" ||
|
|
162
|
+
word === "of" ||
|
|
163
|
+
word === "in" ||
|
|
164
|
+
word === "on" ||
|
|
165
|
+
word === "at" ||
|
|
166
|
+
word === "for");
|
|
167
|
+
}
|
|
168
|
+
function looksLikeModifierPhrase(first, second) {
|
|
169
|
+
return (first.endsWith("ly") ||
|
|
170
|
+
second.endsWith("ive") ||
|
|
171
|
+
second.endsWith("ous") ||
|
|
172
|
+
second.endsWith("al") ||
|
|
173
|
+
second.endsWith("ful") ||
|
|
174
|
+
second.endsWith("less"));
|
|
175
|
+
}
|
|
176
|
+
function looksLikeSubjectDrop(first, second) {
|
|
177
|
+
return (((first.endsWith("ed") && !first.endsWith("eed")) ||
|
|
178
|
+
first.endsWith("ing")) &&
|
|
179
|
+
!isFunctionWord(second));
|
|
180
|
+
}
|
|
181
|
+
function looksLikeSimpleClause(first, second) {
|
|
182
|
+
return (!isFunctionWord(first) &&
|
|
183
|
+
(second.endsWith("ed") || SIMPLE_PAST_VERBS.has(second)));
|
|
184
|
+
}
|
|
185
|
+
function looksLikeBriefImperative(first, second) {
|
|
186
|
+
return (IMPERATIVE_STARTS.has(first) &&
|
|
187
|
+
(OBJECT_WORDS.has(second) || second.endsWith("er") || second.endsWith("ly")));
|
|
188
|
+
}
|
|
189
|
+
function classifyFragment(sentence) {
|
|
190
|
+
const words = sentenceWords(sentence.text);
|
|
191
|
+
if (words.length < 2 || words.length > MAX_FRAGMENT_WORDS) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
if (words.some((word) => word === "")) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
if (words.some((word) => SUBJECT_WORDS.has(word))) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
if (words.some((word) => FINITE_VERBS.has(word))) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
const first = words[0];
|
|
204
|
+
const second = words[1];
|
|
205
|
+
if (first === undefined || second === undefined) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
if (FRAGMENT_LEADS.has(first) || looksLikeModifierPhrase(first, second)) {
|
|
209
|
+
return "modifier-fragment";
|
|
210
|
+
}
|
|
211
|
+
if (looksLikeSimpleClause(first, second) ||
|
|
212
|
+
looksLikeBriefImperative(first, second)) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
if (looksLikeSubjectDrop(first, second)) {
|
|
216
|
+
return "subject-drop";
|
|
217
|
+
}
|
|
218
|
+
return "noun-fragment";
|
|
219
|
+
}
|
|
220
|
+
function looksLikePayoffSentence(sentence) {
|
|
221
|
+
const words = sentenceWords(sentence.text);
|
|
222
|
+
if (words.length <= 2 || words.length > MAX_PAYOFF_WORDS) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const lowered = sentence.text.toLocaleLowerCase("en");
|
|
226
|
+
if (!PAYOFF_STARTS.some((start) => lowered.startsWith(start))) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return !words.some((word) => SUBJECT_WORDS.has(word));
|
|
230
|
+
}
|
|
231
|
+
function isNoFragment(sentence) {
|
|
232
|
+
const words = sentenceWords(sentence.text);
|
|
233
|
+
return (words.length >= 2 &&
|
|
234
|
+
words.length <= MAX_NO_FRAGMENT_WORDS &&
|
|
235
|
+
words[0] === "no" &&
|
|
236
|
+
!words.some((word) => FINITE_VERBS.has(word)) &&
|
|
237
|
+
classifyFragment(sentence) === "noun-fragment");
|
|
238
|
+
}
|
|
239
|
+
function findNoFragmentPairs(text) {
|
|
240
|
+
const sentences = splitSentences(text);
|
|
241
|
+
const matches = [];
|
|
242
|
+
for (let index = 0; index < sentences.length - 1; index += 1) {
|
|
243
|
+
const first = sentences[index];
|
|
244
|
+
const second = sentences[index + 1];
|
|
245
|
+
if (first === undefined || second === undefined) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const pair = [first.text, second.text];
|
|
249
|
+
if (!isNoFragment(first) ||
|
|
250
|
+
!isNoFragment(second) ||
|
|
251
|
+
hasConcreteInventorySubjects(pair)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
matches.push({
|
|
255
|
+
end: second.end,
|
|
256
|
+
fragmentTypes: ["no-fragment", "no-fragment"],
|
|
257
|
+
sentences: pair,
|
|
258
|
+
start: first.start
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return matches;
|
|
262
|
+
}
|
|
263
|
+
function findFragmentStacks(text) {
|
|
264
|
+
const sentences = splitSentences(text);
|
|
265
|
+
const classifications = sentences.map(classifyFragment);
|
|
266
|
+
const matches = [];
|
|
267
|
+
let sentenceIndex = 0;
|
|
268
|
+
while (sentenceIndex < sentences.length) {
|
|
269
|
+
const firstType = classifications[sentenceIndex];
|
|
270
|
+
const firstSentence = sentences[sentenceIndex];
|
|
271
|
+
if (firstType === undefined || firstSentence === undefined) {
|
|
272
|
+
sentenceIndex += 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const runSentences = [firstSentence];
|
|
276
|
+
const fragmentTypes = [firstType];
|
|
277
|
+
let cursor = sentenceIndex + 1;
|
|
278
|
+
while (cursor < sentences.length) {
|
|
279
|
+
const sentence = sentences[cursor];
|
|
280
|
+
const nextType = classifications[cursor];
|
|
281
|
+
if (sentence === undefined) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
if (nextType !== undefined) {
|
|
285
|
+
runSentences.push(sentence);
|
|
286
|
+
fragmentTypes.push(nextType);
|
|
287
|
+
cursor += 1;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (looksLikePayoffSentence(sentence)) {
|
|
291
|
+
runSentences.push(sentence);
|
|
292
|
+
cursor += 1;
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (fragmentTypes.length >= 2 &&
|
|
297
|
+
runSentences.length >= 3 &&
|
|
298
|
+
!hasConcreteInventorySubjects(runSentences.map((sentence) => sentence.text))) {
|
|
299
|
+
const last = runSentences[runSentences.length - 1];
|
|
300
|
+
if (last !== undefined) {
|
|
301
|
+
matches.push({
|
|
302
|
+
end: last.end,
|
|
303
|
+
fragmentTypes,
|
|
304
|
+
sentences: runSentences.map((sentence) => sentence.text),
|
|
305
|
+
start: firstSentence.start
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
sentenceIndex = Math.max(cursor, sentenceIndex + 1);
|
|
310
|
+
}
|
|
311
|
+
return matches;
|
|
312
|
+
}
|
|
313
|
+
export function findFragmentMatches(text) {
|
|
314
|
+
return [...findNoFragmentPairs(text), ...findFragmentStacks(text)];
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=fragment-stack-detector.js.map
|