Update CodeMirror to 3.14.1.
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / External / CodeMirror / javascript.js
1 // TODO actually recognize syntax of TypeScript constructs
2
3 CodeMirror.defineMode("javascript", function(config, parserConfig) {
4   var indentUnit = config.indentUnit;
5   var statementIndent = parserConfig.statementIndent;
6   var jsonMode = parserConfig.json;
7   var isTS = parserConfig.typescript;
8
9   // Tokenizer
10
11   var keywords = function(){
12     function kw(type) {return {type: type, style: "keyword"};}
13     var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
14     var operator = kw("operator"), atom = {type: "atom", style: "atom"};
15
16     var jsKeywords = {
17       "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
18       "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
19       "var": kw("var"), "const": kw("var"), "let": kw("var"),
20       "function": kw("function"), "catch": kw("catch"),
21       "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
22       "in": operator, "typeof": operator, "instanceof": operator,
23       "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
24       "this": kw("this")
25     };
26
27     // Extend the 'normal' keywords with the TypeScript language extensions
28     if (isTS) {
29       var type = {type: "variable", style: "variable-3"};
30       var tsKeywords = {
31         // object-like things
32         "interface": kw("interface"),
33         "class": kw("class"),
34         "extends": kw("extends"),
35         "constructor": kw("constructor"),
36
37         // scope modifiers
38         "public": kw("public"),
39         "private": kw("private"),
40         "protected": kw("protected"),
41         "static": kw("static"),
42
43         "super": kw("super"),
44
45         // types
46         "string": type, "number": type, "bool": type, "any": type
47       };
48
49       for (var attr in tsKeywords) {
50         jsKeywords[attr] = tsKeywords[attr];
51       }
52     }
53
54     return jsKeywords;
55   }();
56
57   var isOperatorChar = /[+\-*&%=<>!?|~^]/;
58
59   function chain(stream, state, f) {
60     state.tokenize = f;
61     return f(stream, state);
62   }
63
64   function nextUntilUnescaped(stream, end) {
65     var escaped = false, next;
66     while ((next = stream.next()) != null) {
67       if (next == end && !escaped)
68         return false;
69       escaped = !escaped && next == "\\";
70     }
71     return escaped;
72   }
73
74   // Used as scratch variables to communicate multiple values without
75   // consing up tons of objects.
76   var type, content;
77   function ret(tp, style, cont) {
78     type = tp; content = cont;
79     return style;
80   }
81
82   function jsTokenBase(stream, state) {
83     var ch = stream.next();
84     if (ch == '"' || ch == "'")
85       return chain(stream, state, jsTokenString(ch));
86     else if (/[\[\]{}\(\),;\:\.]/.test(ch))
87       return ret(ch);
88     else if (ch == "0" && stream.eat(/x/i)) {
89       stream.eatWhile(/[\da-f]/i);
90       return ret("number", "number");
91     }
92     else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
93       stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
94       return ret("number", "number");
95     }
96     else if (ch == "/") {
97       if (stream.eat("*")) {
98         return chain(stream, state, jsTokenComment);
99       }
100       else if (stream.eat("/")) {
101         stream.skipToEnd();
102         return ret("comment", "comment");
103       }
104       else if (state.lastType == "operator" || state.lastType == "keyword c" ||
105                /^[\[{}\(,;:]$/.test(state.lastType)) {
106         nextUntilUnescaped(stream, "/");
107         stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
108         return ret("regexp", "string-2");
109       }
110       else {
111         stream.eatWhile(isOperatorChar);
112         return ret("operator", null, stream.current());
113       }
114     }
115     else if (ch == "#") {
116       stream.skipToEnd();
117       return ret("error", "error");
118     }
119     else if (isOperatorChar.test(ch)) {
120       stream.eatWhile(isOperatorChar);
121       return ret("operator", null, stream.current());
122     }
123     else {
124       stream.eatWhile(/[\w\$_]/);
125       var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
126       return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
127                      ret("variable", "variable", word);
128     }
129   }
130
131   function jsTokenString(quote) {
132     return function(stream, state) {
133       if (!nextUntilUnescaped(stream, quote))
134         state.tokenize = jsTokenBase;
135       return ret("string", "string");
136     };
137   }
138
139   function jsTokenComment(stream, state) {
140     var maybeEnd = false, ch;
141     while (ch = stream.next()) {
142       if (ch == "/" && maybeEnd) {
143         state.tokenize = jsTokenBase;
144         break;
145       }
146       maybeEnd = (ch == "*");
147     }
148     return ret("comment", "comment");
149   }
150
151   // Parser
152
153   var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
154
155   function JSLexical(indented, column, type, align, prev, info) {
156     this.indented = indented;
157     this.column = column;
158     this.type = type;
159     this.prev = prev;
160     this.info = info;
161     if (align != null) this.align = align;
162   }
163
164   function inScope(state, varname) {
165     for (var v = state.localVars; v; v = v.next)
166       if (v.name == varname) return true;
167   }
168
169   function parseJS(state, style, type, content, stream) {
170     var cc = state.cc;
171     // Communicate our context to the combinators.
172     // (Less wasteful than consing up a hundred closures on every call.)
173     cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
174
175     if (!state.lexical.hasOwnProperty("align"))
176       state.lexical.align = true;
177
178     while(true) {
179       var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
180       if (combinator(type, content)) {
181         while(cc.length && cc[cc.length - 1].lex)
182           cc.pop()();
183         if (cx.marked) return cx.marked;
184         if (type == "variable" && inScope(state, content)) return "variable-2";
185         return style;
186       }
187     }
188   }
189
190   // Combinator utils
191
192   var cx = {state: null, column: null, marked: null, cc: null};
193   function pass() {
194     for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
195   }
196   function cont() {
197     pass.apply(null, arguments);
198     return true;
199   }
200   function register(varname) {
201     function inList(list) {
202       for (var v = list; v; v = v.next)
203         if (v.name == varname) return true;
204       return false;
205     }
206     var state = cx.state;
207     if (state.context) {
208       cx.marked = "def";
209       if (inList(state.localVars)) return;
210       state.localVars = {name: varname, next: state.localVars};
211     } else {
212       if (inList(state.globalVars)) return;
213       state.globalVars = {name: varname, next: state.globalVars};
214     }
215   }
216
217   // Combinators
218
219   var defaultVars = {name: "this", next: {name: "arguments"}};
220   function pushcontext() {
221     cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
222     cx.state.localVars = defaultVars;
223   }
224   function popcontext() {
225     cx.state.localVars = cx.state.context.vars;
226     cx.state.context = cx.state.context.prev;
227   }
228   function pushlex(type, info) {
229     var result = function() {
230       var state = cx.state, indent = state.indented;
231       if (state.lexical.type == "stat") indent = state.lexical.indented;
232       state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
233     };
234     result.lex = true;
235     return result;
236   }
237   function poplex() {
238     var state = cx.state;
239     if (state.lexical.prev) {
240       if (state.lexical.type == ")")
241         state.indented = state.lexical.indented;
242       state.lexical = state.lexical.prev;
243     }
244   }
245   poplex.lex = true;
246
247   function expect(wanted) {
248     return function(type) {
249       if (type == wanted) return cont();
250       else if (wanted == ";") return pass();
251       else return cont(arguments.callee);
252     };
253   }
254
255   function statement(type) {
256     if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
257     if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
258     if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
259     if (type == "{") return cont(pushlex("}"), block, poplex);
260     if (type == ";") return cont();
261     if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
262     if (type == "function") return cont(functiondef);
263     if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
264                                    poplex, statement, poplex);
265     if (type == "variable") return cont(pushlex("stat"), maybelabel);
266     if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
267                                       block, poplex, poplex);
268     if (type == "case") return cont(expression, expect(":"));
269     if (type == "default") return cont(expect(":"));
270     if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
271                                      statement, poplex, popcontext);
272     return pass(pushlex("stat"), expression, expect(";"), poplex);
273   }
274   function expression(type) {
275     return expressionInner(type, false);
276   }
277   function expressionNoComma(type) {
278     return expressionInner(type, true);
279   }
280   function expressionInner(type, noComma) {
281     var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
282     if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
283     if (type == "function") return cont(functiondef);
284     if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
285     if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
286     if (type == "operator") return cont(noComma ? expressionNoComma : expression);
287     if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop);
288     if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop);
289     return cont();
290   }
291   function maybeexpression(type) {
292     if (type.match(/[;\}\)\],]/)) return pass();
293     return pass(expression);
294   }
295   function maybeexpressionNoComma(type) {
296     if (type.match(/[;\}\)\],]/)) return pass();
297     return pass(expressionNoComma);
298   }
299
300   function maybeoperatorComma(type, value) {
301     if (type == ",") return cont(expression);
302     return maybeoperatorNoComma(type, value, maybeoperatorComma);
303   }
304   function maybeoperatorNoComma(type, value, me) {
305     if (!me) me = maybeoperatorNoComma;
306     if (type == "operator") {
307       if (/\+\+|--/.test(value)) return cont(me);
308       if (value == "?") return cont(expression, expect(":"), expression);
309       return cont(expression);
310     }
311     if (type == ";") return;
312     if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me);
313     if (type == ".") return cont(property, me);
314     if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
315   }
316   function maybelabel(type) {
317     if (type == ":") return cont(poplex, statement);
318     return pass(maybeoperatorComma, expect(";"), poplex);
319   }
320   function property(type) {
321     if (type == "variable") {cx.marked = "property"; return cont();}
322   }
323   function objprop(type, value) {
324     if (type == "variable") {
325       cx.marked = "property";
326       if (value == "get" || value == "set") return cont(getterSetter);
327     } else if (type == "number" || type == "string") {
328       cx.marked = type + " property";
329     }
330     if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma);
331   }
332   function getterSetter(type) {
333     if (type == ":") return cont(expression);
334     if (type != "variable") return cont(expect(":"), expression);
335     cx.marked = "property";
336     return cont(functiondef);
337   }
338   function commasep(what, end) {
339     function proceed(type) {
340       if (type == ",") {
341         var lex = cx.state.lexical;
342         if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
343         return cont(what, proceed);
344       }
345       if (type == end) return cont();
346       return cont(expect(end));
347     }
348     return function(type) {
349       if (type == end) return cont();
350       else return pass(what, proceed);
351     };
352   }
353   function block(type) {
354     if (type == "}") return cont();
355     return pass(statement, block);
356   }
357   function maybetype(type) {
358     if (type == ":") return cont(typedef);
359     return pass();
360   }
361   function typedef(type) {
362     if (type == "variable"){cx.marked = "variable-3"; return cont();}
363     return pass();
364   }
365   function vardef1(type, value) {
366     if (type == "variable") {
367       register(value);
368       return isTS ? cont(maybetype, vardef2) : cont(vardef2);
369     }
370     return pass();
371   }
372   function vardef2(type, value) {
373     if (value == "=") return cont(expressionNoComma, vardef2);
374     if (type == ",") return cont(vardef1);
375   }
376   function maybeelse(type, value) {
377     if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
378   }
379   function forspec1(type) {
380     if (type == "var") return cont(vardef1, expect(";"), forspec2);
381     if (type == ";") return cont(forspec2);
382     if (type == "variable") return cont(formaybein);
383     return pass(expression, expect(";"), forspec2);
384   }
385   function formaybein(_type, value) {
386     if (value == "in") return cont(expression);
387     return cont(maybeoperatorComma, forspec2);
388   }
389   function forspec2(type, value) {
390     if (type == ";") return cont(forspec3);
391     if (value == "in") return cont(expression);
392     return pass(expression, expect(";"), forspec3);
393   }
394   function forspec3(type) {
395     if (type != ")") cont(expression);
396   }
397   function functiondef(type, value) {
398     if (type == "variable") {register(value); return cont(functiondef);}
399     if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
400   }
401   function funarg(type, value) {
402     if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
403   }
404
405   // Interface
406
407   return {
408     startState: function(basecolumn) {
409       return {
410         tokenize: jsTokenBase,
411         lastType: null,
412         cc: [],
413         lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
414         localVars: parserConfig.localVars,
415         globalVars: parserConfig.globalVars,
416         context: parserConfig.localVars && {vars: parserConfig.localVars},
417         indented: 0
418       };
419     },
420
421     token: function(stream, state) {
422       if (stream.sol()) {
423         if (!state.lexical.hasOwnProperty("align"))
424           state.lexical.align = false;
425         state.indented = stream.indentation();
426       }
427       if (state.tokenize != jsTokenComment && stream.eatSpace()) return null;
428       var style = state.tokenize(stream, state);
429       if (type == "comment") return style;
430       state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
431       return parseJS(state, style, type, content, stream);
432     },
433
434     indent: function(state, textAfter) {
435       if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
436       if (state.tokenize != jsTokenBase) return 0;
437       var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
438       // Kludge to prevent 'maybelse' from blocking lexical scope pops
439       for (var i = state.cc.length - 1; i >= 0; --i) {
440         var c = state.cc[i];
441         if (c == poplex) lexical = lexical.prev;
442         else if (c != maybeelse || /^else\b/.test(textAfter)) break;
443       }
444       if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
445       if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
446         lexical = lexical.prev;
447       var type = lexical.type, closing = firstChar == type;
448
449       if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
450       else if (type == "form" && firstChar == "{") return lexical.indented;
451       else if (type == "form") return lexical.indented + indentUnit;
452       else if (type == "stat")
453         return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
454       else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
455         return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
456       else if (lexical.align) return lexical.column + (closing ? 0 : 1);
457       else return lexical.indented + (closing ? 0 : indentUnit);
458     },
459
460     electricChars: ":{}",
461     blockCommentStart: jsonMode ? null : "/*",
462     blockCommentEnd: jsonMode ? null : "*/",
463     lineComment: jsonMode ? null : "//",
464     fold: "brace",
465
466     helperType: jsonMode ? "json" : "javascript",
467     jsonMode: jsonMode
468   };
469 });
470
471 CodeMirror.defineMIME("text/javascript", "javascript");
472 CodeMirror.defineMIME("text/ecmascript", "javascript");
473 CodeMirror.defineMIME("application/javascript", "javascript");
474 CodeMirror.defineMIME("application/ecmascript", "javascript");
475 CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
476 CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
477 CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
478 CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });