src/format/options.ts   A
last analyzed

Complexity

Total Complexity 17
Complexity/F 0

Size

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