Passed
Pull Request — master (#69)
by
unknown
05:52
created

js/codemirror/addon/hint/sql-hint.js   F

Complexity

Total Complexity 83
Complexity/F 3.46

Size

Lines of Code 268
Function Count 24

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 1572864
dl 0
loc 268
rs 3.12
c 0
b 0
f 0
wmc 83
mnd 5
bc 47
fnc 24
bpm 1.9583
cpm 3.4583
noi 4

How to fix   Complexity   

Complexity

Complex classes like js/codemirror/addon/hint/sql-hint.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
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2
// Distributed under an MIT license: http://codemirror.net/LICENSE
3
4
(function(mod) {
5
  if (typeof exports == "object" && typeof module == "object") // CommonJS
6
    mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
7
  else if (typeof define == "function" && define.amd) // AMD
8
    define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
9
  else // Plain browser env
10
    mod(CodeMirror);
11
})(function(CodeMirror) {
12
  "use strict";
13
14
  var tables;
15
  var defaultTable;
16
  var keywords;
17
  var CONS = {
18
    QUERY_DIV: ";",
19
    ALIAS_KEYWORD: "AS"
20
  };
21
  var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
22
23
  function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
24
25
  function getKeywords(editor) {
26
    var mode = editor.doc.modeOption;
27
    if (mode === "sql") mode = "text/x-sql";
28
    return CodeMirror.resolveMode(mode).keywords;
29
  }
30
31
  function getText(item) {
32
    return typeof item == "string" ? item : item.text;
33
  }
34
35
  function wrapTable(name, value) {
36
    if (isArray(value)) value = {columns: value}
37
    if (!value.text) value.text = name
38
    return value
39
  }
40
41
  function parseTables(input) {
42
    var result = {}
43
    if (isArray(input)) {
44
      for (var i = input.length - 1; i >= 0; i--) {
45
        var item = input[i]
46
        result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
47
      }
48
    } else if (input) {
49
      for (var name in input)
50
        result[name.toUpperCase()] = wrapTable(name, input[name])
51
    }
52
    return result
53
  }
54
55
  function getTable(name) {
56
    return tables[name.toUpperCase()]
57
  }
58
59
  function shallowClone(object) {
60
    var result = {};
61
    for (var key in object) if (object.hasOwnProperty(key))
62
      result[key] = object[key];
63
    return result;
64
  }
65
66
  function match(string, word) {
67
    var len = string.length;
68
    var sub = getText(word).substr(0, len);
69
    return string.toUpperCase() === sub.toUpperCase();
70
  }
71
72
  function addMatches(result, search, wordlist, formatter) {
73
    if (isArray(wordlist)) {
74
      for (var i = 0; i < wordlist.length; i++)
75
        if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
76
    } else {
77
      for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
78
        var val = wordlist[word]
79
        if (!val || val === true)
80
          val = word
81
        else
82
          val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
83
        if (match(search, val)) result.push(formatter(val))
84
      }
85
    }
86
  }
87
88
  function cleanName(name) {
89
    // Get rid name from backticks(`) and preceding dot(.)
90
    if (name.charAt(0) == ".") {
91
      name = name.substr(1);
92
    }
93
    return name.replace(/`/g, "");
94
  }
95
96
  function insertBackticks(name) {
97
    var nameParts = getText(name).split(".");
98
    for (var i = 0; i < nameParts.length; i++)
99
      nameParts[i] = "`" + nameParts[i] + "`";
100
    var escaped = nameParts.join(".");
101
    if (typeof name == "string") return escaped;
102
    name = shallowClone(name);
103
    name.text = escaped;
104
    return name;
105
  }
106
107
  function nameCompletion(cur, token, result, editor) {
108
    // Try to complete table, column names and return start position of completion
109
    var useBacktick = false;
110
    var nameParts = [];
111
    var start = token.start;
112
    var cont = true;
113
    while (cont) {
114
      cont = (token.string.charAt(0) == ".");
115
      useBacktick = useBacktick || (token.string.charAt(0) == "`");
116
117
      start = token.start;
118
      nameParts.unshift(cleanName(token.string));
119
120
      token = editor.getTokenAt(Pos(cur.line, token.start));
121
      if (token.string == ".") {
122
        cont = true;
123
        token = editor.getTokenAt(Pos(cur.line, token.start));
124
      }
125
    }
126
127
    // Try to complete table names
128
    var string = nameParts.join(".");
129
    addMatches(result, string, tables, function(w) {
130
      return useBacktick ? insertBackticks(w) : w;
131
    });
132
133
    // Try to complete columns from defaultTable
134
    addMatches(result, string, defaultTable, function(w) {
135
      return useBacktick ? insertBackticks(w) : w;
136
    });
137
138
    // Try to complete columns
139
    string = nameParts.pop();
140
    var table = nameParts.join(".");
141
142
    var alias = false;
143
    var aliasTable = table;
144
    // Check if table is available. If not, find table by Alias
145
    if (!getTable(table)) {
146
      var oldTable = table;
147
      table = findTableByAlias(table, editor);
148
      if (table !== oldTable) alias = true;
149
    }
150
151
    var columns = getTable(table);
152
    if (columns && columns.columns)
153
      columns = columns.columns;
154
155
    if (columns) {
156
      addMatches(result, string, columns, function(w) {
157
        var tableInsert = table;
158
        if (alias == true) tableInsert = aliasTable;
159
        if (typeof w == "string") {
160
          w = tableInsert + "." + w;
161
        } else {
162
          w = shallowClone(w);
163
          w.text = tableInsert + "." + w.text;
164
        }
165
        return useBacktick ? insertBackticks(w) : w;
166
      });
167
    }
168
169
    return start;
170
  }
171
172
  function eachWord(lineText, f) {
173
    if (!lineText) return;
174
    var excepted = /[,;]/g;
175
    var words = lineText.split(" ");
176
    for (var i = 0; i < words.length; i++) {
177
      f(words[i]?words[i].replace(excepted, '') : '');
178
    }
179
  }
180
181
  function findTableByAlias(alias, editor) {
182
    var doc = editor.doc;
183
    var fullQuery = doc.getValue();
184
    var aliasUpperCase = alias.toUpperCase();
185
    var previousWord = "";
186
    var table = "";
187
    var separator = [];
188
    var validRange = {
189
      start: Pos(0, 0),
190
      end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
191
    };
192
193
    //add separator
194
    var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
195
    while(indexOfSeparator != -1) {
196
      separator.push(doc.posFromIndex(indexOfSeparator));
197
      indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
198
    }
199
    separator.unshift(Pos(0, 0));
200
    separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
201
202
    //find valid range
203
    var prevItem = null;
204
    var current = editor.getCursor()
205
    for (var i = 0; i < separator.length; i++) {
206
      if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
207
        validRange = {start: prevItem, end: separator[i]};
208
        break;
209
      }
210
      prevItem = separator[i];
211
    }
212
213
    var query = doc.getRange(validRange.start, validRange.end, false);
214
215
    for (var i = 0; i < query.length; i++) {
216
      var lineText = query[i];
217
      eachWord(lineText, function(word) {
218
        var wordUpperCase = word.toUpperCase();
219
        if (wordUpperCase === aliasUpperCase && getTable(previousWord))
220
          table = previousWord;
221
        if (wordUpperCase !== CONS.ALIAS_KEYWORD)
222
          previousWord = word;
223
      });
224
      if (table) break;
225
    }
226
    return table;
227
  }
228
229
  CodeMirror.registerHelper("hint", "sql", function(editor, options) {
230
    tables = parseTables(options && options.tables)
231
    var defaultTableName = options && options.defaultTable;
232
    var disableKeywords = options && options.disableKeywords;
233
    defaultTable = defaultTableName && getTable(defaultTableName);
234
    keywords = getKeywords(editor);
235
236
    if (defaultTableName && !defaultTable)
237
      defaultTable = findTableByAlias(defaultTableName, editor);
238
239
    defaultTable = defaultTable || [];
240
241
    if (defaultTable.columns)
242
      defaultTable = defaultTable.columns;
243
244
    var cur = editor.getCursor();
245
    var result = [];
246
    var token = editor.getTokenAt(cur), start, end, search;
247
    if (token.end > cur.ch) {
248
      token.end = cur.ch;
249
      token.string = token.string.slice(0, cur.ch - token.start);
250
    }
251
252
    if (token.string.match(/^[.`\w@]\w*$/)) {
253
      search = token.string;
254
      start = token.start;
255
      end = token.end;
256
    } else {
257
      start = end = cur.ch;
258
      search = "";
259
    }
260
    if (search.charAt(0) == "." || search.charAt(0) == "`") {
261
      start = nameCompletion(cur, token, result, editor);
262
    } else {
263
      addMatches(result, search, tables, function(w) {return w;});
264
      addMatches(result, search, defaultTable, function(w) {return w;});
265
      if (!disableKeywords)
266
        addMatches(result, search, keywords, function(w) {return w.toUpperCase();});
267
    }
268
269
    return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
270
  });
271
});
272