Skip to content

Commit

Permalink
fix: keys are now correctly enocded in dict and table formats for `ou…
Browse files Browse the repository at this point in the history
…tputFormatter`

fix: keys are now correctly enocded in dict formats for `outputFormatter`

fix: keys are now correctly enocded in table formats for `outputFormatter`

[ci-skip]
  • Loading branch information
amydevs committed Nov 9, 2023
1 parent 54493f8 commit 84e7c34
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 13 deletions.
56 changes: 44 additions & 12 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ function encodeEscapedReplacer(_key: string, value: any) {
if (typeof value === 'string') {
return encodeEscaped(value);
}
if (typeof value === 'object' && !Array.isArray(value)) {
for (const valueKey of Object.keys(value)) {
if (typeof valueKey === 'string') {
const newValueKey = encodeEscaped(valueKey);
const valueKeyValue = value[valueKey];
delete value[valueKey];
// This is done in case it is defined as `__proto__`
Object.defineProperty(value, newValueKey, {
value: valueKeyValue,
writable: true,
enumerable: true,
configurable: true,
});
}
}
}
return value;
}

Expand Down Expand Up @@ -252,16 +268,16 @@ function outputFormatterTable(
: Object.keys(options.columns)
: undefined;

// Initialize maxColumnLengths with header lengths if headers are provided
// Initialize maxColumnLengths with header lengths if headers with lengths are provided
if (optionColumns != null) {
for (const column of optionColumns) {
maxColumnLengths[column] = Math.max(
options?.columns?.[column] ?? 0,
column.length,
);
maxColumnLengths[column] = options?.columns?.[column];
}
}

// Map<originalColumn, encodedColumn>
const encodedColumns: Map<string, string> = new Map();

// Precompute max column lengths by iterating over the rows first
for (const row of rows) {
for (const column in options?.columns ?? row) {
Expand All @@ -279,6 +295,16 @@ function outputFormatterTable(
maxColumnLengths[column] || 0,
cellLength, // Use the length of the encoded value
);
// If headers are included, we need to check if the column header length is bigger
if (includeHeaders && !encodedColumns.has(column)) {
// This only has to be done once, so if the column already exists in the map, don't bother
const encodedColumn = encodeEscapedWrapped(column);
encodedColumns.set(column, encodedColumn);
maxColumnLengths[column] = Math.max(
maxColumnLengths[column] || 0,
encodedColumn.length,
);
}
}
}

Expand All @@ -295,7 +321,9 @@ function outputFormatterTable(
options!.columns![column] = maxColumnLength;
}
if (includeHeaders) {
output += column.padEnd(maxColumnLength);
output += (encodedColumns.get(column) ?? column).padEnd(
maxColumnLength,
);
if (i !== optionColumns.length - 1) {
output += '\t';
} else {
Expand Down Expand Up @@ -325,13 +353,17 @@ function outputFormatterTable(
function outputFormatterDict(data: POJO): string {
let output = '';
let maxKeyLength = 0;
// Array<[originalKey, encodedKey]>
const keypairs: Array<[string, string]> = [];
for (const key in data) {
if (key.length > maxKeyLength) {
maxKeyLength = key.length;
const encodedKey = encodeEscapedWrapped(key);
keypairs.push([key, encodedKey]);
if (encodedKey.length > maxKeyLength) {
maxKeyLength = encodedKey.length;
}
}
for (const key in data) {
let value = data[key];
for (const [originalKey, encodedKey] of keypairs) {
let value = data[originalKey];
if (value == null) {
value = '';
}
Expand All @@ -345,8 +377,8 @@ function outputFormatterDict(data: POJO): string {
value = value.replace(/(?:\r\n|\n)$/, '');
value = value.replace(/(\r\n|\n)/g, '$1\t');

const padding = ' '.repeat(maxKeyLength - key.length);
output += `${key}${padding}\t${value}\n`;
const padding = ' '.repeat(maxKeyLength - encodedKey.length);
output += `${encodedKey}${padding}\t${value}\n`;
}
return output;
}
Expand Down
39 changes: 38 additions & 1 deletion tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ describe('bin/utils', () => {
data: { [key]: value },
});

const expectedKey = binUtils.encodeEscapedWrapped(key);

// Construct the expected output
let expectedValue = value;
expectedValue = binUtils.encodeEscapedWrapped(expectedValue);
Expand All @@ -155,7 +157,7 @@ describe('bin/utils', () => {
...Object.keys({ [key]: value }).map((k) => k.length),
);
const padding = ' '.repeat(maxKeyLength - key.length);
const expectedOutput = `${key}${padding}\t${expectedValue}\n`;
const expectedOutput = `${expectedKey}${padding}\t${expectedValue}\n`;
// Assert that the formatted output matches the expected output
expect(formattedOutput).toBe(expectedOutput);
},
Expand Down Expand Up @@ -291,4 +293,39 @@ describe('bin/utils', () => {
);
},
);
testUtils.testIf(testUtils.isTestPlatformEmpty)(
'encodeEscapedReplacer should encode all escapable characters',
() => {
fc.assert(
fc.property(
stringWithNonPrintableCharsArb,
stringWithNonPrintableCharsArb,
(key, value) => {
const encodedKey = binUtils.encodeEscaped(key);
const encodedValue = binUtils.encodeEscaped(value);
const object = {
[key]: value,
[key]: {
[key]: value,
},
[key]: [value],
};
const encodedObject = {
[encodedKey]: encodedValue,
[encodedKey]: {
[encodedKey]: encodedValue,
},
[encodedKey]: [encodedValue],
};
const output = JSON.stringify(
object,
binUtils.encodeEscapedReplacer,
);
expect(JSON.parse(output)).toEqual(encodedObject);
},
),
{ numRuns: 100 }, // Number of times to run the test
);
},
);
});

0 comments on commit 84e7c34

Please sign in to comment.