Passed
Push — main ( 5cacf5...da0f78 )
by Pedro
02:28
created

src/format/options.ts   A

Complexity

Total Complexity 17
Complexity/F 0

Size

Lines of Code 164
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 17
eloc 82
mnd 17
bc 17
fnc 0
dl 0
loc 164
ccs 36
cts 36
cp 1
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
/*
2
 * decimal.js-i18n v0.2.6
3
 * Full internationalization support for decimal.js.
4
 * MIT License
5
 * Copyright (c) 2022 Pedro José Batista <[email protected]>
6
 * https://github.com/pjbatista/decimal.js-i18n
7
 */
8
import type BaseFormatOptions from "./baseOptions";
9 1
import { DECIMAL_LIMIT, DEFAULT_OPTIONS, ECMA_LIMIT } from "./constants";
10
import type FormatLocaleMatcher from "./localeMatcher";
11
import FormatNotation from "./notation";
12
import type ResolvedFormatOptions from "./resolvedFormatOptions";
13
import FormatStyle from "./style";
14
15
type DigitsProperty =
16
    | "maximumFractionDigits"
17
    | "minimumFractionDigits"
18
    | "minimumIntegerDigits"
19
    | "maximumSignificantDigits"
20
    | "minimumSignificantDigits";
21
type DigitsPropertyCallback<T> = (property: DigitsProperty, factor: number) => T;
22
23
// A generator function to be used to access or modify all digits properties
24
//  -> factor is a simple index shifting value (fractions go from 0 to 1e9-1, other from 1 to 1e9)
25 1
const forEachDigitsPropertyGenerator = function* <T>(callback: DigitsPropertyCallback<T>) {
26 3497
    yield callback("maximumFractionDigits", -1);
27 3497
    yield callback("minimumFractionDigits", -1);
28 3497
    yield callback("minimumIntegerDigits", 0);
29 3497
    yield callback("maximumSignificantDigits", 0);
30 3497
    yield callback("minimumSignificantDigits", 0);
31
};
32
33
// Spreads the generated values of `forEachDigitsPropertyGenerator` to an array
34 3497
const forEachDigitsProperty = <T>(callback: DigitsPropertyCallback<T>) => [...forEachDigitsPropertyGenerator(callback)];
35
36
/**
37
 * Creates and returns a new object with the extension of an existing set of options with any number of modifiers.
38
 *
39
 * @param options Object to be extended.
40
 * @param modifiers Anu number of objects containing the modifiers to the `options`.
41
 * @returns An extended format options.
42
 */
43 1
export const extend = <T extends FormatOptions | Intl.NumberFormatOptions>(options: T, ...modifiers: T[]) =>
44 2328
    Object.assign({ ...options }, ...modifiers) as T;
45
46
/**
47
 * Creates and returns an object based on a `Intl.ResolvedNumberFormatOptions`, however re-expanding it to
48
 * beyond the limits of the ECMA-specification.
49
 *
50
 * If the user wants 999999999 fraction digits, they can have it (though it would be larger than 2GBs and take
51
 * quite a while to calculate at in an average PC).
52
 *
53
 * @param options Decimal format options to be merged with the ECMA resolved options.
54
 * @param ecma Object resulting from `Intl.NumberFormat.resolvedOptions()`.
55
 * @returns A resolved decimal format options.
56
 */
57 1
export const resolve = <TNotation extends FormatNotation = "standard", TStyle extends FormatStyle = "decimal">(
58
    options: FormatOptions<TNotation, TStyle>,
59
    ecma: Intl.ResolvedNumberFormatOptions,
60
) => {
61 1164
    const result = { ...ecma } as ResolvedFormatOptions<
62
        typeof ecma.notation extends FormatNotation ? typeof ecma.notation : TNotation,
63
        typeof ecma.style extends FormatStyle ? typeof ecma.style : TStyle
64
    >;
65
66 1164
    forEachDigitsProperty(property => {
67 5820
        if (!(property in result)) {
68 2328
            return;
69
        }
70
71
        // Gets the maximum in between both objects and the defaults
72 3492
        result[property] = Math.max(options[property] ?? 0, result[property] ?? 0, DEFAULT_OPTIONS[property]);
73
    });
74
75
    // Parsed from group 1?
76 1164
    if (typeof result.maximumFractionDigits === "number") {
77 1160
        result.maximumFractionDigits = Math.max(result.minimumFractionDigits!, result.maximumFractionDigits);
78
    }
79
    // Or from group 2?
80
    else {
81 4
        result.maximumSignificantDigits = Math.max(result.minimumSignificantDigits!, result.maximumSignificantDigits!);
82
    }
83
84
    // Our custom defaults:
85 1164
    result.rounding ??= options.rounding ?? DEFAULT_OPTIONS.rounding;
86 1164
    result.trailingZeroDisplay ??= options.trailingZeroDisplay ?? DEFAULT_OPTIONS.trailingZeroDisplay;
87
88 1164
    return result;
89
};
90
91
/**
92
 * Creates and returns an `Intl.NumberFormatOptions` object, copying from a {@link FormatOptions} object with
93
 * its digits limited to ECMA-specification's range.
94
 *
95
 * @template TNotation Numeric notation of formatting.
96
 * @template TStyle Numeric style of formatting.
97
 * @param options Decimal format options used as a baseline for the new object.
98
 * @returns A new `Intl.NumberFormatOptions` object.
99
 */
100 1
export const toEcma = <TNotation extends FormatNotation = "standard", TStyle extends FormatStyle = "decimal">(
101
    options: FormatOptions<TNotation, TStyle>,
102
) => {
103 1164
    const result = { ...options };
104
105 1164
    forEachDigitsProperty((property, factor) => {
106
        // Check if it already exists to prevent creating unnecessary properties
107 5820
        if (property in result && Number(result[property]) > ECMA_LIMIT + factor) {
108 16
            result[property] = ECMA_LIMIT + factor;
109
        }
110
    });
111
112 1164
    return result as Intl.NumberFormatOptions;
113
};
114
115
/**
116
 * Validates whether the given {@link FormatOptions} contains acceptable values.
117
 *
118
 * It specially checks if the digits properties are within `decimal.js`' range, which is 1e9±1.
119
 *
120
 * @template TNotation Numeric notation of formatting.
121
 * @template TStyle Numeric style of formatting.
122
 * @param options Decimal format options to be validated.
123
 * @returns `true` if all properties are valid. Otherwise, an array with the invalid properties names.
124
 */
125 1
export const validate = <TNotation extends FormatNotation = "standard", TStyle extends FormatStyle = "decimal">(
126
    options: FormatOptions<TNotation, TStyle>,
127
) => {
128 1169
    const result = forEachDigitsProperty((property, factor) => {
129 5845
        if (property in options && Number(options[property]) > DECIMAL_LIMIT + factor) {
130 6
            return property;
131
        }
132 5839
        return true;
133
    });
134
135 5835
    if (result.every(entry => entry === true)) {
136 1164
        return true;
137
    }
138
139 25
    return result.filter(entry => entry !== true) as DigitsProperty[];
140
};
141
142
/**
143
 * Object used to configure a `Decimal.Format` object; the following properties fall into two groups:
144
 *
145
 * - {@link minimumIntegerDigits}, {@link minimumFractionDigits}, and {@link maximumFractionDigits} in one group;
146
 * - {@link minimumSignificantDigits} and {@link maximumSignificantDigits} in the other.
147
 *
148
 * If at least one property from the second group is defined, then the first group is ignored.
149
 *
150
 * @template TNotation Numeric notation of formatting.
151
 * @template TStyle Numeric style of formatting.
152
 */
153
export interface FormatOptions<TNotation extends FormatNotation = "standard", TStyle extends FormatStyle = "decimal">
154
    extends Partial<BaseFormatOptions<TNotation, TStyle>> {
155
    /**
156
     * The locale matching algorithm to use. Possible values are "`lookup`" and "`best fit`"; the default is
157
     * "`best fit`". For information about this option, see the [Intl page on
158
     * MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl).
159
     */
160
    localeMatcher?: FormatLocaleMatcher | undefined;
161
}
162
163
export default FormatOptions;
164