• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

C2FO / fast-csv / 13550863665

26 Feb 2025 06:30PM CUT coverage: 95.907%. Remained the same
13550863665

Pull #1101

github

web-flow
Merge 78434839f into 947bf442a
Pull Request #1101: chore(deps): update node.js to v22

325 of 353 branches covered (92.07%)

Branch coverage included in aggregate %.

753 of 771 relevant lines covered (97.67%)

2176197.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.51
/packages/format/src/formatter/RowFormatter.ts
1
import isFunction from 'lodash.isfunction';
60✔
2
import { FormatterOptions } from '../FormatterOptions';
3
import { FieldFormatter } from './FieldFormatter';
60✔
4
import { isSyncTransform, Row, RowArray, RowHashArray, RowTransformCallback, RowTransformFunction } from '../types';
60✔
5

6
type RowFormatterTransform<I extends Row, O extends Row> = (row: I, cb: RowTransformCallback<O>) => void;
7

8
type RowFormatterCallback = (error: Error | null, data?: RowArray) => void;
9

10
export class RowFormatter<I extends Row, O extends Row> {
60✔
11
    private static isRowHashArray(row: Row): row is RowHashArray {
12
        if (Array.isArray(row)) {
2,196✔
13
            return Array.isArray(row[0]) && row[0].length === 2;
1,842✔
14
        }
15
        return false;
354✔
16
    }
17

18
    private static isRowArray(row: Row): row is RowArray {
19
        return Array.isArray(row) && !this.isRowHashArray(row);
480✔
20
    }
21

22
    // get headers from a row item
23
    private static gatherHeaders(row: Row): string[] {
24
        if (RowFormatter.isRowHashArray(row)) {
816✔
25
            // lets assume a multi-dimesional array with item 0 being the header
26
            return row.map((it): string => {
222✔
27
                return it[0];
444✔
28
            });
29
        }
30
        if (Array.isArray(row)) {
594✔
31
            return row;
240✔
32
        }
33
        return Object.keys(row);
354✔
34
    }
35

36
    // eslint-disable-next-line @typescript-eslint/no-shadow
37
    private static createTransform<I extends Row, O extends Row>(
38
        transformFunction: RowTransformFunction<I, O>,
39
    ): RowFormatterTransform<I, O> {
40
        if (isSyncTransform(transformFunction)) {
222✔
41
            return (row: I, cb: RowTransformCallback<O>): void => {
162✔
42
                let transformedRow = null;
354✔
43
                try {
354✔
44
                    transformedRow = transformFunction(row);
354✔
45
                } catch (e) {
46
                    return cb(e);
×
47
                }
48
                return cb(null, transformedRow);
354✔
49
            };
50
        }
51
        return (row: I, cb: RowTransformCallback<O>): void => {
60✔
52
            transformFunction(row, cb);
60✔
53
        };
54
    }
55

56
    private readonly formatterOptions: FormatterOptions<I, O>;
57

58
    private readonly fieldFormatter: FieldFormatter<I, O>;
59

60
    private readonly shouldWriteHeaders: boolean;
61

62
    private _rowTransform?: RowFormatterTransform<I, O>;
63

64
    private headers: string[] | null;
65

66
    private hasWrittenHeaders: boolean;
67

68
    private rowCount = 0;
1,008✔
69

70
    public constructor(formatterOptions: FormatterOptions<I, O>) {
71
        this.formatterOptions = formatterOptions;
1,008✔
72
        this.fieldFormatter = new FieldFormatter(formatterOptions);
1,008✔
73

74
        this.headers = formatterOptions.headers;
1,008✔
75
        this.shouldWriteHeaders = formatterOptions.shouldWriteHeaders;
1,008✔
76
        this.hasWrittenHeaders = false;
1,008✔
77
        if (this.headers !== null) {
1,008✔
78
            this.fieldFormatter.headers = this.headers;
120✔
79
        }
80
        if (formatterOptions.transform) {
1,008✔
81
            this.rowTransform = formatterOptions.transform;
198✔
82
        }
83
    }
84

85
    public set rowTransform(transformFunction: RowTransformFunction<I, O>) {
86
        if (!isFunction(transformFunction)) {
228✔
87
            throw new TypeError('The transform should be a function');
6✔
88
        }
89
        this._rowTransform = RowFormatter.createTransform(transformFunction);
222✔
90
    }
91

92
    public format(row: I, cb: RowFormatterCallback): void {
93
        this.callTransformer(row, (err, transformedRow?: Row): void => {
1,848✔
94
            if (err) {
1,824✔
95
                return cb(err);
18✔
96
            }
97
            if (!row) {
1,806!
98
                return cb(null);
×
99
            }
100
            const rows = [];
1,806✔
101
            if (transformedRow) {
1,806✔
102
                const { shouldFormatColumns, headers } = this.checkHeaders(transformedRow);
1,806✔
103
                if (this.shouldWriteHeaders && headers && !this.hasWrittenHeaders) {
1,806✔
104
                    rows.push(this.formatColumns(headers, true));
798✔
105
                    this.hasWrittenHeaders = true;
798✔
106
                }
107
                if (shouldFormatColumns) {
1,806✔
108
                    const columns = this.gatherColumns(transformedRow);
1,608✔
109
                    rows.push(this.formatColumns(columns, false));
1,608✔
110
                }
111
            }
112
            return cb(null, rows);
1,806✔
113
        });
114
    }
115

116
    public finish(cb: RowFormatterCallback): void {
117
        const rows = [];
756✔
118
        // check if we should write headers and we didnt get any rows
119
        if (this.formatterOptions.alwaysWriteHeaders && this.rowCount === 0) {
756✔
120
            if (!this.headers) {
30✔
121
                return cb(new Error('`alwaysWriteHeaders` option is set to true but `headers` option not provided.'));
12✔
122
            }
123
            rows.push(this.formatColumns(this.headers, true));
18✔
124
        }
125
        if (this.formatterOptions.includeEndRowDelimiter) {
744✔
126
            rows.push(this.formatterOptions.rowDelimiter);
54✔
127
        }
128
        return cb(null, rows);
744✔
129
    }
130

131
    // check if we need to write header return true if we should also write a row
132
    // could be false if headers is true and the header row(first item) is passed in
133
    private checkHeaders(row: Row): { headers?: string[] | null; shouldFormatColumns: boolean } {
134
        if (this.headers) {
1,806✔
135
            // either the headers were provided by the user or we have already gathered them.
136
            return { shouldFormatColumns: true, headers: this.headers };
990✔
137
        }
138
        const headers = RowFormatter.gatherHeaders(row);
816✔
139
        this.headers = headers;
816✔
140
        this.fieldFormatter.headers = headers;
816✔
141
        if (!this.shouldWriteHeaders) {
816✔
142
            // if we are not supposed to write the headers then
143
            // always format the columns
144
            return { shouldFormatColumns: true, headers: null };
108✔
145
        }
146
        // if the row is equal to headers dont format
147
        if (Array.isArray(row) && headers.every((header, i): boolean => header === row[i])) {
708✔
148
            return { shouldFormatColumns: false, headers };
198✔
149
        }
150
        return { shouldFormatColumns: true, headers };
510✔
151
    }
152

153
    // todo change this method to unknown[]
154
    private gatherColumns(row: Row): string[] {
155
        if (this.headers === null) {
1,608!
156
            throw new Error('Headers is currently null');
×
157
        }
158
        if (!Array.isArray(row)) {
1,608✔
159
            return this.headers.map((header): string => {
708✔
160
                return row[header] as string;
1,440✔
161
            });
162
        }
163
        if (RowFormatter.isRowHashArray(row)) {
900✔
164
            return this.headers.map((header, i): string => {
420✔
165
                const col = row[i] as unknown as string;
840✔
166
                if (col) {
840✔
167
                    return col[1];
834✔
168
                }
169
                return '';
6✔
170
            });
171
        }
172
        // if its a one dimensional array and headers were not provided
173
        // then just return the row
174
        if (RowFormatter.isRowArray(row) && !this.shouldWriteHeaders) {
480✔
175
            return row;
84✔
176
        }
177
        return this.headers.map((header, i): string => {
396✔
178
            return row[i];
810✔
179
        });
180
    }
181

182
    private callTransformer(row: I, cb: RowTransformCallback<O>): void {
183
        if (!this._rowTransform) {
1,848✔
184
            return cb(null, row as unknown as O);
1,434✔
185
        }
186
        return this._rowTransform(row, cb);
414✔
187
    }
188

189
    private formatColumns(columns: string[], isHeadersRow: boolean): string {
190
        const formattedCols = columns
2,424✔
191
            .map((field, i): string => {
192
                return this.fieldFormatter.format(field, i, isHeadersRow);
4,872✔
193
            })
194
            .join(this.formatterOptions.delimiter);
195
        const { rowCount } = this;
2,424✔
196
        this.rowCount += 1;
2,424✔
197
        if (rowCount) {
2,424✔
198
            return [this.formatterOptions.rowDelimiter, formattedCols].join('');
1,488✔
199
        }
200
        return formattedCols;
936✔
201
    }
202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc