opengrammar-server 2.0.644277

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.
Files changed (53) hide show
  1. package/README.npm.md +95 -0
  2. package/bin/opengrammar-server.js +111 -0
  3. package/dist/server.js +48639 -0
  4. package/package.json +80 -0
  5. package/server-node.ts +159 -0
  6. package/server.ts +15 -0
  7. package/src/analyzer.ts +542 -0
  8. package/src/dictionary.ts +1973 -0
  9. package/src/index.ts +978 -0
  10. package/src/nlp/nlp-engine.ts +17 -0
  11. package/src/nlp/tone-analyzer.ts +269 -0
  12. package/src/rephraser.ts +146 -0
  13. package/src/rules/categories/academic-writing.ts +182 -0
  14. package/src/rules/categories/adjectives-adverbs.ts +152 -0
  15. package/src/rules/categories/articles.ts +160 -0
  16. package/src/rules/categories/business-writing.ts +250 -0
  17. package/src/rules/categories/capitalization.ts +79 -0
  18. package/src/rules/categories/clarity.ts +117 -0
  19. package/src/rules/categories/common-errors.ts +601 -0
  20. package/src/rules/categories/confused-words.ts +219 -0
  21. package/src/rules/categories/conjunctions.ts +176 -0
  22. package/src/rules/categories/dangling-modifiers.ts +123 -0
  23. package/src/rules/categories/formality.ts +274 -0
  24. package/src/rules/categories/formatting-idioms.ts +323 -0
  25. package/src/rules/categories/gerund-infinitive.ts +274 -0
  26. package/src/rules/categories/grammar-advanced.ts +294 -0
  27. package/src/rules/categories/grammar.ts +286 -0
  28. package/src/rules/categories/inclusive-language.ts +280 -0
  29. package/src/rules/categories/nouns-pronouns.ts +233 -0
  30. package/src/rules/categories/prepositions-extended.ts +217 -0
  31. package/src/rules/categories/prepositions.ts +159 -0
  32. package/src/rules/categories/punctuation.ts +347 -0
  33. package/src/rules/categories/quantity-agreement.ts +200 -0
  34. package/src/rules/categories/readability.ts +293 -0
  35. package/src/rules/categories/sentence-structure.ts +100 -0
  36. package/src/rules/categories/spelling-advanced.ts +164 -0
  37. package/src/rules/categories/spelling.ts +119 -0
  38. package/src/rules/categories/style-tone.ts +511 -0
  39. package/src/rules/categories/style.ts +78 -0
  40. package/src/rules/categories/subject-verb-agreement.ts +201 -0
  41. package/src/rules/categories/tone-rules.ts +206 -0
  42. package/src/rules/categories/verb-tense.ts +582 -0
  43. package/src/rules/context-filter.ts +446 -0
  44. package/src/rules/index.ts +96 -0
  45. package/src/rules/ruleset-part1-cj-pu-sp.json +657 -0
  46. package/src/rules/ruleset-part1-np-ad-aa-pr.json +831 -0
  47. package/src/rules/ruleset-part1-ss-vt.json +907 -0
  48. package/src/rules/ruleset-part2-cw-st-nf.json +318 -0
  49. package/src/rules/ruleset-part3-aw-bw-il-rd.json +161 -0
  50. package/src/rules/types.ts +79 -0
  51. package/src/shared-types.ts +152 -0
  52. package/src/spellchecker.ts +418 -0
  53. package/tsconfig.json +25 -0
@@ -0,0 +1,233 @@
1
+ import type { Issue } from '../../shared-types.js';
2
+ import { createRegexRule, type Rule } from '../types.js';
3
+
4
+ export const nounsPronouns: Rule[] = [
5
+ // ═══ Reflexive Pronoun Misuse (NP_REF) ═══
6
+ createRegexRule({
7
+ id: 'NP_REF_contact_myself',
8
+ category: 'grammar',
9
+ pattern: /\b(contact|call|email|reach|send|give|tell|ask|invite|notify|inform)\s+myself\b/i,
10
+ suggestion: (m) => `${m[1]} me`,
11
+ reason:
12
+ 'Don\'t use "myself" as a substitute for "me". Use "myself" only when you are both subject and object.',
13
+ }),
14
+ createRegexRule({
15
+ id: 'NP_REF_myself_and',
16
+ category: 'grammar',
17
+ pattern: /\bmyself\s+and\s+(\w+)\b/i,
18
+ suggestion: (m) => `${m[1]} and I`,
19
+ reason: '"Myself and..." is incorrect. Use "...and I" as a subject.',
20
+ }),
21
+ createRegexRule({
22
+ id: 'NP_REF_yourself_sub',
23
+ category: 'grammar',
24
+ pattern: /\b(contact|call|email|reach|send|give|tell|ask|invite|notify|inform)\s+yourself\b/i,
25
+ suggestion: (m) => `${m[1]} you`,
26
+ reason: 'Don\'t use "yourself" as a substitute for "you".',
27
+ }),
28
+
29
+ // ═══ Noun Countability Errors (NP_CNT) ═══
30
+ ...[
31
+ 'information',
32
+ 'advice',
33
+ 'furniture',
34
+ 'equipment',
35
+ 'luggage',
36
+ 'baggage',
37
+ 'news',
38
+ 'progress',
39
+ 'research',
40
+ 'homework',
41
+ 'evidence',
42
+ 'knowledge',
43
+ 'traffic',
44
+ 'weather',
45
+ 'money',
46
+ 'music',
47
+ 'art',
48
+ 'luck',
49
+ 'fun',
50
+ 'happiness',
51
+ 'sadness',
52
+ 'anger',
53
+ 'software',
54
+ 'hardware',
55
+ 'feedback',
56
+ 'clothing',
57
+ 'scenery',
58
+ 'poetry',
59
+ 'machinery',
60
+ 'vocabulary',
61
+ ].map((noun) =>
62
+ createRegexRule({
63
+ id: `NP_CNT_a_${noun}`,
64
+ category: 'grammar',
65
+ pattern: new RegExp(`\\b(a|an)\\s+${noun}\\b`, 'i'),
66
+ suggestion: `some ${noun}`,
67
+ reason: `"${noun.charAt(0).toUpperCase() + noun.slice(1)}" is uncountable. Do not use "a/an" with it. Use "some" or a quantifier instead.`,
68
+ }),
69
+ ),
70
+
71
+ // Uncountable plurals
72
+ ...[
73
+ 'informations',
74
+ 'advices',
75
+ 'furnitures',
76
+ 'equipments',
77
+ 'luggages',
78
+ 'knowledges',
79
+ 'evidences',
80
+ 'homeworks',
81
+ 'researches',
82
+ 'feedbacks',
83
+ 'hardwares',
84
+ 'softwares',
85
+ 'weathers',
86
+ 'moneys',
87
+ 'sceneries',
88
+ 'poetries',
89
+ 'vocabularies',
90
+ 'machineries',
91
+ 'clothings',
92
+ 'progresses',
93
+ 'traffics',
94
+ ].map((plural) =>
95
+ createRegexRule({
96
+ id: `NP_CNT_plural_${plural}`,
97
+ category: 'grammar',
98
+ pattern: new RegExp(`\\b${plural}\\b`, 'i'),
99
+ suggestion: plural.replace(/s$/, '').replace(/ies$/, 'y'),
100
+ reason: `"${plural.replace(/s$/, '').replace(/ies$/, 'y').charAt(0).toUpperCase() + plural.replace(/s$/, '').replace(/ies$/, 'y').slice(1)}" is uncountable and should not be pluralized.`,
101
+ }),
102
+ ),
103
+
104
+ // ═══ Irregular Plurals (NP_PLU) ═══
105
+ createRegexRule({
106
+ id: 'NP_PLU_childs',
107
+ category: 'grammar',
108
+ pattern: /\bchilds\b/i,
109
+ suggestion: 'children',
110
+ reason: 'The plural of "child" is "children", not "childs".',
111
+ }),
112
+ createRegexRule({
113
+ id: 'NP_PLU_mans',
114
+ category: 'grammar',
115
+ pattern: /\bmans\b/i,
116
+ suggestion: 'men',
117
+ reason: 'The plural of "man" is "men", not "mans".',
118
+ }),
119
+ createRegexRule({
120
+ id: 'NP_PLU_womans',
121
+ category: 'grammar',
122
+ pattern: /\bwomans\b/i,
123
+ suggestion: 'women',
124
+ reason: 'The plural of "woman" is "women", not "womans".',
125
+ }),
126
+ createRegexRule({
127
+ id: 'NP_PLU_tooths',
128
+ category: 'grammar',
129
+ pattern: /\btooths\b/i,
130
+ suggestion: 'teeth',
131
+ reason: 'The plural of "tooth" is "teeth", not "tooths".',
132
+ }),
133
+ createRegexRule({
134
+ id: 'NP_PLU_foots',
135
+ category: 'grammar',
136
+ pattern: /\bfoots\b/i,
137
+ suggestion: 'feet',
138
+ reason: 'The plural of "foot" is "feet", not "foots".',
139
+ }),
140
+ createRegexRule({
141
+ id: 'NP_PLU_mouses',
142
+ category: 'grammar',
143
+ pattern: /\bmouses\b/i,
144
+ suggestion: 'mice',
145
+ reason: 'The plural of "mouse" is "mice", not "mouses".',
146
+ }),
147
+ createRegexRule({
148
+ id: 'NP_PLU_gooses',
149
+ category: 'grammar',
150
+ pattern: /\bgooses\b/i,
151
+ suggestion: 'geese',
152
+ reason: 'The plural of "goose" is "geese", not "gooses".',
153
+ }),
154
+ createRegexRule({
155
+ id: 'NP_PLU_oxes',
156
+ category: 'grammar',
157
+ pattern: /\boxes\b/i,
158
+ suggestion: 'oxen',
159
+ reason: 'The plural of "ox" is "oxen", not "oxes".',
160
+ }),
161
+ createRegexRule({
162
+ id: 'NP_PLU_criterions',
163
+ category: 'grammar',
164
+ pattern: /\bcriterions\b/i,
165
+ suggestion: 'criteria',
166
+ reason: 'The plural of "criterion" is "criteria", not "criterions".',
167
+ }),
168
+ createRegexRule({
169
+ id: 'NP_PLU_phenomenons',
170
+ category: 'grammar',
171
+ pattern: /\bphenomenons\b/i,
172
+ suggestion: 'phenomena',
173
+ reason: 'The plural of "phenomenon" is "phenomena", not "phenomenons".',
174
+ }),
175
+
176
+ // ═══ Determiner Errors (AD from Part 1) ═══
177
+ // Much vs Many
178
+ createRegexRule({
179
+ id: 'AD_MCH_much_people',
180
+ category: 'grammar',
181
+ pattern:
182
+ /\bhow\s+much\s+(people|books|items|things|students|cars|houses|problems|questions|answers|options|choices|mistakes|errors|changes|members|employees|users|tasks|ideas)\b/i,
183
+ suggestion: (m) => `how many ${m[1]}`,
184
+ reason: 'Use "many" with countable plural nouns, not "much".',
185
+ }),
186
+ createRegexRule({
187
+ id: 'AD_MCH_many_info',
188
+ category: 'grammar',
189
+ pattern:
190
+ /\bmany\s+(information|advice|furniture|equipment|luggage|news|progress|research|work|homework|evidence|knowledge|traffic|money|music|time|water|rice|bread|sugar|milk|coffee|tea|air|space|electricity)\b/i,
191
+ suggestion: (m) => `much ${m[1]}`,
192
+ reason: 'Use "much" with uncountable nouns, not "many".',
193
+ }),
194
+
195
+ // Amount vs Number
196
+ createRegexRule({
197
+ id: 'AD_AMT_amount_people',
198
+ category: 'grammar',
199
+ pattern:
200
+ /\b(amount|amounts)\s+of\s+(people|items|things|students|books|cars|errors|problems|questions|answers|options|members|employees|customers|users|tasks|words|pages)\b/i,
201
+ suggestion: (m) => `number of ${m[2]}`,
202
+ reason: 'Use "number of" with countable nouns, not "amount of".',
203
+ }),
204
+
205
+ // Enough placement
206
+ createRegexRule({
207
+ id: 'AD_ENO_before_adj',
208
+ category: 'grammar',
209
+ pattern:
210
+ /\benough\s+(big|tall|good|strong|fast|old|young|smart|rich|warm|cold|hot|cool|large|small|high|low|wide|deep|long|short|loud|quiet|hard|soft|bright|dark|clean|clear|safe|brave|kind|nice|sweet|easy|difficult|important|interesting|cheap|expensive)\b/i,
211
+ suggestion: (m) => `${m[1]} enough`,
212
+ reason: '"Enough" goes AFTER adjectives, not before. Place "enough" after the adjective.',
213
+ }),
214
+
215
+ // The + sport
216
+ createRegexRule({
217
+ id: 'AD_THE_sport',
218
+ category: 'grammar',
219
+ pattern:
220
+ /\b(play|plays|played|playing)\s+the\s+(tennis|football|soccer|basketball|baseball|cricket|golf|volleyball|badminton|hockey|rugby|chess)\b/i,
221
+ suggestion: (m) => `${m[1]} ${m[2]}`,
222
+ reason: 'Do not use "the" before sport names.',
223
+ }),
224
+ // The + meal
225
+ createRegexRule({
226
+ id: 'AD_THE_meal',
227
+ category: 'grammar',
228
+ pattern:
229
+ /\b(had|have|has|having|ate|eat|eats|eating)\s+the\s+(breakfast|lunch|dinner|supper|brunch)\b/i,
230
+ suggestion: (m) => `${m[1]} ${m[2]}`,
231
+ reason: 'Do not use "the" before meal names in general usage.',
232
+ }),
233
+ ];
@@ -0,0 +1,217 @@
1
+ import { createRegexRule, type Rule } from '../types.js';
2
+
3
+ /**
4
+ * ════════════════════════════════════════════════════════
5
+ * Extended Preposition Rules — PRX Module
6
+ * Covers the most common prepositional errors that
7
+ * Grammarly detects but we were missing.
8
+ * ════════════════════════════════════════════════════════
9
+ */
10
+ export const prepositionExtendedRules: Rule[] = [
11
+
12
+ // ─── ADJECTIVE + PREPOSITION COLLOCATIONS ───
13
+ createRegexRule({
14
+ id: 'PRX_good_in',
15
+ category: 'grammar',
16
+ pattern: /\bgood\s+in\s+(\w+ing|\w+)\b(?!\s+terms|\s+shape|\s+condition)/i,
17
+ suggestion: (m) => `good at ${m[1]}`,
18
+ reason: 'Use "good at" (not "good in") for skills and activities.',
19
+ }),
20
+ createRegexRule({
21
+ id: 'PRX_afraid_from',
22
+ category: 'grammar',
23
+ pattern: /\bafraid\s+from\b/i,
24
+ suggestion: 'afraid of',
25
+ reason: 'Use "afraid of" not "afraid from".',
26
+ }),
27
+ createRegexRule({
28
+ id: 'PRX_responsible_of',
29
+ category: 'grammar',
30
+ pattern: /\bresponsible\s+of\b/i,
31
+ suggestion: 'responsible for',
32
+ reason: 'Use "responsible for" not "responsible of".',
33
+ }),
34
+ createRegexRule({
35
+ id: 'PRX_proud_on',
36
+ category: 'grammar',
37
+ pattern: /\bproud\s+on\b/i,
38
+ suggestion: 'proud of',
39
+ reason: 'Use "proud of" not "proud on".',
40
+ }),
41
+ createRegexRule({
42
+ id: 'PRX_angry_on',
43
+ category: 'grammar',
44
+ pattern: /\bangry\s+on\b/i,
45
+ suggestion: 'angry at / angry with',
46
+ reason: 'Use "angry at" or "angry with" not "angry on".',
47
+ }),
48
+ createRegexRule({
49
+ id: 'PRX_angry_from',
50
+ category: 'grammar',
51
+ pattern: /\bangry\s+from\b/i,
52
+ suggestion: 'angry at / angry with',
53
+ reason: 'Use "angry at" or "angry with" not "angry from".',
54
+ }),
55
+ createRegexRule({
56
+ id: 'PRX_happy_for_event',
57
+ category: 'grammar',
58
+ pattern: /\bhappy\s+from\b/i,
59
+ suggestion: 'happy with / happy about',
60
+ reason: 'Use "happy with" or "happy about" not "happy from".',
61
+ }),
62
+ createRegexRule({
63
+ id: 'PRX_familiar_about',
64
+ category: 'grammar',
65
+ pattern: /\bfamiliar\s+about\b/i,
66
+ suggestion: 'familiar with',
67
+ reason: 'Use "familiar with" not "familiar about".',
68
+ }),
69
+ createRegexRule({
70
+ id: 'PRX_accused_for',
71
+ category: 'grammar',
72
+ pattern: /\baccused\s+for\b/i,
73
+ suggestion: 'accused of',
74
+ reason: 'Use "accused of" not "accused for".',
75
+ }),
76
+ createRegexRule({
77
+ id: 'PRX_capable_to',
78
+ category: 'grammar',
79
+ pattern: /\bcapable\s+to\b/i,
80
+ suggestion: 'capable of',
81
+ reason: 'Use "capable of" not "capable to".',
82
+ }),
83
+ createRegexRule({
84
+ id: 'PRX_sure_about_vs_of',
85
+ category: 'grammar',
86
+ pattern: /\bconfident\s+about\b/i,
87
+ suggestion: 'confident in / confident of',
88
+ reason: 'Use "confident in" or "confident of" not "confident about".',
89
+ }),
90
+ createRegexRule({
91
+ id: 'PRX_depend_of',
92
+ category: 'grammar',
93
+ pattern: /\bdepend\s+of\b/i,
94
+ suggestion: 'depend on',
95
+ reason: 'Use "depend on" not "depend of".',
96
+ }),
97
+ createRegexRule({
98
+ id: 'PRX_consists_from',
99
+ category: 'grammar',
100
+ pattern: /\bconsists?\s+from\b/i,
101
+ suggestion: 'consists of',
102
+ reason: 'Use "consists of" not "consists from".',
103
+ }),
104
+ createRegexRule({
105
+ id: 'PRX_based_in',
106
+ category: 'grammar',
107
+ pattern: /\bbased\s+in\s+(?!the\s+city|the\s+town|the\s+country|the\s+region)/i,
108
+ suggestion: 'based on',
109
+ reason: 'Use "based on" when talking about foundations or reasons, not "based in".',
110
+ }),
111
+
112
+ // ─── VERB + PREPOSITION COLLOCATIONS ───
113
+ createRegexRule({
114
+ id: 'PRX_discuss_about',
115
+ category: 'grammar',
116
+ pattern: /\bdiscussed?\s+about\b/i,
117
+ suggestion: (m) => m[1] || 'discussed',
118
+ reason: '"Discuss" already means "talk about." Drop "about": say "discuss the issue".',
119
+ }),
120
+ createRegexRule({
121
+ id: 'PRX_explain_indirect',
122
+ category: 'grammar',
123
+ pattern: /\bexplained?\s+(me|him|her|them|us|you)\s+/i,
124
+ suggestion: (m) => `explained to ${m[1]} `,
125
+ reason: '"Explain" needs "to" before the indirect object: "explain to me".',
126
+ }),
127
+ createRegexRule({
128
+ id: 'PRX_listen_to',
129
+ category: 'grammar',
130
+ pattern: /\blisten\s+(?!to\b|carefully\b|closely\b|well\b|up\b|in\b|out\b)(\w+)/i,
131
+ suggestion: (m) => `listen to ${m[1]}`,
132
+ reason: '"Listen" requires "to" before the object: "listen to music".',
133
+ }),
134
+ createRegexRule({
135
+ id: 'PRX_cope_with',
136
+ category: 'grammar',
137
+ pattern: /\bcope\s+(?!with\b)(\w+)/i,
138
+ suggestion: (m) => `cope with ${m[1]}`,
139
+ reason: '"Cope" requires "with": "cope with stress".',
140
+ }),
141
+ createRegexRule({
142
+ id: 'PRX_insist_on',
143
+ category: 'grammar',
144
+ pattern: /\binsist\s+(?!on\b|upon\b|that\b)(\w+)/i,
145
+ suggestion: (m) => `insist on ${m[1]}`,
146
+ reason: '"Insist" is usually followed by "on": "insist on quality".',
147
+ }),
148
+ createRegexRule({
149
+ id: 'PRX_agree_with_person',
150
+ category: 'grammar',
151
+ pattern: /\bagree\s+(?:with\s+)?to\s+(?!the\s+terms|the\s+conditions|the\s+proposal|the\s+plan|the\s+deal|this|that|it|them)/i,
152
+ suggestion: 'agree with',
153
+ reason: 'Use "agree with" when referring to a person or opinion.',
154
+ }),
155
+ createRegexRule({
156
+ id: 'PRX_spend_time_on',
157
+ category: 'grammar',
158
+ pattern: /\bspend\s+(?:some\s+|more\s+|less\s+|much\s+|a\s+lot\s+of\s+)?time\s+to\s+(\w+)/i,
159
+ suggestion: (m) => `spend time ${m[1]}ing`,
160
+ reason: '"Spend time" is followed by a gerund: "spend time reading".',
161
+ }),
162
+ createRegexRule({
163
+ id: 'PRX_succeed_in',
164
+ category: 'grammar',
165
+ pattern: /\bsucceeded?\s+to\s+(\w+)/i,
166
+ suggestion: (m) => `succeeded in ${m[1]}ing`,
167
+ reason: '"Succeed" is followed by "in" + gerund: "succeed in achieving".',
168
+ }),
169
+
170
+ // ─── CITY + COUNTRY COMMA ───
171
+ createRegexRule({
172
+ id: 'PRX_city_country_comma',
173
+ category: 'grammar',
174
+ pattern: /\b(Paris|London|Berlin|Rome|Madrid|Tokyo|Beijing|Mumbai|Sydney|Cairo|Lagos|Nairobi|Moscow|Oslo|Seoul|Bangkok|Vienna|Athens|Dublin|Lisbon|Amsterdam|Stockholm|Copenhagen|Brussels|Warsaw|Prague|Budapest|Bucharest|Sofia|Ankara|Tehran|Dhaka|Karachi|Colombo|Kathmandu|Kabul|Islamabad|Baghdad|Riyadh|Dubai|Kuwait|Doha|Amman|Beirut|Damascus|Jerusalem|Tunis|Algiers|Rabat|Accra|Abidjan|Dakar|Addis|Nairobi|Kinshasa|Luanda|Lusaka|Harare|Cape|Johannesburg|Pretoria|Kampala|Dar|Maputo|Antananarivo|Port|Auckland|Wellington|Suva|Honiara|Nuku|Pago|Ottawa|Washington|Mexico|Havana|Kingston|Port|San|Guatemala|Tegucigalpa|Managua|San|Panama|Bogota|Caracas|Georgetown|Paramaribo|Quito|Lima|LaPaz|Asuncion|Santiago|Buenos|Montevideo|Brasilia)\s+(France|Germany|Italy|Spain|Japan|China|India|Australia|Egypt|Nigeria|Kenya|Russia|Norway|South Korea|Thailand|Austria|Greece|Ireland|Portugal|Netherlands|Sweden|Denmark|Belgium|Poland|Czech Republic|Hungary|Romania|Bulgaria|Turkey|Iran|Bangladesh|Pakistan|Sri Lanka|Nepal|Afghanistan|Iraq|Saudi Arabia|UAE|Kuwait|Qatar|Jordan|Lebanon|Syria|Tunisia|Algeria|Morocco|Ghana|Ivory Coast|Senegal|Ethiopia|DR Congo|Angola|Zambia|Zimbabwe|Uganda|Tanzania|Mozambique|Madagascar|Canada|United States|Cuba|Jamaica|Guatemala|Honduras|Nicaragua|El Salvador|Panama|Colombia|Venezuela|Guyana|Ecuador|Peru|Bolivia|Paraguay|Chile|Argentina|Uruguay|Brazil|New Zealand|Fiji)\b/i,
175
+ suggestion: (m) => `${m[1]}, ${m[2]}`,
176
+ reason: 'Use a comma between city and country: "Paris, France".',
177
+ }),
178
+
179
+ // ─── AT vs ON vs IN (time) ───
180
+ createRegexRule({
181
+ id: 'PRX_at_monday',
182
+ category: 'grammar',
183
+ pattern: /\bat\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|weekdays|weekends|the\s+weekend)\b/i,
184
+ suggestion: (m) => `on ${m[1]}`,
185
+ reason: 'Use "on" with days of the week, not "at".',
186
+ }),
187
+ createRegexRule({
188
+ id: 'PRX_in_monday',
189
+ category: 'grammar',
190
+ pattern: /\bin\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\b/i,
191
+ suggestion: (m) => `on ${m[1]}`,
192
+ reason: 'Use "on" with days of the week, not "in".',
193
+ }),
194
+ createRegexRule({
195
+ id: 'PRX_on_morning',
196
+ category: 'grammar',
197
+ pattern: /\bon\s+the?\s*(morning|afternoon|evening|night)\b(?!\s+of)/i,
198
+ suggestion: (m) => `in the ${m[1]}`,
199
+ reason: 'Use "in the morning/afternoon/evening", not "on the morning".',
200
+ }),
201
+ createRegexRule({
202
+ id: 'PRX_at_night_correct',
203
+ category: 'grammar',
204
+ pattern: /\bin\s+(?:the\s+)?night\b(?!\s+sky|\s+air|\s+time|\s+shift|\s+club|\s+vision|\s+owl)/i,
205
+ suggestion: 'at night',
206
+ reason: 'Use "at night" not "in night".',
207
+ }),
208
+
209
+ // ─── ADVERB PLACEMENT ───
210
+ createRegexRule({
211
+ id: 'PRX_like_very_much',
212
+ category: 'grammar',
213
+ pattern: /\b(like|love|enjoy|hate|prefer|want|need|know|understand|believe|think|remember|forget|notice|see|hear|feel|find|get|use|have|do|make|take|give|tell|show|help|keep|let|bring|come|go|run|work|write|read|speak|say)\s+very\s+much\s+(\w+)/i,
214
+ suggestion: (m) => `${m[1]} ${m[2]} very much`,
215
+ reason: 'Place "very much" after the object, not before it.',
216
+ }),
217
+ ];
@@ -0,0 +1,159 @@
1
+ import type { Issue } from '../../shared-types.js';
2
+ import { createRegexRule, type Rule } from '../types.js';
3
+
4
+ export const prepositionRules: Rule[] = [
5
+ // ═══ Time Prepositions (PR_TIM) ═══
6
+ createRegexRule({
7
+ id: 'PR_TIM_in_day',
8
+ category: 'grammar',
9
+ pattern: /\bin\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\b/i,
10
+ suggestion: (m) => `on ${m[1]}`,
11
+ reason: 'Use "on" with days of the week, not "in".',
12
+ }),
13
+ createRegexRule({
14
+ id: 'PR_TIM_at_month',
15
+ category: 'grammar',
16
+ pattern:
17
+ /\bat\s+(January|February|March|April|May|June|July|August|September|October|November|December)\b/i,
18
+ suggestion: (m) => `in ${m[1]}`,
19
+ reason: 'Use "in" with months, not "at".',
20
+ }),
21
+ createRegexRule({
22
+ id: 'PR_TIM_on_time',
23
+ category: 'grammar',
24
+ pattern: /\bon\s+(\d{1,2})\s*(AM|PM|a\.m\.|p\.m\.)\b/i,
25
+ suggestion: (m) => `at ${m[1]} ${m[2]}`,
26
+ reason: 'Use "at" with specific times, not "on".',
27
+ }),
28
+ createRegexRule({
29
+ id: 'PR_TIM_at_year',
30
+ category: 'grammar',
31
+ pattern: /\bat\s+(19|20)\d{2}\b/,
32
+ suggestion: (m) => `in ${m[0].replace('at ', '')}`,
33
+ reason: 'Use "in" with years, not "at".',
34
+ }),
35
+
36
+ // ═══ Place Prepositions (PR_PLC) ═══
37
+ createRegexRule({
38
+ id: 'PR_PLC_in_table',
39
+ category: 'grammar',
40
+ pattern: /\bin\s+the\s+table\b/i,
41
+ suggestion: 'on the table',
42
+ reason: 'Use "on" for surfaces (on the table), not "in".',
43
+ }),
44
+ createRegexRule({
45
+ id: 'PR_PLC_at_room',
46
+ category: 'grammar',
47
+ pattern: /\bat\s+the\s+(room|bedroom|bathroom|kitchen|office|classroom|living room)\b/i,
48
+ suggestion: (m) => `in the ${m[1]}`,
49
+ reason: 'Use "in" for enclosed spaces, not "at".',
50
+ }),
51
+
52
+ // ═══ Redundant Prepositions (PR_RED) ═══
53
+ createRegexRule({
54
+ id: 'PR_RED_return_back',
55
+ category: 'clarity',
56
+ pattern: /\breturn\s+back\b/i,
57
+ suggestion: 'return',
58
+ reason: '"Return" already means "come/go back" — "back" is redundant.',
59
+ }),
60
+ createRegexRule({
61
+ id: 'PR_RED_repeat_again',
62
+ category: 'clarity',
63
+ pattern: /\brepeat\s+again\b/i,
64
+ suggestion: 'repeat',
65
+ reason: '"Repeat" already means "do again" — "again" is redundant.',
66
+ }),
67
+ createRegexRule({
68
+ id: 'PR_RED_revert_back',
69
+ category: 'clarity',
70
+ pattern: /\brevert\s+back\b/i,
71
+ suggestion: 'revert',
72
+ reason: '"Revert" already means "go back" — "back" is redundant.',
73
+ }),
74
+ createRegexRule({
75
+ id: 'PR_RED_continue_on',
76
+ category: 'clarity',
77
+ pattern: /\bcontinue\s+on\b/i,
78
+ suggestion: 'continue',
79
+ reason: '"Continue" doesn\'t need "on" — it\'s redundant.',
80
+ }),
81
+ createRegexRule({
82
+ id: 'PR_RED_combine_together',
83
+ category: 'clarity',
84
+ pattern: /\bcombine\s+together\b/i,
85
+ suggestion: 'combine',
86
+ reason: '"Combine" already implies together — redundant.',
87
+ }),
88
+ createRegexRule({
89
+ id: 'PR_RED_join_together',
90
+ category: 'clarity',
91
+ pattern: /\bjoin\s+together\b/i,
92
+ suggestion: 'join',
93
+ reason: '"Join" already implies together — redundant.',
94
+ }),
95
+ createRegexRule({
96
+ id: 'PR_RED_merge_together',
97
+ category: 'clarity',
98
+ pattern: /\bmerge\s+together\b/i,
99
+ suggestion: 'merge',
100
+ reason: '"Merge" already implies together — redundant.',
101
+ }),
102
+ createRegexRule({
103
+ id: 'PR_RED_rise_up',
104
+ category: 'clarity',
105
+ pattern: /\brise\s+up\b/i,
106
+ suggestion: 'rise',
107
+ reason: '"Rise" already means upward movement — "up" is redundant.',
108
+ }),
109
+ createRegexRule({
110
+ id: 'PR_RED_descend_down',
111
+ category: 'clarity',
112
+ pattern: /\bdescend\s+down\b/i,
113
+ suggestion: 'descend',
114
+ reason: '"Descend" already means go down — "down" is redundant.',
115
+ }),
116
+ createRegexRule({
117
+ id: 'PR_RED_advance_forward',
118
+ category: 'clarity',
119
+ pattern: /\badvance\s+forward\b/i,
120
+ suggestion: 'advance',
121
+ reason: '"Advance" already means move forward — "forward" is redundant.',
122
+ }),
123
+
124
+ // ═══ Preposition + Gerund vs Infinitive (PR_GER) ═══
125
+ createRegexRule({
126
+ id: 'PR_GER_forward',
127
+ category: 'grammar',
128
+ pattern:
129
+ /\blook\s+forward\s+to\s+(meet|see|hear|work|do|make|get|have|be|go|start|finish|visit|attend|receive|discuss|learn|join|speak|talk|read|write|play|travel|explore)\b/i,
130
+ suggestion: (m) => `look forward to ${m[1]}ing`,
131
+ reason: 'After "look forward to", use the gerund (-ing form), not the base verb.',
132
+ }),
133
+ createRegexRule({
134
+ id: 'PR_GER_used_to',
135
+ category: 'grammar',
136
+ pattern:
137
+ /\b(am|is|are|was|were|get|got|gotten)\s+used\s+to\s+(wake|eat|drink|sleep|work|drive|walk|run|swim|cook|clean|study|live|stay|go|come|leave|arrive|sit|stand)\b/i,
138
+ suggestion: (m) => `${m[1]} used to ${m[2]}ing`,
139
+ reason:
140
+ 'After "be used to" (accustomed to), use the gerund: "used to waking", not "used to wake".',
141
+ }),
142
+ createRegexRule({
143
+ id: 'PR_GER_insist',
144
+ category: 'grammar',
145
+ pattern: /\binsisted?\s+on\s+to\s+(\w+)\b/i,
146
+ suggestion: (m) => `insisted on ${m[1]}ing`,
147
+ reason: 'After "insist on", use the gerund (-ing form).',
148
+ }),
149
+
150
+ // ═══ In vs Into (PR_MVR) ═══
151
+ createRegexRule({
152
+ id: 'PR_MVR_walked_in',
153
+ category: 'grammar',
154
+ pattern:
155
+ /\b(walked|ran|jumped|dove|climbed|went|came|fell|stepped|rushed|stormed|burst)\s+in\s+the\s+(room|house|building|office|store|shop|pool|water|car|kitchen|bedroom|bathroom|library|museum|theater|theatre|elevator|lift)\b/i,
156
+ suggestion: (m) => `${m[1]} into the ${m[2]}`,
157
+ reason: 'Use "into" with movement verbs to indicate entering a space.',
158
+ }),
159
+ ];