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