formio/node_modules/csv-stringify/lib/index.js   F
last analyzed

Complexity

Total Complexity 102
Complexity/F 6.8

Size

Lines of Code 355
Function Count 15

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
wmc 102
nc 884736
mnd 7
bc 89
fnc 15
dl 0
loc 355
rs 1.5789
bpm 5.9333
cpm 6.8
noi 6
c 0
b 0
f 0

7 Functions

Rating   Name   Duplication   Size   Complexity  
B Stringifier.headers 0 28 5
F index.js ➔ Stringifier 0 88 22
A Stringifier.end 0 6 2
A Stringifier._transform 0 4 1
C Stringifier.write 0 41 13
C module.exports 0 59 11
D Stringifier.stringify 0 81 36

How to fix   Complexity   

Complexity

Complex classes like formio/node_modules/csv-stringify/lib/index.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// Generated by CoffeeScript 2.0.1
2
// # CSV Stringifier
3
4
// Please look at the [README], the [samples] and the [tests] for additional
5
// information.
6
var Stringifier, get, stream, util;
7
8
stream = require('stream');
9
10
util = require('util');
11
12
get = require('lodash.get');
13
14
// ## Usage
15
16
// This module export a function as its main entry point and return a transform
17
// stream.
18
19
// Refers to the [official prject documentation](http://csv.adaltas.com/stringify/)
20
// on how to call this function.
21
module.exports = function() {
22
  var callback, chunks, data, options, stringifier;
23
  if (arguments.length === 3) {
24
    data = arguments[0];
25
    options = arguments[1];
26
    callback = arguments[2];
27
  } else if (arguments.length === 2) {
28
    if (Array.isArray(arguments[0])) {
29
      data = arguments[0];
30
    } else {
31
      options = arguments[0];
32
    }
33
    if (typeof arguments[1] === 'function') {
34
      callback = arguments[1];
35
    } else {
36
      options = arguments[1];
37
    }
38
  } else if (arguments.length === 1) {
39
    if (typeof arguments[0] === 'function') {
40
      callback = arguments[0];
41
    } else if (Array.isArray(arguments[0])) {
42
      data = arguments[0];
43
    } else {
44
      options = arguments[0];
45
    }
46
  }
47
  if (options == null) {
0 ignored issues
show
Bug introduced by
The variable options seems to not be initialized for all possible execution paths.
Loading history...
48
    options = {};
49
  }
50
  stringifier = new Stringifier(options);
51
  if (data) {
52
    process.nextTick(function() {
53
      var d, j, len;
54
      for (j = 0, len = data.length; j < len; j++) {
55
        d = data[j];
56
        stringifier.write(d);
57
      }
58
      return stringifier.end();
59
    });
60
  }
61
  if (callback) {
62
    chunks = [];
63
    stringifier.on('readable', function() {
64
      var chunk, results;
65
      results = [];
66
      while (chunk = stringifier.read()) {
67
        results.push(chunks.push(chunk));
68
      }
69
      return results;
70
    });
71
    stringifier.on('error', function(err) {
72
      return callback(err);
73
    });
74
    stringifier.on('end', function() {
75
      return callback(null, chunks.join(''));
76
    });
77
  }
78
  return stringifier;
79
};
80
81
// You can also use *util.promisify* native function (Node.js 8+) in order to wrap callbacks into promises for more convenient use when source is a readable stream and you are OK with storing entire result set in memory:
82
83
// ```
84
// const { promisify } = require('util');
85
// const csv = require('csv');
86
// const stringifyAsync = promisify(csv.stringify);
87
88
// //returns promise
89
// function generateCsv(sourceData) {
90
//     return stringifyAsync(sourceData);
91
// }
92
// ```
93
94
// ## `Stringifier([options])`
95
96
// Options are documented [here](http://csv.adaltas.com/stringify/).
97
Stringifier = function(opts = {}) {
98
  var base, base1, base10, base11, base12, base13, base2, base3, base4, base5, base6, base7, base8, base9, k, options, v;
99
  // Immutable options
100
  options = {};
101
  for (k in opts) {
102
    v = opts[k];
103
    options[k] = v;
104
  }
105
  stream.Transform.call(this, options);
106
  //# Default options
107
  this.options = options;
108
  if ((base = this.options).delimiter == null) {
109
    base.delimiter = ',';
110
  }
111
  if ((base1 = this.options).quote == null) {
112
    base1.quote = '"';
113
  }
114
  if ((base2 = this.options).quoted == null) {
115
    base2.quoted = false;
116
  }
117
  if ((base3 = this.options).quotedEmpty == null) {
118
    base3.quotedEmpty = void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
119
  }
120
  if ((base4 = this.options).quotedString == null) {
121
    base4.quotedString = false;
122
  }
123
  if ((base5 = this.options).eof == null) {
124
    base5.eof = true;
125
  }
126
  if ((base6 = this.options).escape == null) {
127
    base6.escape = '"';
128
  }
129
  if ((base7 = this.options).columns == null) {
130
    base7.columns = null;
131
  }
132
  if ((base8 = this.options).header == null) {
133
    base8.header = false;
134
  }
135
  if ((base9 = this.options).formatters == null) {
136
    base9.formatters = {};
137
  }
138
  if ((base10 = this.options.formatters).date == null) {
139
    base10.date = function(value) {
140
      // Cast date to timestamp string by default
141
      return '' + value.getTime();
142
    };
143
  }
144
  if ((base11 = this.options.formatters).bool == null) {
145
    base11.bool = function(value) {
146
      // Cast boolean to string by default
147
      if (value) {
148
        return '1';
149
      } else {
150
        return '';
151
      }
152
    };
153
  }
154
  if ((base12 = this.options.formatters).object == null) {
155
    base12.object = function(value) {
156
      // Stringify object as JSON by default
157
      return JSON.stringify(value);
158
    };
159
  }
160
  if ((base13 = this.options).rowDelimiter == null) {
161
    base13.rowDelimiter = '\n';
162
  }
163
  // Internal usage, state related
164
  if (this.countWriten == null) {
165
    this.countWriten = 0;
166
  }
167
  switch (this.options.rowDelimiter) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
168
    case 'auto':
169
      this.options.rowDelimiter = null;
170
      break;
171
    case 'unix':
172
      this.options.rowDelimiter = "\n";
173
      break;
174
    case 'mac':
175
      this.options.rowDelimiter = "\r";
176
      break;
177
    case 'windows':
178
      this.options.rowDelimiter = "\r\n";
179
      break;
180
    case 'unicode':
181
      this.options.rowDelimiter = "\u2028";
182
  }
183
  return this;
184
};
185
186
util.inherits(Stringifier, stream.Transform);
187
188
module.exports.Stringifier = Stringifier;
189
190
// ## `Stringifier.prototype.headers`
191
192
// Print the header line if the option "header" is "true".
193
Stringifier.prototype.headers = function() {
194
  var k, label, labels;
195
  if (!this.options.header) {
196
    return;
197
  }
198
  if (!this.options.columns) {
199
    return;
200
  }
201
  labels = this.options.columns;
202
  // If columns is an object, keys are fields and values are labels
203
  if (typeof labels === 'object') {
204
    labels = (function() {
205
      var results;
206
      results = [];
207
      for (k in labels) {
208
        label = labels[k];
0 ignored issues
show
introduced by
The variable k is changed by the for-each loop on line 207. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
209
        results.push(label);
0 ignored issues
show
Bug introduced by
The variable label is changed as part of the for-each loop for example by labels.k on line 208. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
210
      }
211
      return results;
212
    })();
213
  }
214
  if (this.options.eof) {
215
    labels = this.stringify(labels) + this.options.rowDelimiter;
216
  } else {
217
    labels = this.stringify(labels);
218
  }
219
  return stream.Transform.prototype.write.call(this, labels);
220
};
221
222
Stringifier.prototype.end = function(chunk, encoding, callback) {
223
  if (this.countWriten === 0) {
224
    this.headers();
225
  }
226
  return stream.Transform.prototype.end.apply(this, arguments);
227
};
228
229
Stringifier.prototype.write = function(chunk, encoding, callback) {
230
  var base, e, preserve;
231
  if (chunk == null) {
232
    return;
233
  }
234
  preserve = typeof chunk !== 'object';
235
  // Emit and stringify the record
236
  if (!preserve) {
237
    if (this.countWriten === 0 && !Array.isArray(chunk)) {
238
      if ((base = this.options).columns == null) {
239
        base.columns = Object.keys(chunk);
240
      }
241
    }
242
    try {
243
      this.emit('record', chunk, this.countWriten);
244
    } catch (error) {
245
      e = error;
246
      return this.emit('error', e);
247
    }
248
    // Convert the record into a string
249
    if (this.options.eof) {
250
      chunk = this.stringify(chunk) + this.options.rowDelimiter;
251
    } else {
252
      chunk = this.stringify(chunk);
253
      if (this.options.header || this.countWriten) {
254
        chunk = this.options.rowDelimiter + chunk;
255
      }
256
    }
257
  }
258
  if (typeof chunk === 'number') {
259
    // Emit the csv
260
    chunk = `${chunk}`;
261
  }
262
  if (this.countWriten === 0) {
263
    this.headers();
264
  }
265
  if (!preserve) {
266
    this.countWriten++;
267
  }
268
  return stream.Transform.prototype.write.call(this, chunk, encoding, callback);
269
};
270
271
// ## `Stringifier.prototype._transform(line)`
272
Stringifier.prototype._transform = function(chunk, encoding, callback) {
273
  this.push(chunk);
274
  return callback();
275
};
276
277
// ## `Stringifier.prototype.stringify(line)`
278
279
// Convert a line to a string. Line may be an object, an array or a string.
280
Stringifier.prototype.stringify = function(line) {
281
  var _line, column, columns, containsEscape, containsLinebreak, containsQuote, containsdelimiter, delimiter, escape, field, i, j, l, newLine, quote, ref, ref1, regexp, shouldQuote, value;
282
  if (typeof line !== 'object') {
283
    return line;
284
  }
285
  columns = this.options.columns;
286
  if (typeof columns === 'object' && columns !== null && !Array.isArray(columns)) {
287
    columns = Object.keys(columns);
288
  }
289
  delimiter = this.options.delimiter;
290
  quote = this.options.quote;
291
  escape = this.options.escape;
292
  if (!Array.isArray(line)) {
293
    _line = [];
294
    if (columns) {
295
      for (i = j = 0, ref = columns.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
296
        column = columns[i];
297
        value = get(line, column);
298
        _line[i] = (typeof value === 'undefined' || value === null) ? '' : value;
299
      }
300
    } else {
301
      for (column in line) {
302
        _line.push(line[column]);
303
      }
304
    }
305
    line = _line;
306
    _line = null;
0 ignored issues
show
Unused Code introduced by
The assignment to _line seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
307
  } else if (columns) { // Note, we used to have @options.columns
308
    // We are getting an array but the user want specified output columns. In
309
    // this case, we respect the columns indexes
310
    line.splice(columns.length);
311
  }
312
  if (Array.isArray(line)) {
313
    newLine = '';
314
    for (i = l = 0, ref1 = line.length; 0 <= ref1 ? l < ref1 : l > ref1; i = 0 <= ref1 ? ++l : --l) {
315
      field = line[i];
316
      if (typeof field === 'string') {
317
318
      // fine 99% of the cases, keep going
319
      } else if (typeof field === 'number') {
320
        // Cast number to string
321
        field = '' + field;
322
      } else if (typeof field === 'boolean') {
323
        field = this.options.formatters.bool(field);
324
      } else if (field instanceof Date) {
325
        field = this.options.formatters.date(field);
326
      } else if (typeof field === 'object' && field !== null) {
327
        field = this.options.formatters.object(field);
328
      }
329
      if (field) {
330
        if (typeof field !== 'string') {
331
          return this.emit('error', Error('Formatter must return a string, null or undefined'));
332
        }
333
        containsdelimiter = field.indexOf(delimiter) >= 0;
334
        containsQuote = field.indexOf(quote) >= 0;
335
        containsEscape = field.indexOf(escape) >= 0 && (escape !== quote);
336
        containsLinebreak = field.indexOf('\r') >= 0 || field.indexOf('\n') >= 0;
337
        shouldQuote = containsQuote || containsdelimiter || containsLinebreak || this.options.quoted || (this.options.quotedString && typeof line[i] === 'string');
338
        if (shouldQuote && containsEscape) {
339
          regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g');
340
          field = field.replace(regexp, escape + escape);
341
        }
342
        if (containsQuote) {
343
          regexp = new RegExp(quote, 'g');
344
          field = field.replace(regexp, escape + quote);
345
        }
346
        if (shouldQuote) {
347
          field = quote + field + quote;
348
        }
349
        newLine += field;
350
      } else if (this.options.quotedEmpty || ((this.options.quotedEmpty == null) && line[i] === '' && this.options.quotedString)) {
351
        newLine += quote + quote;
352
      }
353
      if (i !== line.length - 1) {
354
        newLine += delimiter;
355
      }
356
    }
357
    line = newLine;
358
  }
359
  return line;
360
};
361
362
// [readme]: https://github.com/wdavidw/node-csv-stringify
363
// [samples]: https://github.com/wdavidw/node-csv-stringify/tree/master/samples
364
// [tests]: https://github.com/wdavidw/node-csv-stringify/tree/master/test
365