Passed
Push — master ( 3324e2...2ba76b )
by Ralf
13:44
created

tern.js ➔ getFile   F

Complexity

Conditions 31

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 9
rs 0
cc 31

How to fix   Complexity   

Complexity

Complex classes like tern.js ➔ getFile 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
// Glue code between CodeMirror and Tern.
5
//
6
// Create a CodeMirror.TernServer to wrap an actual Tern server,
7
// register open documents (CodeMirror.Doc instances) with it, and
8
// call its methods to activate the assisting functions that Tern
9
// provides.
10
//
11
// Options supported (all optional):
12
// * defs: An array of JSON definition data structures.
13
// * plugins: An object mapping plugin names to configuration
14
//   options.
15
// * getFile: A function(name, c) that can be used to access files in
16
//   the project that haven't been loaded yet. Simply do c(null) to
17
//   indicate that a file is not available.
18
// * fileFilter: A function(value, docName, doc) that will be applied
19
//   to documents before passing them on to Tern.
20
// * switchToDoc: A function(name, doc) that should, when providing a
21
//   multi-file view, switch the view or focus to the named file.
22
// * showError: A function(editor, message) that can be used to
23
//   override the way errors are displayed.
24
// * completionTip: Customize the content in tooltips for completions.
25
//   Is passed a single argument—the completion's data as returned by
26
//   Tern—and may return a string, DOM node, or null to indicate that
27
//   no tip should be shown. By default the docstring is shown.
28
// * typeTip: Like completionTip, but for the tooltips shown for type
29
//   queries.
30
// * responseFilter: A function(doc, query, request, error, data) that
31
//   will be applied to the Tern responses before treating them
32
//
33
//
34
// It is possible to run the Tern server in a web worker by specifying
35
// these additional options:
36
// * useWorker: Set to true to enable web worker mode. You'll probably
37
//   want to feature detect the actual value you use here, for example
38
//   !!window.Worker.
39
// * workerScript: The main script of the worker. Point this to
40
//   wherever you are hosting worker.js from this directory.
41
// * workerDeps: An array of paths pointing (relative to workerScript)
42
//   to the Acorn and Tern libraries and any Tern plugins you want to
43
//   load. Or, if you minified those into a single script and included
44
//   them in the workerScript, simply leave this undefined.
45
46
(function(mod) {
47
  if (typeof exports == "object" && typeof module == "object") // CommonJS
48
    mod(require("../../lib/codemirror"));
49
  else if (typeof define == "function" && define.amd) // AMD
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
50
    define(["../../lib/codemirror"], mod);
51
  else // Plain browser env
52
    mod(CodeMirror);
0 ignored issues
show
Bug introduced by
The variable CodeMirror seems to be never declared. If this is a global, consider adding a /** global: CodeMirror */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
53
})(function(CodeMirror) {
54
  "use strict";
55
  // declare global: tern
56
57
  CodeMirror.TernServer = function(options) {
58
    var self = this;
59
    this.options = options || {};
60
    var plugins = this.options.plugins || (this.options.plugins = {});
61
    if (!plugins.doc_comment) plugins.doc_comment = true;
62
    if (this.options.useWorker) {
63
      this.server = new WorkerServer(this);
64
    } else {
65
      this.server = new tern.Server({
0 ignored issues
show
Bug introduced by
The variable tern seems to be never declared. If this is a global, consider adding a /** global: tern */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
66
        getFile: function(name, c) { return getFile(self, name, c); },
67
        async: true,
68
        defs: this.options.defs || [],
69
        plugins: plugins
70
      });
71
    }
72
    this.docs = Object.create(null);
73
    this.trackChange = function(doc, change) { trackChange(self, doc, change); };
74
75
    this.cachedArgHints = null;
76
    this.activeArgHints = null;
77
    this.jumpStack = [];
78
79
    this.getHint = function(cm, c) { return hint(self, cm, c); };
80
    this.getHint.async = true;
81
  };
82
83
  CodeMirror.TernServer.prototype = {
84
    addDoc: function(name, doc) {
85
      var data = {doc: doc, name: name, changed: null};
86
      this.server.addFile(name, docValue(this, data));
87
      CodeMirror.on(doc, "change", this.trackChange);
88
      return this.docs[name] = data;
89
    },
90
91
    delDoc: function(id) {
92
      var found = resolveDoc(this, id);
93
      if (!found) return;
94
      CodeMirror.off(found.doc, "change", this.trackChange);
95
      delete this.docs[found.name];
96
      this.server.delFile(found.name);
97
    },
98
99
    hideDoc: function(id) {
100
      closeArgHints(this);
101
      var found = resolveDoc(this, id);
102
      if (found && found.changed) sendDoc(this, found);
103
    },
104
105
    complete: function(cm) {
106
      cm.showHint({hint: this.getHint});
107
    },
108
109
    showType: function(cm, pos, c) { showContextInfo(this, cm, pos, "type", c); },
110
111
    showDocs: function(cm, pos, c) { showContextInfo(this, cm, pos, "documentation", c); },
112
113
    updateArgHints: function(cm) { updateArgHints(this, cm); },
114
115
    jumpToDef: function(cm) { jumpToDef(this, cm); },
116
117
    jumpBack: function(cm) { jumpBack(this, cm); },
118
119
    rename: function(cm) { rename(this, cm); },
120
121
    selectName: function(cm) { selectName(this, cm); },
122
123
    request: function (cm, query, c, pos) {
124
      var self = this;
125
      var doc = findDoc(this, cm.getDoc());
126
      var request = buildRequest(this, doc, query, pos);
127
128
      this.server.request(request, function (error, data) {
129
        if (!error && self.options.responseFilter)
130
          data = self.options.responseFilter(doc, query, request, error, data);
131
        c(error, data);
132
      });
133
    },
134
135
    destroy: function () {
136
      if (this.worker) {
137
        this.worker.terminate();
138
        this.worker = null;
139
      }
140
    }
141
  };
142
143
  var Pos = CodeMirror.Pos;
144
  var cls = "CodeMirror-Tern-";
145
  var bigDoc = 250;
146
147
  function getFile(ts, name, c) {
148
    var buf = ts.docs[name];
149
    if (buf)
150
      c(docValue(ts, buf));
151
    else if (ts.options.getFile)
152
      ts.options.getFile(name, c);
153
    else
154
      c(null);
155
  }
156
157
  function findDoc(ts, doc, name) {
158
    for (var n in ts.docs) {
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...
159
      var cur = ts.docs[n];
160
      if (cur.doc == doc) return cur;
161
    }
162
    if (!name) for (var i = 0;; ++i) {
163
      n = "[doc" + (i || "") + "]";
164
      if (!ts.docs[n]) { name = n; break; }
165
    }
166
    return ts.addDoc(name, doc);
167
  }
168
169
  function resolveDoc(ts, id) {
170
    if (typeof id == "string") return ts.docs[id];
171
    if (id instanceof CodeMirror) id = id.getDoc();
172
    if (id instanceof CodeMirror.Doc) return findDoc(ts, id);
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if id instanceof CodeMirror.Doc 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...
173
  }
174
175
  function trackChange(ts, doc, change) {
176
    var data = findDoc(ts, doc);
177
178
    var argHints = ts.cachedArgHints;
179
    if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
180
      ts.cachedArgHints = null;
181
182
    var changed = data.changed;
183
    if (changed == null)
0 ignored issues
show
Best Practice introduced by
Comparing changed to null using the == operator is not safe. Consider using === instead.
Loading history...
184
      data.changed = changed = {from: change.from.line, to: change.from.line};
185
    var end = change.from.line + (change.text.length - 1);
186
    if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end);
187
    if (end >= changed.to) changed.to = end + 1;
188
    if (changed.from > change.from.line) changed.from = change.from.line;
189
190
    if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() {
191
      if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data);
192
    }, 200);
193
  }
194
195
  function sendDoc(ts, doc) {
196
    ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) {
197
      if (error) window.console.error(error);
198
      else doc.changed = null;
199
    });
200
  }
201
202
  // Completion
203
204
  function hint(ts, cm, c) {
205
    ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) {
206
      if (error) return showError(ts, cm, error);
207
      var completions = [], after = "";
208
      var from = data.start, to = data.end;
209
      if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" &&
210
          cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]")
211
        after = "\"]";
212
213
      for (var i = 0; i < data.completions.length; ++i) {
214
        var completion = data.completions[i], className = typeToIcon(completion.type);
215
        if (data.guess) className += " " + cls + "guess";
216
        completions.push({text: completion.name + after,
217
                          displayText: completion.name,
218
                          className: className,
219
                          data: completion});
220
      }
221
222
      var obj = {from: from, to: to, list: completions};
223
      var tooltip = null;
224
      CodeMirror.on(obj, "close", function() { remove(tooltip); });
225
      CodeMirror.on(obj, "update", function() { remove(tooltip); });
226
      CodeMirror.on(obj, "select", function(cur, node) {
227
        remove(tooltip);
228
        var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
229
        if (content) {
230
          tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
231
                                node.getBoundingClientRect().top + window.pageYOffset, content);
232
          tooltip.className += " " + cls + "hint-doc";
233
        }
234
      });
235
      c(obj);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
236
    });
237
  }
238
239
  function typeToIcon(type) {
240
    var suffix;
241
    if (type == "?") suffix = "unknown";
242
    else if (type == "number" || type == "string" || type == "bool") suffix = type;
243
    else if (/^fn\(/.test(type)) suffix = "fn";
244
    else if (/^\[/.test(type)) suffix = "array";
245
    else suffix = "object";
246
    return cls + "completion " + cls + "completion-" + suffix;
247
  }
248
249
  // Type queries
250
251
  function showContextInfo(ts, cm, pos, queryName, c) {
252
    ts.request(cm, queryName, function(error, data) {
253
      if (error) return showError(ts, cm, error);
254
      if (ts.options.typeTip) {
255
        var tip = ts.options.typeTip(data);
256
      } else {
257
        var tip = elt("span", null, elt("strong", null, data.type || "not found"));
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable tip already seems to be declared on line 255. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
258
        if (data.doc)
259
          tip.appendChild(document.createTextNode(" — " + data.doc));
260
        if (data.url) {
261
          tip.appendChild(document.createTextNode(" "));
262
          var child = tip.appendChild(elt("a", null, "[docs]"));
263
          child.href = data.url;
264
          child.target = "_blank";
265
        }
266
      }
267
      tempTooltip(cm, tip);
268
      if (c) c();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
Complexity Best Practice introduced by
There is no return statement if c 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...
269
    }, pos);
270
  }
271
272
  // Maintaining argument hints
273
274
  function updateArgHints(ts, cm) {
275
    closeArgHints(ts);
276
277
    if (cm.somethingSelected()) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
278
    var state = cm.getTokenAt(cm.getCursor()).state;
279
    var inner = CodeMirror.innerMode(cm.getMode(), state);
280
    if (inner.mode.name != "javascript") return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
281
    var lex = inner.state.lexical;
282
    if (lex.info != "call") return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
283
284
    var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize");
285
    for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
286
      var str = cm.getLine(line), extra = 0;
287
      for (var pos = 0;;) {
288
        var tab = str.indexOf("\t", pos);
289
        if (tab == -1) break;
290
        extra += tabSize - (tab + extra) % tabSize - 1;
291
        pos = tab + 1;
292
      }
293
      ch = lex.column - extra;
294
      if (str.charAt(ch) == "(") {found = true; break;}
295
    }
296
    if (!found) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
297
298
    var start = Pos(line, ch);
0 ignored issues
show
Bug introduced by
The variable ch seems to not be initialized for all possible execution paths. Are you sure Pos handles undefined variables?
Loading history...
299
    var cache = ts.cachedArgHints;
300
    if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
0 ignored issues
show
Best Practice introduced by
Comparing cmpPos(start, cache.start) to 0 using the == operator is not safe. Consider using === instead.
Loading history...
301
      return showArgHints(ts, cm, argPos);
302
303
    ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
304
      if (error || !data.type || !(/^fn\(/).test(data.type)) return;
305
      ts.cachedArgHints = {
306
        start: pos,
0 ignored issues
show
Bug introduced by
The variable pos seems to not be initialized for all possible execution paths.
Loading history...
307
        type: parseFnType(data.type),
308
        name: data.exprName || data.name || "fn",
309
        guess: data.guess,
310
        doc: cm.getDoc()
311
      };
312
      showArgHints(ts, cm, argPos);
313
    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
314
  }
315
316
  function showArgHints(ts, cm, pos) {
317
    closeArgHints(ts);
318
319
    var cache = ts.cachedArgHints, tp = cache.type;
320
    var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
321
                  elt("span", cls + "fname", cache.name), "(");
322
    for (var i = 0; i < tp.args.length; ++i) {
323
      if (i) tip.appendChild(document.createTextNode(", "));
324
      var arg = tp.args[i];
325
      tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?"));
326
      if (arg.type != "?") {
327
        tip.appendChild(document.createTextNode(":\u00a0"));
328
        tip.appendChild(elt("span", cls + "type", arg.type));
329
      }
330
    }
331
    tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
332
    if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
333
    var place = cm.cursorCoords(null, "page");
334
    ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
335
  }
336
337
  function parseFnType(text) {
338
    var args = [], pos = 3;
339
340
    function skipMatching(upto) {
341
      var depth = 0, start = pos;
342
      for (;;) {
343
        var next = text.charAt(pos);
0 ignored issues
show
Bug introduced by
The variable pos is changed as part of the for loop for example by ++pos on line 347. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
344
        if (upto.test(next) && !depth) return text.slice(start, pos);
345
        if (/[{\[\(]/.test(next)) ++depth;
346
        else if (/[}\]\)]/.test(next)) --depth;
347
        ++pos;
348
      }
349
    }
350
351
    // Parse arguments
352
    if (text.charAt(pos) != ")") for (;;) {
353
      var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
354
      if (name) {
355
        pos += name[0].length;
356
        name = name[1];
357
      }
358
      args.push({name: name, type: skipMatching(/[\),]/)});
359
      if (text.charAt(pos) == ")") break;
360
      pos += 2;
361
    }
362
363
    var rettype = text.slice(pos).match(/^\) -> (.*)$/);
364
365
    return {args: args, rettype: rettype && rettype[1]};
366
  }
367
368
  // Moving to the definition of something
369
370
  function jumpToDef(ts, cm) {
371
    function inner(varName) {
372
      var req = {type: "definition", variable: varName || null};
373
      var doc = findDoc(ts, cm.getDoc());
374
      ts.server.request(buildRequest(ts, doc, req), function(error, data) {
375
        if (error) return showError(ts, cm, error);
376
        if (!data.file && data.url) { window.open(data.url); return; }
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
377
378
        if (data.file) {
379
          var localDoc = ts.docs[data.file], found;
380
          if (localDoc && (found = findContext(localDoc.doc, data))) {
381
            ts.jumpStack.push({file: doc.name,
382
                               start: cm.getCursor("from"),
383
                               end: cm.getCursor("to")});
384
            moveTo(ts, doc, localDoc, found.start, found.end);
385
            return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
386
          }
387
        }
388
        showError(ts, cm, "Could not find a definition.");
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
389
      });
390
    }
391
392
    if (!atInterestingExpression(cm))
393
      dialog(cm, "Jump to variable", function(name) { if (name) inner(name); });
394
    else
395
      inner();
396
  }
397
398
  function jumpBack(ts, cm) {
399
    var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file];
400
    if (!doc) return;
401
    moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end);
402
  }
403
404
  function moveTo(ts, curDoc, doc, start, end) {
405
    doc.doc.setSelection(start, end);
406
    if (curDoc != doc && ts.options.switchToDoc) {
407
      closeArgHints(ts);
408
      ts.options.switchToDoc(doc.name, doc.doc);
409
    }
410
  }
411
412
  // The {line,ch} representation of positions makes this rather awkward.
413
  function findContext(doc, data) {
414
    var before = data.context.slice(0, data.contextOffset).split("\n");
415
    var startLine = data.start.line - (before.length - 1);
416
    var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length);
0 ignored issues
show
Best Practice introduced by
Comparing before.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
417
418
    var text = doc.getLine(startLine).slice(start.ch);
419
    for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur)
420
      text += "\n" + doc.getLine(cur);
421
    if (text.slice(0, data.context.length) == data.context) return data;
422
423
    var cursor = doc.getSearchCursor(data.context, 0, false);
424
    var nearest, nearestDist = Infinity;
0 ignored issues
show
Comprehensibility Best Practice introduced by
You seem to be aliasing the built-in name Infinity as nearestDist. This makes your code very difficult to follow, consider using the built-in name directly.
Loading history...
425
    while (cursor.findNext()) {
426
      var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000;
427
      if (!dist) dist = Math.abs(from.ch - start.ch);
428
      if (dist < nearestDist) { nearest = from; nearestDist = dist; }
429
    }
430
    if (!nearest) return null;
431
432
    if (before.length == 1)
433
      nearest.ch += before[0].length;
434
    else
435
      nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length);
436
    if (data.start.line == data.end.line)
437
      var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch));
438
    else
439
      var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable end already seems to be declared on line 437. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
440
    return {start: nearest, end: end};
441
  }
442
443
  function atInterestingExpression(cm) {
444
    var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
445
    if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
446
    return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
447
  }
448
449
  // Variable renaming
450
451
  function rename(ts, cm) {
452
    var token = cm.getTokenAt(cm.getCursor());
453
    if (!/\w/.test(token.string)) return showError(ts, cm, "Not at a variable");
454
    dialog(cm, "New name for " + token.string, function(newName) {
455
      ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
456
        if (error) return showError(ts, cm, error);
457
        applyChanges(ts, data.changes);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
458
      });
459
    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
460
  }
461
462
  function selectName(ts, cm) {
463
    var name = findDoc(ts, cm.doc).name;
464
    ts.request(cm, {type: "refs"}, function(error, data) {
465
      if (error) return showError(ts, cm, error);
466
      var ranges = [], cur = 0;
467
      for (var i = 0; i < data.refs.length; i++) {
468
        var ref = data.refs[i];
469
        if (ref.file == name) {
470
          ranges.push({anchor: ref.start, head: ref.end});
471
          if (cmpPos(cur, ref.start) >= 0 && cmpPos(cur, ref.end) <= 0)
472
            cur = ranges.length - 1;
473
        }
474
      }
475
      cm.setSelections(ranges, cur);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
476
    });
477
  }
478
479
  var nextChangeOrig = 0;
480
  function applyChanges(ts, changes) {
481
    var perFile = Object.create(null);
482
    for (var i = 0; i < changes.length; ++i) {
483
      var ch = changes[i];
484
      (perFile[ch.file] || (perFile[ch.file] = [])).push(ch);
485
    }
486
    for (var file in perFile) {
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...
487
      var known = ts.docs[file], chs = perFile[file];;
488
      if (!known) continue;
489
      chs.sort(function(a, b) { return cmpPos(b.start, a.start); });
490
      var origin = "*rename" + (++nextChangeOrig);
0 ignored issues
show
Bug introduced by
The variable nextChangeOrig is changed as part of the for-each loop for example by ++nextChangeOrig on line 490. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
491
      for (var i = 0; i < chs.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 482. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
492
        var ch = chs[i];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable ch already seems to be declared on line 483. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
493
        known.doc.replaceRange(ch.text, ch.start, ch.end, origin);
494
      }
495
    }
496
  }
497
498
  // Generic request-building helper
499
500
  function buildRequest(ts, doc, query, pos) {
501
    var files = [], offsetLines = 0, allowFragments = !query.fullDocs;
502
    if (!allowFragments) delete query.fullDocs;
503
    if (typeof query == "string") query = {type: query};
504
    query.lineCharPositions = true;
505
    if (query.end == null) {
0 ignored issues
show
Best Practice introduced by
Comparing query.end to null using the == operator is not safe. Consider using === instead.
Loading history...
506
      query.end = pos || doc.doc.getCursor("end");
507
      if (doc.doc.somethingSelected())
508
        query.start = doc.doc.getCursor("start");
509
    }
510
    var startPos = query.start || query.end;
511
512
    if (doc.changed) {
513
      if (doc.doc.lineCount() > bigDoc && allowFragments !== false &&
514
          doc.changed.to - doc.changed.from < 100 &&
515
          doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
516
        files.push(getFragmentAround(doc, startPos, query.end));
517
        query.file = "#0";
518
        var offsetLines = files[0].offsetLines;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable offsetLines already seems to be declared on line 501. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
519
        if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
0 ignored issues
show
Best Practice introduced by
Comparing query.start to null using the != operator is not safe. Consider using !== instead.
Loading history...
520
        query.end = Pos(query.end.line - offsetLines, query.end.ch);
521
      } else {
522
        files.push({type: "full",
523
                    name: doc.name,
524
                    text: docValue(ts, doc)});
525
        query.file = doc.name;
526
        doc.changed = null;
527
      }
528
    } else {
529
      query.file = doc.name;
530
    }
531
    for (var name in ts.docs) {
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...
532
      var cur = ts.docs[name];
533
      if (cur.changed && cur != doc) {
534
        files.push({type: "full", name: cur.name, text: docValue(ts, cur)});
535
        cur.changed = null;
536
      }
537
    }
538
539
    return {query: query, files: files};
540
  }
541
542
  function getFragmentAround(data, start, end) {
543
    var doc = data.doc;
544
    var minIndent = null, minLine = null, endLine, tabSize = 4;
545
    for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
546
      var line = doc.getLine(p), fn = line.search(/\bfunction\b/);
547
      if (fn < 0) continue;
548
      var indent = CodeMirror.countColumn(line, null, tabSize);
549
      if (minIndent != null && minIndent <= indent) continue;
0 ignored issues
show
Best Practice introduced by
Comparing minIndent to null using the != operator is not safe. Consider using !== instead.
Loading history...
550
      minIndent = indent;
551
      minLine = p;
552
    }
553
    if (minLine == null) minLine = min;
0 ignored issues
show
Best Practice introduced by
Comparing minLine to null using the == operator is not safe. Consider using === instead.
Loading history...
554
    var max = Math.min(doc.lastLine(), end.line + 20);
555
    if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize))
556
      endLine = max;
557
    else for (endLine = end.line + 1; endLine < max; ++endLine) {
558
      var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable indent already seems to be declared on line 548. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
559
      if (indent <= minIndent) break;
560
    }
561
    var from = Pos(minLine, 0);
562
563
    return {type: "part",
564
            name: data.name,
565
            offsetLines: from.line,
566
            text: doc.getRange(from, Pos(endLine, 0))};
567
  }
568
569
  // Generic utilities
570
571
  var cmpPos = CodeMirror.cmpPos;
572
573
  function elt(tagname, cls /*, ... elts*/) {
574
    var e = document.createElement(tagname);
575
    if (cls) e.className = cls;
576
    for (var i = 2; i < arguments.length; ++i) {
577
      var elt = arguments[i];
578
      if (typeof elt == "string") elt = document.createTextNode(elt);
579
      e.appendChild(elt);
580
    }
581
    return e;
582
  }
583
584
  function dialog(cm, text, f) {
585
    if (cm.openDialog)
586
      cm.openDialog(text + ": <input type=text>", f);
587
    else
588
      f(prompt(text, ""));
0 ignored issues
show
Debugging Code Best Practice introduced by
The prompt UI element is often considered obtrusive and is generally only used as a temporary measure. Consider replacing it with another UI element.
Loading history...
589
  }
590
591
  // Tooltips
592
593
  function tempTooltip(cm, content) {
594
    if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
595
    var where = cm.cursorCoords();
596
    var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
597
    function maybeClear() {
598
      old = true;
599
      if (!mouseOnTip) clear();
600
    }
601
    function clear() {
602
      cm.state.ternTooltip = null;
603
      if (!tip.parentNode) return;
604
      cm.off("cursorActivity", clear);
605
      cm.off('blur', clear);
606
      cm.off('scroll', clear);
607
      fadeOut(tip);
608
    }
609
    var mouseOnTip = false, old = false;
610
    CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
611
    CodeMirror.on(tip, "mouseout", function(e) {
612
      if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
613
        if (old) clear();
614
        else mouseOnTip = false;
615
      }
616
    });
617
    setTimeout(maybeClear, 1700);
618
    cm.on("cursorActivity", clear);
619
    cm.on('blur', clear);
620
    cm.on('scroll', clear);
621
  }
622
623
  function makeTooltip(x, y, content) {
624
    var node = elt("div", cls + "tooltip", content);
625
    node.style.left = x + "px";
626
    node.style.top = y + "px";
627
    document.body.appendChild(node);
628
    return node;
629
  }
630
631
  function remove(node) {
632
    var p = node && node.parentNode;
633
    if (p) p.removeChild(node);
634
  }
635
636
  function fadeOut(tooltip) {
637
    tooltip.style.opacity = "0";
638
    setTimeout(function() { remove(tooltip); }, 1100);
639
  }
640
641
  function showError(ts, cm, msg) {
642
    if (ts.options.showError)
643
      ts.options.showError(cm, msg);
644
    else
645
      tempTooltip(cm, String(msg));
646
  }
647
648
  function closeArgHints(ts) {
649
    if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
650
  }
651
652
  function docValue(ts, doc) {
653
    var val = doc.doc.getValue();
654
    if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
655
    return val;
656
  }
657
658
  // Worker wrapper
659
660
  function WorkerServer(ts) {
661
    var worker = ts.worker = new Worker(ts.options.workerScript);
0 ignored issues
show
Bug introduced by
The variable Worker seems to be never declared. If this is a global, consider adding a /** global: Worker */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
662
    worker.postMessage({type: "init",
663
                        defs: ts.options.defs,
664
                        plugins: ts.options.plugins,
665
                        scripts: ts.options.workerDeps});
666
    var msgId = 0, pending = {};
667
668
    function send(data, c) {
669
      if (c) {
670
        data.id = ++msgId;
671
        pending[msgId] = c;
672
      }
673
      worker.postMessage(data);
674
    }
675
    worker.onmessage = function(e) {
676
      var data = e.data;
677
      if (data.type == "getFile") {
678
        getFile(ts, data.name, function(err, text) {
679
          send({type: "getFile", err: String(err), text: text, id: data.id});
680
        });
681
      } else if (data.type == "debug") {
682
        window.console.log(data.message);
683
      } else if (data.id && pending[data.id]) {
684
        pending[data.id](data.err, data.body);
685
        delete pending[data.id];
686
      }
687
    };
688
    worker.onerror = function(e) {
689
      for (var id in pending) pending[id](e);
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...
690
      pending = {};
691
    };
692
693
    this.addFile = function(name, text) { send({type: "add", name: name, text: text}); };
694
    this.delFile = function(name) { send({type: "del", name: name}); };
695
    this.request = function(body, c) { send({type: "req", body: body}, c); };
696
  }
697
});
698