src/common/common-util.js   B
last analyzed

Complexity

Total Complexity 51
Complexity/F 2.04

Size

Lines of Code 194
Function Count 25

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 92.59%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
wmc 51
c 2
b 0
f 0
nc 1536
mnd 3
bc 30
fnc 25
dl 0
loc 194
ccs 75
cts 81
cp 0.9259
crap 0
rs 8.3206
bpm 1.2
cpm 2.04
noi 0

How to fix   Complexity   

Complexity

Complex classes like src/common/common-util.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
/**
2
 * Util functions
3
 *
4
 * @since 1.0.0
5
 */
6
7
/**
8
 * Get client IP address
9
 *
10
 * Will return 127.0.0.1 when testing locally
11
 * Useful when you need the user ip for geolocation or serving localized content
12
 *
13
 * @param request
14
 * @returns {string} ip
15
 */
16 5
exports.getClientIp = (request) => {
17
  // workaround to get real client IP
18
  // most likely because our app will be behind a [reverse] proxy or load balancer
19 2
  const clientIp = request.headers['x-client-ip'];
20
  // x-forwarded-for
21
  // (typically when your node app is behind a load-balancer (eg. AWS ELB) or proxy)
22
  //
23
  // x-forwarded-for may return multiple IP addresses in the format:
24
  // "client IP, proxy 1 IP, proxy 2 IP"
25
  // Therefore, the right-most IP address is the IP address of the most recent proxy
26
  // and the left-most IP address is the IP address of the originating client.
27
  // source: http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html
28 2
  const forwardedForAlt = request.headers['x-forwarded-for'];
29
  // x-real-ip
30
  // (default nginx proxy/fcgi)
31
  // alternative to x-forwarded-for, used by some proxies
32 2
  const realIp = request.headers['x-real-ip'];
33
34
  // more obsure ones below
35
36
  // x-cluster-client-ip
37
  // (Rackspace LB and Riverbed's Stingray)
38
  // http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
39
  // https://splash.riverbed.com/docs/DOC-1926
40 2
  const clusterClientIp = request.headers['x-cluster-client-ip'];
41 2
  const forwardedAlt = request.headers['x-forwarded'];
42 2
  const forwardedFor = request.headers['forwarded-for'];
43 2
  const forwarded = request.headers.forwarded;
44
45
  // remote address check
46 2
  const reqConnectionRemoteAddress = request.connection ? request.connection.remoteAddress : null;
47 2
  const reqSocketRemoteAddress = request.socket ? request.socket.remoteAddress : null;
48
  // remote address checks
49 4
  const reqConnectionSocketRemoteAddress = (request.connection && request.connection.socket)
50
    ? request.connection.socket.remoteAddress : null;
51 2
  const reqInfoRemoteAddress = request.info ? request.info.remoteAddress : null;
52
53 12
  return clientIp
54
    || (forwardedForAlt && forwardedForAlt.split(',')[0])
55
    || realIp
56
    || clusterClientIp
57
    || forwardedAlt
58
    || forwardedFor
59
    || forwarded
60
    || reqConnectionRemoteAddress
61
    || reqSocketRemoteAddress
62
    || reqConnectionSocketRemoteAddress
63
    || reqInfoRemoteAddress;
64
};
65
66 5
const regex = /([>=<]{1,2})([0-9a-zA-Z\-.]+)*/g;
67
68 5
const getComparator = (conditionStr) => {
69 8
  let result = conditionStr.charAt(0);
70 8
  if ('<>'.includes(result)) {
71 4
    result = '~';
72
  }
73 8
  return result;
74
};
75
76 5
const parsers = {
77 1
  '*': () => ({ comparator: '*' }),
78
  '=': (cond) => {
79
    let execResult;
80 3
    if ((execResult = regex.exec(cond)) !== null) {
81 3
      return { comparator: '=', version: execResult[2] };
82
    }
83
    return false;
84
  },
85
  '~': (cond) => {
86 4
    const resultItem = { comparator: '~' };
87
    let execResult;
88 4
    while ((execResult = regex.exec(cond)) !== null) {
89 6
      if (execResult[1] === '>=') {
90 3
        resultItem.versionStart = execResult[2];
91 3
      } else if (execResult[1] === '<') {
92 3
        resultItem.versionEnd = execResult[2];
93
      }
94
    }
95 4
    if (resultItem.versionStart || resultItem.versionEnd) {
96 4
      return resultItem;
97
    }
98
    return false;
99
  },
100
};
101
102 5
const stringifier = {
103 1
  '*': () => '*',
104 3
  '=': cond => `=${cond.version}`,
105 4
  '~': cond => `${cond.versionStart ? `>=${cond.versionStart}` : ''} ${cond.versionEnd ? `<${cond.versionEnd}` : ''}`,
106
};
107
108
/**
109
 * Parse below "limited" formatted Semantic Version to an array for UI expression
110
 *  - equals: "=2.3", "=2", "=4.3.3"
111
 *  - range: ">=1.2.3 <3.0", ">=1.2.3", "<3.0.0"  (only permitted gte(>=) and lt(<) for ranges)
112
 *  - all: "*"
113
 *  - mixed (by OR): ">=1.2.3 <3.0.0 || <0.1.2 || >=5.1"
114
 * @param {string} conditionString
115
 * @return {Array}
116
 */
117
118 5
exports.parseSemVersion = (semVerString) => {
119 5
  if (!semVerString) {
120
    return [{ comparator: '*' }];   // default
121
  }
122 8
  const conditions = semVerString.split('||').map(cond => cond.trim());  // OR 연산을 기준으로 분리
123 5
  const result = [];
124 5
  conditions.forEach((cond) => {
125 8
    regex.lastIndex = 0;  // reset find index
126 8
    const comparator = getComparator(cond);
127 8
    const parsedObj = parsers[comparator](cond);
128 8
    if (parsedObj) {
129 8
      result.push(parsedObj);
130
    }
131
  });
132 5
  return result;
133
};
134
135
/**
136
 * Stringify parsed version conditions to SemVer representation
137
 * @param {Array} parsedConditions
138
 * @return {string}
139
 */
140 5
exports.stringifySemVersion = (parsedConditions) => {
141 5
  const result = parsedConditions.map((cond) => {
142 8
    if (stringifier[cond.comparator]) {
143 8
      return stringifier[cond.comparator](cond);
144
    }
145
    return '';
146
  });
147 5
  if (result.includes('*')) {
148 1
    return '*';
149
  }
150 7
  return result.filter(cond => !!cond).join(' || ').replace(/\s\s/g, ' ').trim();
151
};
152
153
/**
154
 * Replace camelCase string to snake_case
155
 * @param {string} str
156
 * @returns {string}
157
 */
158 5
exports.camel2snake = (str) => {
159 318
  if (typeof str === 'string') {
160
    // ignore first occurrence of underscore(_) and capital
161 318
    return str.replace(/^_/, '').replace(/^([A-Z])/, $1 => `${$1.toLowerCase()}`).replace(/([A-Z])/g, $1 => `_${$1.toLowerCase()}`);
162
  }
163
  return str;
164
};
165
166
/**
167
 * Replace camelCase string to snake_case on the property names in objects
168
 * @param {Array|Object} object
169
 * @returns {*}
170
 */
171 5
exports.camel2snakeObject = (object) => {
172 423
  if (object instanceof Array) {
173 78
    return object.map(value => exports.camel2snakeObject(value));
174 378
  } else if (object && typeof object === 'object') {
175 83
    const result = {};
176 313
    Object.keys(object).forEach((key) => { result[exports.camel2snake(key)] = exports.camel2snakeObject(object[key]); });
177 83
    return result;
178
  }
179 295
  return object;
180
};
181
182
/**
183
 * Replace snake_case string to camelCase
184
 * @param {string} str
185
 * @returns {string}
186
 */
187 5
exports.snake2camel = (str) => {
188 106
  if (typeof str === 'string') {
189
    // ignore first occurrence of underscore(_)
190 106
    return str.replace(/^_/, '').replace(/_([a-z0-9]?)/g, ($1, $2) => `${$2.toUpperCase()}`);
191
  }
192
  return str;
193
};
194
195
/**
196
 * Replace snake_case string to camelCase on the property names in objects
197
 * @param {Array|Object} object
198
 * @returns {*}
199
 */
200 5
exports.snake2camelObject = (object) => {
201 173
  if (object instanceof Array) {
202 14
    return object.map(value => exports.snake2camelObject(value));
203 164
  } else if (object && typeof object === 'object') {
204 62
    const result = {};
205 101
    Object.keys(object).forEach((key) => { result[exports.snake2camel(key)] = exports.snake2camelObject(object[key]); });
206 62
    return result;
207
  }
208 102
  return object;
209
};
210