libs/src/node-localstorage/LocalStorage.js   F
last analyzed

Complexity

Total Complexity 73
Complexity/F 2.15

Size

Lines of Code 360
Function Count 34

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 254
dl 0
loc 360
rs 2.56
c 0
b 0
f 0
wmc 73
mnd 39
bc 39
fnc 34
bpm 1.147
cpm 2.1469
noi 10

6 Functions

Rating   Name   Duplication   Size   Complexity  
F LocalStorage.js ➔ JSONStorage 0 3 34
A LocalStorage.js ➔ ctor 0 1 5
A LocalStorage.js ➔ StorageEvent 0 7 4
A LocalStorage.js ➔ MetaKey 0 7 3
C LocalStorage.js ➔ QUOTA_EXCEEDED_ERR 0 8 11
F LocalStorage.js ➔ LocalStorage 0 46 14

How to fix   Complexity   

Complexity

Complex classes like libs/src/node-localstorage/LocalStorage.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 1.12.7
2
(function() {
3
  var JSONStorage, KEY_FOR_EMPTY_STRING, LocalStorage, MetaKey, QUOTA_EXCEEDED_ERR, StorageEvent, _emptyDirectory, _escapeKey, _rm, createMap, dirname, events, fs, path, writeSync,
4
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
Coding Style Best Practice introduced by
By convention, constructors like ctor should be capitalized.
Loading history...
5
    hasProp = {}.hasOwnProperty;
6
7
  path = require('path');
8
9
  dirname = path.dirname;
10
11
  fs = require('fs');
12
13
  events = require('events');
14
15
  writeSync = require('write-file-atomic').sync;
16
17
  KEY_FOR_EMPTY_STRING = '---.EMPTY_STRING.---';
18
19
  _emptyDirectory = function(target) {
20
    var i, len, p, ref, results;
21
    ref = fs.readdirSync(target);
22
    results = [];
23
    for (i = 0, len = ref.length; i < len; i++) {
24
      p = ref[i];
25
      results.push(_rm(path.join(target, p)));
26
    }
27
    return results;
28
  };
29
30
  _rm = function(target) {
31
    if (!fs.existsSync(dirname(target))) {
32
      fs.mkdirSync(dirname(target));
33
    }
34
    if (fs.statSync(target).isDirectory()) {
35
      _emptyDirectory(target);
36
      return fs.rmdirSync(target);
37
    } else {
38
      return fs.unlinkSync(target);
39
    }
40
  };
41
42
  _escapeKey = function(key) {
43
    var newKey;
44
    if (key === '') {
45
      newKey = KEY_FOR_EMPTY_STRING;
46
    } else {
47
      newKey = key.toString();
48
    }
49
    return newKey;
50
  };
51
52
  QUOTA_EXCEEDED_ERR = (function(superClass) {
53
    extend(QUOTA_EXCEEDED_ERR, superClass);
54
55
    function QUOTA_EXCEEDED_ERR(message) {
56
      this.message = message != null ? message : 'Unknown error.';
57
      QUOTA_EXCEEDED_ERR.__super__.constructor.call(this);
58
      if (Error.captureStackTrace != null) {
59
        Error.captureStackTrace(this, this.constructor);
60
      }
61
      this.name = this.constructor.name;
62
    }
63
64
    QUOTA_EXCEEDED_ERR.prototype.toString = function() {
65
      return this.name + ": " + this.message;
66
    };
67
68
    return QUOTA_EXCEEDED_ERR;
69
70
  })(Error);
71
72
  StorageEvent = (function() {
73
    function StorageEvent(key1, oldValue1, newValue1, url, storageArea) {
74
      this.key = key1;
75
      this.oldValue = oldValue1;
76
      this.newValue = newValue1;
77
      this.url = url;
78
      this.storageArea = storageArea != null ? storageArea : 'localStorage';
79
    }
80
81
    return StorageEvent;
82
83
  })();
84
85
  MetaKey = (function() {
86
    function MetaKey(key1, index1) {
87
      this.key = key1;
88
      this.index = index1;
89
      if (!(this instanceof MetaKey)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(this instanceof MetaKey) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
90
        return new MetaKey(this.key, this.index);
91
      }
92
    }
93
94
    return MetaKey;
95
96
  })();
97
98
  createMap = function() {
99
    var Map;
100
    Map = function() {};
0 ignored issues
show
Comprehensibility introduced by
You are shadowing the built-in type Map. This makes code hard to read, consider using a different name.
Loading history...
101
    Map.prototype = Object.create(null);
102
    return new Map();
103
  };
104
105
  LocalStorage = (function(superClass) {
106
    var instanceMap;
107
108
    extend(LocalStorage, superClass);
109
110
    instanceMap = {};
111
112
    function LocalStorage(_location, quota) {
113
      var handler;
114
      this._location = _location;
115
      this.quota = quota != null ? quota : 5 * 1024 * 1024;
116
      LocalStorage.__super__.constructor.call(this);
117
      if (!(this instanceof LocalStorage)) {
118
        return new LocalStorage(this._location, this.quota);
119
      }
120
      this._location = path.resolve(this._location);
121
      if (instanceMap[this._location] != null) {
122
        return instanceMap[this._location];
123
      }
124
      this.length = 0;
125
      this._bytesInUse = 0;
126
      this._keys = [];
127
      this._metaKeyMap = createMap();
128
      this._eventUrl = "pid:" + process.pid;
129
      this._init();
130
      this._QUOTA_EXCEEDED_ERR = QUOTA_EXCEEDED_ERR;
131
      if (typeof Proxy !== "undefined" && Proxy !== null) {
132
        handler = {
133
          set: (function(_this) {
134
            return function(receiver, key, value) {
135
              if (_this[key] != null) {
136
                return _this[key] = value;
137
              } else {
138
                return _this.setItem(key, value);
139
              }
140
            };
141
          })(this),
142
          get: (function(_this) {
143
            return function(receiver, key) {
144
              if (_this[key] != null) {
145
                return _this[key];
146
              } else {
147
                return _this.getItem(key);
148
              }
149
            };
150
          })(this)
151
        };
152
        instanceMap[this._location] = new Proxy(this, handler);
153
        return instanceMap[this._location];
154
      }
155
      instanceMap[this._location] = this;
156
      return instanceMap[this._location];
157
    }
158
159
    LocalStorage.prototype._init = function() {
160
      var _MetaKey, _decodedKey, _keys, e, i, index, k, len, stat;
161
      try {
162
        stat = fs.statSync(this._location);
163
        if ((stat != null) && !stat.isDirectory()) {
164
          throw new Error("A file exists at the location '" + this._location + "' when trying to create/open localStorage");
165
        }
166
        this._bytesInUse = 0;
167
        this.length = 0;
168
        _keys = fs.readdirSync(this._location);
169
        for (index = i = 0, len = _keys.length; i < len; index = ++i) {
170
          k = _keys[index];
171
          _decodedKey = decodeURIComponent(k);
172
          this._keys.push(_decodedKey);
173
          _MetaKey = new MetaKey(k, index);
174
          this._metaKeyMap[_decodedKey] = _MetaKey;
175
          stat = this._getStat(k);
176
          if ((stat != null ? stat.size : void 0) != null) {
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...
177
            _MetaKey.size = stat.size;
178
            this._bytesInUse += stat.size;
179
          }
180
        }
181
        this.length = _keys.length;
182
      } catch (error) {
183
        e = error;
184
        if (e.code !== "ENOENT") {
185
          throw e;
186
        }
187
        try {
188
          fs.mkdirSync(this._location, {
189
            recursive: true
190
          });
191
        } catch (error) {
192
          e = error;
193
          if (e.code !== "EEXIST") {
194
            throw e;
195
          }
196
        }
197
      }
198
    };
199
200
    LocalStorage.prototype.setItem = function(key, value) {
201
      var encodedKey, evnt, existsBeforeSet, filename, hasListeners, metaKey, oldLength, oldValue, valueString, valueStringLength;
202
      hasListeners = events.EventEmitter.listenerCount(this, 'storage');
203
      oldValue = null;
204
      if (hasListeners) {
205
        oldValue = this.getItem(key);
206
      }
207
      key = _escapeKey(key);
208
      encodedKey = encodeURIComponent(key);
209
      filename = path.join(this._location, encodedKey);
210
      valueString = value.toString();
211
      valueStringLength = valueString.length;
212
      metaKey = this._metaKeyMap[key];
213
      existsBeforeSet = !!metaKey;
214
      if (existsBeforeSet) {
215
        oldLength = metaKey.size;
216
      } else {
217
        oldLength = 0;
218
      }
219
      if (this._bytesInUse - oldLength + valueStringLength > this.quota) {
220
        throw new QUOTA_EXCEEDED_ERR();
221
      }
222
      writeSync(filename, valueString, 'utf8');
223
      if (!existsBeforeSet) {
224
        metaKey = new MetaKey(encodedKey, (this._keys.push(key)) - 1);
225
        metaKey.size = valueStringLength;
226
        this._metaKeyMap[key] = metaKey;
227
        this.length += 1;
228
        this._bytesInUse += valueStringLength;
229
      }
230
      if (hasListeners) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if hasListeners is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
231
        evnt = new StorageEvent(key, oldValue, value, this._eventUrl);
232
        return this.emit('storage', evnt);
233
      }
234
    };
235
236
    LocalStorage.prototype.getItem = function(key) {
237
      var filename, metaKey;
238
      key = _escapeKey(key);
239
      metaKey = this._metaKeyMap[key];
240
      if (!!metaKey) {
241
        filename = path.join(this._location, metaKey.key);
242
        if (fs.existsSync(filename)) {
243
          return fs.readFileSync(filename, 'utf8');
244
        } else {
245
          return false;
246
        }
247
      } else {
248
        return null;
249
      }
250
    };
251
252
    LocalStorage.prototype._getStat = function(key) {
253
      var filename;
254
      key = _escapeKey(key);
255
      filename = path.join(this._location, encodeURIComponent(key));
256
      try {
257
        return fs.statSync(filename);
258
      } catch (error) {
259
        return null;
260
      }
261
    };
262
263
    LocalStorage.prototype.removeItem = function(key) {
264
      var evnt, filename, hasListeners, k, meta, metaKey, oldValue, ref, v;
265
      key = _escapeKey(key);
266
      metaKey = this._metaKeyMap[key];
267
      if (!!metaKey) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(!metaKey) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
268
        hasListeners = events.EventEmitter.listenerCount(this, 'storage');
269
        oldValue = null;
270
        if (hasListeners) {
271
          oldValue = this.getItem(key);
272
        }
273
        delete this._metaKeyMap[key];
274
        this.length -= 1;
275
        this._bytesInUse -= metaKey.size;
276
        filename = path.join(this._location, metaKey.key);
277
        this._keys.splice(metaKey.index, 1);
278
        ref = this._metaKeyMap;
279
        for (k in ref) {
280
          v = ref[k];
0 ignored issues
show
Unused Code introduced by
The variable v seems to be never used. Consider removing it.
Loading history...
281
          meta = this._metaKeyMap[k];
282
          if (meta.index > metaKey.index) {
283
            meta.index -= 1;
284
          }
285
        }
286
        _rm(filename);
287
        if (hasListeners) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if hasListeners is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
288
          evnt = new StorageEvent(key, oldValue, null, this._eventUrl);
289
          return this.emit('storage', evnt);
290
        }
291
      }
292
    };
293
294
    LocalStorage.prototype.key = function(n) {
295
      var rawKey;
296
      rawKey = this._keys[n];
297
      if (rawKey === KEY_FOR_EMPTY_STRING) {
298
        return '';
299
      } else {
300
        return rawKey;
301
      }
302
    };
303
304
    LocalStorage.prototype.clear = function() {
305
      var evnt;
306
      _emptyDirectory(this._location);
307
      this._metaKeyMap = createMap();
308
      this._keys = [];
309
      this.length = 0;
310
      this._bytesInUse = 0;
311
      if (events.EventEmitter.listenerCount(this, 'storage')) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if events.EventEmitter.list...rCount(this, "storage") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
312
        evnt = new StorageEvent(null, null, null, this._eventUrl);
313
        return this.emit('storage', evnt);
314
      }
315
    };
316
317
    LocalStorage.prototype._getBytesInUse = function() {
318
      return this._bytesInUse;
319
    };
320
321
    LocalStorage.prototype._deleteLocation = function() {
322
      delete instanceMap[this._location];
323
      _rm(this._location);
324
      this._metaKeyMap = {};
325
      this._keys = [];
326
      this.length = 0;
327
      return this._bytesInUse = 0;
328
    };
329
330
    return LocalStorage;
331
332
  })(events.EventEmitter);
333
334
  JSONStorage = (function(superClass) {
335
    extend(JSONStorage, superClass);
336
337
    function JSONStorage() {
338
      return JSONStorage.__super__.constructor.apply(this, arguments);
339
    }
340
341
    JSONStorage.prototype.setItem = function(key, value) {
342
      var newValue;
343
      newValue = JSON.stringify(value);
344
      return JSONStorage.__super__.setItem.call(this, key, newValue);
345
    };
346
347
    JSONStorage.prototype.getItem = function(key) {
348
      return JSON.parse(JSONStorage.__super__.getItem.call(this, key));
349
    };
350
351
    return JSONStorage;
352
353
  })(LocalStorage);
354
355
  exports.LocalStorage = LocalStorage;
356
357
  exports.JSONStorage = JSONStorage;
358
359
  exports.QUOTA_EXCEEDED_ERR = QUOTA_EXCEEDED_ERR;
360
361
}).call(this);
362