opengrammar-server 2.0.615350
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.npm.md +95 -0
- package/bin/opengrammar-server.js +111 -0
- package/dist/server.js +48639 -0
- package/package.json +80 -0
- package/server-node.ts +159 -0
- package/server.ts +15 -0
- package/src/analyzer.ts +542 -0
- package/src/dictionary.ts +1973 -0
- package/src/index.ts +978 -0
- package/src/nlp/nlp-engine.ts +17 -0
- package/src/nlp/tone-analyzer.ts +269 -0
- package/src/rephraser.ts +146 -0
- package/src/rules/categories/academic-writing.ts +182 -0
- package/src/rules/categories/adjectives-adverbs.ts +152 -0
- package/src/rules/categories/articles.ts +160 -0
- package/src/rules/categories/business-writing.ts +250 -0
- package/src/rules/categories/capitalization.ts +79 -0
- package/src/rules/categories/clarity.ts +117 -0
- package/src/rules/categories/common-errors.ts +601 -0
- package/src/rules/categories/confused-words.ts +219 -0
- package/src/rules/categories/conjunctions.ts +176 -0
- package/src/rules/categories/dangling-modifiers.ts +123 -0
- package/src/rules/categories/formality.ts +274 -0
- package/src/rules/categories/formatting-idioms.ts +323 -0
- package/src/rules/categories/gerund-infinitive.ts +274 -0
- package/src/rules/categories/grammar-advanced.ts +294 -0
- package/src/rules/categories/grammar.ts +286 -0
- package/src/rules/categories/inclusive-language.ts +280 -0
- package/src/rules/categories/nouns-pronouns.ts +233 -0
- package/src/rules/categories/prepositions-extended.ts +217 -0
- package/src/rules/categories/prepositions.ts +159 -0
- package/src/rules/categories/punctuation.ts +347 -0
- package/src/rules/categories/quantity-agreement.ts +200 -0
- package/src/rules/categories/readability.ts +293 -0
- package/src/rules/categories/sentence-structure.ts +100 -0
- package/src/rules/categories/spelling-advanced.ts +164 -0
- package/src/rules/categories/spelling.ts +119 -0
- package/src/rules/categories/style-tone.ts +511 -0
- package/src/rules/categories/style.ts +78 -0
- package/src/rules/categories/subject-verb-agreement.ts +201 -0
- package/src/rules/categories/tone-rules.ts +206 -0
- package/src/rules/categories/verb-tense.ts +582 -0
- package/src/rules/context-filter.ts +446 -0
- package/src/rules/index.ts +96 -0
- package/src/rules/ruleset-part1-cj-pu-sp.json +657 -0
- package/src/rules/ruleset-part1-np-ad-aa-pr.json +831 -0
- package/src/rules/ruleset-part1-ss-vt.json +907 -0
- package/src/rules/ruleset-part2-cw-st-nf.json +318 -0
- package/src/rules/ruleset-part3-aw-bw-il-rd.json +161 -0
- package/src/rules/types.ts +79 -0
- package/src/shared-types.ts +152 -0
- package/src/spellchecker.ts +418 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Issue } from '../../shared-types.js';
|
|
2
|
+
import { createRegexRule, type Rule } from '../types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ═══════════════════════════════════════════════════
|
|
6
|
+
* Commonly Confused Words (CW)
|
|
7
|
+
* Contextual detection of homophones & near-matches
|
|
8
|
+
* ═══════════════════════════════════════════════════
|
|
9
|
+
*/
|
|
10
|
+
export const confusedWordsRules: Rule[] = [
|
|
11
|
+
// ═══ affect vs effect ═══
|
|
12
|
+
createRegexRule({
|
|
13
|
+
id: 'CW_affect_noun',
|
|
14
|
+
category: 'grammar',
|
|
15
|
+
pattern:
|
|
16
|
+
/\b(a|an|the|this|that|its|no|any|great|big|huge|major|significant|positive|negative|profound|lasting|adverse|direct|indirect|overall|desired|intended|net|cumulative)\s+affect\b/i,
|
|
17
|
+
suggestion: (m) => `${m[1]} effect`,
|
|
18
|
+
reason: '"Affect" is usually a verb (to influence). The noun form is "effect" (a result).',
|
|
19
|
+
}),
|
|
20
|
+
createRegexRule({
|
|
21
|
+
id: 'CW_effect_verb',
|
|
22
|
+
category: 'grammar',
|
|
23
|
+
pattern:
|
|
24
|
+
/\b(will|would|can|could|may|might|shall|should|did|does|do|didn't|doesn't|won't|cannot)\s+effect\s+(my|your|his|her|our|their|the|a|an|this|that)\b/i,
|
|
25
|
+
suggestion: (m) => `${m[1]} affect ${m[2]}`,
|
|
26
|
+
reason: '"Effect" as a verb means "to bring about". You likely mean "affect" (to influence).',
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
// ═══ accept vs except ═══
|
|
30
|
+
createRegexRule({
|
|
31
|
+
id: 'CW_except_verb',
|
|
32
|
+
category: 'grammar',
|
|
33
|
+
pattern:
|
|
34
|
+
/\b(I|we|they|he|she|you|will|would|can|please)\s+except\s+(your|the|this|that|a|an|my|his|her|our|their)\s+(offer|invitation|apology|terms|conditions|proposal|gift|award|position|responsibility|challenge|nomination|request)\b/i,
|
|
35
|
+
suggestion: (m) => `${m[1]} accept ${m[2]} ${m[3]}`,
|
|
36
|
+
reason: '"Except" means "excluding". You likely mean "accept" (to receive).',
|
|
37
|
+
}),
|
|
38
|
+
createRegexRule({
|
|
39
|
+
id: 'CW_accept_prep',
|
|
40
|
+
category: 'grammar',
|
|
41
|
+
pattern: /\beveryone\s+accept\b/i,
|
|
42
|
+
suggestion: 'everyone except',
|
|
43
|
+
reason: '"Accept" means to receive. You likely mean "except" (excluding).',
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
// ═══ loose vs lose ═══
|
|
47
|
+
createRegexRule({
|
|
48
|
+
id: 'CW_loose_verb',
|
|
49
|
+
category: 'grammar',
|
|
50
|
+
pattern:
|
|
51
|
+
/\b(will|would|can|could|might|may|don't|didn't|doesn't|won't|cannot|going\s+to|afraid\s+to|about\s+to|hate\s+to|want\s+to|don't\s+want\s+to)\s+loose\b/i,
|
|
52
|
+
suggestion: (m) => `${m[1]} lose`,
|
|
53
|
+
reason: '"Loose" means not tight. The verb meaning "to misplace/fail" is "lose".',
|
|
54
|
+
}),
|
|
55
|
+
createRegexRule({
|
|
56
|
+
id: 'CW_loose_money',
|
|
57
|
+
category: 'grammar',
|
|
58
|
+
pattern:
|
|
59
|
+
/\bloose\s+(money|weight|hope|faith|interest|patience|sight|track|control|touch|time|ground|sleep|focus|balance|confidence|consciousness|grip|footing|temper|mind)\b/i,
|
|
60
|
+
suggestion: (m) => `lose ${m[1]}`,
|
|
61
|
+
reason: '"Loose" means not tight. You likely mean "lose" (to misplace/fail to keep).',
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
// ═══ than vs then ═══
|
|
65
|
+
createRegexRule({
|
|
66
|
+
id: 'CW_then_compare',
|
|
67
|
+
category: 'grammar',
|
|
68
|
+
pattern:
|
|
69
|
+
/\b(more|less|better|worse|greater|fewer|higher|lower|bigger|smaller|faster|slower|older|younger|easier|harder|stronger|weaker|longer|shorter|wider|taller|cheaper|richer|smarter|nicer)\s+then\b/i,
|
|
70
|
+
suggestion: (m) => `${m[1]} than`,
|
|
71
|
+
reason: 'Use "than" for comparisons. "Then" refers to time.',
|
|
72
|
+
}),
|
|
73
|
+
createRegexRule({
|
|
74
|
+
id: 'CW_then_rather',
|
|
75
|
+
category: 'grammar',
|
|
76
|
+
pattern: /\brather\s+then\b/i,
|
|
77
|
+
suggestion: 'rather than',
|
|
78
|
+
reason: 'The correct phrase is "rather than", not "rather then".',
|
|
79
|
+
}),
|
|
80
|
+
createRegexRule({
|
|
81
|
+
id: 'CW_then_other',
|
|
82
|
+
category: 'grammar',
|
|
83
|
+
pattern: /\bother\s+then\b/i,
|
|
84
|
+
suggestion: 'other than',
|
|
85
|
+
reason: 'The correct phrase is "other than", not "other then".',
|
|
86
|
+
}),
|
|
87
|
+
|
|
88
|
+
// ═══ whose vs who's ═══
|
|
89
|
+
createRegexRule({
|
|
90
|
+
id: 'CW_whos_poss',
|
|
91
|
+
category: 'grammar',
|
|
92
|
+
pattern:
|
|
93
|
+
/\bwho's\s+(car|house|book|phone|computer|bag|dog|cat|idea|fault|problem|responsibility|job|turn|birthday|name|number|address)\b/i,
|
|
94
|
+
suggestion: (m) => `whose ${m[1]}`,
|
|
95
|
+
reason: '"Who\'s" means "who is" or "who has". For possession, use "whose".',
|
|
96
|
+
}),
|
|
97
|
+
createRegexRule({
|
|
98
|
+
id: 'CW_whose_contraction',
|
|
99
|
+
category: 'grammar',
|
|
100
|
+
pattern:
|
|
101
|
+
/\bwhose\s+(going|coming|doing|making|getting|trying|looking|working|playing|running|been|is|are|was|the\s+one)\b/i,
|
|
102
|
+
suggestion: (m) => `who's ${m[1]}`,
|
|
103
|
+
reason: '"Whose" shows possession. You likely mean "who\'s" (who is/who has).',
|
|
104
|
+
}),
|
|
105
|
+
|
|
106
|
+
// ═══ weather vs whether ═══
|
|
107
|
+
createRegexRule({
|
|
108
|
+
id: 'CW_weather_if',
|
|
109
|
+
category: 'grammar',
|
|
110
|
+
pattern: /\bweather\s+(or\s+not|I|you|he|she|it|we|they|the|this|that|to)\b/i,
|
|
111
|
+
suggestion: (m) => `whether ${m[1]}`,
|
|
112
|
+
reason: '"Weather" refers to climate. For "if", use "whether".',
|
|
113
|
+
}),
|
|
114
|
+
|
|
115
|
+
// ═══ dessert vs desert ═══
|
|
116
|
+
createRegexRule({
|
|
117
|
+
id: 'CW_desert_food',
|
|
118
|
+
category: 'grammar',
|
|
119
|
+
pattern:
|
|
120
|
+
/\b(for|ate|had|ordered|served|eating|delicious|chocolate|ice\s+cream|cake)\s+desert\b/i,
|
|
121
|
+
suggestion: (m) => `${m[1]} dessert`,
|
|
122
|
+
reason:
|
|
123
|
+
'"Desert" is arid land. The sweet food is "dessert" (two s\'s — because you want more!).',
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
// ═══ advise vs advice ═══
|
|
127
|
+
createRegexRule({
|
|
128
|
+
id: 'CW_advice_verb',
|
|
129
|
+
category: 'grammar',
|
|
130
|
+
pattern: /\b(I|we|they|he|she|you|would|will|can|please|let\s+me|should)\s+advice\b/i,
|
|
131
|
+
suggestion: (m) => `${m[1]} advise`,
|
|
132
|
+
reason: '"Advice" is a noun. The verb form is "advise".',
|
|
133
|
+
}),
|
|
134
|
+
createRegexRule({
|
|
135
|
+
id: 'CW_advise_noun',
|
|
136
|
+
category: 'grammar',
|
|
137
|
+
pattern:
|
|
138
|
+
/\b(some|good|bad|my|your|his|her|their|our|excellent|terrible|a\s+piece\s+of|any)\s+advise\b/i,
|
|
139
|
+
suggestion: (m) => `${m[1]} advice`,
|
|
140
|
+
reason: '"Advise" is a verb. The noun form is "advice".',
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
// ═══ complement vs compliment ═══
|
|
144
|
+
createRegexRule({
|
|
145
|
+
id: 'CW_compliment_enhance',
|
|
146
|
+
category: 'grammar',
|
|
147
|
+
pattern: /\b(perfectly|nicely|well)\s+compliments\b/i,
|
|
148
|
+
suggestion: (m) => `${m[1]} complements`,
|
|
149
|
+
reason: '"Compliment" means praise. "Complement" means to enhance/complete.',
|
|
150
|
+
}),
|
|
151
|
+
|
|
152
|
+
// ═══ principal vs principle ═══
|
|
153
|
+
createRegexRule({
|
|
154
|
+
id: 'CW_principle_person',
|
|
155
|
+
category: 'grammar',
|
|
156
|
+
pattern: /\b(school|the)\s+principle\b/i,
|
|
157
|
+
suggestion: (m) => `${m[1]} principal`,
|
|
158
|
+
reason: 'The head of a school is the "principal". A "principle" is a rule or belief.',
|
|
159
|
+
}),
|
|
160
|
+
|
|
161
|
+
// ═══ ensure vs insure ═══
|
|
162
|
+
createRegexRule({
|
|
163
|
+
id: 'CW_insure_certain',
|
|
164
|
+
category: 'grammar',
|
|
165
|
+
pattern: /\binsure\s+that\b/i,
|
|
166
|
+
suggestion: 'ensure that',
|
|
167
|
+
reason: '"Insure" means financial protection. For "make certain", use "ensure".',
|
|
168
|
+
}),
|
|
169
|
+
|
|
170
|
+
// ═══ elicit vs illicit ═══
|
|
171
|
+
createRegexRule({
|
|
172
|
+
id: 'CW_illicit_verb',
|
|
173
|
+
category: 'grammar',
|
|
174
|
+
pattern:
|
|
175
|
+
/\b(to|will|would|can|could|trying\s+to|designed\s+to|intended\s+to|meant\s+to)\s+illicit\b/i,
|
|
176
|
+
suggestion: (m) => `${m[1]} elicit`,
|
|
177
|
+
reason: '"Illicit" means illegal (adjective). The verb "to draw out" is "elicit".',
|
|
178
|
+
}),
|
|
179
|
+
|
|
180
|
+
// ═══ precede vs proceed ═══
|
|
181
|
+
createRegexRule({
|
|
182
|
+
id: 'CW_proceed_before',
|
|
183
|
+
category: 'grammar',
|
|
184
|
+
pattern: /\bproceeds?\s+(the|a|this|each|every|any)\b/i,
|
|
185
|
+
suggestion: (m) => `precedes ${m[1]}`,
|
|
186
|
+
reason: '"Proceed" means to move forward. "Precede" means to come before.',
|
|
187
|
+
}),
|
|
188
|
+
|
|
189
|
+
// ═══ cite vs site ═══
|
|
190
|
+
createRegexRule({
|
|
191
|
+
id: 'CW_site_reference',
|
|
192
|
+
category: 'grammar',
|
|
193
|
+
pattern:
|
|
194
|
+
/\b(please|must|should|need\s+to|remember\s+to|don't\s+forget\s+to)\s+site\s+(the|your|this|a|an|all|every|each)\s+(source|reference|work|paper|article|study|author|book|evidence|example|data|finding|research|statistic|quote|passage)\b/i,
|
|
195
|
+
suggestion: (m) => `${m[1]} cite ${m[2]} ${m[3]}`,
|
|
196
|
+
reason: '"Site" is a location. To reference a source, use "cite".',
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
// ═══ allusion vs illusion ═══
|
|
200
|
+
createRegexRule({
|
|
201
|
+
id: 'CW_illusion_reference',
|
|
202
|
+
category: 'grammar',
|
|
203
|
+
pattern:
|
|
204
|
+
/\b(made|makes?\s+an?|contains?\s+an?|biblical|literary|cultural|historical)\s+illusion\b/i,
|
|
205
|
+
suggestion: (m) => `${m[1]} allusion`,
|
|
206
|
+
reason: '"Illusion" is a false perception. An indirect reference is an "allusion".',
|
|
207
|
+
}),
|
|
208
|
+
|
|
209
|
+
// ═══ literally misuse ═══
|
|
210
|
+
createRegexRule({
|
|
211
|
+
id: 'CW_literally',
|
|
212
|
+
category: 'style',
|
|
213
|
+
pattern:
|
|
214
|
+
/\bliterally\s+(dying|exploding|on\s+fire|killing\s+me|the\s+best|the\s+worst|died|killed|melting|starving|freezing|screaming|crying|shaking|bursting|insane|crazy|dead)\b/i,
|
|
215
|
+
suggestion: (m) => `figuratively ${m[1]}`,
|
|
216
|
+
reason:
|
|
217
|
+
'"Literally" means "actually/in reality". If you\'re using hyperbole, the word you want is "figuratively" — or simply drop "literally".',
|
|
218
|
+
}),
|
|
219
|
+
];
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Issue } from '../../shared-types.js';
|
|
2
|
+
import { createRegexRule, type Rule } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const conjunctionRules: Rule[] = [
|
|
5
|
+
// ═══ Redundant Conjunctions (CJ_RED) ═══
|
|
6
|
+
createRegexRule({
|
|
7
|
+
id: 'CJ_RED_although_but',
|
|
8
|
+
category: 'grammar',
|
|
9
|
+
pattern: /\b(although|though|even\s+though)\b([^.]*?)\bbut\b/i,
|
|
10
|
+
suggestion: (m) => `${m[1]}${m[2]}`,
|
|
11
|
+
reason: '"Although" and "but" are redundant together. Remove one of them.',
|
|
12
|
+
}),
|
|
13
|
+
createRegexRule({
|
|
14
|
+
id: 'CJ_RED_because_so',
|
|
15
|
+
category: 'grammar',
|
|
16
|
+
pattern: /\bbecause\b([^.]*?)\bso\b/i,
|
|
17
|
+
suggestion: (m) => `because${m[1]}`,
|
|
18
|
+
reason: '"Because" and "so" are redundant together. Remove one of them.',
|
|
19
|
+
}),
|
|
20
|
+
createRegexRule({
|
|
21
|
+
id: 'CJ_RED_despite_but',
|
|
22
|
+
category: 'grammar',
|
|
23
|
+
pattern: /\b(despite|in\s+spite\s+of)\b([^.]*?)\bbut\b/i,
|
|
24
|
+
suggestion: (m) => `${m[1]}${m[2]}`,
|
|
25
|
+
reason: '"Despite" and "but" are redundant together. Remove "but".',
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
// ═══ Wordy Conjunction Replacements (CJ_WRD) ═══
|
|
29
|
+
createRegexRule({
|
|
30
|
+
id: 'CJ_WRD_due_to',
|
|
31
|
+
category: 'clarity',
|
|
32
|
+
pattern: /\bdue\s+to\s+the\s+fact\s+that\b/i,
|
|
33
|
+
suggestion: 'because',
|
|
34
|
+
reason: '"Due to the fact that" is wordy. Use "because" instead.',
|
|
35
|
+
}),
|
|
36
|
+
createRegexRule({
|
|
37
|
+
id: 'CJ_WRD_in_spite',
|
|
38
|
+
category: 'clarity',
|
|
39
|
+
pattern: /\bin\s+spite\s+of\s+the\s+fact\s+that\b/i,
|
|
40
|
+
suggestion: 'although',
|
|
41
|
+
reason: '"In spite of the fact that" is wordy. Use "although" instead.',
|
|
42
|
+
}),
|
|
43
|
+
createRegexRule({
|
|
44
|
+
id: 'CJ_WRD_regardless',
|
|
45
|
+
category: 'clarity',
|
|
46
|
+
pattern: /\bregardless\s+of\s+the\s+fact\s+that\b/i,
|
|
47
|
+
suggestion: 'although',
|
|
48
|
+
reason: '"Regardless of the fact that" is wordy. Use "although" instead.',
|
|
49
|
+
}),
|
|
50
|
+
createRegexRule({
|
|
51
|
+
id: 'CJ_WRD_for_purpose',
|
|
52
|
+
category: 'clarity',
|
|
53
|
+
pattern: /\bfor\s+the\s+purpose\s+of\b/i,
|
|
54
|
+
suggestion: 'to',
|
|
55
|
+
reason: '"For the purpose of" is wordy. Use "to" instead.',
|
|
56
|
+
}),
|
|
57
|
+
createRegexRule({
|
|
58
|
+
id: 'CJ_WRD_in_order',
|
|
59
|
+
category: 'clarity',
|
|
60
|
+
pattern: /\bin\s+order\s+to\b/i,
|
|
61
|
+
suggestion: 'to',
|
|
62
|
+
reason: '"In order to" is wordy. Use "to" instead.',
|
|
63
|
+
}),
|
|
64
|
+
createRegexRule({
|
|
65
|
+
id: 'CJ_WRD_in_event',
|
|
66
|
+
category: 'clarity',
|
|
67
|
+
pattern: /\bin\s+the\s+event\s+that\b/i,
|
|
68
|
+
suggestion: 'if',
|
|
69
|
+
reason: '"In the event that" is wordy. Use "if" instead.',
|
|
70
|
+
}),
|
|
71
|
+
createRegexRule({
|
|
72
|
+
id: 'CJ_WRD_in_light',
|
|
73
|
+
category: 'clarity',
|
|
74
|
+
pattern: /\bin\s+light\s+of\s+the\s+fact\s+that\b/i,
|
|
75
|
+
suggestion: 'because',
|
|
76
|
+
reason: '"In light of the fact that" is wordy. Use "because" instead.',
|
|
77
|
+
}),
|
|
78
|
+
createRegexRule({
|
|
79
|
+
id: 'CJ_WRD_exception',
|
|
80
|
+
category: 'clarity',
|
|
81
|
+
pattern: /\bwith\s+the\s+exception\s+of\b/i,
|
|
82
|
+
suggestion: 'except',
|
|
83
|
+
reason: '"With the exception of" is wordy. Use "except" instead.',
|
|
84
|
+
}),
|
|
85
|
+
createRegexRule({
|
|
86
|
+
id: 'CJ_WRD_at_this_point',
|
|
87
|
+
category: 'clarity',
|
|
88
|
+
pattern: /\bat\s+this\s+point\s+in\s+time\b/i,
|
|
89
|
+
suggestion: 'now',
|
|
90
|
+
reason: '"At this point in time" is wordy. Use "now" or "currently" instead.',
|
|
91
|
+
}),
|
|
92
|
+
createRegexRule({
|
|
93
|
+
id: 'CJ_WRD_in_the_near',
|
|
94
|
+
category: 'clarity',
|
|
95
|
+
pattern: /\bin\s+the\s+near\s+future\b/i,
|
|
96
|
+
suggestion: 'soon',
|
|
97
|
+
reason: '"In the near future" is wordy. Use "soon" instead.',
|
|
98
|
+
}),
|
|
99
|
+
createRegexRule({
|
|
100
|
+
id: 'CJ_WRD_at_present',
|
|
101
|
+
category: 'clarity',
|
|
102
|
+
pattern: /\bat\s+the\s+present\s+time\b/i,
|
|
103
|
+
suggestion: 'now',
|
|
104
|
+
reason: '"At the present time" is wordy. Use "now" instead.',
|
|
105
|
+
}),
|
|
106
|
+
createRegexRule({
|
|
107
|
+
id: 'CJ_WRD_on_account',
|
|
108
|
+
category: 'clarity',
|
|
109
|
+
pattern: /\bon\s+account\s+of\s+the\s+fact\s+that\b/i,
|
|
110
|
+
suggestion: 'because',
|
|
111
|
+
reason: '"On account of the fact that" is wordy. Use "because" instead.',
|
|
112
|
+
}),
|
|
113
|
+
createRegexRule({
|
|
114
|
+
id: 'CJ_WRD_by_means',
|
|
115
|
+
category: 'clarity',
|
|
116
|
+
pattern: /\bby\s+means\s+of\b/i,
|
|
117
|
+
suggestion: 'by',
|
|
118
|
+
reason: '"By means of" is wordy. Use "by" or "using" instead.',
|
|
119
|
+
}),
|
|
120
|
+
createRegexRule({
|
|
121
|
+
id: 'CJ_WRD_in_regard',
|
|
122
|
+
category: 'clarity',
|
|
123
|
+
pattern: /\bin\s+regard\s+to\b/i,
|
|
124
|
+
suggestion: 'regarding',
|
|
125
|
+
reason: '"In regard to" is wordy. Use "regarding" or "about" instead.',
|
|
126
|
+
}),
|
|
127
|
+
createRegexRule({
|
|
128
|
+
id: 'CJ_WRD_with_respect',
|
|
129
|
+
category: 'clarity',
|
|
130
|
+
pattern: /\bwith\s+respect\s+to\b/i,
|
|
131
|
+
suggestion: 'regarding',
|
|
132
|
+
reason: '"With respect to" is wordy. Use "regarding" or "about" instead.',
|
|
133
|
+
}),
|
|
134
|
+
createRegexRule({
|
|
135
|
+
id: 'CJ_WRD_has_ability',
|
|
136
|
+
category: 'clarity',
|
|
137
|
+
pattern: /\bhas\s+the\s+ability\s+to\b/i,
|
|
138
|
+
suggestion: 'can',
|
|
139
|
+
reason: '"Has the ability to" is wordy. Use "can" instead.',
|
|
140
|
+
}),
|
|
141
|
+
createRegexRule({
|
|
142
|
+
id: 'CJ_WRD_is_able',
|
|
143
|
+
category: 'clarity',
|
|
144
|
+
pattern: /\bis\s+able\s+to\b/i,
|
|
145
|
+
suggestion: 'can',
|
|
146
|
+
reason: '"Is able to" is wordy. Use "can" instead.',
|
|
147
|
+
}),
|
|
148
|
+
createRegexRule({
|
|
149
|
+
id: 'CJ_WRD_make_use',
|
|
150
|
+
category: 'clarity',
|
|
151
|
+
pattern: /\bmake\s+use\s+of\b/i,
|
|
152
|
+
suggestion: 'use',
|
|
153
|
+
reason: '"Make use of" is wordy. Use "use" instead.',
|
|
154
|
+
}),
|
|
155
|
+
createRegexRule({
|
|
156
|
+
id: 'CJ_WRD_take_into',
|
|
157
|
+
category: 'clarity',
|
|
158
|
+
pattern: /\btake\s+into\s+consideration\b/i,
|
|
159
|
+
suggestion: 'consider',
|
|
160
|
+
reason: '"Take into consideration" is wordy. Use "consider" instead.',
|
|
161
|
+
}),
|
|
162
|
+
createRegexRule({
|
|
163
|
+
id: 'CJ_WRD_a_large_number',
|
|
164
|
+
category: 'clarity',
|
|
165
|
+
pattern: /\ba\s+large\s+number\s+of\b/i,
|
|
166
|
+
suggestion: 'many',
|
|
167
|
+
reason: '"A large number of" is wordy. Use "many" instead.',
|
|
168
|
+
}),
|
|
169
|
+
createRegexRule({
|
|
170
|
+
id: 'CJ_WRD_sufficient_amount',
|
|
171
|
+
category: 'clarity',
|
|
172
|
+
pattern: /\ba\s+sufficient\s+amount\s+of\b/i,
|
|
173
|
+
suggestion: 'enough',
|
|
174
|
+
reason: '"A sufficient amount of" is wordy. Use "enough" instead.',
|
|
175
|
+
}),
|
|
176
|
+
];
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createRegexRule, type Rule } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ════════════════════════════════════════════════════════════
|
|
5
|
+
* Dangling Modifier Rules — DM Module
|
|
6
|
+
*
|
|
7
|
+
* A dangling modifier is a word or phrase that describes
|
|
8
|
+
* something that is implied but not explicitly stated.
|
|
9
|
+
* The classic test: "who is doing the [verb]ing?"
|
|
10
|
+
* If the answer doesn't match the subject of the main clause → dangling.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* "Walking down the street, the rain started." (rain can't walk)
|
|
14
|
+
* "Having finished the essay, the TV was turned on." (TV didn't finish)
|
|
15
|
+
* "To improve your writing, grammar must be studied." (grammar isn't improving)
|
|
16
|
+
* ════════════════════════════════════════════════════════════
|
|
17
|
+
*/
|
|
18
|
+
export const danglingModifierRules: Rule[] = [
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════
|
|
21
|
+
// 1. PRESENT PARTICIPLE PHRASES (Walking/Running/etc. + ,)
|
|
22
|
+
// Pattern: gerund phrase → comma → inanimate/wrong subject
|
|
23
|
+
// ═══════════════════════════════════════════════════════
|
|
24
|
+
createRegexRule({
|
|
25
|
+
id: 'DM_PRES_PART_inanimate',
|
|
26
|
+
category: 'grammar',
|
|
27
|
+
pattern: /^(Walking|Running|Driving|Looking|Talking|Working|Sitting|Standing|Playing|Eating|Drinking|Reading|Writing|Watching|Thinking|Speaking|Listening|Waiting|Sleeping|Swimming|Flying|Climbing|Jumping|Dancing|Singing|Laughing|Crying|Shopping|Cooking|Cleaning|Fixing|Building|Breaking|Opening|Closing|Carrying|Pushing|Pulling|Lifting|Drawing|Painting|Teaching|Studying|Planning|Deciding|Moving|Turning)\s+[^,]+,\s+(the\s+(?!speaker|author|writer|narrator|user|reader|person|man|woman|student|teacher|team|police|doctor|nurse|officer|employee|manager|owner|player|customer|client|victim)\w+|a\s+\w+|it\s+|there\s+|this\s+|that\s+)\b/i,
|
|
28
|
+
suggestion: (m) => {
|
|
29
|
+
const verb = m[1].toLowerCase();
|
|
30
|
+
const gerund = verb.endsWith('ing') ? verb : verb + 'ing';
|
|
31
|
+
return `[Subject who is ${gerund}], ${m[2].trim()}...`;
|
|
32
|
+
},
|
|
33
|
+
reason: 'Dangling modifier: clarify who is performing the action at the start of the sentence. The participial phrase must describe the main clause\'s subject.',
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
// ═══════════════════════════════════════════════════════
|
|
37
|
+
// 2. PERFECT PARTICIPIAL PHRASES (Having + past participle)
|
|
38
|
+
// ═══════════════════════════════════════════════════════
|
|
39
|
+
createRegexRule({
|
|
40
|
+
id: 'DM_PERF_PART_having',
|
|
41
|
+
category: 'grammar',
|
|
42
|
+
pattern: /^Having\s+(?:just\s+|recently\s+|finally\s+|already\s+|nearly\s+|almost\s+)?(finished|completed|done|eaten|drunk|written|read|watched|seen|heard|found|met|left|arrived|returned|decided|chosen|learned|studied|prepared|reviewed|checked|fixed|broken|opened|closed|taken|given|sent|received|made|built|drawn|painted|bought|sold|lost|won|started|ended|begun|stopped|tried|used|worked|gone|come|got|become|felt|thought|said|told|called|visited|helped|supported|managed|organized|set|put|run)\s+[^,]+,\s+(the\s+(?!author|speaker|writer|subject|team|group|reader|narrator)\w+|a\s+\w+|it\s+|there\s+|this\s+|that\s+)/i,
|
|
43
|
+
suggestion: (m) => `Having ${m[1]} [what], [the person who did it] ...`,
|
|
44
|
+
reason: 'Dangling modifier: "Having [done something]..." must describe the subject of the main clause. Clarify who performed this action.',
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
// ═══════════════════════════════════════════════════════
|
|
48
|
+
// 3. INFINITIVE PHRASES (To + verb + ..., passive main clause)
|
|
49
|
+
// "To improve writing, grammar must be studied." ← dangling
|
|
50
|
+
// ═══════════════════════════════════════════════════════
|
|
51
|
+
createRegexRule({
|
|
52
|
+
id: 'DM_INF_passive',
|
|
53
|
+
category: 'grammar',
|
|
54
|
+
pattern: /^To\s+(\w+)\s+(?:your\s+|the\s+|a\s+|an\s+|our\s+|their\s+|its\s+|one'?s\s+)?\w+[^,]*,\s+\w+\s+(must\s+be|should\s+be|needs?\s+to\s+be|has?\s+to\s+be|will\s+be|can\s+be|could\s+be|would\s+be|is\s+being|was\s+being|are\s+being)\s+/i,
|
|
55
|
+
suggestion: (m) => `To ${m[1]} ..., [you/one] should ... (avoid passive voice after an infinitive phrase)`,
|
|
56
|
+
reason: 'Dangling infinitive: "To [verb]..." should be followed by the person performing the action, not a passive clause.',
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
// ═══════════════════════════════════════════════════════
|
|
60
|
+
// 4. "BEING" PHRASES
|
|
61
|
+
// "Being a student, the exam was stressful." ← dangling
|
|
62
|
+
// ═══════════════════════════════════════════════════════
|
|
63
|
+
createRegexRule({
|
|
64
|
+
id: 'DM_BEING_phrase',
|
|
65
|
+
category: 'grammar',
|
|
66
|
+
pattern: /^Being\s+(?:a\s+|an\s+|the\s+)?(?:young\s+|old\s+|good\s+|new\s+|senior\s+|junior\s+)?\w+[^,]*,\s+(the\s+(?!author|speaker|writer|person|team)\w+|a\s+\w+|it\s+|this\s+|that\s+)/i,
|
|
67
|
+
suggestion: (m) => `Being [description], [the person] ... (not "${m[1].trim()}")`,
|
|
68
|
+
reason: 'Dangling modifier: "Being ..." should describe the subject of the main clause, not a different object.',
|
|
69
|
+
}),
|
|
70
|
+
|
|
71
|
+
// ═══════════════════════════════════════════════════════
|
|
72
|
+
// 5. "AFTER/BEFORE/WHILE + GERUND" PHRASES (passive main clause)
|
|
73
|
+
// ═══════════════════════════════════════════════════════
|
|
74
|
+
createRegexRule({
|
|
75
|
+
id: 'DM_PREP_GERUND_passive',
|
|
76
|
+
category: 'grammar',
|
|
77
|
+
pattern: /^(After|Before|While|Upon|When)\s+(\w+ing)\s+[^,]+,\s+(?:the\s+|a\s+|an\s+)\w+\s+(was|were|is|are|will\s+be|has\s+been|have\s+been|had\s+been)\s+(?:given|awarded|presented|offered|provided|sent|told|shown|handed|issued|assigned|denied|refused|granted)\b/i,
|
|
78
|
+
suggestion: (m) => `${m[1]} ${m[2]} ..., [the person] received / was given...`,
|
|
79
|
+
reason: 'Dangling modifier: the participial phrase implies a subject that differs from the passive main clause\'s subject.',
|
|
80
|
+
}),
|
|
81
|
+
|
|
82
|
+
// ═══════════════════════════════════════════════════════
|
|
83
|
+
// 6. "WHEN/WHILE" OMITTED SUBJECT
|
|
84
|
+
// "When cooking, the smoke alarm went off." ← dangling
|
|
85
|
+
// ═══════════════════════════════════════════════════════
|
|
86
|
+
createRegexRule({
|
|
87
|
+
id: 'DM_WHEN_omitted',
|
|
88
|
+
category: 'grammar',
|
|
89
|
+
pattern: /^(When|While|Whenever)\s+(cooking|baking|driving|sleeping|running|swimming|exercising|showering|bathing|reading|studying|working|traveling|hiking|climbing|skiing|playing|drinking|eating)\s*,\s+(the\s+(?!person|speaker|author|user|student|teacher)\w+|a\s+\w+|it\s+|no\s+one|nothing|everyone|everything)\s+(went|started|began|happened|occurred|was|appeared|seemed|became|felt)\b/i,
|
|
90
|
+
suggestion: (m) => `${m[1]} [someone was] ${m[2]}ing, ${m[3]} ${m[4]}...`,
|
|
91
|
+
reason: 'Dangling modifier: add an explicit subject ("When I was cooking..." not "When cooking, the alarm...").',
|
|
92
|
+
}),
|
|
93
|
+
|
|
94
|
+
// ═══════════════════════════════════════════════════════
|
|
95
|
+
// 7. AS A + NOUN PHRASES
|
|
96
|
+
// "As a student, the book was assigned to us." ← dangling
|
|
97
|
+
// ═══════════════════════════════════════════════════════
|
|
98
|
+
createRegexRule({
|
|
99
|
+
id: 'DM_AS_A_phrase',
|
|
100
|
+
category: 'grammar',
|
|
101
|
+
pattern: /^As\s+(?:a|an|the)\s+\w+[^,]*,\s+(the\s+(?!author|speaker|writer|narrator|person|teacher|student|team|group|doctor|manager|officer)\w+|a\s+\w+|it\s+|this\s+|that\s+)\s+(was|were|is|are|has\s+been|had\s+been|will\s+be|needs?\s+to)\b/i,
|
|
102
|
+
suggestion: (m) => `As [the role], [I/we/one] ... (not "${m[1].trim()} ${m[2]}")`,
|
|
103
|
+
reason: 'Dangling modifier: "As a [role]..." must describe the subject of the main clause — the subject should be the person in that role.',
|
|
104
|
+
}),
|
|
105
|
+
|
|
106
|
+
// ═══════════════════════════════════════════════════════
|
|
107
|
+
// 8. "ONLY" MISPLACEMENT
|
|
108
|
+
// ═══════════════════════════════════════════════════════
|
|
109
|
+
createRegexRule({
|
|
110
|
+
id: 'DM_ONLY_misplaced_time',
|
|
111
|
+
category: 'grammar',
|
|
112
|
+
pattern: /\b(I|he|she|they|we|you)\s+only\s+(eat|ate|drink|drank|work|worked|go|went|use|used|spend|spent|visit|visited|call|called|meet|met|write|wrote|read|run|play|played|watch|watched)\s+(\w+)\s+on\b/i,
|
|
113
|
+
suggestion: (m) => `${m[1]} ${m[2]} ${m[3]} only on`,
|
|
114
|
+
reason: 'Misplaced "only": move it directly before the word it modifies. E.g., "I eat vegetables only on Mondays."',
|
|
115
|
+
}),
|
|
116
|
+
createRegexRule({
|
|
117
|
+
id: 'DM_ONLY_misplaced_verb',
|
|
118
|
+
category: 'grammar',
|
|
119
|
+
pattern: /\b(I|he|she|they|we|you)\s+only\s+(need|needed|want|wanted|have|had|like|liked|love|loved|hate|hated|prefer|preferred|do|did|can|could|will|would|should|must|may|might)\s+(?:to\s+)?(\w+)\s+(because|when|if|since|after|although)\b/i,
|
|
120
|
+
suggestion: (m) => `${m[1]} ${m[2]} ${m[3]} only ${m[4]} — or move "only" before what it modifies`,
|
|
121
|
+
reason: 'Misplaced "only": position it right before the word it logically modifies to avoid ambiguity.',
|
|
122
|
+
}),
|
|
123
|
+
];
|