1// CodeMirror, copyright (c) by Marijn Haverbeke and others
2// Distributed under an MIT license: http://codemirror.net/LICENSE
3
4// TODO actually recognize syntax of TypeScript constructs
5
6(function(mod) {
7  if (typeof exports == "object" && typeof module == "object") // CommonJS
8    mod(require("../../lib/codemirror"));
9  else if (typeof define == "function" && define.amd) // AMD
10    define(["../../lib/codemirror"], mod);
11  else // Plain browser env
12    mod(CodeMirror);
13})(function(CodeMirror) {
14"use strict";
15
16CodeMirror.defineMode("javascript", function(config, parserConfig) {
17  var indentUnit = config.indentUnit;
18  var statementIndent = parserConfig.statementIndent;
19  var jsonldMode = parserConfig.jsonld;
20  var jsonMode = parserConfig.json || jsonldMode;
21  var isTS = parserConfig.typescript;
22
23  // Tokenizer
24
25  var keywords = function(){
26    function kw(type) {return {type: type, style: "keyword"};}
27    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
28    var operator = kw("operator"), atom = {type: "atom", style: "atom"};
29
30    var jsKeywords = {
31      "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
32      "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
33      "var": kw("var"), "const": kw("var"), "let": kw("var"),
34      "function": kw("function"), "catch": kw("catch"),
35      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
36      "in": operator, "typeof": operator, "instanceof": operator,
37      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
38      "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
39      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
40    };
41
42    // Extend the 'normal' keywords with the TypeScript language extensions
43    if (isTS) {
44      var type = {type: "variable", style: "variable-3"};
45      var tsKeywords = {
46        // object-like things
47        "interface": kw("interface"),
48        "extends": kw("extends"),
49        "constructor": kw("constructor"),
50
51        // scope modifiers
52        "public": kw("public"),
53        "private": kw("private"),
54        "protected": kw("protected"),
55        "static": kw("static"),
56
57        // types
58        "string": type, "number": type, "bool": type, "any": type
59      };
60
61      for (var attr in tsKeywords) {
62        jsKeywords[attr] = tsKeywords[attr];
63      }
64    }
65
66    return jsKeywords;
67  }();
68
69  var isOperatorChar = /[+\-*&%=<>!?|~^]/;
70  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
71
72  function readRegexp(stream) {
73    var escaped = false, next, inSet = false;
74    while ((next = stream.next()) != null) {
75      if (!escaped) {
76        if (next == "/" && !inSet) return;
77        if (next == "[") inSet = true;
78        else if (inSet && next == "]") inSet = false;
79      }
80      escaped = !escaped && next == "\\";
81    }
82  }
83
84  // Used as scratch variables to communicate multiple values without
85  // consing up tons of objects.
86  var type, content;
87  function ret(tp, style, cont) {
88    type = tp; content = cont;
89    return style;
90  }
91  function tokenBase(stream, state) {
92    var ch = stream.next();
93    if (ch == '"' || ch == "'") {
94      state.tokenize = tokenString(ch);
95      return state.tokenize(stream, state);
96    } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
97      return ret("number", "number");
98    } else if (ch == "." && stream.match("..")) {
99      return ret("spread", "meta");
100    } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
101      return ret(ch);
102    } else if (ch == "=" && stream.eat(">")) {
103      return ret("=>", "operator");
104    } else if (ch == "0" && stream.eat(/x/i)) {
105      stream.eatWhile(/[\da-f]/i);
106      return ret("number", "number");
107    } else if (/\d/.test(ch)) {
108      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
109      return ret("number", "number");
110    } else if (ch == "/") {
111      if (stream.eat("*")) {
112        state.tokenize = tokenComment;
113        return tokenComment(stream, state);
114      } else if (stream.eat("/")) {
115        stream.skipToEnd();
116        return ret("comment", "comment");
117      } else if (state.lastType == "operator" || state.lastType == "keyword c" ||
118               state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
119        readRegexp(stream);
120        stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
121        return ret("regexp", "string-2");
122      } else {
123        stream.eatWhile(isOperatorChar);
124        return ret("operator", "operator", stream.current());
125      }
126    } else if (ch == "`") {
127      state.tokenize = tokenQuasi;
128      return tokenQuasi(stream, state);
129    } else if (ch == "#") {
130      stream.skipToEnd();
131      return ret("error", "error");
132    } else if (isOperatorChar.test(ch)) {
133      stream.eatWhile(isOperatorChar);
134      return ret("operator", "operator", stream.current());
135    } else {
136      stream.eatWhile(/[\w\$_]/);
137      var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
138      return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
139                     ret("variable", "variable", word);
140    }
141  }
142
143  function tokenString(quote) {
144    return function(stream, state) {
145      var escaped = false, next;
146      if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
147        state.tokenize = tokenBase;
148        return ret("jsonld-keyword", "meta");
149      }
150      while ((next = stream.next()) != null) {
151        if (next == quote && !escaped) break;
152        escaped = !escaped && next == "\\";
153      }
154      if (!escaped) state.tokenize = tokenBase;
155      return ret("string", "string");
156    };
157  }
158
159  function tokenComment(stream, state) {
160    var maybeEnd = false, ch;
161    while (ch = stream.next()) {
162      if (ch == "/" && maybeEnd) {
163        state.tokenize = tokenBase;
164        break;
165      }
166      maybeEnd = (ch == "*");
167    }
168    return ret("comment", "comment");
169  }
170
171  function tokenQuasi(stream, state) {
172    var escaped = false, next;
173    while ((next = stream.next()) != null) {
174      if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
175        state.tokenize = tokenBase;
176        break;
177      }
178      escaped = !escaped && next == "\\";
179    }
180    return ret("quasi", "string-2", stream.current());
181  }
182
183  var brackets = "([{}])";
184  // This is a crude lookahead trick to try and notice that we're
185  // parsing the argument patterns for a fat-arrow function before we
186  // actually hit the arrow token. It only works if the arrow is on
187  // the same line as the arguments and there's no strange noise
188  // (comments) in between. Fallback is to only notice when we hit the
189  // arrow, and not declare the arguments as locals for the arrow
190  // body.
191  function findFatArrow(stream, state) {
192    if (state.fatArrowAt) state.fatArrowAt = null;
193    var arrow = stream.string.indexOf("=>", stream.start);
194    if (arrow < 0) return;
195
196    var depth = 0, sawSomething = false;
197    for (var pos = arrow - 1; pos >= 0; --pos) {
198      var ch = stream.string.charAt(pos);
199      var bracket = brackets.indexOf(ch);
200      if (bracket >= 0 && bracket < 3) {
201        if (!depth) { ++pos; break; }
202        if (--depth == 0) break;
203      } else if (bracket >= 3 && bracket < 6) {
204        ++depth;
205      } else if (/[$\w]/.test(ch)) {
206        sawSomething = true;
207      } else if (sawSomething && !depth) {
208        ++pos;
209        break;
210      }
211    }
212    if (sawSomething && !depth) state.fatArrowAt = pos;
213  }
214
215  // Parser
216
217  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
218
219  function JSLexical(indented, column, type, align, prev, info) {
220    this.indented = indented;
221    this.column = column;
222    this.type = type;
223    this.prev = prev;
224    this.info = info;
225    if (align != null) this.align = align;
226  }
227
228  function inScope(state, varname) {
229    for (var v = state.localVars; v; v = v.next)
230      if (v.name == varname) return true;
231    for (var cx = state.context; cx; cx = cx.prev) {
232      for (var v = cx.vars; v; v = v.next)
233        if (v.name == varname) return true;
234    }
235  }
236
237  function parseJS(state, style, type, content, stream) {
238    var cc = state.cc;
239    // Communicate our context to the combinators.
240    // (Less wasteful than consing up a hundred closures on every call.)
241    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
242
243    if (!state.lexical.hasOwnProperty("align"))
244      state.lexical.align = true;
245
246    while(true) {
247      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
248      if (combinator(type, content)) {
249        while(cc.length && cc[cc.length - 1].lex)
250          cc.pop()();
251        if (cx.marked) return cx.marked;
252        if (type == "variable" && inScope(state, content)) return "variable-2";
253        return style;
254      }
255    }
256  }
257
258  // Combinator utils
259
260  var cx = {state: null, column: null, marked: null, cc: null};
261  function pass() {
262    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
263  }
264  function cont() {
265    pass.apply(null, arguments);
266    return true;
267  }
268  function register(varname) {
269    function inList(list) {
270      for (var v = list; v; v = v.next)
271        if (v.name == varname) return true;
272      return false;
273    }
274    var state = cx.state;
275    if (state.context) {
276      cx.marked = "def";
277      if (inList(state.localVars)) return;
278      state.localVars = {name: varname, next: state.localVars};
279    } else {
280      if (inList(state.globalVars)) return;
281      if (parserConfig.globalVars)
282        state.globalVars = {name: varname, next: state.globalVars};
283    }
284  }
285
286  // Combinators
287
288  var defaultVars = {name: "this", next: {name: "arguments"}};
289  function pushcontext() {
290    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
291    cx.state.localVars = defaultVars;
292  }
293  function popcontext() {
294    cx.state.localVars = cx.state.context.vars;
295    cx.state.context = cx.state.context.prev;
296  }
297  function pushlex(type, info) {
298    var result = function() {
299      var state = cx.state, indent = state.indented;
300      if (state.lexical.type == "stat") indent = state.lexical.indented;
301      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
302    };
303    result.lex = true;
304    return result;
305  }
306  function poplex() {
307    var state = cx.state;
308    if (state.lexical.prev) {
309      if (state.lexical.type == ")")
310        state.indented = state.lexical.indented;
311      state.lexical = state.lexical.prev;
312    }
313  }
314  poplex.lex = true;
315
316  function expect(wanted) {
317    function exp(type) {
318      if (type == wanted) return cont();
319      else if (wanted == ";") return pass();
320      else return cont(exp);
321    };
322    return exp;
323  }
324
325  function statement(type, value) {
326    if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
327    if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
328    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
329    if (type == "{") return cont(pushlex("}"), block, poplex);
330    if (type == ";") return cont();
331    if (type == "if") {
332      if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
333        cx.state.cc.pop()();
334      return cont(pushlex("form"), expression, statement, poplex, maybeelse);
335    }
336    if (type == "function") return cont(functiondef);
337    if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
338    if (type == "variable") return cont(pushlex("stat"), maybelabel);
339    if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
340                                      block, poplex, poplex);
341    if (type == "case") return cont(expression, expect(":"));
342    if (type == "default") return cont(expect(":"));
343    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
344                                     statement, poplex, popcontext);
345    if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
346    if (type == "class") return cont(pushlex("form"), className, poplex);
347    if (type == "export") return cont(pushlex("form"), afterExport, poplex);
348    if (type == "import") return cont(pushlex("form"), afterImport, poplex);
349    return pass(pushlex("stat"), expression, expect(";"), poplex);
350  }
351  function expression(type) {
352    return expressionInner(type, false);
353  }
354  function expressionNoComma(type) {
355    return expressionInner(type, true);
356  }
357  function expressionInner(type, noComma) {
358    if (cx.state.fatArrowAt == cx.stream.start) {
359      var body = noComma ? arrowBodyNoComma : arrowBody;
360      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
361      else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
362    }
363
364    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
365    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
366    if (type == "function") return cont(functiondef, maybeop);
367    if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
368    if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
369    if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
370    if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
371    if (type == "{") return contCommasep(objprop, "}", null, maybeop);
372    if (type == "quasi") { return pass(quasi, maybeop); }
373    return cont();
374  }
375  function maybeexpression(type) {
376    if (type.match(/[;\}\)\],]/)) return pass();
377    return pass(expression);
378  }
379  function maybeexpressionNoComma(type) {
380    if (type.match(/[;\}\)\],]/)) return pass();
381    return pass(expressionNoComma);
382  }
383
384  function maybeoperatorComma(type, value) {
385    if (type == ",") return cont(expression);
386    return maybeoperatorNoComma(type, value, false);
387  }
388  function maybeoperatorNoComma(type, value, noComma) {
389    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
390    var expr = noComma == false ? expression : expressionNoComma;
391    if (value == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
392    if (type == "operator") {
393      if (/\+\+|--/.test(value)) return cont(me);
394      if (value == "?") return cont(expression, expect(":"), expr);
395      return cont(expr);
396    }
397    if (type == "quasi") { return pass(quasi, me); }
398    if (type == ";") return;
399    if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
400    if (type == ".") return cont(property, me);
401    if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
402  }
403  function quasi(type, value) {
404    if (type != "quasi") return pass();
405    if (value.slice(value.length - 2) != "${") return cont(quasi);
406    return cont(expression, continueQuasi);
407  }
408  function continueQuasi(type) {
409    if (type == "}") {
410      cx.marked = "string-2";
411      cx.state.tokenize = tokenQuasi;
412      return cont(quasi);
413    }
414  }
415  function arrowBody(type) {
416    findFatArrow(cx.stream, cx.state);
417    if (type == "{") return pass(statement);
418    return pass(expression);
419  }
420  function arrowBodyNoComma(type) {
421    findFatArrow(cx.stream, cx.state);
422    if (type == "{") return pass(statement);
423    return pass(expressionNoComma);
424  }
425  function maybelabel(type) {
426    if (type == ":") return cont(poplex, statement);
427    return pass(maybeoperatorComma, expect(";"), poplex);
428  }
429  function property(type) {
430    if (type == "variable") {cx.marked = "property"; return cont();}
431  }
432  function objprop(type, value) {
433    if (type == "variable" || cx.style == "keyword") {
434      cx.marked = "property";
435      if (value == "get" || value == "set") return cont(getterSetter);
436      return cont(afterprop);
437    } else if (type == "number" || type == "string") {
438      cx.marked = jsonldMode ? "property" : (cx.style + " property");
439      return cont(afterprop);
440    } else if (type == "jsonld-keyword") {
441      return cont(afterprop);
442    } else if (type == "[") {
443      return cont(expression, expect("]"), afterprop);
444    }
445  }
446  function getterSetter(type) {
447    if (type != "variable") return pass(afterprop);
448    cx.marked = "property";
449    return cont(functiondef);
450  }
451  function afterprop(type) {
452    if (type == ":") return cont(expressionNoComma);
453    if (type == "(") return pass(functiondef);
454  }
455  function commasep(what, end) {
456    function proceed(type) {
457      if (type == ",") {
458        var lex = cx.state.lexical;
459        if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
460        return cont(what, proceed);
461      }
462      if (type == end) return cont();
463      return cont(expect(end));
464    }
465    return function(type) {
466      if (type == end) return cont();
467      return pass(what, proceed);
468    };
469  }
470  function contCommasep(what, end, info) {
471    for (var i = 3; i < arguments.length; i++)
472      cx.cc.push(arguments[i]);
473    return cont(pushlex(end, info), commasep(what, end), poplex);
474  }
475  function block(type) {
476    if (type == "}") return cont();
477    return pass(statement, block);
478  }
479  function maybetype(type) {
480    if (isTS && type == ":") return cont(typedef);
481  }
482  function typedef(type) {
483    if (type == "variable"){cx.marked = "variable-3"; return cont();}
484  }
485  function vardef() {
486    return pass(pattern, maybetype, maybeAssign, vardefCont);
487  }
488  function pattern(type, value) {
489    if (type == "variable") { register(value); return cont(); }
490    if (type == "[") return contCommasep(pattern, "]");
491    if (type == "{") return contCommasep(proppattern, "}");
492  }
493  function proppattern(type, value) {
494    if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
495      register(value);
496      return cont(maybeAssign);
497    }
498    if (type == "variable") cx.marked = "property";
499    return cont(expect(":"), pattern, maybeAssign);
500  }
501  function maybeAssign(_type, value) {
502    if (value == "=") return cont(expressionNoComma);
503  }
504  function vardefCont(type) {
505    if (type == ",") return cont(vardef);
506  }
507  function maybeelse(type, value) {
508    if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
509  }
510  function forspec(type) {
511    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
512  }
513  function forspec1(type) {
514    if (type == "var") return cont(vardef, expect(";"), forspec2);
515    if (type == ";") return cont(forspec2);
516    if (type == "variable") return cont(formaybeinof);
517    return pass(expression, expect(";"), forspec2);
518  }
519  function formaybeinof(_type, value) {
520    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
521    return cont(maybeoperatorComma, forspec2);
522  }
523  function forspec2(type, value) {
524    if (type == ";") return cont(forspec3);
525    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
526    return pass(expression, expect(";"), forspec3);
527  }
528  function forspec3(type) {
529    if (type != ")") cont(expression);
530  }
531  function functiondef(type, value) {
532    if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
533    if (type == "variable") {register(value); return cont(functiondef);}
534    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
535  }
536  function funarg(type) {
537    if (type == "spread") return cont(funarg);
538    return pass(pattern, maybetype);
539  }
540  function className(type, value) {
541    if (type == "variable") {register(value); return cont(classNameAfter);}
542  }
543  function classNameAfter(type, value) {
544    if (value == "extends") return cont(expression, classNameAfter);
545    if (type == "{") return cont(pushlex("}"), classBody, poplex);
546  }
547  function classBody(type, value) {
548    if (type == "variable" || cx.style == "keyword") {
549      cx.marked = "property";
550      if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
551      return cont(functiondef, classBody);
552    }
553    if (value == "*") {
554      cx.marked = "keyword";
555      return cont(classBody);
556    }
557    if (type == ";") return cont(classBody);
558    if (type == "}") return cont();
559  }
560  function classGetterSetter(type) {
561    if (type != "variable") return pass();
562    cx.marked = "property";
563    return cont();
564  }
565  function afterModule(type, value) {
566    if (type == "string") return cont(statement);
567    if (type == "variable") { register(value); return cont(maybeFrom); }
568  }
569  function afterExport(_type, value) {
570    if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
571    if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
572    return pass(statement);
573  }
574  function afterImport(type) {
575    if (type == "string") return cont();
576    return pass(importSpec, maybeFrom);
577  }
578  function importSpec(type, value) {
579    if (type == "{") return contCommasep(importSpec, "}");
580    if (type == "variable") register(value);
581    return cont();
582  }
583  function maybeFrom(_type, value) {
584    if (value == "from") { cx.marked = "keyword"; return cont(expression); }
585  }
586  function arrayLiteral(type) {
587    if (type == "]") return cont();
588    return pass(expressionNoComma, maybeArrayComprehension);
589  }
590  function maybeArrayComprehension(type) {
591    if (type == "for") return pass(comprehension, expect("]"));
592    if (type == ",") return cont(commasep(expressionNoComma, "]"));
593    return pass(commasep(expressionNoComma, "]"));
594  }
595  function comprehension(type) {
596    if (type == "for") return cont(forspec, comprehension);
597    if (type == "if") return cont(expression, comprehension);
598  }
599
600  // Interface
601
602  return {
603    startState: function(basecolumn) {
604      var state = {
605        tokenize: tokenBase,
606        lastType: "sof",
607        cc: [],
608        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
609        localVars: parserConfig.localVars,
610        context: parserConfig.localVars && {vars: parserConfig.localVars},
611        indented: 0
612      };
613      if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
614        state.globalVars = parserConfig.globalVars;
615      return state;
616    },
617
618    token: function(stream, state) {
619      if (stream.sol()) {
620        if (!state.lexical.hasOwnProperty("align"))
621          state.lexical.align = false;
622        state.indented = stream.indentation();
623        findFatArrow(stream, state);
624      }
625      if (state.tokenize != tokenComment && stream.eatSpace()) return null;
626      var style = state.tokenize(stream, state);
627      if (type == "comment") return style;
628      state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
629      return parseJS(state, style, type, content, stream);
630    },
631
632    indent: function(state, textAfter) {
633      if (state.tokenize == tokenComment) return CodeMirror.Pass;
634      if (state.tokenize != tokenBase) return 0;
635      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
636      // Kludge to prevent 'maybelse' from blocking lexical scope pops
637      if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
638        var c = state.cc[i];
639        if (c == poplex) lexical = lexical.prev;
640        else if (c != maybeelse) break;
641      }
642      if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
643      if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
644        lexical = lexical.prev;
645      var type = lexical.type, closing = firstChar == type;
646
647      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
648      else if (type == "form" && firstChar == "{") return lexical.indented;
649      else if (type == "form") return lexical.indented + indentUnit;
650      else if (type == "stat")
651        return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
652      else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
653        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
654      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
655      else return lexical.indented + (closing ? 0 : indentUnit);
656    },
657
658    electricChars: ":{}",
659    blockCommentStart: jsonMode ? null : "/*",
660    blockCommentEnd: jsonMode ? null : "*/",
661    lineComment: jsonMode ? null : "//",
662    fold: "brace",
663
664    helperType: jsonMode ? "json" : "javascript",
665    jsonldMode: jsonldMode,
666    jsonMode: jsonMode
667  };
668});
669
670CodeMirror.registerHelper("wordChars", "javascript", /[\\w$]/);
671
672CodeMirror.defineMIME("text/javascript", "javascript");
673CodeMirror.defineMIME("text/ecmascript", "javascript");
674CodeMirror.defineMIME("application/javascript", "javascript");
675CodeMirror.defineMIME("application/x-javascript", "javascript");
676CodeMirror.defineMIME("application/ecmascript", "javascript");
677CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
678CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
679CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
680CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
681CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
682
683});
684