Skip to content

Commit 99ac295

Browse files
authored
refactor(ini): limit INI value type to possible allowed INI values. (#6495)
1 parent cb0f8f0 commit 99ac295

File tree

5 files changed

+57
-57
lines changed

5 files changed

+57
-57
lines changed

ini/_ini_map.ts

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,21 @@ export interface FormattingOptions {
1717
pretty?: boolean;
1818
}
1919

20+
/** Possible INI value types. */
21+
type IniValue =
22+
| string
23+
| number
24+
| boolean
25+
| null
26+
| undefined;
27+
28+
/** Represents an INI section. */
29+
type IniSection = Record<string, IniValue>;
30+
2031
/** Options for parsing INI strings. */
21-
// deno-lint-ignore no-explicit-any
22-
interface ParseOptions<T = any> {
32+
interface ParseOptions {
2333
/** Provide custom parsing of the value in a key/value pair. */
24-
reviver?: ReviverFunction<T>;
34+
reviver?: ReviverFunction;
2535
}
2636

2737
/** Function for replacing JavaScript values with INI string values. */
@@ -33,12 +43,11 @@ export type ReplacerFunction = (
3343
) => string;
3444

3545
/** Function for replacing INI values with JavaScript values. */
36-
// deno-lint-ignore no-explicit-any
37-
export type ReviverFunction<T = any> = (
46+
export type ReviverFunction = (
3847
key: string,
3948
value: string,
4049
section?: string,
41-
) => T;
50+
) => unknown;
4251

4352
const ASSIGNMENT_MARK = "=";
4453

@@ -54,8 +63,7 @@ const NON_WHITESPACE_REGEXP = /\S/;
5463
/**
5564
* Class implementation for fine control of INI data structures.
5665
*/
57-
// deno-lint-ignore no-explicit-any
58-
export class IniMap<T = any> {
66+
export class IniMap {
5967
#global = new Map<string, LineValue>();
6068
#sections = new Map<string, LineSection>();
6169
#lines: Line[] = [];
@@ -76,7 +84,7 @@ export class IniMap<T = any> {
7684
* @param value The value to set
7785
* @returns The map object itself
7886
*/
79-
set(key: string, value: unknown): this;
87+
set(key: string, value: IniValue): this;
8088
/**
8189
* Set the value of a section key in the INI.
8290
*
@@ -85,9 +93,12 @@ export class IniMap<T = any> {
8593
* @param value The value to set
8694
* @return The map object itself
8795
*/
88-
set(section: string, key: string, value: unknown): this;
89-
// deno-lint-ignore no-explicit-any
90-
set(keyOrSection: string, valueOrKey: any, value?: any): this {
96+
set(section: string, key: string, value: IniValue): this;
97+
set(
98+
keyOrSection: string,
99+
valueOrKey: string | IniValue,
100+
value?: IniValue,
101+
): this {
91102
if (typeof valueOrKey === "string" && value !== undefined) {
92103
const section = this.#getOrCreateSection(keyOrSection);
93104
const exists = section.map.get(valueOrKey);
@@ -230,8 +241,8 @@ export class IniMap<T = any> {
230241
*
231242
* @returns The object equivalent to this {@code IniMap}
232243
*/
233-
toObject(): Record<string, T | Record<string, T>> {
234-
const obj: Record<string, T | Record<string, T>> = {};
244+
toObject<T extends object>(): T {
245+
const obj: T = {} as T;
235246

236247
for (const { key, val } of this.#global.values()) {
237248
Object.defineProperty(obj, key, {
@@ -242,7 +253,7 @@ export class IniMap<T = any> {
242253
});
243254
}
244255
for (const { sec, map } of this.#sections.values()) {
245-
const section: Record<string, T> = {};
256+
const section: IniSection = {};
246257
Object.defineProperty(obj, sec, {
247258
value: section,
248259
writable: true,
@@ -269,9 +280,8 @@ export class IniMap<T = any> {
269280
* @returns Ini string
270281
*/
271282
toString(replacer?: ReplacerFunction): string {
272-
const replacerFunc: ReplacerFunction = typeof replacer === "function"
273-
? replacer
274-
: (_key, value, _section) => `${value}`;
283+
replacer ??= (_key, value, _section) => `${value}`;
284+
275285
const pretty = this.#formatting?.pretty ?? false;
276286
const assignment = pretty ? ` ${ASSIGNMENT_MARK} ` : ASSIGNMENT_MARK;
277287
const lines = this.#lines;
@@ -284,7 +294,7 @@ export class IniMap<T = any> {
284294
return `[${line.sec}]`;
285295
case "value":
286296
return line.key + assignment +
287-
replacerFunc(line.key, line.val, line.sec);
297+
replacer(line.key, line.val, line.sec);
288298
}
289299
}).join(this.#formatting?.lineBreak ?? "\n");
290300
}
@@ -300,14 +310,14 @@ export class IniMap<T = any> {
300310
if (typeof text !== "string") {
301311
throw new SyntaxError(`Unexpected token ${text} in INI at line 0`);
302312
}
303-
const reviverFunc: ReviverFunction = typeof reviver === "function"
304-
? reviver
305-
: (_key, value, _section) => {
306-
if (!isNaN(+value) && !value.includes('"')) return +value;
307-
if (value === "null") return null;
308-
if (value === "true" || value === "false") return value === "true";
309-
return trimQuotes(value);
310-
};
313+
314+
reviver ??= (_key, value, _section) => {
315+
if (!isNaN(+value) && !value.includes('"')) return +value;
316+
if (value === "null") return null;
317+
if (value === "true" || value === "false") return value === "true";
318+
return trimQuotes(value);
319+
};
320+
311321
let lineNumber = 1;
312322
let currentSection: LineSection | undefined;
313323

@@ -368,7 +378,7 @@ export class IniMap<T = any> {
368378
num: lineNumber,
369379
sec: currentSection.sec,
370380
key,
371-
val: reviverFunc(key, value, currentSection.sec),
381+
val: reviver(key, value, currentSection.sec),
372382
};
373383
currentSection.map.set(key, lineValue);
374384
this.#lines.push(lineValue);
@@ -378,7 +388,7 @@ export class IniMap<T = any> {
378388
type: "value",
379389
num: lineNumber,
380390
key,
381-
val: reviverFunc(key, value),
391+
val: reviver(key, value),
382392
};
383393
this.#global.set(key, lineValue);
384394
this.#lines.push(lineValue);
@@ -409,22 +419,19 @@ export class IniMap<T = any> {
409419
* @param formatting The options to use
410420
* @returns The parsed {@code IniMap}
411421
*/
422+
static from(input: object, formatting?: FormattingOptions): IniMap;
412423
static from(
413-
input: Record<string, unknown>,
414-
formatting?: FormattingOptions,
415-
): IniMap;
416-
static from(
417-
// deno-lint-ignore no-explicit-any
418-
input: Record<string, any> | string,
424+
input: string | object,
419425
formatting?: ParseOptions & FormattingOptions,
420426
): IniMap {
421427
const ini = new IniMap(formatting);
422428
if (typeof input === "object" && input !== null) {
423-
// deno-lint-ignore no-explicit-any
424-
const isRecord = (val: any): val is Record<string, any> =>
429+
const isRecord = (val: unknown): val is IniSection =>
425430
typeof val === "object" && val !== null;
426-
// deno-lint-ignore no-explicit-any
427-
const sort = ([_a, valA]: [string, any], [_b, valB]: [string, any]) => {
431+
const sort = (
432+
[_a, valA]: [string, unknown],
433+
[_b, valB]: [string, unknown],
434+
) => {
428435
if (isRecord(valA)) return 1;
429436
if (isRecord(valB)) return -1;
430437
return 0;
@@ -499,8 +506,7 @@ interface LineValue {
499506
num: number;
500507
sec?: string;
501508
key: string;
502-
// deno-lint-ignore no-explicit-any
503-
val: any;
509+
val: unknown;
504510
}
505511

506512
type Line = LineComment | LineSection | LineValue;

ini/parse.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import { IniMap, type ReviverFunction } from "./_ini_map.ts";
55
export type { ReviverFunction };
66

77
/** Options for {@linkcode parse}. */
8-
// deno-lint-ignore no-explicit-any
9-
export interface ParseOptions<T = any> {
8+
export interface ParseOptions {
109
/**
1110
* Provide custom parsing of the value in a key/value pair. Similar to the
1211
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#reviver | reviver}
1312
* function in {@linkcode JSON.parse}.
1413
*/
15-
reviver?: ReviverFunction<T>;
14+
reviver?: ReviverFunction;
1615
}
1716

1817
/**
@@ -80,10 +79,9 @@ export interface ParseOptions<T = any> {
8079
* @typeParam T The type of the value
8180
* @return The parsed object
8281
*/
83-
// deno-lint-ignore no-explicit-any
84-
export function parse<T = any>(
82+
export function parse<T extends object>(
8583
text: string,
86-
options?: ParseOptions<T>,
87-
): Record<string, T | Record<string, T>> {
88-
return IniMap.from(text, options).toObject();
84+
options?: ParseOptions,
85+
): T {
86+
return IniMap.from(text, options).toObject<T>();
8987
}

ini/parse_test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010

1111
function assertValidParse(
1212
text: string,
13-
expected: unknown,
13+
expected: object,
1414
options?: ParseOptions,
1515
) {
1616
assertEquals(parse(text, options), expected);

ini/stringify.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ export type { ReplacerFunction };
8686
* @param options The option to use
8787
* @returns The INI string
8888
*/
89-
export function stringify(
90-
// deno-lint-ignore no-explicit-any
91-
object: any,
92-
options?: StringifyOptions,
93-
): string {
89+
export function stringify(object: object, options?: StringifyOptions): string {
9490
return IniMap.from(object, options).toString(options?.replacer);
9591
}

ini/stringify_test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { stringify, type StringifyOptions } from "./mod.ts";
44
import { assertEquals } from "@std/assert";
55

66
function assertValidStringify(
7-
obj: unknown,
8-
expected: unknown,
7+
obj: object,
8+
expected: string,
99
options?: StringifyOptions,
1010
) {
1111
assertEquals(stringify(obj, options), expected);

0 commit comments

Comments
 (0)