slopless 0.2.22 → 0.2.24
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 +0 -4
- package/dist/rules/semantic-thinness/patterns/empty-quantification.json +33 -0
- package/dist/rules/semantic-thinness/private/pattern-data-e.js +2 -0
- package/dist/rules/semantic-thinness/private/pattern-matcher.js +6 -1
- package/dist/rules/syntactic-patterns/closers/affirmation-closers.js +13 -3
- package/dist/rules/syntactic-patterns/contrast/private/copular-reframe.js +41 -0
- package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-matcher.js +16 -68
- package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-parts.js +66 -10
- package/dist/rules/syntactic-patterns/contrast/private/negative-slop-frames.js +110 -7
- package/dist/rules/syntactic-patterns/contrast/private/policy-object.js +55 -0
- package/dist/rules/syntactic-patterns/contrast/private/reframe-classification.js +193 -0
- package/dist/rules/syntactic-patterns/lead-ins/generic-signposting.js +14 -83
- package/dist/rules/syntactic-patterns/lead-ins/private/discourse-evaluation.js +149 -4
- package/dist/rules/syntactic-patterns/repetition/fragment-stacking.js +2 -280
- package/dist/rules/syntactic-patterns/repetition/private/fragment-stack-detector.js +316 -0
- package/package.json +1 -1
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.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "empty-quantification",
|
|
3
|
+
"class": "empty-quantification",
|
|
4
|
+
"purpose": "Catch claims that minimize an unexplained act of quantification instead of naming the metric, measurement, or result.",
|
|
5
|
+
"matchMode": "suffix",
|
|
6
|
+
"maxTokens": 24,
|
|
7
|
+
"templates": ["{minimizer} put a number on {deicticObject}."],
|
|
8
|
+
"slots": {
|
|
9
|
+
"minimizer": ["just", "simply", "merely", "basically"],
|
|
10
|
+
"deicticObject": ["it", "this", "that"]
|
|
11
|
+
},
|
|
12
|
+
"rejectIf": [
|
|
13
|
+
"sentence names the metric, measured value, scale, or numbered label",
|
|
14
|
+
"sentence explains how the number was calculated or where it came from",
|
|
15
|
+
"sentence uses put literally rather than as an empty quantification claim"
|
|
16
|
+
],
|
|
17
|
+
"positiveExamples": [
|
|
18
|
+
"The report just put a number on it.",
|
|
19
|
+
"The dashboard simply put a number on this.",
|
|
20
|
+
"The analysis merely put a number on that.",
|
|
21
|
+
"The model basically put a number on it."
|
|
22
|
+
],
|
|
23
|
+
"negativeExamples": [
|
|
24
|
+
"The analyst labeled the third bar 42.",
|
|
25
|
+
"The meter measured the pressure at 18 PSI.",
|
|
26
|
+
"The report calculated the score from twelve survey responses.",
|
|
27
|
+
"She put the numbered label on the archive box."
|
|
28
|
+
],
|
|
29
|
+
"notes": [
|
|
30
|
+
"The template begins at the minimizer so suffix mode permits arbitrary preceding subjects without enumerating them.",
|
|
31
|
+
"Suffix mode permits arbitrary preceding subjects but requires the vague quantification phrase to end the sentence, excluding measurements, explanations, and literal continuations."
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import emptyQuantification from "../patterns/empty-quantification.json" with { type: "json" };
|
|
1
2
|
import recursiveMeaningFrame from "../patterns/recursive-meaning-frame.json" with { type: "json" };
|
|
2
3
|
export const semanticThinnessPatternSetE = [
|
|
4
|
+
emptyQuantification,
|
|
3
5
|
recursiveMeaningFrame
|
|
4
6
|
];
|
|
5
7
|
//# sourceMappingURL=pattern-data-e.js.map
|
|
@@ -86,7 +86,12 @@ function compileSlotValues(slots) {
|
|
|
86
86
|
return compiled;
|
|
87
87
|
}
|
|
88
88
|
export function compileSemanticThinnessPatterns(patterns) {
|
|
89
|
-
const matchModeFor = (pattern) =>
|
|
89
|
+
const matchModeFor = (pattern) => {
|
|
90
|
+
if (pattern.matchMode === "contains" || pattern.matchMode === "suffix") {
|
|
91
|
+
return pattern.matchMode;
|
|
92
|
+
}
|
|
93
|
+
return "full";
|
|
94
|
+
};
|
|
90
95
|
const defaultMaxTokensFor = (pattern) => matchModeFor(pattern) === "contains"
|
|
91
96
|
? CONTAINS_MAX_TOKENS
|
|
92
97
|
: FULL_MAX_TOKENS;
|
|
@@ -2,6 +2,10 @@ import { defineTextlintRule } from "../../../adapters/textlint/rule.js";
|
|
|
2
2
|
import { sectionLastSentenceUnits, sentenceUnits } from "../../../adapters/textlint/units.js";
|
|
3
3
|
import { wordTokens } from "../../../shared/text/tokens.js";
|
|
4
4
|
const CLOSERS = ["and that's the key.", "that's what matters."];
|
|
5
|
+
const EXACT_FORMULA_LINES = new Set([
|
|
6
|
+
"that's the good stuff",
|
|
7
|
+
"that is the good stuff"
|
|
8
|
+
]);
|
|
5
9
|
const FORMULA_TAILS = [
|
|
6
10
|
"key",
|
|
7
11
|
"point",
|
|
@@ -13,9 +17,15 @@ const FORMULA_TAILS = [
|
|
|
13
17
|
];
|
|
14
18
|
function isFormulaLine(text) {
|
|
15
19
|
const lower = text.toLocaleLowerCase("en");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
let punctuationStart = lower.length;
|
|
21
|
+
while ([".", "!", "?"].includes(lower[punctuationStart - 1] ?? "")) {
|
|
22
|
+
punctuationStart -= 1;
|
|
23
|
+
}
|
|
24
|
+
const withoutTerminalPunctuation = lower.slice(0, punctuationStart);
|
|
25
|
+
return (EXACT_FORMULA_LINES.has(withoutTerminalPunctuation) ||
|
|
26
|
+
(wordTokens(text).length <= 6 &&
|
|
27
|
+
(lower.startsWith("that's the ") || lower.startsWith("that is the ")) &&
|
|
28
|
+
FORMULA_TAILS.some((tail) => lower.includes(tail))));
|
|
19
29
|
}
|
|
20
30
|
const rule = defineTextlintRule({
|
|
21
31
|
detector: {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NEGATION_WORDS, findCopularNegation, pronounCopulaStart, skipOptionalAdverbs, startsWithPronounCopula, startsWithWords, startsWithSubjectCopula, validSubject, words } from "./negation-reframe-parts.js";
|
|
2
|
+
export function sameSubjectCopularReframe(aTokens, bTokens) {
|
|
3
|
+
const negation = findCopularNegation(aTokens);
|
|
4
|
+
if (negation === undefined || !validSubject(negation.subject)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return startsWithSubjectCopula(bTokens, negation.subject, negation.affirmativeAux);
|
|
8
|
+
}
|
|
9
|
+
export function pronounCopularReframe(aTokens, bTokens) {
|
|
10
|
+
const negation = findCopularNegation(aTokens);
|
|
11
|
+
return (negation !== undefined &&
|
|
12
|
+
validSubject(negation.subject) &&
|
|
13
|
+
!startsWithNegatedPronounCopula(bTokens) &&
|
|
14
|
+
startsWithPronounCopula(bTokens));
|
|
15
|
+
}
|
|
16
|
+
export function progressiveVerbMirror(aTokens, bTokens) {
|
|
17
|
+
const negation = findCopularNegation(aTokens);
|
|
18
|
+
if (negation === undefined || !validSubject(negation.subject)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const tokenWords = words(aTokens);
|
|
22
|
+
const predicateIndex = skipOptionalAdverbs(tokenWords, negation.negatedPredicateStart);
|
|
23
|
+
const verb = tokenWords[predicateIndex];
|
|
24
|
+
return (verb !== undefined &&
|
|
25
|
+
verb.endsWith("ing") &&
|
|
26
|
+
startsWithWords(bTokens, [
|
|
27
|
+
...negation.subject,
|
|
28
|
+
negation.affirmativeAux,
|
|
29
|
+
verb
|
|
30
|
+
]));
|
|
31
|
+
}
|
|
32
|
+
export function startsWithNegatedPronounCopula(tokens) {
|
|
33
|
+
const start = pronounCopulaStart(tokens);
|
|
34
|
+
if (start === undefined) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const tokenWords = words(tokens);
|
|
38
|
+
const predicateIndex = skipOptionalAdverbs(tokenWords, start.predicateStart);
|
|
39
|
+
return NEGATION_WORDS.has(tokenWords[predicateIndex] ?? "");
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=copular-reframe.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DO_NEGATIONS, EXPLICIT_DO_AUXILIARIES,
|
|
1
|
+
import { DO_NEGATIONS, EXPLICIT_DO_AUXILIARIES, contrastPivotSubject, findCopularNegation, findNegationIndex, isCompleteSentence, skipOptionalAdverbs, startsWithSubjectOrPronoun, startsWithSubjectVerb, startsWithWords, stripLeadingPairPivot, validSubject, words } from "./negation-reframe-parts.js";
|
|
2
2
|
import { hasAbstractCommaContrast, hasAbstractNegationPayoff, hasFactualConnectorAfterNegation, hasMetaContext } from "./negation-context-gates.js";
|
|
3
3
|
import { hasInlineContrastConnectorAfterNegation } from "./inline-contrast-connector.js";
|
|
4
4
|
import { inlineNotBecauseReframe } from "./inline-not-because-reframe.js";
|
|
5
5
|
import { inlineNotJustCopularReframe, inlineShortNegatedBeat } from "./inline-short-negation.js";
|
|
6
|
-
import { hasNegativeSlopPairSignal, negativeSlopReframe } from "./negative-slop-frames.js";
|
|
6
|
+
import { hasNegativeSlopPairSignal, negatedActionPronounPayoff, negatedActionSetReplacement, negativeSlopReframe, progressiveVerbMirror, pronounCopularReframe, sameSubjectCopularReframe, shouldReportCopularReframe, startsWithNegatedPronounCopula } from "./negative-slop-frames.js";
|
|
7
7
|
import { makeMeaningReframe, meaningReframe } from "./meaning-reframe.js";
|
|
8
8
|
import { hasConcreteCorrectionEvidence } from "../../../../shared/matchers/concrete-evidence.js";
|
|
9
9
|
import { splitSentences } from "../../../../shared/text/sentences.js";
|
|
@@ -60,51 +60,6 @@ function inlineNegationContrast(sentence) {
|
|
|
60
60
|
}
|
|
61
61
|
: undefined;
|
|
62
62
|
}
|
|
63
|
-
function sameSubjectCopularReframe(aTokens, bTokens) {
|
|
64
|
-
const negation = findCopularNegation(aTokens);
|
|
65
|
-
if (negation === undefined || !validSubject(negation.subject)) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
return startsWithWords(bTokens, [
|
|
69
|
-
...negation.subject,
|
|
70
|
-
negation.affirmativeAux
|
|
71
|
-
]);
|
|
72
|
-
}
|
|
73
|
-
function pronounCopularReframe(aTokens, bTokens) {
|
|
74
|
-
const negation = findCopularNegation(aTokens);
|
|
75
|
-
return (negation !== undefined &&
|
|
76
|
-
validSubject(negation.subject) &&
|
|
77
|
-
!looksLikePassiveDefinition(aTokens, bTokens) &&
|
|
78
|
-
!startsWithNegatedPronounCopula(bTokens) &&
|
|
79
|
-
startsWithAny(bTokens, PRONOUN_REFRAME_STARTS));
|
|
80
|
-
}
|
|
81
|
-
function startsWithNegatedPronounCopula(tokens) {
|
|
82
|
-
for (const start of PRONOUN_REFRAME_STARTS) {
|
|
83
|
-
if (!startsWithWords(tokens, start)) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const tokenWords = words(tokens);
|
|
87
|
-
const predicateIndex = skipOptionalAdverbs(tokenWords, start.length);
|
|
88
|
-
return NEGATION_WORDS.has(tokenWords[predicateIndex] ?? "");
|
|
89
|
-
}
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
function progressiveVerbMirror(aTokens, bTokens) {
|
|
93
|
-
const negation = findCopularNegation(aTokens);
|
|
94
|
-
if (negation === undefined || !validSubject(negation.subject)) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
const tokenWords = words(aTokens);
|
|
98
|
-
const predicateIndex = skipOptionalAdverbs(tokenWords, negation.negatedPredicateStart);
|
|
99
|
-
const verb = tokenWords[predicateIndex];
|
|
100
|
-
return (verb !== undefined &&
|
|
101
|
-
verb.endsWith("ing") &&
|
|
102
|
-
startsWithWords(bTokens, [
|
|
103
|
-
...negation.subject,
|
|
104
|
-
negation.affirmativeAux,
|
|
105
|
-
verb
|
|
106
|
-
]));
|
|
107
|
-
}
|
|
108
63
|
function needReframe(aTokens, bTokens) {
|
|
109
64
|
const tokenWords = words(aTokens);
|
|
110
65
|
for (let index = 0; index < tokenWords.length; index += 1) {
|
|
@@ -172,43 +127,36 @@ function startsWithExplicitReplacement(tokens) {
|
|
|
172
127
|
}
|
|
173
128
|
function hasPairNegationSignal(tokens) {
|
|
174
129
|
return (findNegationIndex(tokens) !== undefined ||
|
|
130
|
+
findCopularNegation(tokens) !== undefined ||
|
|
175
131
|
contrastPivotSubject(tokens) !== undefined ||
|
|
176
132
|
hasNegativeSlopPairSignal(tokens));
|
|
177
133
|
}
|
|
178
|
-
function looksLikePassiveDefinition(aTokens, bTokens) {
|
|
179
|
-
const aWords = words(aTokens);
|
|
180
|
-
const bWords = words(bTokens);
|
|
181
|
-
for (let index = 0; index < aWords.length - 2; index += 1) {
|
|
182
|
-
const current = aWords[index];
|
|
183
|
-
if ((current === "has" || current === "have" || current === "had") &&
|
|
184
|
-
aWords[index + 1] === "not" &&
|
|
185
|
-
aWords[index + 2] === "been" &&
|
|
186
|
-
startsWithWords(bTokens, ["it", "is"]) &&
|
|
187
|
-
PASSIVE_DEFINITION_VERBS.has(bWords[2] ?? "")) {
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
134
|
function sentencePairReframe(a, b) {
|
|
194
135
|
const aTokens = wordTokens(a.text);
|
|
195
136
|
const bTokens = wordTokens(b.text);
|
|
137
|
+
const pairText = `${a.text} ${b.text}`;
|
|
138
|
+
const hasNegatedActionSetReplacement = negatedActionSetReplacement(aTokens, bTokens);
|
|
139
|
+
const hasAllowedReplacementColon = b.text.trimEnd().endsWith(":") && hasNegatedActionSetReplacement;
|
|
196
140
|
if (!isCompleteSentence(a) ||
|
|
197
|
-
!isCompleteSentence(b) ||
|
|
141
|
+
(!isCompleteSentence(b) && !hasAllowedReplacementColon) ||
|
|
198
142
|
!hasPairNegationSignal(aTokens)) {
|
|
199
143
|
return undefined;
|
|
200
144
|
}
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
145
|
+
if ((shouldReportCopularReframe(aTokens, bTokens, pairText) &&
|
|
146
|
+
(sameSubjectCopularReframe(aTokens, bTokens) ||
|
|
147
|
+
pronounCopularReframe(aTokens, bTokens) ||
|
|
148
|
+
progressiveVerbMirror(aTokens, bTokens))) ||
|
|
204
149
|
(startsWithExplicitReplacement(bTokens) &&
|
|
205
|
-
|
|
150
|
+
shouldReportCopularReframe(aTokens, bTokens, pairText) &&
|
|
151
|
+
!hasConcreteCorrectionEvidence(pairText)) ||
|
|
206
152
|
meaningReframe(aTokens, bTokens) ||
|
|
207
153
|
makeMeaningReframe(aTokens, bTokens) ||
|
|
208
154
|
needReframe(aTokens, bTokens) ||
|
|
209
155
|
actionVerbMirror(aTokens, bTokens) ||
|
|
156
|
+
negatedActionPronounPayoff(aTokens, bTokens) ||
|
|
210
157
|
negativeSlopReframe(aTokens, bTokens) ||
|
|
211
|
-
|
|
158
|
+
(shouldReportCopularReframe(aTokens, bTokens, pairText) &&
|
|
159
|
+
explicitContrastPivotReframe(aTokens, bTokens))) {
|
|
212
160
|
return {
|
|
213
161
|
end: b.end,
|
|
214
162
|
start: a.start,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const NEGATION_WORDS = new Set([
|
|
2
2
|
"not",
|
|
3
|
+
"never",
|
|
3
4
|
"isn't",
|
|
4
5
|
"aren't",
|
|
5
6
|
"wasn't",
|
|
@@ -52,18 +53,22 @@ export const PRONOUN_REFRAME_STARTS = [
|
|
|
52
53
|
["you", "are"],
|
|
53
54
|
["we", "are"]
|
|
54
55
|
];
|
|
55
|
-
export const PASSIVE_DEFINITION_VERBS = new Set([
|
|
56
|
-
"associated",
|
|
57
|
-
"caused",
|
|
58
|
-
"classified",
|
|
59
|
-
"defined",
|
|
60
|
-
"described",
|
|
61
|
-
"linked",
|
|
62
|
-
"marked",
|
|
63
|
-
"produced"
|
|
64
|
-
]);
|
|
65
56
|
const IRREGULAR_PAST_TENSE = new Map([["go", "went"]]);
|
|
66
57
|
const CONTRAST_PIVOT_AFTER_NOT = new Set(["just", "merely", "only"]);
|
|
58
|
+
const NEGATIVE_COPULAR_PREDICATES = new Set([
|
|
59
|
+
"never",
|
|
60
|
+
"nobody",
|
|
61
|
+
"none",
|
|
62
|
+
"nothing",
|
|
63
|
+
"nowhere"
|
|
64
|
+
]);
|
|
65
|
+
const CONTRACTED_PRONOUN_COPULAS = new Map([
|
|
66
|
+
["it's", { affirmativeAux: "is", subject: ["it"] }],
|
|
67
|
+
["that's", { affirmativeAux: "is", subject: ["that"] }],
|
|
68
|
+
["they're", { affirmativeAux: "are", subject: ["they"] }],
|
|
69
|
+
["we're", { affirmativeAux: "are", subject: ["we"] }],
|
|
70
|
+
["you're", { affirmativeAux: "are", subject: ["you"] }]
|
|
71
|
+
]);
|
|
67
72
|
export function findNegationIndex(tokens) {
|
|
68
73
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
69
74
|
const token = tokens[index];
|
|
@@ -121,6 +126,39 @@ export function skipOptionalAdverbs(tokens, start) {
|
|
|
121
126
|
export function startsWithAny(tokens, starts) {
|
|
122
127
|
return starts.some((start) => startsWithWords(tokens, start));
|
|
123
128
|
}
|
|
129
|
+
export function pronounCopulaStart(tokens) {
|
|
130
|
+
const firstToken = tokens[0];
|
|
131
|
+
const contracted = firstToken === undefined
|
|
132
|
+
? undefined
|
|
133
|
+
: CONTRACTED_PRONOUN_COPULAS.get(firstToken.normalized);
|
|
134
|
+
if (contracted !== undefined) {
|
|
135
|
+
return { ...contracted, predicateStart: 1 };
|
|
136
|
+
}
|
|
137
|
+
for (const start of PRONOUN_REFRAME_STARTS) {
|
|
138
|
+
if (startsWithWords(tokens, start)) {
|
|
139
|
+
return {
|
|
140
|
+
affirmativeAux: start[1],
|
|
141
|
+
predicateStart: start.length,
|
|
142
|
+
subject: [start[0]]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
export function startsWithPronounCopula(tokens) {
|
|
149
|
+
return pronounCopulaStart(tokens) !== undefined;
|
|
150
|
+
}
|
|
151
|
+
export function startsWithSubjectCopula(tokens, subject, affirmativeAux) {
|
|
152
|
+
if (startsWithWords(tokens, [...subject, affirmativeAux])) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const contracted = CONTRACTED_PRONOUN_COPULAS.get(tokens[0]?.normalized ?? "");
|
|
156
|
+
if (contracted?.affirmativeAux !== affirmativeAux) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return (subject.length === contracted.subject.length &&
|
|
160
|
+
subject.every((word, index) => word === contracted.subject[index]));
|
|
161
|
+
}
|
|
124
162
|
export function isCompleteSentence(sentence) {
|
|
125
163
|
const trimmed = sentence.text.trim();
|
|
126
164
|
const last = trimmed.at(-1);
|
|
@@ -161,6 +199,24 @@ export function findCopularNegation(tokens) {
|
|
|
161
199
|
subject: tokenWords.slice(0, index)
|
|
162
200
|
};
|
|
163
201
|
}
|
|
202
|
+
if (explicitAux !== undefined &&
|
|
203
|
+
next !== undefined &&
|
|
204
|
+
NEGATIVE_COPULAR_PREDICATES.has(next)) {
|
|
205
|
+
return {
|
|
206
|
+
affirmativeAux: explicitAux,
|
|
207
|
+
negatedPredicateStart: index + 1,
|
|
208
|
+
subject: tokenWords.slice(0, index)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (explicitAux !== undefined &&
|
|
212
|
+
next === "no" &&
|
|
213
|
+
(tokenWords[index + 2] === "one" || tokenWords[index + 2] === "single")) {
|
|
214
|
+
return {
|
|
215
|
+
affirmativeAux: explicitAux,
|
|
216
|
+
negatedPredicateStart: index + 1,
|
|
217
|
+
subject: tokenWords.slice(0, index)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
164
220
|
}
|
|
165
221
|
return undefined;
|
|
166
222
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { hasFactualConnectorAfterNegation } from "./negation-context-gates.js";
|
|
2
|
+
import { hasAbstractPolicyDirectObject } from "./policy-object.js";
|
|
3
|
+
export { shouldReportCopularReframe } from "./reframe-classification.js";
|
|
4
|
+
export { progressiveVerbMirror, pronounCopularReframe, sameSubjectCopularReframe, startsWithNegatedPronounCopula } from "./copular-reframe.js";
|
|
1
5
|
import { DO_NEGATIONS, EXPLICIT_DO_AUXILIARIES, FACTUAL_NEGATION_CONNECTORS, PRONOUN_REFRAME_STARTS, hasAnyWord, skipOptionalAdverbs, startsWithAny, startsWithSubjectOrPronoun, startsWithWords, stripLeadingPairPivot, validSubject, words } from "./negation-reframe-parts.js";
|
|
2
6
|
const GENERIC_ACTION_VERBS = new Set([
|
|
3
7
|
"asked",
|
|
@@ -27,6 +31,40 @@ const GENERIC_ACTION_VERBS = new Set([
|
|
|
27
31
|
"walked",
|
|
28
32
|
"went"
|
|
29
33
|
]);
|
|
34
|
+
const NEGATED_ACTION_REFRAME_VERBS = new Set([
|
|
35
|
+
"avoid",
|
|
36
|
+
"change",
|
|
37
|
+
"create",
|
|
38
|
+
"end",
|
|
39
|
+
"erase",
|
|
40
|
+
"fix",
|
|
41
|
+
"guarantee",
|
|
42
|
+
"make",
|
|
43
|
+
"mean",
|
|
44
|
+
"remove",
|
|
45
|
+
"replace",
|
|
46
|
+
"require",
|
|
47
|
+
"skip",
|
|
48
|
+
"solve"
|
|
49
|
+
]);
|
|
50
|
+
const REPLACEMENT_SUBJECT_PRONOUNS = new Set(["he", "she"]);
|
|
51
|
+
const PRONOUN_PAYOFF_VERBS = new Set([
|
|
52
|
+
"becomes",
|
|
53
|
+
"creates",
|
|
54
|
+
"depends",
|
|
55
|
+
"exposes",
|
|
56
|
+
"lands",
|
|
57
|
+
"means",
|
|
58
|
+
"moves",
|
|
59
|
+
"needs",
|
|
60
|
+
"points",
|
|
61
|
+
"requires",
|
|
62
|
+
"reveals",
|
|
63
|
+
"shifts",
|
|
64
|
+
"shows",
|
|
65
|
+
"turns"
|
|
66
|
+
]);
|
|
67
|
+
const PRONOUN_SUBJECTS = new Set(["it", "this", "that", "they", "we", "you"]);
|
|
30
68
|
function startsWithPassiveCopula(tokens) {
|
|
31
69
|
const tokenWords = words(tokens);
|
|
32
70
|
const predicateIndex = startsWithAny(tokens, PRONOUN_REFRAME_STARTS)
|
|
@@ -81,7 +119,34 @@ function negatedActionSubject(tokens) {
|
|
|
81
119
|
}
|
|
82
120
|
return undefined;
|
|
83
121
|
}
|
|
84
|
-
function
|
|
122
|
+
function constrainedSetSubjectLength(tokens, subject) {
|
|
123
|
+
if (startsWithWords(tokens, subject)) {
|
|
124
|
+
return subject.length;
|
|
125
|
+
}
|
|
126
|
+
if (startsWithSubjectOrPronoun(tokens, subject)) {
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
return subject.length > 1 &&
|
|
130
|
+
REPLACEMENT_SUBJECT_PRONOUNS.has(tokens[0]?.normalized ?? "")
|
|
131
|
+
? 1
|
|
132
|
+
: undefined;
|
|
133
|
+
}
|
|
134
|
+
export function negatedActionReplacement(aTokens, bTokens) {
|
|
135
|
+
const tokenWords = words(aTokens);
|
|
136
|
+
const subject = negatedActionSubject(aTokens);
|
|
137
|
+
if (subject === undefined) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const bContentTokens = stripLeadingPairPivot(bTokens);
|
|
141
|
+
const bWords = words(bContentTokens);
|
|
142
|
+
const genericVerbIndex = skipOptionalAdverbs(bWords, subject.length);
|
|
143
|
+
return (tokenWords.length <= 14 &&
|
|
144
|
+
!hasAnyWord(tokenWords, FACTUAL_NEGATION_CONNECTORS) &&
|
|
145
|
+
((startsWithSubjectOrPronoun(bContentTokens, subject) &&
|
|
146
|
+
GENERIC_ACTION_VERBS.has(bWords[genericVerbIndex] ?? "")) ||
|
|
147
|
+
negatedActionSetReplacement(aTokens, bTokens)));
|
|
148
|
+
}
|
|
149
|
+
export function negatedActionSetReplacement(aTokens, bTokens) {
|
|
85
150
|
const tokenWords = words(aTokens);
|
|
86
151
|
const subject = negatedActionSubject(aTokens);
|
|
87
152
|
if (subject === undefined) {
|
|
@@ -89,13 +154,14 @@ function negativeActionReplacement(aTokens, bTokens) {
|
|
|
89
154
|
}
|
|
90
155
|
const bContentTokens = stripLeadingPairPivot(bTokens);
|
|
91
156
|
const bWords = words(bContentTokens);
|
|
92
|
-
const
|
|
93
|
-
const
|
|
157
|
+
const subjectLength = constrainedSetSubjectLength(bContentTokens, subject);
|
|
158
|
+
const verbIndex = subjectLength === undefined
|
|
159
|
+
? undefined
|
|
160
|
+
: skipOptionalAdverbs(bWords, subjectLength);
|
|
94
161
|
return (tokenWords.length <= 14 &&
|
|
95
162
|
!hasAnyWord(tokenWords, FACTUAL_NEGATION_CONNECTORS) &&
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
GENERIC_ACTION_VERBS.has(verb));
|
|
163
|
+
verbIndex !== undefined &&
|
|
164
|
+
hasAbstractPolicyDirectObject(bWords, verbIndex));
|
|
99
165
|
}
|
|
100
166
|
function notBecauseReframe(aTokens, bTokens) {
|
|
101
167
|
return (startsWithWords(aTokens, ["not", "because"]) &&
|
|
@@ -130,10 +196,47 @@ export function hasNegativeSlopPairSignal(tokens) {
|
|
|
130
196
|
hasTrailingProblemFrame(tokenWords) ||
|
|
131
197
|
hasLeadingProblemFrame(tokenWords));
|
|
132
198
|
}
|
|
199
|
+
export function negatedActionPronounPayoff(aTokens, bTokens) {
|
|
200
|
+
const aWords = words(aTokens);
|
|
201
|
+
const bWords = words(stripLeadingPairPivot(bTokens));
|
|
202
|
+
const pronounStart = PRONOUN_SUBJECTS.has(bWords[0] ?? "") ? 1 : undefined;
|
|
203
|
+
if (pronounStart === undefined) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
const payoffVerbIndex = skipOptionalAdverbs(bWords, pronounStart);
|
|
207
|
+
if (!PRONOUN_PAYOFF_VERBS.has(bWords[payoffVerbIndex] ?? "")) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
for (let index = 0; index < aWords.length; index += 1) {
|
|
211
|
+
if (hasNegatedReframeVerb(aTokens, aWords, index)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
function hasNegatedReframeVerb(tokens, tokenWords, index) {
|
|
218
|
+
const current = tokenWords[index];
|
|
219
|
+
const next = tokenWords[index + 1];
|
|
220
|
+
const subject = tokenWords.slice(0, index);
|
|
221
|
+
if (!validSubject(subject)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const negationIndex = DO_NEGATIONS.has(current ?? "")
|
|
225
|
+
? index
|
|
226
|
+
: EXPLICIT_DO_AUXILIARIES.has(current ?? "") && next === "not"
|
|
227
|
+
? index + 1
|
|
228
|
+
: undefined;
|
|
229
|
+
if (negationIndex === undefined ||
|
|
230
|
+
hasFactualConnectorAfterNegation(tokens, negationIndex)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const verbIndex = skipOptionalAdverbs(tokenWords, negationIndex + 1);
|
|
234
|
+
return NEGATED_ACTION_REFRAME_VERBS.has(tokenWords[verbIndex] ?? "");
|
|
235
|
+
}
|
|
133
236
|
export function negativeSlopReframe(aTokens, bTokens) {
|
|
134
237
|
return (noLongerCopularReframe(aTokens, bTokens) ||
|
|
135
238
|
fragmentDefinitionReframe(aTokens, bTokens) ||
|
|
136
|
-
|
|
239
|
+
negatedActionReplacement(aTokens, bTokens) ||
|
|
137
240
|
notBecauseReframe(aTokens, bTokens) ||
|
|
138
241
|
notProblemReframe(aTokens, bTokens));
|
|
139
242
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const ABSTRACT_POLICY_OBJECTS = new Set([
|
|
2
|
+
"condition",
|
|
3
|
+
"criterion",
|
|
4
|
+
"policy",
|
|
5
|
+
"principle",
|
|
6
|
+
"requirement",
|
|
7
|
+
"rule",
|
|
8
|
+
"standard",
|
|
9
|
+
"threshold"
|
|
10
|
+
]);
|
|
11
|
+
const DIRECT_OBJECT_DETERMINERS = new Set([
|
|
12
|
+
"a",
|
|
13
|
+
"an",
|
|
14
|
+
"one",
|
|
15
|
+
"that",
|
|
16
|
+
"the",
|
|
17
|
+
"this"
|
|
18
|
+
]);
|
|
19
|
+
const POLICY_OBJECT_MODIFIERS = new Set([
|
|
20
|
+
"access",
|
|
21
|
+
"approval",
|
|
22
|
+
"business",
|
|
23
|
+
"content",
|
|
24
|
+
"deployment",
|
|
25
|
+
"editorial",
|
|
26
|
+
"governance",
|
|
27
|
+
"launch",
|
|
28
|
+
"operating",
|
|
29
|
+
"pricing",
|
|
30
|
+
"quality",
|
|
31
|
+
"release",
|
|
32
|
+
"review",
|
|
33
|
+
"safety",
|
|
34
|
+
"security",
|
|
35
|
+
"service",
|
|
36
|
+
"spending",
|
|
37
|
+
"technical",
|
|
38
|
+
"testing",
|
|
39
|
+
"usage"
|
|
40
|
+
]);
|
|
41
|
+
export function hasAbstractPolicyDirectObject(tokenWords, verbIndex) {
|
|
42
|
+
if (!["set", "sets"].includes(tokenWords[verbIndex] ?? "")) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const objectTokens = tokenWords.slice(verbIndex + 1);
|
|
46
|
+
const directObject = DIRECT_OBJECT_DETERMINERS.has(objectTokens[0] ?? "")
|
|
47
|
+
? objectTokens.slice(1)
|
|
48
|
+
: objectTokens;
|
|
49
|
+
const [first, second] = directObject;
|
|
50
|
+
return ((directObject.length === 1 && ABSTRACT_POLICY_OBJECTS.has(first ?? "")) ||
|
|
51
|
+
(directObject.length === 2 &&
|
|
52
|
+
POLICY_OBJECT_MODIFIERS.has(first ?? "") &&
|
|
53
|
+
ABSTRACT_POLICY_OBJECTS.has(second ?? "")));
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=policy-object.js.map
|