1 // TODO actually recognize syntax of TypeScript constructs
3 CodeMirror
.defineMode("javascript", function(config
, parserConfig
) {
4 var indentUnit
= config
.indentUnit
;
5 var jsonMode
= parserConfig
.json
;
6 var isTS
= parserConfig
.typescript
;
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"};
16 "if": kw("if"), "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
,
26 // Extend the 'normal' keywords with the TypeScript language extensions
28 var type
= {type
: "variable", style
: "variable-3"};
31 "interface": kw("interface"),
33 "extends": kw("extends"),
34 "constructor": kw("constructor"),
37 "public": kw("public"),
38 "private": kw("private"),
39 "protected": kw("protected"),
40 "static": kw("static"),
45 "string": type
, "number": type
, "bool": type
, "any": type
48 for (var attr
in tsKeywords
) {
49 jsKeywords
[attr
] = tsKeywords
[attr
];
56 var isOperatorChar
= /[+\-*&%=<>!?|~^]/;
58 function chain(stream
, state
, f
) {
60 return f(stream
, state
);
63 function nextUntilUnescaped(stream
, end
) {
64 var escaped
= false, next
;
65 while ((next
= stream
.next()) != null) {
66 if (next
== end
&& !escaped
)
68 escaped
= !escaped
&& next
== "\\";
73 // Used as scratch variables to communicate multiple values without
74 // consing up tons of objects.
76 function ret(tp
, style
, cont
) {
77 type
= tp
; content
= cont
;
81 function jsTokenBase(stream
, state
) {
82 var ch
= stream
.next();
83 if (ch
== '"' || ch
== "'")
84 return chain(stream
, state
, jsTokenString(ch
));
85 else if (/[\[\]{}\(\),;\:\.]/.test(ch
))
87 else if (ch
== "0" && stream
.eat(/x
/i
)) {
88 stream
.eatWhile(/[\da-f]/i);
89 return ret("number", "number");
91 else if (/\d/.test(ch
) || ch
== "-" && stream
.eat(/\d/)) {
92 stream
.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
93 return ret("number", "number");
96 if (stream
.eat("*")) {
97 return chain(stream
, state
, jsTokenComment
);
99 else if (stream
.eat("/")) {
101 return ret("comment", "comment");
103 else if (state
.lastType
== "operator" || state
.lastType
== "keyword c" ||
104 /^[\[{}\(,;:]$/.test(state
.lastType
)) {
105 nextUntilUnescaped(stream
, "/");
106 stream
.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
107 return ret("regexp", "string-2");
110 stream
.eatWhile(isOperatorChar
);
111 return ret("operator", null, stream
.current());
114 else if (ch
== "#") {
116 return ret("error", "error");
118 else if (isOperatorChar
.test(ch
)) {
119 stream
.eatWhile(isOperatorChar
);
120 return ret("operator", null, stream
.current());
123 stream
.eatWhile(/[\w\$_]/);
124 var word
= stream
.current(), known
= keywords
.propertyIsEnumerable(word
) && keywords
[word
];
125 return (known
&& state
.lastType
!= ".") ? ret(known
.type
, known
.style
, word
) :
126 ret("variable", "variable", word
);
130 function jsTokenString(quote
) {
131 return function(stream
, state
) {
132 if (!nextUntilUnescaped(stream
, quote
))
133 state
.tokenize
= jsTokenBase
;
134 return ret("string", "string");
138 function jsTokenComment(stream
, state
) {
139 var maybeEnd
= false, ch
;
140 while (ch
= stream
.next()) {
141 if (ch
== "/" && maybeEnd
) {
142 state
.tokenize
= jsTokenBase
;
145 maybeEnd
= (ch
== "*");
147 return ret("comment", "comment");
152 var atomicTypes
= {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
154 function JSLexical(indented
, column
, type
, align
, prev
, info
) {
155 this.indented
= indented
;
156 this.column
= column
;
160 if (align
!= null) this.align
= align
;
163 function inScope(state
, varname
) {
164 for (var v
= state
.localVars
; v
; v
= v
.next
)
165 if (v
.name
== varname
) return true;
168 function parseJS(state
, style
, type
, content
, stream
) {
170 // Communicate our context to the combinators.
171 // (Less wasteful than consing up a hundred closures on every call.)
172 cx
.state
= state
; cx
.stream
= stream
; cx
.marked
= null, cx
.cc
= cc
;
174 if (!state
.lexical
.hasOwnProperty("align"))
175 state
.lexical
.align
= true;
178 var combinator
= cc
.length
? cc
.pop() : jsonMode
? expression
: statement
;
179 if (combinator(type
, content
)) {
180 while(cc
.length
&& cc
[cc
.length
- 1].lex
)
182 if (cx
.marked
) return cx
.marked
;
183 if (type
== "variable" && inScope(state
, content
)) return "variable-2";
191 var cx
= {state
: null, column
: null, marked
: null, cc
: null};
193 for (var i
= arguments
.length
- 1; i
>= 0; i
--) cx
.cc
.push(arguments
[i
]);
196 pass
.apply(null, arguments
);
199 function register(varname
) {
200 function inList(list
) {
201 for (var v
= list
; v
; v
= v
.next
)
202 if (v
.name
== varname
) return true;
205 var state
= cx
.state
;
208 if (inList(state
.localVars
)) return;
209 state
.localVars
= {name
: varname
, next
: state
.localVars
};
211 if (inList(state
.globalVars
)) return;
212 state
.globalVars
= {name
: varname
, next
: state
.globalVars
};
218 var defaultVars
= {name
: "this", next
: {name
: "arguments"}};
219 function pushcontext() {
220 cx
.state
.context
= {prev
: cx
.state
.context
, vars
: cx
.state
.localVars
};
221 cx
.state
.localVars
= defaultVars
;
223 function popcontext() {
224 cx
.state
.localVars
= cx
.state
.context
.vars
;
225 cx
.state
.context
= cx
.state
.context
.prev
;
227 function pushlex(type
, info
) {
228 var result = function() {
229 var state
= cx
.state
;
230 state
.lexical
= new JSLexical(state
.indented
, cx
.stream
.column(), type
, null, state
.lexical
, info
);
236 var state
= cx
.state
;
237 if (state
.lexical
.prev
) {
238 if (state
.lexical
.type
== ")")
239 state
.indented
= state
.lexical
.indented
;
240 state
.lexical
= state
.lexical
.prev
;
245 function expect(wanted
) {
246 return function(type
) {
247 if (type
== wanted
) return cont();
248 else if (wanted
== ";") return pass();
249 else return cont(arguments
.callee
);
253 function statement(type
) {
254 if (type
== "var") return cont(pushlex("vardef"), vardef1
, expect(";"), poplex
);
255 if (type
== "keyword a") return cont(pushlex("form"), expression
, statement
, poplex
);
256 if (type
== "keyword b") return cont(pushlex("form"), statement
, poplex
);
257 if (type
== "{") return cont(pushlex("}"), block
, poplex
);
258 if (type
== ";") return cont();
259 if (type
== "if") return cont(pushlex("form"), expression
, statement
, poplex
, maybeelse(cx
.state
.indented
));
260 if (type
== "function") return cont(functiondef
);
261 if (type
== "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1
, expect(")"),
262 poplex
, statement
, poplex
);
263 if (type
== "variable") return cont(pushlex("stat"), maybelabel
);
264 if (type
== "switch") return cont(pushlex("form"), expression
, pushlex("}", "switch"), expect("{"),
265 block
, poplex
, poplex
);
266 if (type
== "case") return cont(expression
, expect(":"));
267 if (type
== "default") return cont(expect(":"));
268 if (type
== "catch") return cont(pushlex("form"), pushcontext
, expect("("), funarg
, expect(")"),
269 statement
, poplex
, popcontext
);
270 return pass(pushlex("stat"), expression
, expect(";"), poplex
);
272 function expression(type
) {
273 return expressionInner(type
, maybeoperatorComma
);
275 function expressionNoComma(type
) {
276 return expressionInner(type
, maybeoperatorNoComma
);
278 function expressionInner(type
, maybeop
) {
279 if (atomicTypes
.hasOwnProperty(type
)) return cont(maybeop
);
280 if (type
== "function") return cont(functiondef
);
281 if (type
== "keyword c") return cont(maybeexpression
);
282 if (type
== "(") return cont(pushlex(")"), maybeexpression
, expect(")"), poplex
, maybeop
);
283 if (type
== "operator") return cont(expression
);
284 if (type
== "[") return cont(pushlex("]"), commasep(expressionNoComma
, "]"), poplex
, maybeop
);
285 if (type
== "{") return cont(pushlex("}"), commasep(objprop
, "}"), poplex
, maybeop
);
288 function maybeexpression(type
) {
289 if (type
.match(/[;\}\)\],]/)) return pass();
290 return pass(expression
);
293 function maybeoperatorComma(type
, value
) {
294 if (type
== ",") return pass();
295 return maybeoperatorNoComma(type
, value
, maybeoperatorComma
);
297 function maybeoperatorNoComma(type
, value
, me
) {
298 if (!me
) me
= maybeoperatorNoComma
;
299 if (type
== "operator") {
300 if (/\+\+|--/.test(value
)) return cont(me
);
301 if (value
== "?") return cont(expression
, expect(":"), expression
);
302 return cont(expression
);
304 if (type
== ";") return;
305 if (type
== "(") return cont(pushlex(")", "call"), commasep(expressionNoComma
, ")"), poplex
, me
);
306 if (type
== ".") return cont(property
, me
);
307 if (type
== "[") return cont(pushlex("]"), expression
, expect("]"), poplex
, me
);
309 function maybelabel(type
) {
310 if (type
== ":") return cont(poplex
, statement
);
311 return pass(maybeoperatorComma
, expect(";"), poplex
);
313 function property(type
) {
314 if (type
== "variable") {cx
.marked
= "property"; return cont();}
316 function objprop(type
, value
) {
317 if (type
== "variable") {
318 cx
.marked
= "property";
319 if (value
== "get" || value
== "set") return cont(getterSetter
);
320 } else if (type
== "number" || type
== "string") {
321 cx
.marked
= type
+ " property";
323 if (atomicTypes
.hasOwnProperty(type
)) return cont(expect(":"), expressionNoComma
);
325 function getterSetter(type
) {
326 if (type
== ":") return cont(expression
);
327 if (type
!= "variable") return cont(expect(":"), expression
);
328 cx
.marked
= "property";
329 return cont(functiondef
);
331 function commasep(what
, end
) {
332 function proceed(type
) {
334 var lex
= cx
.state
.lexical
;
335 if (lex
.info
== "call") lex
.pos
= (lex
.pos
|| 0) + 1;
336 return cont(what
, proceed
);
338 if (type
== end
) return cont();
339 return cont(expect(end
));
341 return function(type
) {
342 if (type
== end
) return cont();
343 else return pass(what
, proceed
);
346 function block(type
) {
347 if (type
== "}") return cont();
348 return pass(statement
, block
);
350 function maybetype(type
) {
351 if (type
== ":") return cont(typedef
);
354 function typedef(type
) {
355 if (type
== "variable"){cx
.marked
= "variable-3"; return cont();}
358 function vardef1(type
, value
) {
359 if (type
== "variable") {
361 return isTS
? cont(maybetype
, vardef2
) : cont(vardef2
);
365 function vardef2(type
, value
) {
366 if (value
== "=") return cont(expressionNoComma
, vardef2
);
367 if (type
== ",") return cont(vardef1
);
369 function maybeelse(indent
) {
370 return function(type
, value
) {
371 if (type
== "keyword b" && value
== "else") {
372 cx
.state
.lexical
= new JSLexical(indent
, 0, "form", null, cx
.state
.lexical
);
373 return cont(statement
, poplex
);
378 function forspec1(type
) {
379 if (type
== "var") return cont(vardef1
, expect(";"), forspec2
);
380 if (type
== ";") return cont(forspec2
);
381 if (type
== "variable") return cont(formaybein
);
382 return pass(expression
, expect(";"), forspec2
);
384 function formaybein(_type
, value
) {
385 if (value
== "in") return cont(expression
);
386 return cont(maybeoperatorComma
, forspec2
);
388 function forspec2(type
, value
) {
389 if (type
== ";") return cont(forspec3
);
390 if (value
== "in") return cont(expression
);
391 return pass(expression
, expect(";"), forspec3
);
393 function forspec3(type
) {
394 if (type
!= ")") cont(expression
);
396 function functiondef(type
, value
) {
397 if (type
== "variable") {register(value
); return cont(functiondef
);}
398 if (type
== "(") return cont(pushlex(")"), pushcontext
, commasep(funarg
, ")"), poplex
, statement
, popcontext
);
400 function funarg(type
, value
) {
401 if (type
== "variable") {register(value
); return isTS
? cont(maybetype
) : cont();}
407 startState: function(basecolumn
) {
409 tokenize
: jsTokenBase
,
412 lexical
: new JSLexical((basecolumn
|| 0) - indentUnit
, 0, "block", false),
413 localVars
: parserConfig
.localVars
,
414 globalVars
: parserConfig
.globalVars
,
415 context
: parserConfig
.localVars
&& {vars
: parserConfig
.localVars
},
420 token: function(stream
, state
) {
422 if (!state
.lexical
.hasOwnProperty("align"))
423 state
.lexical
.align
= false;
424 state
.indented
= stream
.indentation();
426 if (state
.tokenize
!= jsTokenComment
&& stream
.eatSpace()) return null;
427 var style
= state
.tokenize(stream
, state
);
428 if (type
== "comment") return style
;
429 state
.lastType
= type
== "operator" && (content
== "++" || content
== "--") ? "incdec" : type
;
430 return parseJS(state
, style
, type
, content
, stream
);
433 indent: function(state
, textAfter
) {
434 if (state
.tokenize
== jsTokenComment
) return CodeMirror
.Pass
;
435 if (state
.tokenize
!= jsTokenBase
) return 0;
436 var firstChar
= textAfter
&& textAfter
.charAt(0), lexical
= state
.lexical
;
437 if (lexical
.type
== "stat" && firstChar
== "}") lexical
= lexical
.prev
;
438 var type
= lexical
.type
, closing
= firstChar
== type
;
439 if (parserConfig
.statementIndent
!= null) {
440 if (type
== ")" && lexical
.prev
&& lexical
.prev
.type
== "stat") lexical
= lexical
.prev
;
441 if (lexical
.type
== "stat") return lexical
.indented
+ parserConfig
.statementIndent
;
444 if (type
== "vardef") return lexical
.indented
+ (state
.lastType
== "operator" || state
.lastType
== "," ? 4 : 0);
445 else if (type
== "form" && firstChar
== "{") return lexical
.indented
;
446 else if (type
== "form") return lexical
.indented
+ indentUnit
;
447 else if (type
== "stat")
448 return lexical
.indented
+ (state
.lastType
== "operator" || state
.lastType
== "," ? indentUnit
: 0);
449 else if (lexical
.info
== "switch" && !closing
)
450 return lexical
.indented
+ (/^(?:case|default)\b/.test(textAfter
) ? indentUnit
: 2 * indentUnit
);
451 else if (lexical
.align
) return lexical
.column
+ (closing
? 0 : 1);
452 else return lexical
.indented
+ (closing
? 0 : indentUnit
);
455 electricChars
: ":{}",
456 blockCommentStart
: jsonMode
? null : "/*",
457 blockCommentEnd
: jsonMode
? null : "*/",
458 lineComment
: jsonMode
? null : "//",
464 CodeMirror
.defineMIME("text/javascript", "javascript");
465 CodeMirror
.defineMIME("text/ecmascript", "javascript");
466 CodeMirror
.defineMIME("application/javascript", "javascript");
467 CodeMirror
.defineMIME("application/ecmascript", "javascript");
468 CodeMirror
.defineMIME("application/json", {name
: "javascript", json
: true});
469 CodeMirror
.defineMIME("application/x-json", {name
: "javascript", json
: true});
470 CodeMirror
.defineMIME("text/typescript", { name
: "javascript", typescript
: true });
471 CodeMirror
.defineMIME("application/typescript", { name
: "javascript", typescript
: true });