browser/devtools/sourceeditor/codemirror/javascript.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial