Skip to content

Commit e81cd60

Browse files
committed
fix: infinite loop in flatten
1 parent 8c7895c commit e81cd60

File tree

14 files changed

+297
-46
lines changed

14 files changed

+297
-46
lines changed

packages/cli/test/CLI.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,20 @@ export default function (
833833
},
834834
);
835835

836+
testRunner.add(
837+
'should support custom flatten separator using the flatten transform',
838+
async (t) => {
839+
const opts =
840+
'--delimiter , --flatten-objects --flatten-arrays --flatten-separator .';
841+
842+
const { stdout: csv } = await execAsync(
843+
`${cli} -i "${getFixturePath('/json/objectWithEmptyFields.json')}" ${opts}`,
844+
);
845+
846+
t.equal(csv, csvFixtures.objectWithEmptyFieldsStream);
847+
},
848+
);
849+
836850
testRunner.add(
837851
'should support multiple transforms and honor the order in which they are declared',
838852
async (t) => {

packages/node/test/AsyncParser.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,24 @@ export default function (
914914
},
915915
);
916916

917+
testRunner.add(
918+
'should support custom flatten separator using the flatten transform',
919+
async (t) => {
920+
const opts: ParserOptions = {
921+
delimiter: ';',
922+
transforms: [flatten({ separator: '.', arrays: true, objects: true })],
923+
};
924+
925+
const parser = new Parser(opts);
926+
const csv = await parseInput(
927+
parser,
928+
jsonFixtures.objectWithEmptyFields(),
929+
);
930+
931+
t.equal(csv, csvFixtures.objectWithEmptyFieldsStream);
932+
},
933+
);
934+
917935
testRunner.add(
918936
'should support multiple transforms and honor the order in which they are declared',
919937
async (t) => {

packages/node/test/AsyncParserInMemory.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,24 @@ export default function (
914914
},
915915
);
916916

917+
testRunner.add(
918+
'should support custom flatten separator using the flatten transform',
919+
async (t) => {
920+
const opts: ParserOptions = {
921+
delimiter: ';',
922+
transforms: [flatten({ separator: '.', arrays: true, objects: true })],
923+
};
924+
925+
const parser = new Parser(opts);
926+
const csv = await parseInput(
927+
parser,
928+
jsonFixtures.objectWithEmptyFields(),
929+
);
930+
931+
t.equal(csv, csvFixtures.objectWithEmptyFieldsStream);
932+
},
933+
);
934+
917935
testRunner.add(
918936
'should support multiple transforms and honor the order in which they are declared',
919937
async (t) => {

packages/node/test/Transform.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,24 @@ export default function (
900900
},
901901
);
902902

903+
testRunner.add(
904+
'should support custom flatten separator using the flatten transform',
905+
async (t) => {
906+
const opts: ParserOptions = {
907+
delimiter: ';',
908+
transforms: [flatten({ separator: '.', arrays: true, objects: true })],
909+
};
910+
911+
const parser = new Parser(opts);
912+
const csv = await parseInput(
913+
parser,
914+
jsonFixtures.objectWithEmptyFields(),
915+
);
916+
917+
t.equal(csv, csvFixtures.objectWithEmptyFieldsStream);
918+
},
919+
);
920+
903921
testRunner.add(
904922
'should support multiple transforms and honor the order in which they are declared',
905923
async (t) => {

packages/plainjs/src/utils.ts

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,60 @@ type GetFieldType<T, P> = P extends `${infer Left}.${infer Right}`
4141

4242
type PropertyName = string | number | symbol;
4343

44-
const rePropName = RegExp(
45-
// Match anything that isn't a dot or bracket.
46-
'[^.[\\]]+' +
47-
'|' +
48-
// Or match property names within brackets.
49-
'\\[(?:' +
50-
// Match a non-string expression.
51-
'([^"\'][^[]*)' +
52-
'|' +
53-
// Or match strings (supports escaping characters).
54-
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
55-
')\\]' +
56-
'|' +
57-
// Or match "" as the space between consecutive dots or empty brackets.
58-
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))',
59-
'g',
60-
);
44+
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/;
45+
const reIsPlainProp = /^\w*$/;
46+
const rePropName =
47+
/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
48+
const reEscapeChar = /\\(\\)?/g;
49+
50+
/**
51+
* Checks if `value` is a property name and not a property path.
52+
*
53+
* @private
54+
* @param {*} value The value to check.
55+
* @param {Object} [object] The object to query keys on.
56+
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
57+
*/
58+
function isKey<TObject extends object>(value: any, object: TObject): boolean {
59+
if (Array.isArray(value)) {
60+
return false;
61+
}
62+
const type = typeof value;
63+
if (
64+
type == 'number' ||
65+
type == 'symbol' ||
66+
type == 'boolean' ||
67+
value == null
68+
) {
69+
return true;
70+
}
71+
return (
72+
reIsPlainProp.test(value) ||
73+
!reIsDeepProp.test(value) ||
74+
(object != null && value in Object(object))
75+
);
76+
}
77+
78+
/**
79+
* Converts `string` to a property path array.
80+
*
81+
* @private
82+
* @param {string} string The string to convert.
83+
* @returns {Array} Returns the property path array.
84+
*/
85+
function stringToPath(string: string): string[] {
86+
const result = [];
87+
if (string.charCodeAt(0) === 46 /* . */) {
88+
result.push('');
89+
}
90+
string.replace(rePropName, (match, number, quote, subString) => {
91+
result.push(
92+
quote ? subString.replace(reEscapeChar, '$1') : number || match,
93+
);
94+
return match;
95+
});
96+
return result;
97+
}
6198

6299
/**
63100
* Casts `value` to a path array if it's not one.
@@ -75,13 +112,14 @@ function castPath<TPath extends string, TObject>(
75112
path: TPath,
76113
obj: TObject,
77114
): Exclude<GetFieldType<TObject, TPath>, null | undefined>;
78-
function castPath(value: string): string[] {
79-
const result: string[] = [];
80-
let match: RegExpExecArray | null;
81-
while ((match = rePropName.exec(value))) {
82-
result.push(match[3] ?? match[1]?.trim() ?? match[0]);
115+
function castPath<TObject extends object>(
116+
value: string,
117+
object: TObject,
118+
): string[] {
119+
if (Array.isArray(value)) {
120+
return value;
83121
}
84-
return result;
122+
return isKey(value, object) ? [value] : stringToPath(String(value));
85123
}
86124

87125
export function getProp<TObject extends object, TKey extends keyof TObject>(

packages/plainjs/test/Parser.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,24 @@ export default function (
791791
},
792792
);
793793

794+
testRunner.add(
795+
'should support custom flatten separator using the flatten transform',
796+
async (t) => {
797+
const opts: ParserOptions = {
798+
delimiter: ';',
799+
transforms: [flatten({ separator: '.', arrays: true, objects: true })],
800+
};
801+
802+
const parser = new Parser(opts);
803+
const csv = await parseInput(
804+
parser,
805+
jsonFixtures.objectWithEmptyFields(),
806+
);
807+
808+
t.equal(csv, csvFixtures.objectWithEmptyFields);
809+
},
810+
);
811+
794812
testRunner.add(
795813
'should support multiple transforms and honor the order in which they are declared',
796814
async (t) => {

packages/plainjs/test/StreamParser.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,24 @@ export default function (
872872
},
873873
);
874874

875+
testRunner.add(
876+
'should support custom flatten separator using the flatten transform',
877+
async (t) => {
878+
const opts: ParserOptions = {
879+
delimiter: ';',
880+
transforms: [flatten({ separator: '.', arrays: true, objects: true })],
881+
};
882+
883+
const parser = new Parser(opts);
884+
const csv = await parseInput(
885+
parser,
886+
jsonFixtures['objectWithEmptyFields'](),
887+
);
888+
889+
t.equal(csv, csvFixtures.objectWithEmptyFieldsStream);
890+
},
891+
);
892+
875893
testRunner.add(
876894
'should support multiple transforms and honor the order in which they are declared',
877895
async (t) => {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"a..b";"anything"
2+
1;
3+
;2
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"a..b"
2+
1
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"a": {
4+
"": {
5+
"b": 1
6+
}
7+
}
8+
},
9+
{
10+
"anything": 2
11+
}
12+
]

0 commit comments

Comments
 (0)