@faircopy/rules-nlp 1.2.1 → 1.4.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 +32 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +132 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @faircopy/rules-nlp
|
|
2
|
+
|
|
3
|
+
Optional NLP-powered ruleset for faircopy using `compromise`.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm i -D @faircopy/rules-nlp
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Load the ruleset once, then configure rules with bare rule IDs:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
rulesets: ['@faircopy/rules-nlp'],
|
|
13
|
+
rules: {
|
|
14
|
+
'no-filter-words': 'warn',
|
|
15
|
+
'no-passive-voice': 'warn',
|
|
16
|
+
'no-weak-modals': 'warn',
|
|
17
|
+
'no-stacked-adjectives': 'warn',
|
|
18
|
+
'no-nominalized-phrases': 'warn',
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Package-qualified IDs like `@faircopy/rules-nlp/no-passive-voice` still work and are required if another loaded ruleset exposes the same bare rule name.
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
| Rule | Description |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `no-filter-words` | Ban filter phrases like `I think` and `it seems` |
|
|
29
|
+
| `no-passive-voice` | Flag likely passive-voice constructions |
|
|
30
|
+
| `no-weak-modals` | Flag hedged modal claims like `can help` and `might improve` |
|
|
31
|
+
| `no-stacked-adjectives` | Flag noun phrases with multiple adjectives before the noun |
|
|
32
|
+
| `no-nominalized-phrases` | Flag nominalized `X of Y` phrases like `optimization of onboarding` |
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,29 @@ interface NoFilterWordsOptions {
|
|
|
5
5
|
}
|
|
6
6
|
declare const noFilterWords: Rule<NoFilterWordsOptions>;
|
|
7
7
|
|
|
8
|
+
interface NoNominalizedPhrasesOptions {
|
|
9
|
+
suffixes?: string[];
|
|
10
|
+
allowedWords?: string[];
|
|
11
|
+
}
|
|
12
|
+
declare const noNominalizedPhrases: Rule<NoNominalizedPhrasesOptions>;
|
|
13
|
+
|
|
8
14
|
interface NoPassiveVoiceOptions {
|
|
9
15
|
allowedAuxiliaries?: string[];
|
|
10
16
|
}
|
|
11
17
|
declare const noPassiveVoice: Rule<NoPassiveVoiceOptions>;
|
|
12
18
|
|
|
19
|
+
interface NoStackedAdjectivesOptions {
|
|
20
|
+
allowedPhrases?: string[];
|
|
21
|
+
}
|
|
22
|
+
declare const noStackedAdjectives: Rule<NoStackedAdjectivesOptions>;
|
|
23
|
+
|
|
24
|
+
interface NoWeakModalsOptions {
|
|
25
|
+
modals?: string[];
|
|
26
|
+
verbs?: string[];
|
|
27
|
+
}
|
|
28
|
+
declare const noWeakModals: Rule<NoWeakModalsOptions>;
|
|
29
|
+
|
|
13
30
|
/** All NLP rules keyed by their rule ID. */
|
|
14
31
|
declare const ruleRegistry: Map<string, Rule>;
|
|
15
32
|
|
|
16
|
-
export { type NoFilterWordsOptions, type NoPassiveVoiceOptions, noFilterWords, noPassiveVoice, ruleRegistry };
|
|
33
|
+
export { type NoFilterWordsOptions, type NoNominalizedPhrasesOptions, type NoPassiveVoiceOptions, type NoStackedAdjectivesOptions, type NoWeakModalsOptions, noFilterWords, noNominalizedPhrases, noPassiveVoice, noStackedAdjectives, noWeakModals, ruleRegistry };
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,12 @@ function getMatchOccurrences(text, matches) {
|
|
|
18
18
|
}];
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
function getOccurrenceRange(sourceMap, occurrence) {
|
|
22
|
+
const start = sourceMap[occurrence.start];
|
|
23
|
+
const end = sourceMap[occurrence.end - 1];
|
|
24
|
+
if (start === void 0 || end === void 0) return null;
|
|
25
|
+
return { start, end: end + 1 };
|
|
26
|
+
}
|
|
21
27
|
function sumTermLengths(terms) {
|
|
22
28
|
if (!terms?.length) return void 0;
|
|
23
29
|
let total = 0;
|
|
@@ -64,6 +70,53 @@ var noFilterWords = {
|
|
|
64
70
|
}
|
|
65
71
|
};
|
|
66
72
|
|
|
73
|
+
// src/no-nominalized-phrases.ts
|
|
74
|
+
var DEFAULT_SUFFIXES = ["tion", "sion", "ment", "ance", "ence", "ity"];
|
|
75
|
+
var DEFAULT_ALLOWED_WORDS = [
|
|
76
|
+
"accessibility",
|
|
77
|
+
"availability",
|
|
78
|
+
"capacity",
|
|
79
|
+
"community",
|
|
80
|
+
"identity",
|
|
81
|
+
"opportunity",
|
|
82
|
+
"privacy",
|
|
83
|
+
"quality",
|
|
84
|
+
"reliability",
|
|
85
|
+
"security"
|
|
86
|
+
];
|
|
87
|
+
var noNominalizedPhrases = {
|
|
88
|
+
id: "no-nominalized-phrases",
|
|
89
|
+
description: 'Flag nominalized "X of Y" phrases that hide the action in a noun',
|
|
90
|
+
defaults: { suffixes: DEFAULT_SUFFIXES, allowedWords: DEFAULT_ALLOWED_WORDS },
|
|
91
|
+
help: "Nominalized phrases bury the action. Rewrite the phrase with a verb so the sentence says who does what.",
|
|
92
|
+
check({ text, sourceMap, options }) {
|
|
93
|
+
const diagnostics = [];
|
|
94
|
+
const suffixes = options.suffixes?.length ? options.suffixes : DEFAULT_SUFFIXES;
|
|
95
|
+
const allowedWords = new Set((options.allowedWords?.length ? options.allowedWords : DEFAULT_ALLOWED_WORDS).map((value) => value.toLowerCase()));
|
|
96
|
+
const suffixPattern = suffixes.map(escapeRegex).join("|");
|
|
97
|
+
if (!suffixPattern) return diagnostics;
|
|
98
|
+
const doc = createDoc(text);
|
|
99
|
+
const matches = doc.match(`/[a-z]+(${suffixPattern})/ of .`);
|
|
100
|
+
for (const occurrence of getMatchOccurrences(text, matches)) {
|
|
101
|
+
const nominalization = occurrence.text.trim().split(/\s+/)[0]?.toLowerCase();
|
|
102
|
+
if (!nominalization || allowedWords.has(nominalization)) continue;
|
|
103
|
+
const range = getOccurrenceRange(sourceMap, occurrence);
|
|
104
|
+
if (!range) continue;
|
|
105
|
+
diagnostics.push({
|
|
106
|
+
ruleId: "no-nominalized-phrases",
|
|
107
|
+
severity: "warn",
|
|
108
|
+
message: `rewrite "${occurrence.text}" with a verb`,
|
|
109
|
+
range,
|
|
110
|
+
help: noNominalizedPhrases.help
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return diagnostics;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
function escapeRegex(value) {
|
|
117
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
118
|
+
}
|
|
119
|
+
|
|
67
120
|
// src/no-passive-voice.ts
|
|
68
121
|
var DEFAULT_ALLOWED_AUXILIARIES = ["is", "are", "was", "were", "be", "been", "being"];
|
|
69
122
|
var noPassiveVoice = {
|
|
@@ -104,14 +157,92 @@ function dedupeDiagnostics(diagnostics) {
|
|
|
104
157
|
});
|
|
105
158
|
}
|
|
106
159
|
|
|
160
|
+
// src/no-stacked-adjectives.ts
|
|
161
|
+
var noStackedAdjectives = {
|
|
162
|
+
id: "no-stacked-adjectives",
|
|
163
|
+
description: "Flag noun phrases with multiple adjectives before the noun",
|
|
164
|
+
defaults: { allowedPhrases: [] },
|
|
165
|
+
help: "Stacked adjectives make copy feel generic. Keep the one descriptor that earns its place or replace the phrase with concrete evidence.",
|
|
166
|
+
check({ text, sourceMap, options }) {
|
|
167
|
+
const diagnostics = [];
|
|
168
|
+
const allowedPhrases = new Set((options.allowedPhrases ?? []).map((value) => value.toLowerCase()));
|
|
169
|
+
const doc = createDoc(text);
|
|
170
|
+
const matches = doc.match("#Adjective #Adjective+ #Noun");
|
|
171
|
+
for (const occurrence of getMatchOccurrences(text, matches)) {
|
|
172
|
+
if (allowedPhrases.has(occurrence.text.toLowerCase())) continue;
|
|
173
|
+
const range = getOccurrenceRange(sourceMap, occurrence);
|
|
174
|
+
if (!range) continue;
|
|
175
|
+
diagnostics.push({
|
|
176
|
+
ruleId: "no-stacked-adjectives",
|
|
177
|
+
severity: "warn",
|
|
178
|
+
message: `cut stacked adjectives in "${occurrence.text}"`,
|
|
179
|
+
range,
|
|
180
|
+
help: noStackedAdjectives.help
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return diagnostics;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/no-weak-modals.ts
|
|
188
|
+
var DEFAULT_MODALS = ["can", "could", "may", "might"];
|
|
189
|
+
var DEFAULT_VERBS = [
|
|
190
|
+
"boost",
|
|
191
|
+
"drive",
|
|
192
|
+
"enable",
|
|
193
|
+
"help",
|
|
194
|
+
"improve",
|
|
195
|
+
"increase",
|
|
196
|
+
"make",
|
|
197
|
+
"reduce",
|
|
198
|
+
"support",
|
|
199
|
+
"transform",
|
|
200
|
+
"unlock"
|
|
201
|
+
];
|
|
202
|
+
var noWeakModals = {
|
|
203
|
+
id: "no-weak-modals",
|
|
204
|
+
description: 'Flag hedged modal claims like "can help" and "might improve"',
|
|
205
|
+
defaults: { modals: DEFAULT_MODALS, verbs: DEFAULT_VERBS },
|
|
206
|
+
help: "Hedged modal claims sound tentative. Replace them with the outcome, capability, or proof you can stand behind.",
|
|
207
|
+
check({ text, sourceMap, options }) {
|
|
208
|
+
const diagnostics = [];
|
|
209
|
+
const modals = new Set((options.modals?.length ? options.modals : DEFAULT_MODALS).map((value) => value.toLowerCase()));
|
|
210
|
+
const verbs = new Set((options.verbs?.length ? options.verbs : DEFAULT_VERBS).map((value) => value.toLowerCase()));
|
|
211
|
+
const doc = createDoc(text);
|
|
212
|
+
const matches = doc.match("#Modal #Adverb? #Verb");
|
|
213
|
+
for (const occurrence of getMatchOccurrences(text, matches)) {
|
|
214
|
+
const words = occurrence.text.trim().split(/\s+/);
|
|
215
|
+
const modal = words[0]?.toLowerCase();
|
|
216
|
+
const verb = words[words.length - 1]?.toLowerCase();
|
|
217
|
+
if (!modal || !verb || !modals.has(modal) || !verbs.has(verb)) continue;
|
|
218
|
+
const range = getOccurrenceRange(sourceMap, occurrence);
|
|
219
|
+
if (!range) continue;
|
|
220
|
+
diagnostics.push({
|
|
221
|
+
ruleId: "no-weak-modals",
|
|
222
|
+
severity: "warn",
|
|
223
|
+
message: `replace "${occurrence.text.toLowerCase()}" with a direct claim`,
|
|
224
|
+
range,
|
|
225
|
+
help: noWeakModals.help
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return diagnostics;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
107
232
|
// src/index.ts
|
|
108
233
|
var ruleRegistry = /* @__PURE__ */ new Map([
|
|
109
234
|
["no-filter-words", noFilterWords],
|
|
110
|
-
["no-
|
|
235
|
+
["no-nominalized-phrases", noNominalizedPhrases],
|
|
236
|
+
["no-passive-voice", noPassiveVoice],
|
|
237
|
+
["no-stacked-adjectives", noStackedAdjectives],
|
|
238
|
+
["no-weak-modals", noWeakModals]
|
|
111
239
|
]);
|
|
112
240
|
export {
|
|
113
241
|
noFilterWords,
|
|
242
|
+
noNominalizedPhrases,
|
|
114
243
|
noPassiveVoice,
|
|
244
|
+
noStackedAdjectives,
|
|
245
|
+
noWeakModals,
|
|
115
246
|
ruleRegistry
|
|
116
247
|
};
|
|
117
248
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts","../src/no-filter-words.ts","../src/no-passive-voice.ts","../src/index.ts"],"sourcesContent":["import nlp from 'compromise'\nimport type { DocView, JsonOffsetEntry, JsonOffsetTerm, MatchView } from './types.js'\n\nexport interface MatchOccurrence {\n text: string\n start: number\n end: number\n}\n\nexport function createDoc(text: string): DocView {\n return nlp(text) as unknown as DocView\n}\n\nexport function getMatchOccurrences(text: string, matches: MatchView): MatchOccurrence[] {\n const json = matches.json({ offset: true, text: true, terms: { offset: true } }) as JsonOffsetEntry[]\n\n return json.flatMap((entry) => {\n const start = entry.offset?.start ?? entry.terms?.[0]?.offset?.start\n const length = entry.offset?.length ?? sumTermLengths(entry.terms)\n\n if (typeof start !== 'number' || typeof length !== 'number' || length <= 0) {\n return []\n }\n\n return [{\n text: entry.text ?? text.slice(start, start + length),\n start,\n end: start + length,\n }]\n })\n}\n\nfunction sumTermLengths(terms: JsonOffsetTerm[] | undefined): number | undefined {\n if (!terms?.length) return undefined\n\n let total = 0\n for (const term of terms) {\n const length = term.offset?.length\n if (typeof length !== 'number') return undefined\n total += length\n }\n\n return total\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoFilterWordsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'I think',\n 'it seems',\n 'basically',\n 'in order to',\n]\n\nexport const noFilterWords: Rule<NoFilterWordsOptions> = {\n id: 'no-filter-words',\n description: 'Ban filter phrases that distance the claim from the reader',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Filter phrases announce a perspective or pad the sentence instead of making the point. Delete the phrase or rewrite the sentence so the claim stands on its own.',\n\n check({ text, sourceMap, options }: RuleInput<NoFilterWordsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const doc = createDoc(text)\n\n for (const phrase of phrases) {\n const matches = doc.match(phrase)\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-filter-words',\n severity: 'error',\n message: `remove \"${occurrence.text.toLowerCase()}\" — state the claim directly`,\n range: { start, end: end + 1 },\n help: noFilterWords.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoPassiveVoiceOptions {\n allowedAuxiliaries?: string[]\n}\n\nconst DEFAULT_ALLOWED_AUXILIARIES = ['is', 'are', 'was', 'were', 'be', 'been', 'being']\n\nexport const noPassiveVoice: Rule<NoPassiveVoiceOptions> = {\n id: 'no-passive-voice',\n description: 'Flag likely passive-voice constructions using POS tagging patterns',\n defaults: { allowedAuxiliaries: DEFAULT_ALLOWED_AUXILIARIES },\n help: 'Passive voice often hides the actor and adds drag. Prefer naming who did the action unless the actor genuinely does not matter.',\n\n check({ text, sourceMap, options }: RuleInput<NoPassiveVoiceOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const auxiliaries = new Set((options.allowedAuxiliaries?.length ? options.allowedAuxiliaries : DEFAULT_ALLOWED_AUXILIARIES).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('(#Copula|#Auxiliary) #PastTense')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n if (words.length < 2) continue\n if (!auxiliaries.has(words[0]!.toLowerCase())) continue\n\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-passive-voice',\n severity: 'warn',\n message: `rewrite passive construction \"${occurrence.text}\" with a named actor`,\n range: { start, end: end + 1 },\n help: noPassiveVoice.help,\n })\n }\n\n return dedupeDiagnostics(diagnostics)\n },\n}\n\nfunction dedupeDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {\n const seen = new Set<string>()\n return diagnostics.filter((diagnostic) => {\n const key = `${diagnostic.range.start}:${diagnostic.range.end}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n}\n","import type { Rule } from '@faircopy/core'\nimport { noFilterWords } from './no-filter-words.js'\nimport { noPassiveVoice } from './no-passive-voice.js'\n\nexport { noFilterWords } from './no-filter-words.js'\nexport { noPassiveVoice } from './no-passive-voice.js'\nexport type { NoFilterWordsOptions } from './no-filter-words.js'\nexport type { NoPassiveVoiceOptions } from './no-passive-voice.js'\n\n/** All NLP rules keyed by their rule ID. */\nexport const ruleRegistry: Map<string, Rule> = new Map([\n ['no-filter-words', noFilterWords as Rule],\n ['no-passive-voice', noPassiveVoice as Rule],\n])\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/no-filter-words.ts","../src/no-nominalized-phrases.ts","../src/no-passive-voice.ts","../src/no-stacked-adjectives.ts","../src/no-weak-modals.ts","../src/index.ts"],"sourcesContent":["import nlp from 'compromise'\nimport type { Diagnostic } from '@faircopy/core'\nimport type { DocView, JsonOffsetEntry, JsonOffsetTerm, MatchView } from './types.js'\n\nexport interface MatchOccurrence {\n text: string\n start: number\n end: number\n}\n\nexport function createDoc(text: string): DocView {\n return nlp(text) as unknown as DocView\n}\n\nexport function getMatchOccurrences(text: string, matches: MatchView): MatchOccurrence[] {\n const json = matches.json({ offset: true, text: true, terms: { offset: true } }) as JsonOffsetEntry[]\n\n return json.flatMap((entry) => {\n const start = entry.offset?.start ?? entry.terms?.[0]?.offset?.start\n const length = entry.offset?.length ?? sumTermLengths(entry.terms)\n\n if (typeof start !== 'number' || typeof length !== 'number' || length <= 0) {\n return []\n }\n\n return [{\n text: entry.text ?? text.slice(start, start + length),\n start,\n end: start + length,\n }]\n })\n}\n\nexport function getOccurrenceRange(sourceMap: number[], occurrence: MatchOccurrence): Diagnostic['range'] | null {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) return null\n\n return { start, end: end + 1 }\n}\n\nfunction sumTermLengths(terms: JsonOffsetTerm[] | undefined): number | undefined {\n if (!terms?.length) return undefined\n\n let total = 0\n for (const term of terms) {\n const length = term.offset?.length\n if (typeof length !== 'number') return undefined\n total += length\n }\n\n return total\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoFilterWordsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'I think',\n 'it seems',\n 'basically',\n 'in order to',\n]\n\nexport const noFilterWords: Rule<NoFilterWordsOptions> = {\n id: 'no-filter-words',\n description: 'Ban filter phrases that distance the claim from the reader',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Filter phrases announce a perspective or pad the sentence instead of making the point. Delete the phrase or rewrite the sentence so the claim stands on its own.',\n\n check({ text, sourceMap, options }: RuleInput<NoFilterWordsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const doc = createDoc(text)\n\n for (const phrase of phrases) {\n const matches = doc.match(phrase)\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-filter-words',\n severity: 'error',\n message: `remove \"${occurrence.text.toLowerCase()}\" — state the claim directly`,\n range: { start, end: end + 1 },\n help: noFilterWords.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoNominalizedPhrasesOptions {\n suffixes?: string[]\n allowedWords?: string[]\n}\n\nconst DEFAULT_SUFFIXES = ['tion', 'sion', 'ment', 'ance', 'ence', 'ity']\nconst DEFAULT_ALLOWED_WORDS = [\n 'accessibility',\n 'availability',\n 'capacity',\n 'community',\n 'identity',\n 'opportunity',\n 'privacy',\n 'quality',\n 'reliability',\n 'security',\n]\n\nexport const noNominalizedPhrases: Rule<NoNominalizedPhrasesOptions> = {\n id: 'no-nominalized-phrases',\n description: 'Flag nominalized \"X of Y\" phrases that hide the action in a noun',\n defaults: { suffixes: DEFAULT_SUFFIXES, allowedWords: DEFAULT_ALLOWED_WORDS },\n help: 'Nominalized phrases bury the action. Rewrite the phrase with a verb so the sentence says who does what.',\n\n check({ text, sourceMap, options }: RuleInput<NoNominalizedPhrasesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const suffixes = options.suffixes?.length ? options.suffixes : DEFAULT_SUFFIXES\n const allowedWords = new Set((options.allowedWords?.length ? options.allowedWords : DEFAULT_ALLOWED_WORDS).map(value => value.toLowerCase()))\n const suffixPattern = suffixes.map(escapeRegex).join('|')\n if (!suffixPattern) return diagnostics\n\n const doc = createDoc(text)\n const matches = doc.match(`/[a-z]+(${suffixPattern})/ of .`)\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const nominalization = occurrence.text.trim().split(/\\s+/)[0]?.toLowerCase()\n if (!nominalization || allowedWords.has(nominalization)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-nominalized-phrases',\n severity: 'warn',\n message: `rewrite \"${occurrence.text}\" with a verb`,\n range,\n help: noNominalizedPhrases.help,\n })\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoPassiveVoiceOptions {\n allowedAuxiliaries?: string[]\n}\n\nconst DEFAULT_ALLOWED_AUXILIARIES = ['is', 'are', 'was', 'were', 'be', 'been', 'being']\n\nexport const noPassiveVoice: Rule<NoPassiveVoiceOptions> = {\n id: 'no-passive-voice',\n description: 'Flag likely passive-voice constructions using POS tagging patterns',\n defaults: { allowedAuxiliaries: DEFAULT_ALLOWED_AUXILIARIES },\n help: 'Passive voice often hides the actor and adds drag. Prefer naming who did the action unless the actor genuinely does not matter.',\n\n check({ text, sourceMap, options }: RuleInput<NoPassiveVoiceOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const auxiliaries = new Set((options.allowedAuxiliaries?.length ? options.allowedAuxiliaries : DEFAULT_ALLOWED_AUXILIARIES).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('(#Copula|#Auxiliary) #PastTense')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n if (words.length < 2) continue\n if (!auxiliaries.has(words[0]!.toLowerCase())) continue\n\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-passive-voice',\n severity: 'warn',\n message: `rewrite passive construction \"${occurrence.text}\" with a named actor`,\n range: { start, end: end + 1 },\n help: noPassiveVoice.help,\n })\n }\n\n return dedupeDiagnostics(diagnostics)\n },\n}\n\nfunction dedupeDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {\n const seen = new Set<string>()\n return diagnostics.filter((diagnostic) => {\n const key = `${diagnostic.range.start}:${diagnostic.range.end}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoStackedAdjectivesOptions {\n allowedPhrases?: string[]\n}\n\nexport const noStackedAdjectives: Rule<NoStackedAdjectivesOptions> = {\n id: 'no-stacked-adjectives',\n description: 'Flag noun phrases with multiple adjectives before the noun',\n defaults: { allowedPhrases: [] },\n help: 'Stacked adjectives make copy feel generic. Keep the one descriptor that earns its place or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoStackedAdjectivesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const allowedPhrases = new Set((options.allowedPhrases ?? []).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('#Adjective #Adjective+ #Noun')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n if (allowedPhrases.has(occurrence.text.toLowerCase())) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-stacked-adjectives',\n severity: 'warn',\n message: `cut stacked adjectives in \"${occurrence.text}\"`,\n range,\n help: noStackedAdjectives.help,\n })\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoWeakModalsOptions {\n modals?: string[]\n verbs?: string[]\n}\n\nconst DEFAULT_MODALS = ['can', 'could', 'may', 'might']\nconst DEFAULT_VERBS = [\n 'boost',\n 'drive',\n 'enable',\n 'help',\n 'improve',\n 'increase',\n 'make',\n 'reduce',\n 'support',\n 'transform',\n 'unlock',\n]\n\nexport const noWeakModals: Rule<NoWeakModalsOptions> = {\n id: 'no-weak-modals',\n description: 'Flag hedged modal claims like \"can help\" and \"might improve\"',\n defaults: { modals: DEFAULT_MODALS, verbs: DEFAULT_VERBS },\n help: 'Hedged modal claims sound tentative. Replace them with the outcome, capability, or proof you can stand behind.',\n\n check({ text, sourceMap, options }: RuleInput<NoWeakModalsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const modals = new Set((options.modals?.length ? options.modals : DEFAULT_MODALS).map(value => value.toLowerCase()))\n const verbs = new Set((options.verbs?.length ? options.verbs : DEFAULT_VERBS).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('#Modal #Adverb? #Verb')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n const modal = words[0]?.toLowerCase()\n const verb = words[words.length - 1]?.toLowerCase()\n if (!modal || !verb || !modals.has(modal) || !verbs.has(verb)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-weak-modals',\n severity: 'warn',\n message: `replace \"${occurrence.text.toLowerCase()}\" with a direct claim`,\n range,\n help: noWeakModals.help,\n })\n }\n\n return diagnostics\n },\n}\n","import type { Rule } from '@faircopy/core'\nimport { noFilterWords } from './no-filter-words.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noPassiveVoice } from './no-passive-voice.js'\nimport { noStackedAdjectives } from './no-stacked-adjectives.js'\nimport { noWeakModals } from './no-weak-modals.js'\n\nexport { noFilterWords } from './no-filter-words.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noPassiveVoice } from './no-passive-voice.js'\nexport { noStackedAdjectives } from './no-stacked-adjectives.js'\nexport { noWeakModals } from './no-weak-modals.js'\nexport type { NoFilterWordsOptions } from './no-filter-words.js'\nexport type { NoNominalizedPhrasesOptions } from './no-nominalized-phrases.js'\nexport type { NoPassiveVoiceOptions } from './no-passive-voice.js'\nexport type { NoStackedAdjectivesOptions } from './no-stacked-adjectives.js'\nexport type { NoWeakModalsOptions } from './no-weak-modals.js'\n\n/** All NLP rules keyed by their rule ID. */\nexport const ruleRegistry: Map<string, Rule> = new Map([\n ['no-filter-words', noFilterWords as Rule],\n ['no-nominalized-phrases', noNominalizedPhrases as Rule],\n ['no-passive-voice', noPassiveVoice as Rule],\n ['no-stacked-adjectives', noStackedAdjectives as Rule],\n ['no-weak-modals', noWeakModals as Rule],\n])\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faircopy/rules-nlp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Optional NLP-powered ruleset for faircopy using compromise",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsup",
|
|
17
17
|
"typecheck": "tsc --noEmit",
|
|
18
|
-
"test": "
|
|
18
|
+
"test": "pnpm run build && node --test test/*.test.mjs",
|
|
19
19
|
"prepublishOnly": "pnpm run build"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@faircopy/core": "1.
|
|
22
|
+
"@faircopy/core": "1.4.0",
|
|
23
23
|
"compromise": "^14.15.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|