@larsgw/formica 0.8.6 → 0.8.7

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.8.7](https://github.com/identification-resources/formica/compare/v0.8.6...v0.8.7) (2025-11-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **resources:** fix handling of corrections ([49c05b4](https://github.com/identification-resources/formica/commit/49c05b499262e64514b832313360d1e6a4cc6378))
7
+
8
+
9
+
1
10
  ## [0.8.6](https://github.com/identification-resources/formica/compare/v0.8.5...v0.8.6) (2025-10-28)
2
11
 
3
12
 
@@ -255,6 +255,10 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
255
255
  }
256
256
  // Amend "parent" with corrections, exit
257
257
  if (item.taxonomicStatus === 'incorrect') {
258
+ if (parent.taxonomicStatus !== 'accepted') {
259
+ // Remove corrected synonym from parentage
260
+ parents[parents.length - 1] = null;
261
+ }
258
262
  if (parent.incorrect) {
259
263
  errors.push(makeParseError('Cannot apply a correction to a previous correction', lineNumber));
260
264
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "SDK and tools for data from the Library of Identification Resources",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -243,6 +243,11 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
243
243
 
244
244
  // Amend "parent" with corrections, exit
245
245
  if (item.taxonomicStatus === 'incorrect') {
246
+ if (parent.taxonomicStatus !== 'accepted') {
247
+ // Remove corrected synonym from parentage
248
+ parents[parents.length - 1] = null
249
+ }
250
+
246
251
  if (parent.incorrect) {
247
252
  errors.push(makeParseError('Cannot apply a correction to a previous correction', lineNumber))
248
253
  continue
package/test/resources.js CHANGED
@@ -3,8 +3,8 @@ const assert = require('assert')
3
3
 
4
4
  const { catalog, resources } = require('../lib')
5
5
 
6
- suite('catalog', async (t) => {
7
- await test('reports missing required fields', (t) => {
6
+ suite('catalog', () => {
7
+ test('reports missing required fields', () => {
8
8
  const errors = catalog.loadData(`id
9
9
  B1`, 'catalog').validate()
10
10
  assert.deepStrictEqual(errors, [
@@ -18,8 +18,8 @@ B1`, 'catalog').validate()
18
18
  })
19
19
  })
20
20
 
21
- suite('resources', async (t) => {
22
- await test('parses author with initials', (t) => {
21
+ suite('resources', () => {
22
+ test('parses author with initials', () => {
23
23
  const [resource] = resources.parseTextFile(`---
24
24
  levels: [family, genus, species]
25
25
  ---
@@ -33,32 +33,7 @@ Sphecidae A. Costa, 1886
33
33
  assert.deepStrictEqual(Object.values(resource.taxa).map(taxon => taxon.scientificNameAuthorship), Array(3).fill('A. Costa, 1886'))
34
34
  })
35
35
 
36
- await test('does not validate name with correction', (t) => {
37
- const [resource] = resources.parseTextFile(`---
38
- levels: [species]
39
- ---
40
-
41
- Clytochrysus lapidarius (Panzer, 1804)
42
- = Crabo chrysostomus Lepeletier & Brullé, 1835
43
- > Crabro chrysostomus Lepeletier & Brullé, 1835
44
- `, 'T1')
45
- assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Clytochrysus lapidarius (Panzer, 1804)')
46
- })
47
-
48
- await test('errors for missing leaf taxa', (t) => {
49
- assert.throws(() => {
50
- resources.parseTextFile(`---
51
- levels: [family, genus, species]
52
- ---
53
-
54
- Cydnidae
55
- Cydnidae
56
- Legnotus
57
- limbosus`, 'T1')
58
- })
59
- })
60
-
61
- await test('parses synonyms starting with intraspecific ranks', (t) => {
36
+ test('parses synonyms starting with intraspecific ranks', () => {
62
37
  const [resource] = resources.parseTextFile(`---
63
38
  levels: [species]
64
39
  ---
@@ -69,7 +44,7 @@ Lygaeus equestris (Linnaeus, 1758)
69
44
  assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Lygaeus equestris f. lactans Horváth, 1899')
70
45
  })
71
46
 
72
- await test('parses accepted taxa starting with intraspecific ranks', (t) => {
47
+ test('parses accepted taxa starting with intraspecific ranks', () => {
73
48
  const [resource] = resources.parseTextFile(`---
74
49
  levels: [species, form]
75
50
  ---
@@ -80,18 +55,7 @@ Lygaeus equestris (Linnaeus, 1758)
80
55
  assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Lygaeus equestris f. lactans Horváth, 1899')
81
56
  })
82
57
 
83
- await test('does not validate "indet." lines', (t) => {
84
- const [resource] = resources.parseTextFile(`---
85
- levels: [genus, species]
86
- ---
87
-
88
- Drymus
89
- [indet]
90
- `, 'T1')
91
- assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Drymus')
92
- })
93
-
94
- await test('parses names containing non-ASCII characters', (t) => {
58
+ test('parses names containing non-ASCII characters', () => {
95
59
  const [resource] = resources.parseTextFile(`---
96
60
  levels: [species]
97
61
  ---
@@ -101,7 +65,7 @@ Nematus fåhraei Thomson
101
65
  assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Nematus fåhraei Thomson')
102
66
  })
103
67
 
104
- await test('parses synonyms in different genera', (t) => {
68
+ test('parses synonyms in different genera', () => {
105
69
  const [resource] = resources.parseTextFile(`---
106
70
  levels: [species]
107
71
  ---
@@ -113,7 +77,7 @@ Katamenes arbustorum subsp. burlinii
113
77
  assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Eumenes arbustorum var. burlinii')
114
78
  })
115
79
 
116
- await test('parses species without generic names', (t) => {
80
+ test('parses species without generic names', () => {
117
81
  const [resource] = resources.parseTextFile(`---
118
82
  levels: [genus, subgenus, species]
119
83
  ---
@@ -125,7 +89,7 @@ Microdynerus Thomson, 1874
125
89
  assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Microdynerus microdynerus (Dalla Torre, 1889)')
126
90
  })
127
91
 
128
- await test('parses hybrids', (t) => {
92
+ test('parses hybrids', () => {
129
93
  const [resource] = resources.parseTextFile(`---
130
94
  levels: [species]
131
95
  ---
@@ -137,20 +101,7 @@ Tilia x vulgaris
137
101
  assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Tilia ×vulgaris')
138
102
  })
139
103
 
140
- await test('outputs corrected generic names', (t) => {
141
- const [resource] = resources.parseTextFile(`---
142
- levels: [genus, species]
143
- ---
144
-
145
- Bogdania Kerzhner, 1964
146
- > Bogdiana Kerzhner, 1964
147
- myrmica Kerzhner, 1964
148
- `, 'T1')
149
- assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bogdiana Kerzhner, 1964')
150
- assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Bogdiana myrmica Kerzhner, 1964')
151
- })
152
-
153
- await test('parses cross-genus hybrids', (t) => {
104
+ test('parses cross-genus hybrids', () => {
154
105
  const [resource] = resources.parseTextFile(`---
155
106
  levels: [genus, species]
156
107
  ---
@@ -165,7 +116,7 @@ x Festulpia
165
116
  assert.strictEqual(resource.taxa['T1:1:3'].scientificName, '×Festulpia Festuca rubra×Vulpia bromoides')
166
117
  })
167
118
 
168
- await test('parses cross-genus hybrids without parent context', (t) => {
119
+ test('parses cross-genus hybrids without parent context', () => {
169
120
  const [resource] = resources.parseTextFile(`---
170
121
  levels: [species]
171
122
  ---
@@ -176,7 +127,7 @@ x Festulpia Festuca_rubra x Vulpia_bromoides
176
127
  assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, '× Festulpia Festuca rubra × Vulpia bromoides')
177
128
  })
178
129
 
179
- await test('handles skips in ranks', (t) => {
130
+ test('handles skips in ranks', () => {
180
131
  const [resource] = resources.parseTextFile(`---
181
132
  levels: [family, genus, species]
182
133
  ---
@@ -189,8 +140,59 @@ Apidae
189
140
  assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'species')
190
141
  })
191
142
 
192
- await test('synonyms do not break recognition of missing leaf taxa (1)', (t) => {
143
+ test('parses genera with subgenus-rank synonyms', () => {
193
144
  const [resource] = resources.parseTextFile(`---
145
+ levels: [genus]
146
+ ---
147
+
148
+ Ectemnius Dahlbom
149
+ = Crabro (Ectemnius) Dahlbom
150
+ `, 'T1')
151
+ assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'subgenus')
152
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Ectemnius Dahlbom')
153
+ assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Crabro')
154
+ })
155
+
156
+ test('parses genera with like-name subgenera', () => {
157
+ const [resource] = resources.parseTextFile(`---
158
+ levels: [genus, subgenus]
159
+ ---
160
+
161
+ Polistes Latreille, 1802
162
+ Polistes Latreille, 1802
163
+ `, 'T1')
164
+ assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'subgenus')
165
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Polistes Latreille, 1802')
166
+ assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Polistes')
167
+ })
168
+
169
+ suite('leaf taxa checks', () => {
170
+ test('errors for missing leaf taxa', () => {
171
+ assert.throws(() => {
172
+ resources.parseTextFile(`---
173
+ levels: [family, genus, species]
174
+ ---
175
+
176
+ Cydnidae
177
+ Cydnidae
178
+ Legnotus
179
+ limbosus`, 'T1')
180
+ })
181
+ })
182
+
183
+ test('does not validate "indet." lines', () => {
184
+ const [resource] = resources.parseTextFile(`---
185
+ levels: [genus, species]
186
+ ---
187
+
188
+ Drymus
189
+ [indet]
190
+ `, 'T1')
191
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Drymus')
192
+ })
193
+
194
+ test('synonyms do not break recognition of missing leaf taxa (1)', () => {
195
+ const [resource] = resources.parseTextFile(`---
194
196
  levels: [genus, subgenus, species]
195
197
  ---
196
198
 
@@ -199,12 +201,12 @@ Bombus
199
201
  Bombus
200
202
  pascuorum
201
203
  `, 'T1')
202
- assert.strictEqual(resource.taxa['T1:1:4'].scientificName, 'Bombus pascuorum')
203
- })
204
+ assert.strictEqual(resource.taxa['T1:1:4'].scientificName, 'Bombus pascuorum')
205
+ })
204
206
 
205
- await test('synonyms do not break recognition of missing leaf taxa (2)', (t) => {
206
- assert.throws(() => {
207
- resources.parseTextFile(`---
207
+ test('synonyms do not break recognition of missing leaf taxa (2)', () => {
208
+ assert.throws(() => {
209
+ resources.parseTextFile(`---
208
210
  levels: [genus, subgenus, species]
209
211
  ---
210
212
 
@@ -214,12 +216,12 @@ Bombus
214
216
  Bombus
215
217
  pascuorum
216
218
  `, 'T1')
219
+ })
217
220
  })
218
- })
219
221
 
220
- await test('synonyms do not break recognition of missing leaf taxa (3)', (t) => {
221
- assert.throws(() => {
222
- resources.parseTextFile(`---
222
+ test('synonyms do not break recognition of missing leaf taxa (3)', () => {
223
+ assert.throws(() => {
224
+ resources.parseTextFile(`---
223
225
  levels: [family, genus, species]
224
226
  ---
225
227
 
@@ -229,50 +231,51 @@ Cydnidae
229
231
  Legnotus
230
232
  limbosus
231
233
  `, 'T1')
234
+ })
232
235
  })
233
236
  })
234
237
 
235
- await test('parses genera with subgenus-rank synonyms', (t) => {
236
- const [resource] = resources.parseTextFile(`---
237
- levels: [genus]
238
+ suite('corrections', () => {
239
+ test('outputs corrected generic names', () => {
240
+ const [resource] = resources.parseTextFile(`---
241
+ levels: [genus, species]
238
242
  ---
239
243
 
240
- Ectemnius Dahlbom
241
- = Crabro (Ectemnius) Dahlbom
244
+ Bogdania Kerzhner, 1964
245
+ > Bogdiana Kerzhner, 1964
246
+ myrmica Kerzhner, 1964
242
247
  `, 'T1')
243
- assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'subgenus')
244
- assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Ectemnius Dahlbom')
245
- assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Crabro')
246
- })
248
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bogdiana Kerzhner, 1964')
249
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Bogdiana myrmica Kerzhner, 1964')
250
+ })
247
251
 
248
- await test('parses genera with like-name subgenera', (t) => {
249
- const [resource] = resources.parseTextFile(`---
250
- levels: [genus, subgenus]
252
+ test('does not validate name with correction', () => {
253
+ const [resource] = resources.parseTextFile(`---
254
+ levels: [species]
251
255
  ---
252
256
 
253
- Polistes Latreille, 1802
254
- Polistes Latreille, 1802
257
+ Clytochrysus lapidarius (Panzer, 1804)
258
+ = Crabo chrysostomus Lepeletier & Brullé, 1835
259
+ > Crabro chrysostomus Lepeletier & Brullé, 1835
255
260
  `, 'T1')
256
- assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'subgenus')
257
- assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Polistes Latreille, 1802')
258
- assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Polistes')
259
- })
261
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Clytochrysus lapidarius (Panzer, 1804)')
262
+ })
260
263
 
261
- await test('parses invalid but corrected name', (t) => {
262
- const [resource] = resources.parseTextFile(`---
264
+ test('parses invalid but corrected name', () => {
265
+ const [resource] = resources.parseTextFile(`---
263
266
  levels: [species]
264
267
  ---
265
268
 
266
269
  Crabro Kiesenwetteri A. Morawitz. 1866
267
270
  > Crabro kiesenwetteri A. Morawitz. 1866
268
271
  `, 'T1')
269
- assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Crabro kiesenwetteri A. Morawitz. 1866')
270
- assert.strictEqual(resource.taxa['T1:1:1'].scientificNameAuthorship, 'A. Morawitz. 1866')
271
- assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Crabro Kiesenwetteri A. Morawitz. 1866')
272
- })
272
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Crabro kiesenwetteri A. Morawitz. 1866')
273
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificNameAuthorship, 'A. Morawitz. 1866')
274
+ assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Crabro Kiesenwetteri A. Morawitz. 1866')
275
+ })
273
276
 
274
- await test('parses children of invalid but corrected name', (t) => {
275
- const [resource] = resources.parseTextFile(`---
277
+ test('parses children of invalid but corrected name', () => {
278
+ const [resource] = resources.parseTextFile(`---
276
279
  levels: [genus, species]
277
280
  ---
278
281
 
@@ -283,40 +286,68 @@ Pirus L.
283
286
  Pirus aria Ehrh.
284
287
  > Pyrus aria Ehrh.
285
288
  `, 'T1')
286
- assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Pyrus aucuparia Gaertn.')
287
- assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Pyrus domestica Sm.')
288
- assert.strictEqual(resource.taxa['T1:1:4'].scientificName, 'Pyrus aria Ehrh.')
289
- })
289
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Pyrus aucuparia Gaertn.')
290
+ assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Pyrus domestica Sm.')
291
+ assert.strictEqual(resource.taxa['T1:1:4'].scientificName, 'Pyrus aria Ehrh.')
292
+ })
290
293
 
291
- await test('does not keep parts of invalid but corrected name', (t) => {
292
- const [resource] = resources.parseTextFile(`---
294
+ test('does not keep parts of invalid but corrected name', () => {
295
+ const [resource] = resources.parseTextFile(`---
293
296
  levels: [species]
294
297
  ---
295
298
 
296
299
  Scolia 5-punctata FABRICIUS, 1781
297
300
  > Scolia quinquepunctata FABRICIUS, 1781
298
301
  `, 'T1')
299
- assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Scolia quinquepunctata Fabricius, 1781')
300
- assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Scolia 5-punctata FABRICIUS, 1781')
301
- })
302
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Scolia quinquepunctata Fabricius, 1781')
303
+ assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Scolia 5-punctata FABRICIUS, 1781')
304
+ })
302
305
 
303
- await test('make correct diff when last line changes', (t) => {
304
- const newText = `---
306
+ test('make correct diff when last line changes', () => {
307
+ const newText = `---
305
308
  levels: [species]
306
309
  ---
307
310
 
308
311
  Bittacus Hageni Brauer
309
312
  > Bittacus hageni Brauer
310
313
  `
311
- const oldText = `---
314
+ const oldText = `---
312
315
  levels: [genus, species]
313
316
  ---
314
317
 
315
318
  Bittacus hageni Brauer
316
319
  `
317
320
 
318
- const [resource] = resources.parseTextFile(newText, 'T1', { txt: oldText, dwc: [[null, ['T:1:1'], ['T:1:2']]] })
319
- assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bittacus hageni Brauer')
320
- assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Bittacus Hageni Brauer')
321
+ const [resource] = resources.parseTextFile(newText, 'T1', { txt: oldText, dwc: [[null, ['T:1:1'], ['T:1:2']]] })
322
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bittacus hageni Brauer')
323
+ assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Bittacus Hageni Brauer')
324
+ })
325
+
326
+ test('corrections of synonyms are correctly applied', () => {
327
+ const [resource] = resources.parseTextFile(`---
328
+ levels: [genus, subgenus, species]
329
+ ---
330
+
331
+ Lasius F.
332
+ = Domisthorpea Mor. & Drnt., 1915
333
+ > Donisthorpea Mor. & Drnt., 1915
334
+ fuliginosus Latr.
335
+ `, 'T1')
336
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Donisthorpea Mor. & Drnt., 1915')
337
+ assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Lasius fuliginosus Latr.')
338
+ })
339
+
340
+ test('corrections are correctly applied', () => {
341
+ const [resource] = resources.parseTextFile(`---
342
+ levels: [genus, species]
343
+ ---
344
+
345
+ Neopachygaster Austin, 1901
346
+ > Neopachygaster Austen, 1901
347
+ meromelas (Dufour, 1841)
348
+ `, 'T1')
349
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Neopachygaster Austen, 1901')
350
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Neopachygaster meromelas (Dufour, 1841)')
351
+ })
321
352
  })
322
353
  })