From f8462551c15725878abb0b209f4f6062d228cf37 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:28:24 +0100 Subject: [PATCH 1/6] refactor: getInfo generic with ts --- packages/mf-parser/src/MF.js | 4 +- packages/mf-parser/src/index.js | 9 +++ packages/mf-parser/src/util/getInfo.ts | 18 +++++ packages/mf-parser/src/util/getInfo.types.ts | 76 +++++++++++++++++-- .../util/{getInfo.js => getInfoInternal.js} | 2 +- 5 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 packages/mf-parser/src/util/getInfo.ts rename packages/mf-parser/src/util/{getInfo.js => getInfoInternal.js} (98%) diff --git a/packages/mf-parser/src/MF.js b/packages/mf-parser/src/MF.js index d1ec0e59..2d100215 100644 --- a/packages/mf-parser/src/MF.js +++ b/packages/mf-parser/src/MF.js @@ -3,7 +3,7 @@ import { parse } from './parse'; import { flatten } from './util/flatten'; import { getEA } from './util/getEA'; import { getElements } from './util/getElements'; -import { getInfo } from './util/getInfo'; +import { getInfoInternal } from './util/getInfoInternal'; import { getIsotopesInfo } from './util/getIsotopesInfo'; import { partsToDisplay } from './util/partsToDisplay'; import { partsToMF } from './util/partsToMF'; @@ -93,7 +93,7 @@ export class MF { getInfo(options = {}) { if (!this.cache.info) { this.toParts(); - this.cache.info = getInfo(this.cache.parts, options); + this.cache.info = getInfoInternal(this.cache.parts, options); } return this.cache.info; } diff --git a/packages/mf-parser/src/index.js b/packages/mf-parser/src/index.js index 18aa80dd..996ad4b7 100644 --- a/packages/mf-parser/src/index.js +++ b/packages/mf-parser/src/index.js @@ -9,3 +9,12 @@ export * from './Style.js'; export * from './ensureCase.js'; export * from './MF.js'; export * from './parseToHtml.js'; + +// types +export { IsotopesInfo } from './util/getIsotopesInfo.types'; +export { + PartInfo, + PartInfoWithParts, + GetInfoOptions, + GetInfoOptionsAllowed, +} from './util/getInfo.types'; diff --git a/packages/mf-parser/src/util/getInfo.ts b/packages/mf-parser/src/util/getInfo.ts new file mode 100644 index 00000000..b21773b6 --- /dev/null +++ b/packages/mf-parser/src/util/getInfo.ts @@ -0,0 +1,18 @@ +import type { + GetInfoOptions, + GetInfoOptionsAllowed, + PartInfo, + PartInfoWithParts, +} from './getInfo.types'; +import { getInfoInternal } from './getInfoInternal'; + +// TODO: replace any with the actual type from `./toParts` +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Part = any; + +export function getInfo( + parts: Part[][], + options?: GIO, +): PartInfo | PartInfoWithParts { + return getInfoInternal(parts, options); +} diff --git a/packages/mf-parser/src/util/getInfo.types.ts b/packages/mf-parser/src/util/getInfo.types.ts index 76a7a570..1d3ad906 100644 --- a/packages/mf-parser/src/util/getInfo.types.ts +++ b/packages/mf-parser/src/util/getInfo.types.ts @@ -1,18 +1,78 @@ import type { AtomsMap } from './partToAtoms'; -export interface PartInfo { +type ImpossibleCustomNames = + | 'mass' + | 'charge' + | 'mf' + | 'atoms' + | 'unsaturation' + | 'parts'; +type AllowedCustomNames = Exclude | undefined; + +type GetEM = + GIO extends GetInfoOptions + ? em extends undefined + ? 'monoisotopicMass' + : em + : never; +type GetMSEM = + GIO extends GetInfoOptions + ? msem extends undefined + ? 'observedMonoisotopicMass' + : msem + : never; + +type CustomNameFields = Record< + GetEM, + number +> & + Record, number | undefined>; + +export type PartInfo = { mass: number; charge: number; mf: string; atoms: AtomsMap; unsaturation?: number; -} +} & CustomNameFields; -export interface PartInfoWithParts { - parts: PartInfo[]; - mass: number; - charge: number; +export type PartInfoWithParts = Omit< + PartInfo, + 'unsaturation' +> & { + parts: Array>; unsaturation: number; - atoms: AtomsMap; - mf: string; +} & CustomNameFields; + +/** + * final options type for the `getInfo` function + * + * @example + * ```ts + * const options: GetInfoOptions<'em', 'msem'> = {emFieldName: 'em', msemFieldName: 'msem'}; + * const info = getInfo(parts, options); + * + * // info.em should exists + * // info.parts[0].msem should exists + * ``` + */ +export interface GetInfoOptions< + EM extends AllowedCustomNames = 'monoisotopicMass', + MSEM extends AllowedCustomNames = 'observedMonoisotopicMass', +> { + customUnsaturations?: object; + emFieldName?: EM; + msemFieldName?: MSEM; } + +/** + * Guard type to check if the provided options are allowed + * @example + * ```ts + * function getInfo(options?: GIO): PartInfo | PartInfoWithParts;` + * ``` + */ +export type GetInfoOptionsAllowed = GetInfoOptions< + AllowedCustomNames, + AllowedCustomNames +>; diff --git a/packages/mf-parser/src/util/getInfo.js b/packages/mf-parser/src/util/getInfoInternal.js similarity index 98% rename from packages/mf-parser/src/util/getInfo.js rename to packages/mf-parser/src/util/getInfoInternal.js index ce62f7fb..e8024b73 100644 --- a/packages/mf-parser/src/util/getInfo.js +++ b/packages/mf-parser/src/util/getInfoInternal.js @@ -21,7 +21,7 @@ import { partToMF } from './partToMF'; * @param {*} [options={}] * @returns {object|PartInfo|PartInfoWithParts} */ -export function getInfo(parts, options = {}) { +export function getInfoInternal(parts, options = {}) { let { customUnsaturations = {}, emFieldName = 'monoisotopicMass', From 9f2cc2778aa0dd0200994e5ed62a7f40c71a2c55 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:01:40 +0100 Subject: [PATCH 2/6] refactor: MF Facade for incremental migration to TS --- packages/mf-parser/src/MF.ts | 114 ++++++++++++++++++ packages/mf-parser/src/MF.types.ts | 45 +++++++ .../mf-parser/src/{MF.js => MFInternal.js} | 14 ++- packages/mf-parser/src/util/getEA.js | 2 - packages/mf-parser/src/util/getElements.js | 2 - packages/mf-parser/src/util/toDisplay.js | 1 + 6 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 packages/mf-parser/src/MF.ts create mode 100644 packages/mf-parser/src/MF.types.ts rename packages/mf-parser/src/{MF.js => MFInternal.js} (94%) diff --git a/packages/mf-parser/src/MF.ts b/packages/mf-parser/src/MF.ts new file mode 100644 index 00000000..88f882f0 --- /dev/null +++ b/packages/mf-parser/src/MF.ts @@ -0,0 +1,114 @@ +import type { + DisplayPart, + EA, + Element, + FlattenOptions, + Parts, +} from './MF.types'; +import { MFInternal } from './MFInternal'; +import type { + GetInfoOptions, + GetInfoOptionsAllowed, + PartInfo, + PartInfoWithParts, +} from './util/getInfo.types'; +import type { IsotopesInfo } from './util/getIsotopesInfo.types'; + +export interface MFConstructorOptions { + ensureCase?: boolean; +} + +export class MF { + private readonly internal: MFInternal; + + constructor(mf: string, options: MFConstructorOptions = {}) { + this.internal = new MFInternal(mf, options); + } + + /** + * Returns an array of objects with kind and value that can be used to easily + * display the molecular formula. + */ + toDisplay(): DisplayPart[] { + return this.internal.toDisplay(); + } + + /** + * Returns a string that represents the molecular formula adding subscript and superscript in HTML. + */ + toHtml(): string { + return this.internal.toHtml(); + } + + /** + * Returns a string that represents the molecular formula adding subscript and superscript + * using Unicode characters. This can not be parsed anymore so kind of dead end ... + */ + toText(): string { + return this.internal.toText(); + } + + /** + * Similar to toText but returns a canonic string in which the atoms are sorted using the Hill system + */ + toCanonicText(): string { + return this.internal.toCanonicText(); + } + + toParts(options?: { expand?: boolean }): Parts[] { + return this.internal.toParts(options); + } + + /** + * Returns an object with the global MF, global charge, monoisotopic mass and mass + * as well as the same information for all the parts + */ + getInfo( + options?: GIO, + ): PartInfo | PartInfoWithParts { + return this.internal.getInfo(options); + } + + /** + * Returns an object with the elemental analysis + */ + getEA(): EA[] { + return this.internal.getEA(); + } + + /** + * Get the different elements for each part + */ + getElements(): Element[] { + return this.internal.getElements(); + } + + /** + * Returns an array with each atom and isotopic composition + */ + getIsotopesInfo(options = {}): IsotopesInfo | [] { + return this.internal.getIsotopesInfo(options); + } + + /** + * Get a canonized parsable Molecule Formula + */ + toMF(): string { + return this.internal.toMF(); + } + + /** + * Get a canonized MF + */ + toNeutralMF(): string { + return this.internal.toNeutralMF(); + } + + canonize(): void { + return this.internal.canonize(); + } + + flatten(options?: FlattenOptions): string[] { + return this.internal.flatten(options); + } +} diff --git a/packages/mf-parser/src/MF.types.ts b/packages/mf-parser/src/MF.types.ts new file mode 100644 index 00000000..f4b21776 --- /dev/null +++ b/packages/mf-parser/src/MF.types.ts @@ -0,0 +1,45 @@ +/** + * Temporary interface for incremental migrations to typescript + */ + +/** + * approximately toDisplay return type + */ +export interface DisplayPart { + kind: string; + value: string; +} + +/** + * approximately toParts return type + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Parts = any[]; + +/** + * approximately getEA return type + */ +export interface EA { + element: string; + mass: number; + ratio: number; +} + +/** + * approximately getElements return type + */ +export interface Element { + symbol: string; + number: number; + isotope?: number; +} + +/** + * approximately flatten options type + */ +export interface FlattenOptions { + /** @default false */ + groupIdentical: boolean; + /** @default 100000 */ + limit: number; +} diff --git a/packages/mf-parser/src/MF.js b/packages/mf-parser/src/MFInternal.js similarity index 94% rename from packages/mf-parser/src/MF.js rename to packages/mf-parser/src/MFInternal.js index 2d100215..29778e57 100644 --- a/packages/mf-parser/src/MF.js +++ b/packages/mf-parser/src/MFInternal.js @@ -19,7 +19,7 @@ import { toText } from './util/toText'; /** * Class allowing to deal with molecular formula and derived information */ -export class MF { +export class MFInternal { constructor(mf, options = {}) { if (options.ensureCase) { mf = ensureCase(mf); @@ -52,7 +52,7 @@ export class MF { /** * Returns a string that represents the molecular formula adding subscript and superscript - * using unicode characters. This can not be parsed anymore so kind of dead end ... + * using Unicode characters. This can not be parsed anymore so kind of dead end ... * @returns {string} */ toText() { @@ -69,7 +69,7 @@ export class MF { */ toCanonicText() { if (!this.cache.canonicText) { - this.cache.canonicText = new MF(this.toMF()).toText(this.cache.displayed); + this.cache.canonicText = new MFInternal(this.toMF()).toText(); } return this.cache.canonicText; } @@ -100,18 +100,19 @@ export class MF { /** * Returns an object with the elemental analysis + * @returns {*[]} */ - getEA(options = {}) { + getEA() { if (!this.cache.ea) { this.toParts(); - this.cache.ea = getEA(this.cache.parts, options); + this.cache.ea = getEA(this.cache.parts); } return this.cache.ea; } /** * Get the different elements for each part - * @returns an array + * @returns {*[]} */ getElements() { if (!this.cache.elements) { @@ -147,6 +148,7 @@ export class MF { /** * Get a canonized MF + * @returns {string} */ toNeutralMF() { if (!this.cache.neutralMF) { diff --git a/packages/mf-parser/src/util/getEA.js b/packages/mf-parser/src/util/getEA.js index a8893166..4e81ab78 100644 --- a/packages/mf-parser/src/util/getEA.js +++ b/packages/mf-parser/src/util/getEA.js @@ -9,9 +9,7 @@ import { Kind } from '../Kind'; import { getIsotopeRatioInfo } from './getIsotopeRatioInfo'; /** - * * @param {*} parts - * @param {*} [options={}] */ export function getEA(parts) { let results = {}; diff --git a/packages/mf-parser/src/util/getElements.js b/packages/mf-parser/src/util/getElements.js index 2659a768..56fd8c67 100644 --- a/packages/mf-parser/src/util/getElements.js +++ b/packages/mf-parser/src/util/getElements.js @@ -3,9 +3,7 @@ import { elementsObject, elementsAndIsotopesObject } from 'chemical-elements'; import { Kind } from '../Kind'; /** - * * @param {*} parts - * @param {*} [options={}] */ export function getElements(parts) { const elements = []; diff --git a/packages/mf-parser/src/util/toDisplay.js b/packages/mf-parser/src/util/toDisplay.js index cdc523bf..361010d4 100644 --- a/packages/mf-parser/src/util/toDisplay.js +++ b/packages/mf-parser/src/util/toDisplay.js @@ -6,6 +6,7 @@ import { formatCharge } from './formatCharge.js'; /** * Converts an array of mf elements to an array of formatting information * @param {object[]} lines of the parse method + * @returns {{kind: string, value: string}[]} */ export function toDisplay(lines) { From ff231187db4b79eb06bea607b904dbe11cb24b52 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:07:50 +0100 Subject: [PATCH 3/6] fix: types IsotopicDistribution imports --- .../isotopic-distribution/src/IsotopicDistribution.types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/isotopic-distribution/src/IsotopicDistribution.types.ts b/packages/isotopic-distribution/src/IsotopicDistribution.types.ts index 52f1f598..56021a89 100644 --- a/packages/isotopic-distribution/src/IsotopicDistribution.types.ts +++ b/packages/isotopic-distribution/src/IsotopicDistribution.types.ts @@ -1,4 +1,4 @@ -import type { IsotopesInfo, PartInfo } from 'mf-parser'; +import type { IsotopesInfo, PartInfo, GetInfoOptions } from 'mf-parser'; /** * An object containing two arrays. @@ -8,9 +8,10 @@ export interface XY { y: number[]; } -export interface IsotopicDistributionPart extends PartInfo { +export interface IsotopicDistributionPart extends PartInfo { confidence: number; isotopesInfo: IsotopesInfo; + /** alias to monoisotopicMass */ em: number; } From e8a44c26493da2e38472ab37921f41513426cc09 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:11:58 +0100 Subject: [PATCH 4/6] chore: use getInfo with good types --- packages/mf-parser/src/MFInternal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mf-parser/src/MFInternal.js b/packages/mf-parser/src/MFInternal.js index 29778e57..5cef133a 100644 --- a/packages/mf-parser/src/MFInternal.js +++ b/packages/mf-parser/src/MFInternal.js @@ -3,7 +3,7 @@ import { parse } from './parse'; import { flatten } from './util/flatten'; import { getEA } from './util/getEA'; import { getElements } from './util/getElements'; -import { getInfoInternal } from './util/getInfoInternal'; +import { getInfo } from './util/getInfo'; import { getIsotopesInfo } from './util/getIsotopesInfo'; import { partsToDisplay } from './util/partsToDisplay'; import { partsToMF } from './util/partsToMF'; @@ -93,7 +93,7 @@ export class MFInternal { getInfo(options = {}) { if (!this.cache.info) { this.toParts(); - this.cache.info = getInfoInternal(this.cache.parts, options); + this.cache.info = getInfo(this.cache.parts, options); } return this.cache.info; } From f38ce2b0f7d0dfff28a5986b9a78e167627d8d26 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:27:13 +0100 Subject: [PATCH 5/6] chore: ts files must be imported with no-ext path --- packages/mf-parser/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mf-parser/src/index.js b/packages/mf-parser/src/index.js index 996ad4b7..19672e29 100644 --- a/packages/mf-parser/src/index.js +++ b/packages/mf-parser/src/index.js @@ -7,7 +7,7 @@ export * from './Kind.js'; export * from './Format.js'; export * from './Style.js'; export * from './ensureCase.js'; -export * from './MF.js'; +export * from './MF'; export * from './parseToHtml.js'; // types From 0ee1597c47e952d018ed27aeddad33375ebcf223 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:13:12 +0100 Subject: [PATCH 6/6] test: add typing test Refs: https://github.com/cheminfo/mass-tools/pull/231#issuecomment-2518047600 --- .../src/__tests__/{MF.test.js => MF.test.ts} | 150 +++++++++++------- 1 file changed, 90 insertions(+), 60 deletions(-) rename packages/mf-parser/src/__tests__/{MF.test.js => MF.test.ts} (75%) diff --git a/packages/mf-parser/src/__tests__/MF.test.js b/packages/mf-parser/src/__tests__/MF.test.ts similarity index 75% rename from packages/mf-parser/src/__tests__/MF.test.js rename to packages/mf-parser/src/__tests__/MF.test.ts index d1efecb7..02e15439 100644 --- a/packages/mf-parser/src/__tests__/MF.test.js +++ b/packages/mf-parser/src/__tests__/MF.test.ts @@ -1,25 +1,25 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, expectTypeOf } from 'vitest'; import { MF } from '..'; describe('MF', () => { it('C', () => { - let mf = new MF('C'); - let parts = mf.toParts(); + const mf = new MF('C'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [{ kind: 'atom', value: 'C', multiplier: 1 }], ]); - let newMF = mf.toMF(); + const newMF = mf.toMF(); expect(newMF).toBe('C'); mf.canonize(); - let html = mf.toHtml(); + const html = mf.toHtml(); expect(html).toBe('C'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ monoisotopicMass: 12, @@ -32,23 +32,23 @@ describe('MF', () => { }); it('C.C', () => { - let mf = new MF('C.C'); - let parts = mf.toParts(); + const mf = new MF('C.C'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [{ kind: 'atom', value: 'C', multiplier: 1 }], [{ kind: 'atom', value: 'C', multiplier: 1 }], ]); - let newMF = mf.toMF(); + const newMF = mf.toMF(); expect(newMF).toBe('C . C'); mf.canonize(); - let html = mf.toHtml(); + const html = mf.toHtml(); expect(html).toBe('C • C'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ parts: [ @@ -79,8 +79,8 @@ describe('MF', () => { }); it('[11C][11C]', () => { - let mf = new MF('[11C][11C]'); - let parts = mf.toParts(); + const mf = new MF('[11C][11C]'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [ @@ -95,15 +95,15 @@ describe('MF', () => { ], ]); - let newMF = mf.toMF(); + const newMF = mf.toMF(); expect(newMF).toBe('[11C]2'); mf.canonize(); - let html = mf.toHtml(); + const html = mf.toHtml(); expect(html).toBe('11C2'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ monoisotopicMass: 22.0228672, @@ -116,10 +116,10 @@ describe('MF', () => { }); it('D', () => { - let mfD = new MF('D'); - let infoD = mfD.getInfo(); - let mf2H = new MF('[2H]'); - let info2H = mf2H.getInfo(); + const mfD = new MF('D'); + const infoD = mfD.getInfo(); + const mf2H = new MF('[2H]'); + const info2H = mf2H.getInfo(); expect(infoD).toStrictEqual(info2H); expect(infoD).toStrictEqual({ atoms: { H: 1 }, @@ -132,10 +132,10 @@ describe('MF', () => { }); it('T', () => { - let mfT = new MF('T'); - let infoT = mfT.getInfo(); - let mf3H = new MF('[3H]'); - let info3H = mf3H.getInfo(); + const mfT = new MF('T'); + const infoT = mfT.getInfo(); + const mf3H = new MF('[3H]'); + const info3H = mf3H.getInfo(); expect(infoT).toStrictEqual(info3H); expect(infoT).toStrictEqual({ @@ -149,7 +149,7 @@ describe('MF', () => { }); it('H2Si(OH)2', () => { - let mf = new MF('H2Si(OH)2'); + const mf = new MF('H2Si(OH)2'); expect(mf.getInfo()).toStrictEqual({ mass: 64.11607157056562, monoisotopicMass: 63.99805590271001, @@ -161,8 +161,8 @@ describe('MF', () => { }); it('Et3N.HCl', () => { - let mf = new MF('Et3N.HCl'); - let parts = mf.toParts(); + const mf = new MF('Et3N.HCl'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [ @@ -176,15 +176,15 @@ describe('MF', () => { ], ]); - let newMF = mf.toMF(); + const newMF = mf.toMF(); expect(newMF).toBe('C6H15N . HCl'); mf.canonize(); - let html = mf.toHtml(); + const html = mf.toHtml(); expect(html).toBe('C6H15N • HCl'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ parts: [ { @@ -214,13 +214,13 @@ describe('MF', () => { }); it('(ch3ch2)3n', () => { - let mf = new MF('(ch3ch2)3n', { ensureCase: true }); + const mf = new MF('(ch3ch2)3n', { ensureCase: true }); expect(mf.toMF()).toBe('C6H15N'); }); it('(Me2CH)3N no expand', () => { - let mf = new MF('(Me2CH)3N'); - let parts = mf.toParts({ expand: false }); + const mf = new MF('(Me2CH)3N'); + const parts = mf.toParts({ expand: false }); expect(parts).toStrictEqual([ [ @@ -231,10 +231,10 @@ describe('MF', () => { ], ]); - let newMF = mf.toMF(); + const newMF = mf.toMF(); expect(newMF).toBe('C3H3Me6N'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ mass: 143.27008211723435, monoisotopicMass: 143.16739968126, @@ -246,8 +246,8 @@ describe('MF', () => { }); it('(Me2CH)3N with expand', () => { - let mf = new MF('(Me2CH)3N'); - let parts = mf.toParts({ expand: true }); + const mf = new MF('(Me2CH)3N'); + const parts = mf.toParts({ expand: true }); expect(parts).toStrictEqual([ [ @@ -257,10 +257,10 @@ describe('MF', () => { ], ]); - let newMF = mf.toMF(); + const newMF = mf.toMF(); expect(newMF).toBe('C9H21N'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ mass: 143.27008211723435, monoisotopicMass: 143.16739968126, @@ -272,8 +272,8 @@ describe('MF', () => { }); it('(+)SO4(+)(-2)2', () => { - let mf = new MF('(+)SO4(+)(-2)2'); - let parts = mf.toParts(); + const mf = new MF('(+)SO4(+)(-2)2'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [ @@ -287,10 +287,10 @@ describe('MF', () => { expect(mf.toText()).toBe('⁺SO₄⁺⁻²₂'); expect(mf.toCanonicText()).toBe('O₄S⁻²'); - let neutralMF = mf.toNeutralMF(); + const neutralMF = mf.toNeutralMF(); expect(neutralMF).toBe('O4S'); - let info = mf.getInfo({ customUnsaturations: { S: 4 } }); + const info = mf.getInfo({ customUnsaturations: { S: 4 } }); expect(info).toStrictEqual({ monoisotopicMass: 95.95172965268, mass: 96.06240710340018, @@ -303,9 +303,9 @@ describe('MF', () => { }); it('customFieldName', () => { - let mf = new MF('Na+.Cl-'); + const mf = new MF('Na+.Cl-'); - let info = mf.getInfo({ emFieldName: 'em', msemFieldName: 'msem' }); + const info = mf.getInfo({ emFieldName: 'em', msemFieldName: 'msem' }); expect(info).toMatchObject({ parts: [ { @@ -348,8 +348,8 @@ describe('MF', () => { }); it('NC[13C][15N]2NN2', () => { - let mf = new MF('NC[13C][15N]2NN2'); - let parts = mf.toParts(); + const mf = new MF('NC[13C][15N]2NN2'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [ { kind: 'atom', value: 'C', multiplier: 1 }, @@ -359,7 +359,7 @@ describe('MF', () => { ], ]); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ monoisotopicMass: 111.01586865055, mass: 111.04112137534844, @@ -375,8 +375,8 @@ describe('MF', () => { }); it('DNA HODampDtmpDcmpDgmpH', () => { - let mf = new MF('HODampDtmpDgmpDcmpH'); - let info = mf.getInfo(); + const mf = new MF('HODampDtmpDgmpDcmpH'); + const info = mf.getInfo(); expect(info).toStrictEqual({ mass: 1253.8043977028433, monoisotopicMass: 1253.21310019311, @@ -388,8 +388,8 @@ describe('MF', () => { }); it('RNA HOAmpUmpH', () => { - let mf = new MF('HOAmpUmpH'); - let info = mf.getInfo(); + const mf = new MF('HOAmpUmpH'); + const info = mf.getInfo(); expect(info).toStrictEqual({ mass: 653.388021231099, monoisotopicMass: 653.08838712715, @@ -401,8 +401,8 @@ describe('MF', () => { }); it('CC{50,50}H', () => { - let mf = new MF('HC{50,50}C'); - let parts = mf.toParts(); + const mf = new MF('HC{50,50}C'); + const parts = mf.toParts(); expect(parts).toStrictEqual([ [ { kind: 'atom', value: 'C', multiplier: 1 }, @@ -418,7 +418,7 @@ describe('MF', () => { expect(mf.toMF()).toBe('CC{50,50}H'); expect(mf.toText()).toBe('HC⁽⁵⁰˙⁵⁰⁾C'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ monoisotopicMass: 25.00782503223, mass: 25.520354068326025, @@ -430,11 +430,11 @@ describe('MF', () => { }); it('H(+)(H+)-1H', () => { - let mf = new MF('H(+)(H+)-1H'); + const mf = new MF('H(+)(H+)-1H'); expect(mf.toMF()).toBe('H'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ atoms: { H: 1 }, charge: 0, @@ -446,11 +446,11 @@ describe('MF', () => { }); it('C10#1H20', () => { - let mf = new MF('C10#1H20'); + const mf = new MF('C10#1H20'); expect(mf.toMF()).toBe('C10H20'); - let info = mf.getInfo(); + const info = mf.getInfo(); expect(info).toStrictEqual({ mass: 140.26617404846803, monoisotopicMass: 140.1565006446, @@ -462,9 +462,39 @@ describe('MF', () => { }); it('2NH3 . 2HCl', () => { - let mf = new MF('2NH3 . 2HCl'); + const mf = new MF('2NH3 . 2HCl'); expect(mf.toMF()).toBe('H6N2 . H2Cl2'); expect(mf.toText()).toBe('2NH₃ • 2HCl'); }); + + it('Types getInfo default shape', () => { + const mf = new MF('C10#1H20'); + const info = mf.getInfo(); + expectTypeOf(info).toMatchTypeOf<{ monoisotopicMass: number }>(); + expectTypeOf(info).not.toMatchTypeOf<{ em: number }>(); + expect(info).toStrictEqual({ + mass: 140.26617404846803, + monoisotopicMass: 140.1565006446, + charge: 0, + mf: 'C10H20', + atoms: { C: 10, H: 20 }, + unsaturation: 1, + }); + }); + + it('Types getInfo custom shape', () => { + const mf = new MF('C10#1H20'); + const info = mf.getInfo({ emFieldName: 'em' as const }); + expectTypeOf(info).toMatchTypeOf<{ em: number }>(); + expectTypeOf(info).not.toMatchTypeOf<{ monoisotopicMass: number }>(); + expect(info).toStrictEqual({ + mass: 140.26617404846803, + em: 140.1565006446, + charge: 0, + mf: 'C10H20', + atoms: { C: 10, H: 20 }, + unsaturation: 1, + }); + }); });