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

ruby.js ➔ tokenBase   F

Complexity

Conditions 44

Size

Total Lines 112
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 96
c 0
b 0
f 0
dl 0
loc 112
rs 0
cc 44

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 ruby.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
(function(mod) {
5
  if (typeof exports == "object" && typeof module == "object") // CommonJS
6
    mod(require("../../lib/codemirror"));
7
  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...
8
    define(["../../lib/codemirror"], mod);
9
  else // Plain browser env
10
    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...
11
})(function(CodeMirror) {
12
"use strict";
13
14
CodeMirror.defineMode("ruby", function(config) {
15
  function wordObj(words) {
16
    var o = {};
17
    for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true;
18
    return o;
19
  }
20
  var keywords = wordObj([
21
    "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
22
    "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or",
23
    "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
24
    "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc",
25
    "caller", "lambda", "proc", "public", "protected", "private", "require", "load",
26
    "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
27
  ]);
28
  var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then",
29
                             "catch", "loop", "proc", "begin"]);
30
  var dedentWords = wordObj(["end", "until"]);
31
  var matching = {"[": "]", "{": "}", "(": ")"};
32
  var curPunc;
33
34
  function chain(newtok, stream, state) {
35
    state.tokenize.push(newtok);
36
    return newtok(stream, state);
37
  }
38
39
  function tokenBase(stream, state) {
40
    curPunc = null;
41
    if (stream.sol() && stream.match("=begin") && stream.eol()) {
42
      state.tokenize.push(readBlockComment);
43
      return "comment";
44
    }
45
    if (stream.eatSpace()) return null;
46
    var ch = stream.next(), m;
47
    if (ch == "`" || ch == "'" || ch == '"') {
48
      return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state);
49
    } else if (ch == "/") {
50
      var currentIndex = stream.current().length;
51
      if (stream.skipTo("/")) {
52
        var search_till = stream.current().length;
53
        stream.backUp(stream.current().length - currentIndex);
54
        var balance = 0;  // balance brackets
55
        while (stream.current().length < search_till) {
56
          var chchr = stream.next();
57
          if (chchr == "(") balance += 1;
58
          else if (chchr == ")") balance -= 1;
59
          if (balance < 0) break;
60
        }
61
        stream.backUp(stream.current().length - currentIndex);
62
        if (balance == 0)
0 ignored issues
show
Best Practice introduced by
Comparing balance to 0 using the == operator is not safe. Consider using === instead.
Loading history...
63
          return chain(readQuoted(ch, "string-2", true), stream, state);
64
      }
65
      return "operator";
66
    } else if (ch == "%") {
67
      var style = "string", embed = true;
68
      if (stream.eat("s")) style = "atom";
69
      else if (stream.eat(/[WQ]/)) style = "string";
70
      else if (stream.eat(/[r]/)) style = "string-2";
71
      else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; }
72
      var delim = stream.eat(/[^\w\s=]/);
73
      if (!delim) return "operator";
74
      if (matching.propertyIsEnumerable(delim)) delim = matching[delim];
75
      return chain(readQuoted(delim, style, embed, true), stream, state);
76
    } else if (ch == "#") {
77
      stream.skipToEnd();
78
      return "comment";
79
    } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) {
80
      return chain(readHereDoc(m[1]), stream, state);
81
    } else if (ch == "0") {
82
      if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/);
83
      else if (stream.eat("b")) stream.eatWhile(/[01]/);
84
      else stream.eatWhile(/[0-7]/);
85
      return "number";
86
    } else if (/\d/.test(ch)) {
87
      stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/);
88
      return "number";
89
    } else if (ch == "?") {
90
      while (stream.match(/^\\[CM]-/)) {}
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
91
      if (stream.eat("\\")) stream.eatWhile(/\w/);
92
      else stream.next();
93
      return "string";
94
    } else if (ch == ":") {
95
      if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state);
96
      if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state);
97
98
      // :> :>> :< :<< are valid symbols
99
      if (stream.eat(/[\<\>]/)) {
100
        stream.eat(/[\<\>]/);
101
        return "atom";
102
      }
103
104
      // :+ :- :/ :* :| :& :! are valid symbols
105
      if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) {
106
        return "atom";
107
      }
108
109
      // Symbols can't start by a digit
110
      if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) {
111
        stream.eatWhile(/[\w$\xa1-\uffff]/);
112
        // Only one ? ! = is allowed and only as the last character
113
        stream.eat(/[\?\!\=]/);
114
        return "atom";
115
      }
116
      return "operator";
117
    } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) {
118
      stream.eat("@");
119
      stream.eatWhile(/[\w\xa1-\uffff]/);
120
      return "variable-2";
121
    } else if (ch == "$") {
122
      if (stream.eat(/[a-zA-Z_]/)) {
123
        stream.eatWhile(/[\w]/);
124
      } else if (stream.eat(/\d/)) {
125
        stream.eat(/\d/);
126
      } else {
127
        stream.next(); // Must be a special global like $: or $!
128
      }
129
      return "variable-3";
130
    } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) {
131
      stream.eatWhile(/[\w\xa1-\uffff]/);
132
      stream.eat(/[\?\!]/);
133
      if (stream.eat(":")) return "atom";
134
      return "ident";
135
    } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) {
136
      curPunc = "|";
137
      return null;
138
    } else if (/[\(\)\[\]{}\\;]/.test(ch)) {
139
      curPunc = ch;
140
      return null;
141
    } else if (ch == "-" && stream.eat(">")) {
142
      return "arrow";
143
    } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) {
144
      var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
145
      if (ch == "." && !more) curPunc = ".";
146
      return "operator";
147
    } else {
148
      return null;
149
    }
150
  }
151
152
  function tokenBaseUntilBrace(depth) {
153
    if (!depth) depth = 1;
154
    return function(stream, state) {
155
      if (stream.peek() == "}") {
156
        if (depth == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing depth to 1 using the == operator is not safe. Consider using === instead.
Loading history...
157
          state.tokenize.pop();
158
          return state.tokenize[state.tokenize.length-1](stream, state);
159
        } 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...
160
          state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1);
161
        }
162
      } else if (stream.peek() == "{") {
163
        state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1);
164
      }
165
      return tokenBase(stream, state);
166
    };
167
  }
168
  function tokenBaseOnce() {
169
    var alreadyCalled = false;
170
    return function(stream, state) {
171
      if (alreadyCalled) {
172
        state.tokenize.pop();
173
        return state.tokenize[state.tokenize.length-1](stream, state);
174
      }
175
      alreadyCalled = true;
176
      return tokenBase(stream, state);
177
    };
178
  }
179
  function readQuoted(quote, style, embed, unescaped) {
180
    return function(stream, state) {
181
      var escaped = false, ch;
182
183
      if (state.context.type === 'read-quoted-paused') {
184
        state.context = state.context.prev;
185
        stream.eat("}");
186
      }
187
188
      while ((ch = stream.next()) != null) {
0 ignored issues
show
Best Practice introduced by
Comparing ch = stream.next() to null using the != operator is not safe. Consider using !== instead.
Loading history...
189
        if (ch == quote && (unescaped || !escaped)) {
190
          state.tokenize.pop();
191
          break;
192
        }
193
        if (embed && ch == "#" && !escaped) {
194
          if (stream.eat("{")) {
195
            if (quote == "}") {
196
              state.context = {prev: state.context, type: 'read-quoted-paused'};
197
            }
198
            state.tokenize.push(tokenBaseUntilBrace());
199
            break;
200
          } else if (/[@\$]/.test(stream.peek())) {
201
            state.tokenize.push(tokenBaseOnce());
202
            break;
203
          }
204
        }
205
        escaped = !escaped && ch == "\\";
206
      }
207
      return style;
208
    };
209
  }
210
  function readHereDoc(phrase) {
211
    return function(stream, state) {
212
      if (stream.match(phrase)) state.tokenize.pop();
213
      else stream.skipToEnd();
214
      return "string";
215
    };
216
  }
217
  function readBlockComment(stream, state) {
218
    if (stream.sol() && stream.match("=end") && stream.eol())
219
      state.tokenize.pop();
220
    stream.skipToEnd();
221
    return "comment";
222
  }
223
224
  return {
225
    startState: function() {
226
      return {tokenize: [tokenBase],
227
              indented: 0,
228
              context: {type: "top", indented: -config.indentUnit},
229
              continuedLine: false,
230
              lastTok: null,
231
              varList: false};
232
    },
233
234
    token: function(stream, state) {
235
      if (stream.sol()) state.indented = stream.indentation();
236
      var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
237
      var thisTok = curPunc;
238
      if (style == "ident") {
239
        var word = stream.current();
240
        style = state.lastTok == "." ? "property"
241
          : keywords.propertyIsEnumerable(stream.current()) ? "keyword"
242
          : /^[A-Z]/.test(word) ? "tag"
243
          : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def"
244
          : "variable";
245
        if (style == "keyword") {
246
          thisTok = word;
247
          if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
248
          else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
249
          else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
250
            kwtype = "indent";
251
          else if (word == "do" && state.context.indented < state.indented)
252
            kwtype = "indent";
253
        }
254
      }
255
      if (curPunc || (style && style != "comment")) state.lastTok = thisTok;
256
      if (curPunc == "|") state.varList = !state.varList;
0 ignored issues
show
Bug introduced by
The variable curPunc seems to not be initialized for all possible execution paths.
Loading history...
257
258
      if (kwtype == "indent" || /[\(\[\{]/.test(curPunc))
0 ignored issues
show
Bug introduced by
The variable kwtype seems to not be initialized for all possible execution paths.
Loading history...
259
        state.context = {prev: state.context, type: curPunc || style, indented: state.indented};
260
      else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev)
261
        state.context = state.context.prev;
262
263
      if (stream.eol())
264
        state.continuedLine = (curPunc == "\\" || style == "operator");
265
      return style;
266
    },
267
268
    indent: function(state, textAfter) {
269
      if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0;
270
      var firstChar = textAfter && textAfter.charAt(0);
271
      var ct = state.context;
272
      var closing = ct.type == matching[firstChar] ||
273
        ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter);
274
      return ct.indented + (closing ? 0 : config.indentUnit) +
275
        (state.continuedLine ? config.indentUnit : 0);
276
    },
277
278
    electricChars: "}de", // enD and rescuE
279
    lineComment: "#"
280
  };
281
});
282
283
CodeMirror.defineMIME("text/x-ruby", "ruby");
284
285
});
286