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

coffeescript.js ➔ tokenBase   F

Complexity

Conditions 30

Size

Total Lines 126
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 70
c 0
b 0
f 0
dl 0
loc 126
rs 0
cc 30

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like coffeescript.js ➔ tokenBase 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
/**
5
 * Link to the project's GitHub page:
6
 * https://github.com/pickhardt/coffeescript-codemirror-mode
7
 */
8
(function(mod) {
9
  if (typeof exports == "object" && typeof module == "object") // CommonJS
10
    mod(require("../../lib/codemirror"));
11
  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...
12
    define(["../../lib/codemirror"], mod);
13
  else // Plain browser env
14
    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...
15
})(function(CodeMirror) {
16
"use strict";
17
18
CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
19
  var ERRORCLASS = "error";
20
21
  function wordRegexp(words) {
22
    return new RegExp("^((" + words.join(")|(") + "))\\b");
23
  }
24
25
  var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
26
  var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
27
  var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
28
  var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
29
30
  var wordOperators = wordRegexp(["and", "or", "not",
31
                                  "is", "isnt", "in",
32
                                  "instanceof", "typeof"]);
33
  var indentKeywords = ["for", "while", "loop", "if", "unless", "else",
34
                        "switch", "try", "catch", "finally", "class"];
35
  var commonKeywords = ["break", "by", "continue", "debugger", "delete",
36
                        "do", "in", "of", "new", "return", "then",
37
                        "this", "@", "throw", "when", "until", "extends"];
38
39
  var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
40
41
  indentKeywords = wordRegexp(indentKeywords);
42
43
44
  var stringPrefixes = /^('{3}|\"{3}|['\"])/;
45
  var regexPrefixes = /^(\/{3}|\/)/;
46
  var commonConstants = ["Infinity", "NaN", "undefined", "null", "true", "false", "on", "off", "yes", "no"];
47
  var constants = wordRegexp(commonConstants);
48
49
  // Tokenizers
50
  function tokenBase(stream, state) {
51
    // Handle scope changes
52
    if (stream.sol()) {
53
      if (state.scope.align === null) state.scope.align = false;
54
      var scopeOffset = state.scope.offset;
55
      if (stream.eatSpace()) {
56
        var lineOffset = stream.indentation();
57
        if (lineOffset > scopeOffset && state.scope.type == "coffee") {
58
          return "indent";
59
        } else if (lineOffset < scopeOffset) {
60
          return "dedent";
61
        }
62
        return null;
63
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
64
        if (scopeOffset > 0) {
65
          dedent(stream, state);
66
        }
67
      }
68
    }
69
    if (stream.eatSpace()) {
70
      return null;
71
    }
72
73
    var ch = stream.peek();
74
75
    // Handle docco title comment (single line)
76
    if (stream.match("####")) {
77
      stream.skipToEnd();
78
      return "comment";
79
    }
80
81
    // Handle multi line comments
82
    if (stream.match("###")) {
83
      state.tokenize = longComment;
84
      return state.tokenize(stream, state);
85
    }
86
87
    // Single line comment
88
    if (ch === "#") {
89
      stream.skipToEnd();
90
      return "comment";
91
    }
92
93
    // Handle number literals
94
    if (stream.match(/^-?[0-9\.]/, false)) {
95
      var floatLiteral = false;
96
      // Floats
97
      if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
98
        floatLiteral = true;
99
      }
100
      if (stream.match(/^-?\d+\.\d*/)) {
101
        floatLiteral = true;
102
      }
103
      if (stream.match(/^-?\.\d+/)) {
104
        floatLiteral = true;
105
      }
106
107
      if (floatLiteral) {
108
        // prevent from getting extra . on 1..
109
        if (stream.peek() == "."){
110
          stream.backUp(1);
111
        }
112
        return "number";
113
      }
114
      // Integers
115
      var intLiteral = false;
116
      // Hex
117
      if (stream.match(/^-?0x[0-9a-f]+/i)) {
118
        intLiteral = true;
119
      }
120
      // Decimal
121
      if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
122
        intLiteral = true;
123
      }
124
      // Zero by itself with no other piece of number.
125
      if (stream.match(/^-?0(?![\dx])/i)) {
126
        intLiteral = true;
127
      }
128
      if (intLiteral) {
129
        return "number";
130
      }
131
    }
132
133
    // Handle strings
134
    if (stream.match(stringPrefixes)) {
135
      state.tokenize = tokenFactory(stream.current(), false, "string");
136
      return state.tokenize(stream, state);
137
    }
138
    // Handle regex literals
139
    if (stream.match(regexPrefixes)) {
140
      if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division
141
        state.tokenize = tokenFactory(stream.current(), true, "string-2");
142
        return state.tokenize(stream, state);
143
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
144
        stream.backUp(1);
145
      }
146
    }
147
148
    // Handle operators and delimiters
149
    if (stream.match(operators) || stream.match(wordOperators)) {
150
      return "operator";
151
    }
152
    if (stream.match(delimiters)) {
153
      return "punctuation";
154
    }
155
156
    if (stream.match(constants)) {
157
      return "atom";
158
    }
159
160
    if (stream.match(keywords)) {
161
      return "keyword";
162
    }
163
164
    if (stream.match(identifiers)) {
165
      return "variable";
166
    }
167
168
    if (stream.match(properties)) {
169
      return "property";
170
    }
171
172
    // Handle non-detected items
173
    stream.next();
174
    return ERRORCLASS;
175
  }
176
177
  function tokenFactory(delimiter, singleline, outclass) {
178
    return function(stream, state) {
179
      while (!stream.eol()) {
180
        stream.eatWhile(/[^'"\/\\]/);
181
        if (stream.eat("\\")) {
182
          stream.next();
183
          if (singleline && stream.eol()) {
184
            return outclass;
185
          }
186
        } else if (stream.match(delimiter)) {
187
          state.tokenize = tokenBase;
188
          return outclass;
189
        } else {
190
          stream.eat(/['"\/]/);
191
        }
192
      }
193
      if (singleline) {
194
        if (parserConf.singleLineStringErrors) {
195
          outclass = ERRORCLASS;
196
        } else {
197
          state.tokenize = tokenBase;
198
        }
199
      }
200
      return outclass;
201
    };
202
  }
203
204
  function longComment(stream, state) {
205
    while (!stream.eol()) {
206
      stream.eatWhile(/[^#]/);
207
      if (stream.match("###")) {
208
        state.tokenize = tokenBase;
209
        break;
210
      }
211
      stream.eatWhile("#");
212
    }
213
    return "comment";
214
  }
215
216
  function indent(stream, state, type) {
217
    type = type || "coffee";
218
    var offset = 0, align = false, alignOffset = null;
219
    for (var scope = state.scope; scope; scope = scope.prev) {
220
      if (scope.type === "coffee" || scope.type == "}") {
221
        offset = scope.offset + conf.indentUnit;
222
        break;
223
      }
224
    }
225
    if (type !== "coffee") {
226
      align = null;
227
      alignOffset = stream.column() + stream.current().length;
228
    } else if (state.scope.align) {
229
      state.scope.align = false;
230
    }
231
    state.scope = {
232
      offset: offset,
233
      type: type,
234
      prev: state.scope,
235
      align: align,
236
      alignOffset: alignOffset
237
    };
238
  }
239
240
  function dedent(stream, state) {
241
    if (!state.scope.prev) 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...
242
    if (state.scope.type === "coffee") {
243
      var _indent = stream.indentation();
244
      var matched = false;
245
      for (var scope = state.scope; scope; scope = scope.prev) {
246
        if (_indent === scope.offset) {
247
          matched = true;
248
          break;
249
        }
250
      }
251
      if (!matched) {
252
        return true;
253
      }
254
      while (state.scope.prev && state.scope.offset !== _indent) {
255
        state.scope = state.scope.prev;
256
      }
257
      return false;
258
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
259
      state.scope = state.scope.prev;
260
      return false;
261
    }
262
  }
263
264
  function tokenLexer(stream, state) {
265
    var style = state.tokenize(stream, state);
266
    var current = stream.current();
267
268
    // Handle "." connected identifiers
269
    if (current === ".") {
270
      style = state.tokenize(stream, state);
0 ignored issues
show
Unused Code introduced by
The assignment to variable style seems to be never used. Consider removing it.
Loading history...
271
      current = stream.current();
272
      if (/^\.[\w$]+$/.test(current)) {
273
        return "variable";
274
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
275
        return ERRORCLASS;
276
      }
277
    }
278
279
    // Handle scope changes.
280
    if (current === "return") {
281
      state.dedent = true;
282
    }
283
    if (((current === "->" || current === "=>") &&
284
         !state.lambda &&
285
         !stream.peek())
286
        || style === "indent") {
287
      indent(stream, state);
288
    }
289
    var delimiter_index = "[({".indexOf(current);
290
    if (delimiter_index !== -1) {
291
      indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
292
    }
293
    if (indentKeywords.exec(current)){
294
      indent(stream, state);
295
    }
296
    if (current == "then"){
297
      dedent(stream, state);
298
    }
299
300
301
    if (style === "dedent") {
302
      if (dedent(stream, state)) {
303
        return ERRORCLASS;
304
      }
305
    }
306
    delimiter_index = "])}".indexOf(current);
307
    if (delimiter_index !== -1) {
308
      while (state.scope.type == "coffee" && state.scope.prev)
309
        state.scope = state.scope.prev;
310
      if (state.scope.type == current)
311
        state.scope = state.scope.prev;
312
    }
313
    if (state.dedent && stream.eol()) {
314
      if (state.scope.type == "coffee" && state.scope.prev)
315
        state.scope = state.scope.prev;
316
      state.dedent = false;
317
    }
318
319
    return style;
320
  }
321
322
  var external = {
323
    startState: function(basecolumn) {
324
      return {
325
        tokenize: tokenBase,
326
        scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
327
        lastToken: null,
328
        lambda: false,
329
        dedent: 0
330
      };
331
    },
332
333
    token: function(stream, state) {
334
      var fillAlign = state.scope.align === null && state.scope;
335
      if (fillAlign && stream.sol()) fillAlign.align = false;
336
337
      var style = tokenLexer(stream, state);
338
      if (fillAlign && style && style != "comment") fillAlign.align = true;
339
340
      state.lastToken = {style:style, content: stream.current()};
341
342
      if (stream.eol() && stream.lambda) {
343
        state.lambda = false;
344
      }
345
346
      return style;
347
    },
348
349
    indent: function(state, text) {
350
      if (state.tokenize != tokenBase) return 0;
351
      var scope = state.scope;
352
      var closer = text && "])}".indexOf(text.charAt(0)) > -1;
353
      if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev;
354
      var closes = closer && scope.type === text.charAt(0);
355
      if (scope.align)
356
        return scope.alignOffset - (closes ? 1 : 0);
357
      else
358
        return (closes ? scope.prev : scope).offset;
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
359
    },
360
361
    lineComment: "#",
362
    fold: "indent"
363
  };
364
  return external;
365
});
366
367
CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
368
369
});
370