final tt updates
[emacs-init.git] / elpa / typescript-mode-20200430.1232 / typescript-mode.el
1 ;;; typescript-mode.el --- Major mode for editing typescript
2
3 ;; -----------------------------------------------------------------------------------
4 ;;     TypeScript support for Emacs
5 ;;     Unmodified original sourve available at http://www.karllandstrom.se/downloads/emacs/javascript.el
6 ;;     Copyright (c) 2008 Free Software Foundation
7 ;;     Portions Copyright (C) Microsoft Open Technologies, Inc. All rights reserved.
8 ;;
9 ;;     This program is free software: you can redistribute it and/or modify
10 ;;     it under the terms of the GNU General Public License as published by
11 ;;     the Free Software Foundation, either version 3 of the License, or
12 ;;     (at your option) any later version.
13 ;;
14 ;;     This program is distributed in the hope that it will be useful,
15 ;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;;     GNU General Public License for more details.
18 ;;
19 ;;     You should have received a copy of the GNU General Public License
20 ;;     along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 ;; -------------------------------------------------------------------------------------------
22
23 ;; URL: http://github.com/ananthakumaran/typescript.el
24 ;; Version: 0.1
25 ;; Keywords: typescript languages
26 ;; Package-Requires: ((emacs "24.3"))
27
28 ;; This file is not part of GNU Emacs.
29
30 ;;; Commentary:
31
32 ;; This is based on Karl Landstrom's barebones typescript-mode.  This
33 ;; is much more robust and works with cc-mode's comment filling
34 ;; (mostly).
35 ;; The modifications to the original javascript.el mode mainly consisted in
36 ;; replacing "javascript" with "typescript"
37 ;;
38 ;; The main features of this typescript mode are syntactic
39 ;; highlighting (enabled with `font-lock-mode' or
40 ;; `global-font-lock-mode'), automatic indentation and filling of
41 ;; comments.
42 ;;
43 ;;
44 ;; General Remarks:
45 ;;
46 ;; XXX: This mode assumes that block comments are not nested inside block
47 ;; XXX: comments
48 ;;
49 ;; Exported names start with "typescript-"; private names start with
50 ;; "typescript--".
51
52 ;;; Code:
53
54 (eval-and-compile
55   (require 'compile)
56   (require 'cc-mode)
57   (require 'font-lock)
58   (require 'rx)
59   (require 'newcomment))
60
61 (eval-when-compile
62   (require 'cl-lib))
63
64 ;;; Constants
65
66 (defconst typescript--type-name-re "\\(?:[A-Z][A-Za-z0-9]+\\.\\)\\{0,\\}\\(?:[A-Z][A-Za-z0-9]+\\)"
67   "Regexp matching a conventional TypeScript type-name.  Must start with upper-case letter!")
68
69 (defconst typescript--name-start-re "[a-zA-Z_$]"
70   "Regexp matching the start of a typescript identifier, without grouping.")
71
72 (defconst typescript--name-re (concat typescript--name-start-re
73                               "\\(?:\\s_\\|\\sw\\)*")
74   "Regexp matching a typescript identifier, without grouping.")
75
76 (defconst typescript--objfield-re (concat typescript--name-re ":")
77   "Regexp matching the start of a typescript object field.")
78
79 (defconst typescript--dotted-name-re
80   (concat typescript--name-re "\\(?:\\." typescript--name-re "\\)*")
81   "Regexp matching a dot-separated sequence of typescript names.")
82
83 (defconst typescript--plain-method-re
84   (concat "^\\s-*?\\(" typescript--dotted-name-re "\\)\\.prototype"
85           "\\.\\(" typescript--name-re "\\)\\s-*?=\\s-*?\\(function\\)\\_>")
86   "Regexp matching an explicit typescript prototype \"method\" declaration.
87 Group 1 is a (possibly-dotted) class name, group 2 is a method name,
88 and group 3 is the 'function' keyword.")
89
90 (defconst typescript--plain-class-re
91   (concat "^\\s-*\\(" typescript--dotted-name-re "\\)\\.prototype"
92           "\\s-*=\\s-*{")
93   "Regexp matching a typescript explicit prototype \"class\" declaration.
94 An example of this is \"Class.prototype = { method1: ...}\".")
95
96 (defconst typescript--module-declaration-re
97   "^\\s-*\\(?:declare\\|\\(?:export\\(?:\\s-+default\\)?\\)\\)?"
98   "Regexp matching ambient declaration modifier or export declaration.")
99
100 ;; var NewClass = BaseClass.extend(
101 (defconst typescript--mp-class-decl-re
102   (concat "^\\s-*var\\s-+"
103           "\\(" typescript--name-re "\\)"
104           "\\s-*=\\s-*"
105           "\\(" typescript--dotted-name-re
106           "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$"))
107
108 ;; var NewClass = Class.create()
109 (defconst typescript--prototype-obsolete-class-decl-re
110   (concat "^\\s-*\\(?:var\\s-+\\)?"
111           "\\(" typescript--dotted-name-re "\\)"
112           "\\s-*=\\s-*Class\\.create()"))
113
114 (defconst typescript--prototype-objextend-class-decl-re-1
115   (concat "^\\s-*Object\\.extend\\s-*("
116           "\\(" typescript--dotted-name-re "\\)"
117           "\\s-*,\\s-*{"))
118
119 (defconst typescript--prototype-objextend-class-decl-re-2
120   (concat "^\\s-*\\(?:var\\s-+\\)?"
121           "\\(" typescript--dotted-name-re "\\)"
122           "\\s-*=\\s-*Object\\.extend\\s-*\("))
123
124 ;; var NewClass = Class.create({
125 (defconst typescript--prototype-class-decl-re
126   (concat "^\\s-*\\(?:var\\s-+\\)?"
127           "\\(" typescript--name-re "\\)"
128           "\\s-*=\\s-*Class\\.create\\s-*(\\s-*"
129           "\\(?:\\(" typescript--dotted-name-re "\\)\\s-*,\\s-*\\)?{?"))
130
131 ;; Parent class name(s) (yes, multiple inheritance in typescript) are
132 ;; matched with dedicated font-lock matchers
133 (defconst typescript--dojo-class-decl-re
134   (concat "^\\s-*dojo\\.declare\\s-*(\"\\(" typescript--dotted-name-re "\\)"))
135
136 (defconst typescript--exttypescript-class-decl-re-1
137   (concat "^\\s-*Ext\\.extend\\s-*("
138           "\\s-*\\(" typescript--dotted-name-re "\\)"
139           "\\s-*,\\s-*\\(" typescript--dotted-name-re "\\)")
140   "Regexp matching an ExtTYPESCRIPT class declaration (style 1).")
141
142 (defconst typescript--exttypescript-class-decl-re-2
143   (concat "^\\s-*\\(?:var\\s-+\\)?"
144           "\\(" typescript--name-re "\\)"
145           "\\s-*=\\s-*Ext\\.extend\\s-*(\\s-*"
146           "\\(" typescript--dotted-name-re "\\)")
147   "Regexp matching an ExtTYPESCRIPT class declaration (style 2).")
148
149 (defconst typescript--mochikit-class-re
150   (concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
151           "\\(" typescript--dotted-name-re "\\)")
152   "Regexp matching a MochiKit class declaration.")
153
154 (defconst typescript--dummy-class-style
155   '(:name "[Automatically Generated Class]"))
156
157 (defconst typescript--class-styles
158   `((:name            "Plain"
159      :class-decl      ,typescript--plain-class-re
160      :prototype       t
161      :contexts        (toplevel)
162      :framework       typescript)
163
164     (:name            "MochiKit"
165      :class-decl      ,typescript--mochikit-class-re
166      :prototype       t
167      :contexts        (toplevel)
168      :framework       mochikit)
169
170     (:name            "Prototype (Obsolete)"
171      :class-decl      ,typescript--prototype-obsolete-class-decl-re
172      :contexts        (toplevel)
173      :framework       prototype)
174
175     (:name            "Prototype (Modern)"
176      :class-decl      ,typescript--prototype-class-decl-re
177      :contexts        (toplevel)
178      :framework       prototype)
179
180     (:name            "Prototype (Object.extend)"
181      :class-decl      ,typescript--prototype-objextend-class-decl-re-1
182      :prototype       t
183      :contexts        (toplevel)
184      :framework       prototype)
185
186     (:name            "Prototype (Object.extend) 2"
187      :class-decl      ,typescript--prototype-objextend-class-decl-re-2
188      :prototype       t
189      :contexts        (toplevel)
190      :framework       prototype)
191
192     (:name            "Dojo"
193      :class-decl      ,typescript--dojo-class-decl-re
194      :contexts        (toplevel)
195      :framework       dojo)
196
197     (:name            "ExtTYPESCRIPT (style 1)"
198      :class-decl      ,typescript--exttypescript-class-decl-re-1
199      :prototype       t
200      :contexts        (toplevel)
201      :framework       exttypescript)
202
203     (:name            "ExtTYPESCRIPT (style 2)"
204      :class-decl      ,typescript--exttypescript-class-decl-re-2
205      :contexts        (toplevel)
206      :framework       exttypescript)
207
208     (:name            "Merrill Press"
209      :class-decl      ,typescript--mp-class-decl-re
210      :contexts        (toplevel)
211      :framework       merrillpress))
212
213   "List of typescript class definition styles.
214
215 A class definition style is a plist with the following keys:
216
217 :name is a human-readable name of the class type
218
219 :class-decl is a regular expression giving the start of the
220 class.  Its first group must match the name of its class.  If there
221 is a parent class, the second group should match, and it should be
222 the name of the class.
223
224 If :prototype is present and non-nil, the parser will merge
225 declarations for this constructs with others at the same lexical
226 level that have the same name.  Otherwise, multiple definitions
227 will create multiple top-level entries.  Don't use :prototype
228 unnecessarily: it has an associated cost in performance.
229
230 If :strip-prototype is present and non-nil, then if the class
231 name as matched contains")
232
233 (defconst typescript--available-frameworks
234   (cl-loop with available-frameworks
235         for style in typescript--class-styles
236         for framework = (plist-get style :framework)
237         unless (memq framework available-frameworks)
238         collect framework into available-frameworks
239         finally return available-frameworks)
240   "List of available typescript frameworks symbols.")
241
242 (defconst typescript--function-heading-1-re
243   (concat
244    typescript--module-declaration-re
245    "\\s-*function\\s-+\\(" typescript--name-re "\\)")
246   "Regexp matching the start of a typescript function header.
247 Match group 1 is the name of the function.")
248
249 (defconst typescript--function-heading-2-re
250   (concat
251    "^\\s-*\\(" typescript--name-re "\\)\\s-*:\\s-*function\\_>")
252   "Regexp matching the start of a function entry in an associative array.
253 Match group 1 is the name of the function.")
254
255 (defconst typescript--function-heading-3-re
256   (concat
257    "^\\s-*\\(?:var\\s-+\\)?\\(" typescript--dotted-name-re "\\)"
258    "\\s-*=\\s-*function\\_>")
259   "Regexp matching a line in the typescript form \"var MUMBLE = function\".
260 Match group 1 is MUMBLE.")
261
262 (defun typescript--regexp-opt-symbol (list)
263   "Like `regexp-opt', but surround the result with `\\\\_<' and `\\\\_>'."
264   (concat "\\_<" (regexp-opt list t) "\\_>"))
265
266 (defconst typescript--keyword-re
267   (typescript--regexp-opt-symbol
268    '("abstract" "any" "as" "async" "await" "boolean" "bigint" "break" "case" "catch" "class" "const"
269      "constructor" "continue" "declare" "default" "delete" "do" "else"
270      "enum" "export" "extends" "extern" "false" "finally" "for"
271      "function" "from" "get" "goto" "if" "implements" "import" "in" "instanceof"
272      "interface" "keyof" "let" "module" "namespace" "never" "new" "null" "number" "object" "of"
273      "private" "protected" "public" "readonly" "return" "set" "static" "string"
274      "super" "switch"  "this" "throw" "true"
275      "try" "type" "typeof" "unknown" "var" "void"
276      "while")) ; yield is handled separately
277   "Regexp matching any typescript keyword.")
278
279 (defconst typescript--basic-type-re
280   (typescript--regexp-opt-symbol
281    '("any" "bool" "boolean" "bigint" "never" "number" "string" "unknown" "void"))
282   "Regular expression matching any predefined type in typescript.")
283
284 (defconst typescript--access-modifier-re
285   (typescript--regexp-opt-symbol
286    '("private" "protected" "public" "readonly" "static" "extends" "implements"))
287   "Regular expression matching access modifiers.")
288
289 (defconst typescript--decorator-re
290   (concat "\\(@" typescript--name-re "\\)"))
291
292 (defconst typescript--constant-re
293   (typescript--regexp-opt-symbol '("false" "null" "undefined"
294                                  "Infinity" "NaN"
295                                  "true" "arguments" "this"))
296   "Regular expression matching any future reserved words in typescript.")
297
298 (defconst typescript--builtin-re
299   (typescript--regexp-opt-symbol
300    '("console"))
301   "Regular expression matching builtins.")
302
303 (defconst typescript--function-call-re "\\(\\(?:\\w\\|\\s_\\)+\\)\\(<.+>\\)?\s*("
304   "Regular expression matching function calls.")
305
306 (defconst typescript--font-lock-keywords-1
307   (list
308    "\\_<import\\_>"
309    (list typescript--function-heading-1-re 1 font-lock-function-name-face)
310    (list typescript--function-heading-2-re 1 font-lock-function-name-face))
311   "Level one font lock keywords for `typescript-mode'.")
312
313 (defconst typescript--font-lock-keywords-2
314   (append typescript--font-lock-keywords-1
315           (list (list typescript--keyword-re 1 font-lock-keyword-face)
316                 (list "\\_<for\\_>"
317                       "\\s-+\\(each\\)\\_>" nil nil
318                       (list 1 'font-lock-keyword-face))
319                 (cons "\\_<yield\\(\\*\\|\\_>\\)" 'font-lock-keyword-face)
320                 (cons typescript--basic-type-re font-lock-type-face)
321                 (cons typescript--constant-re font-lock-constant-face)))
322   "Level two font lock keywords for `typescript-mode'.")
323
324 ;; typescript--pitem is the basic building block of the lexical
325 ;; database. When one refers to a real part of the buffer, the region
326 ;; of text to which it refers is split into a conceptual header and
327 ;; body. Consider the (very short) block described by a hypothetical
328 ;; typescript--pitem:
329 ;;
330 ;;   function foo(a,b,c) { return 42; }
331 ;;   ^                    ^            ^
332 ;;   |                    |            |
333 ;;   +- h-begin           +- h-end     +- b-end
334 ;;
335 ;; (Remember that these are buffer positions, and therefore point
336 ;; between characters, not at them. An arrow drawn to a character
337 ;; indicates the corresponding position is between that character and
338 ;; the one immediately preceding it.)
339 ;;
340 ;; The header is the region of text [h-begin, h-end], and is
341 ;; the text needed to unambiguously recognize the start of the
342 ;; construct. If the entire header is not present, the construct is
343 ;; not recognized at all. No other pitems may be nested inside the
344 ;; header.
345 ;;
346 ;; The body is the region [h-end, b-end]. It may contain nested
347 ;; typescript--pitem instances. The body of a pitem may be empty: in
348 ;; that case, b-end is equal to header-end.
349 ;;
350 ;; The three points obey the following relationship:
351 ;;
352 ;;   h-begin < h-end <= b-end
353 ;;
354 ;; We put a text property in the buffer on the character *before*
355 ;; h-end, and if we see it, on the character *before* b-end.
356 ;;
357 ;; The text property for h-end, typescript--pstate, is actually a list
358 ;; of all typescript--pitem instances open after the marked character.
359 ;;
360 ;; The text property for b-end, typescript--pend, is simply the
361 ;; typescript--pitem that ends after the marked character. (Because
362 ;; pitems always end when the paren-depth drops below a critical
363 ;; value, and because we can only drop one level per character, only
364 ;; one pitem may end at a given character.)
365 ;;
366 ;; In the structure below, we only store h-begin and (sometimes)
367 ;; b-end. We can trivially and quickly find h-end by going to h-begin
368 ;; and searching for an typescript--pstate text property. Since no other
369 ;; typescript--pitem instances can be nested inside the header of a
370 ;; pitem, the location after the character with this text property
371 ;; must be h-end.
372 ;;
373 ;; typescript--pitem instances are never modified (with the exception
374 ;; of the b-end field). Instead, modified copies are added at subseqnce parse points.
375 ;; (The exception for b-end and its caveats is described below.)
376 ;;
377
378 (cl-defstruct (typescript--pitem (:type list))
379   ;; IMPORTANT: Do not alter the position of fields within the list.
380   ;; Various bits of code depend on their positions, particularly
381   ;; anything that manipulates the list of children.
382
383   ;; List of children inside this pitem's body
384   (children nil :read-only t)
385
386   ;; When we reach this paren depth after h-end, the pitem ends
387   (paren-depth nil :read-only t)
388
389   ;; Symbol or class-style plist if this is a class
390   (type nil :read-only t)
391
392   ;; See above
393   (h-begin nil :read-only t)
394
395   ;; List of strings giving the parts of the name of this pitem (e.g.,
396   ;; '("MyClass" "myMethod"), or t if this pitem is anonymous
397   (name nil :read-only t)
398
399   ;; THIS FIELD IS MUTATED, and its value is shared by all copies of
400   ;; this pitem: when we copy-and-modify pitem instances, we share
401   ;; their tail structures, so all the copies actually have the same
402   ;; terminating cons cell. We modify that shared cons cell directly.
403   ;;
404   ;; The field value is either a number (buffer location) or nil if
405   ;; unknown.
406   ;;
407   ;; If the field's value is greater than `typescript--cache-end', the
408   ;; value is stale and must be treated as if it were nil. Conversely,
409   ;; if this field is nil, it is guaranteed that this pitem is open up
410   ;; to at least `typescript--cache-end'. (This property is handy when
411   ;; computing whether we're inside a given pitem.)
412   ;;
413   (b-end nil))
414
415 ;; The pitem we start parsing with.
416 (defconst typescript--initial-pitem
417   (make-typescript--pitem
418    :paren-depth most-negative-fixnum
419    :type 'toplevel))
420
421 ;; When we say "jsdoc" here, we mean "jsdoc 3". There exist multiple dialects of
422 ;; "jsdoc documentation".
423
424 ;; Note that all typedoc/jsdoc regexp by themselves would match occurrences that appear outside
425 ;; documentation comments. The logic that uses these regexps must guard against it.
426 (defconst typescript-typedoc-link-tag-regexp
427   "\\[\\[.*?\\]\\]"
428   "Matches a typedoc link.")
429
430 (defconst typescript-typedoc-literal-markup-regexp
431   "\\(`+\\).*?\\1"
432   "Matches a typedoc keyword markup.")
433
434 (defconst typescript-jsdoc-before-tag-regexp
435   "\\(?:^\\s-*\\*+\\|/\\*\\*\\)\\s-*"
436   "Matches everything we allow before the @ of a jsdoc tag.")
437
438 ;; This was taken from js2-mode.
439 (defconst typescript-jsdoc-param-tag-regexp
440   (concat typescript-jsdoc-before-tag-regexp
441           "\\(@"
442           (regexp-opt
443            '("arg"
444              "argument"
445              "param"
446              "prop"
447              "property"
448              "typedef"))
449           "\\)"
450           "\\s-*\\({[^}]+}\\)?"         ; optional type
451           "\\s-*\\[?\\([[:alnum:]_$\.]+\\)?\\]?"  ; name
452           "\\_>")
453   "Matches jsdoc tags with optional type and optional param name.")
454
455 ;; This was taken from js2-mode.
456 ;; and extended with tags in http://usejsdoc.org/
457 (defconst typescript-jsdoc-typed-tag-regexp
458   (concat typescript-jsdoc-before-tag-regexp
459           "\\(@"
460           (regexp-opt
461            '("enum"
462              "extends"
463              "field"
464              "id"
465              "implements"
466              "lends"
467              "mods"
468              "requires"
469              "return"
470              "returns"
471              "throw"
472              "throws"
473              "type"
474              "yield"
475              "yields"))
476           "\\)\\s-*\\({[^}]+}\\)?")
477   "Matches jsdoc tags with optional type.")
478
479 ;; This was taken from js2-mode.
480 ;; and extended with tags in http://usejsdoc.org/
481 (defconst typescript-jsdoc-arg-tag-regexp
482   (concat typescript-jsdoc-before-tag-regexp
483           "\\(@"
484           (regexp-opt
485            '("access"
486              "alias"
487              "augments"
488              "base"
489              "borrows"
490              "bug"
491              "callback"
492              "config"
493              "default"
494              "define"
495              "emits"
496              "exception"
497              "extends"
498              "external"
499              "fires"
500              "func"
501              "function"
502              "host"
503              "kind"
504              "listens"
505              "member"
506              "memberof"
507              "method"
508              "mixes"
509              "module"
510              "name"
511              "namespace"
512              "requires"
513              "since"
514              "suppress"
515              "this"
516              "throws"
517              "var"
518              "variation"
519              "version"))
520           "\\)\\s-+\\([^ \t]+\\)")
521   "Matches jsdoc tags with a single argument.")
522
523 ;; This was taken from js2-mode
524 ;; and extended with tags in http://usejsdoc.org/
525 (defconst typescript-jsdoc-empty-tag-regexp
526   (concat typescript-jsdoc-before-tag-regexp
527           "\\(@"
528           (regexp-opt
529            '("abstract"
530              "addon"
531              "async"
532              "author"
533              "class"
534              "classdesc"
535              "const"
536              "constant"
537              "constructor"
538              "constructs"
539              "copyright"
540              "default"
541              "defaultvalue"
542              "deprecated"
543              "desc"
544              "description"
545              "event"
546              "example"
547              "exec"
548              "export"
549              "exports"
550              "file"
551              "fileoverview"
552              "final"
553              "func"
554              "function"
555              "generator"
556              "global"
557              "hidden"
558              "hideconstructor"
559              "ignore"
560              "implicitcast"
561              "inheritdoc"
562              "inner"
563              "instance"
564              "interface"
565              "license"
566              "method"
567              "mixin"
568              "noalias"
569              "noshadow"
570              "notypecheck"
571              "override"
572              "overview"
573              "owner"
574              "package"
575              "preserve"
576              "preservetry"
577              "private"
578              "protected"
579              "public"
580              "readonly"
581              "static"
582              "summary"
583              "supported"
584              "todo"
585              "tutorial"
586              "virtual"))
587           "\\)\\s-*")
588   "Matches empty jsdoc tags.")
589
590 ;; Note that this regexp by itself would match tslint flags that appear inside
591 ;; strings. The logic using this regexp must guard against it.
592 (defconst typescript-tslint-flag-regexp
593   "\\(?://\\|/\\*\\)\\s-*\\(tslint:.*?\\)\\(?:\\*/\\|$\\)"
594   "Matches tslint flags.")
595
596 ;;; Faces
597
598 (defface typescript-jsdoc-tag
599   '((t :foreground "SlateGray"))
600   "Face used to highlight @whatever tags in jsdoc comments."
601   :group 'typescript)
602
603 (defface typescript-jsdoc-type
604   '((t :foreground "SteelBlue"))
605   "Face used to highlight {FooBar} types in jsdoc comments."
606   :group 'typescript)
607
608 (defface typescript-jsdoc-value
609   '((t :foreground "gold4"))
610   "Face used to highlight tag values in jsdoc comments."
611   :group 'typescript)
612
613 (defface typescript-access-modifier-face
614   '((t (:inherit font-lock-keyword-face)))
615   "Face used to highlight access modifiers."
616   :group 'typescript)
617
618 (defface typescript-this-face
619   '((t (:inherit font-lock-keyword-face)))
620   "Face used to highlight 'this' keyword."
621   :group 'typescript)
622
623 (defface typescript-primitive-face
624   '((t (:inherit font-lock-keyword-face)))
625   "Face used to highlight builtin types."
626   :group 'typescript)
627
628 ;;; User Customization
629
630 (defgroup typescript nil
631   "Customization variables for typescript mode."
632   :tag "typescript"
633   :group 'languages)
634
635 (defcustom typescript-indent-level 4
636   "Number of spaces for each indentation step in `typescript-mode'."
637   :type 'integer
638   :safe 'integerp
639   :group 'typescript)
640 ;;;###autoload(put 'typescript-indent-level 'safe-local-variable #'integerp)
641
642 (defcustom typescript-expr-indent-offset 0
643   "Number of additional spaces used for indentation of continued expressions.
644 The value must be no less than minus `typescript-indent-level'."
645   :type 'integer
646   :safe 'integerp
647   :group 'typescript)
648
649 (defcustom typescript-indent-switch-clauses t
650   "Enable indenting of switch case and default clauses to
651 replicate tsserver behaviour. Indent level is taken to be
652 `typescript-indent-level'."
653   :type 'boolean
654   :group 'typescript)
655
656 (defcustom typescript-auto-indent-flag t
657   "Whether to automatically indent when typing punctuation characters.
658 If non-nil, the characters {}();,: also indent the current line
659 in typescript mode."
660   :type 'boolean
661   :group 'typescript)
662
663 (defcustom typescript-flat-functions nil
664   "Treat nested functions as top-level functions in `typescript-mode'.
665 This applies to function movement, marking, and so on."
666   :type 'boolean
667   :group 'typescript)
668
669 (defcustom typescript-comment-lineup-func #'c-lineup-C-comments
670   "Lineup function for `cc-mode-style', for C comments in `typescript-mode'."
671   :type 'function
672   :group 'typescript)
673
674 (defcustom typescript-enabled-frameworks typescript--available-frameworks
675   "Frameworks recognized by `typescript-mode'.
676 To improve performance, you may turn off some frameworks you
677 seldom use, either globally or on a per-buffer basis."
678   :type (cons 'set (mapcar (lambda (x)
679                              (list 'const x))
680                            typescript--available-frameworks))
681   :group 'typescript)
682
683 (defcustom typescript-mode-hook nil
684   "*Hook called by `typescript-mode'."
685   :type 'hook
686   :group 'typescript)
687
688 (defcustom typescript-autoconvert-to-template-flag nil
689   "Non-nil means automatically convert plain strings to templates.
690
691 When the flag is non-nil the `typescript-autoconvert-to-template'
692 is called whenever a plain string delimiter is typed in the buffer."
693   :type 'boolean
694   :group 'typescript)
695
696 ;;; Public utilities
697
698 (defun typescript-convert-to-template ()
699   "Convert the string at point to a template string."
700   (interactive)
701   (save-restriction
702     (widen)
703     (save-excursion
704       (let* ((syntax (syntax-ppss))
705              (str-terminator (nth 3 syntax))
706              (string-start (or (and str-terminator (nth 8 syntax))
707                                ;; We have to consider the case that we're on the start delimiter of a string.
708                                ;; We tentatively take (point) as string-start. If it turns out we're
709                                ;; wrong, then typescript--move-to-end-of-plain-string will fail anway,
710                                ;; and we won't use the bogus value.
711                                (progn
712                                  (forward-char)
713                                  (point)))))
714         (when (typescript--move-to-end-of-plain-string)
715           (let ((end-start (or (nth 8 (syntax-ppss)) -1)))
716             (undo-boundary)
717             (when (=  end-start string-start)
718               (delete-char 1)
719               (insert "`")))
720           (goto-char string-start)
721           (delete-char 1)
722           (insert "`"))))))
723
724 (defun typescript-autoconvert-to-template ()
725   "Automatically convert a plain string to a teplate string, if needed.
726
727 This function is meant to be automatically invoked when the user
728 enters plain string delimiters.  It checks whether the character
729 before point is the end of a string.  If it is, then it checks
730 whether the string contains ${...}.  If it does, then it converts
731 the string from a plain string to a template."
732   (interactive)
733   (save-restriction
734     (widen)
735     (save-excursion
736       (backward-char)
737       (when (and (memq (char-after) '(?' ?\"))
738                  (not (eq (char-before) ?\\)))
739         (let* ((string-start (nth 8 (syntax-ppss))))
740           (when (and string-start
741                      (save-excursion
742                        (re-search-backward "\\${.*?}" string-start t)))
743             (typescript-convert-to-template)))))))
744
745 ;;; KeyMap
746
747 (defvar typescript-mode-map
748   (let ((keymap (make-sparse-keymap)))
749     (define-key keymap (kbd "C-c '") #'typescript-convert-to-template)
750     keymap)
751   "Keymap for `typescript-mode'.")
752
753 (defun typescript--post-self-insert-function ()
754   (when (and (derived-mode-p 'typescript-mode)
755              typescript-autoconvert-to-template-flag
756              (or (eq last-command-event ?\')
757                  (eq last-command-event ?\")))
758     (typescript-autoconvert-to-template)))
759
760 ;;; Syntax table and parsing
761
762 (defvar typescript-mode-syntax-table
763   (let ((table (make-syntax-table)))
764     (c-populate-syntax-table table)
765     (modify-syntax-entry ?$ "_" table)
766     (modify-syntax-entry ?` "\"" table)
767     table)
768   "Syntax table for `typescript-mode'.")
769
770 (defvar typescript--quick-match-re nil
771   "Autogenerated regexp used by `typescript-mode' to match buffer constructs.")
772
773 (defvar typescript--quick-match-re-func nil
774   "Autogenerated regexp used by `typescript-mode' to match constructs and functions.")
775
776 (make-variable-buffer-local 'typescript--quick-match-re)
777 (make-variable-buffer-local 'typescript--quick-match-re-func)
778
779 (defvar typescript--cache-end 1
780   "Last valid buffer position for the `typescript-mode' function cache.")
781 (make-variable-buffer-local 'typescript--cache-end)
782
783 (defvar typescript--last-parse-pos nil
784   "Latest parse position reached by `typescript--ensure-cache'.")
785 (make-variable-buffer-local 'typescript--last-parse-pos)
786
787 (defvar typescript--state-at-last-parse-pos nil
788   "Parse state at `typescript--last-parse-pos'.")
789 (make-variable-buffer-local 'typescript--state-at-last-parse-pos)
790
791 (defun typescript--flatten-list (list)
792   (cl-loop for item in list
793         nconc (cond ((consp item)
794                      (typescript--flatten-list item))
795                     (item (list item)))))
796
797 (defun typescript--maybe-join (prefix separator suffix &rest list)
798   "Helper function for `typescript--update-quick-match-re'.
799 If LIST contains any element that is not nil, return its non-nil
800 elements, separated by SEPARATOR, prefixed by PREFIX, and ended
801 with SUFFIX as with `concat'.  Otherwise, if LIST is empty, return
802 nil.  If any element in LIST is itself a list, flatten that
803 element."
804   (setq list (typescript--flatten-list list))
805   (when list
806     (concat prefix (mapconcat #'identity list separator) suffix)))
807
808 (defun typescript--update-quick-match-re ()
809   "Internal function used by `typescript-mode' for caching buffer constructs.
810 This updates `typescript--quick-match-re', based on the current set of
811 enabled frameworks."
812   (setq typescript--quick-match-re
813         (typescript--maybe-join
814          "^[ \t]*\\(?:" "\\|" "\\)"
815
816          ;; #define mumble
817          "#define[ \t]+[a-zA-Z_]"
818
819          (when (memq 'exttypescript typescript-enabled-frameworks)
820            "Ext\\.extend")
821
822          (when (memq 'prototype typescript-enabled-frameworks)
823            "Object\\.extend")
824
825           ;; var mumble = THING (
826          (typescript--maybe-join
827           "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
828           "\\|"
829           "\\)[ \t]*\("
830
831           (when (memq 'prototype typescript-enabled-frameworks)
832                     "Class\\.create")
833
834           (when (memq 'exttypescript typescript-enabled-frameworks)
835             "Ext\\.extend")
836
837           (when (memq 'merrillpress typescript-enabled-frameworks)
838             "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))
839
840          (when (memq 'dojo typescript-enabled-frameworks)
841            "dojo\\.declare[ \t]*\(")
842
843          (when (memq 'mochikit typescript-enabled-frameworks)
844            "MochiKit\\.Base\\.update[ \t]*\(")
845
846          ;; mumble.prototypeTHING
847          (typescript--maybe-join
848           "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"
849
850           (when (memq 'typescript typescript-enabled-frameworks)
851             '( ;; foo.prototype.bar = function(
852               "\\.[a-zA-Z_$0-9]+[ \t]*=[ \t]*function[ \t]*\("
853
854               ;; mumble.prototype = {
855               "[ \t]*=[ \t]*{")))))
856
857   (setq typescript--quick-match-re-func
858         (concat "function\\|" typescript--quick-match-re)))
859
860 (defun typescript--forward-text-property (propname)
861   "Move over the next value of PROPNAME in the buffer.
862 If found, return that value and leave point after the character
863 having that value; otherwise, return nil and leave point at EOB."
864   (let ((next-value (get-text-property (point) propname)))
865     (if next-value
866         (forward-char)
867
868       (goto-char (next-single-property-change
869                   (point) propname nil (point-max)))
870       (unless (eobp)
871         (setq next-value (get-text-property (point) propname))
872         (forward-char)))
873
874     next-value))
875
876 (defun typescript--backward-text-property (propname)
877   "Move over the previous value of PROPNAME in the buffer.
878 If found, return that value and leave point just before the
879 character that has that value, otherwise return nil and leave
880 point at BOB."
881     (unless (bobp)
882       (let ((prev-value (get-text-property (1- (point)) propname)))
883         (if prev-value
884             (backward-char)
885
886           (goto-char (previous-single-property-change
887                       (point) propname nil (point-min)))
888
889           (unless (bobp)
890             (backward-char)
891             (setq prev-value (get-text-property (point) propname))))
892
893         prev-value)))
894
895 (defsubst typescript--forward-pstate ()
896   (typescript--forward-text-property 'typescript--pstate))
897
898 (defsubst typescript--backward-pstate ()
899   (typescript--backward-text-property 'typescript--pstate))
900
901 (defun typescript--pitem-goto-h-end (pitem)
902   (goto-char (typescript--pitem-h-begin pitem))
903   (typescript--forward-pstate))
904
905 (defun typescript--re-search-forward-inner (regexp &optional bound count)
906   "Helper function for `typescript--re-search-forward'."
907   (let ((parse)
908         str-terminator)
909     (while (> count 0)
910       (re-search-forward regexp bound)
911       (setq parse (syntax-ppss))
912       (cond ((setq str-terminator (nth 3 parse))
913              (when (eq str-terminator t)
914                (setq str-terminator ?/))
915              (re-search-forward
916               (concat "\\([^\\]\\|^\\)" (string str-terminator))
917               (save-excursion (end-of-line) (point)) t))
918             ((nth 7 parse)
919              (forward-line))
920             ((or (nth 4 parse)
921                  (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
922              (re-search-forward "\\*/"))
923             (t
924              (setq count (1- count))))))
925   (point))
926
927
928 (defun typescript--re-search-forward (regexp &optional bound noerror count)
929   "Search forward, ignoring strings and comments.
930 This function invokes `re-search-forward', but treats the buffer
931 as if strings and comments have been removed."
932   (let ((saved-point (point))
933         (search-expr
934          (cond ((null count)
935                 '(typescript--re-search-forward-inner regexp bound 1))
936                ((< count 0)
937                 '(typescript--re-search-backward-inner regexp bound (- count)))
938                ((> count 0)
939                 '(typescript--re-search-forward-inner regexp bound count)))))
940     (condition-case err
941         (eval search-expr)
942       (search-failed
943        (goto-char saved-point)
944        (unless noerror
945          (error (error-message-string err)))))))
946
947
948 (defun typescript--re-search-backward-inner (regexp &optional bound count)
949   "Auxiliary function for `typescript--re-search-backward'."
950   (let ((parse))
951     (while (> count 0)
952       (re-search-backward regexp bound)
953       (when (and (> (point) (point-min))
954                  (save-excursion (backward-char) (looking-at "/[/*]")))
955         (forward-char))
956       (setq parse (syntax-ppss))
957       (cond
958        ;; If we are in a comment or a string, jump back to the start
959        ;; of the comment or string.
960        ((nth 8 parse)
961         (goto-char (nth 8 parse)))
962        ((and (eq (char-before) ?/) (eq (char-after) ?*))
963         (re-search-backward "/\\*"))
964        (t
965         (setq count (1- count))))))
966   (point))
967
968
969 (defun typescript--re-search-backward (regexp &optional bound noerror count)
970   "Search backward, ignoring strings, and comments.
971
972 This function invokes `re-search-backward' but treats the buffer
973 as if strings and comments have been removed.
974
975 IMPORTANT NOTE: searching for \"\\n\" with this function to find
976 line breaks will generally not work, because the final newline of
977 a one-line comment is considered to be part of the comment and
978 will be skipped.  Take the following code:
979
980   let a = 1;
981   let b = 2; // Foo
982   let c = 3;
983
984 If the point is in the last line, searching back for \"\\n\" will
985 skip over the line with \"let b\". The newline found will be the
986 one at the end of the line with \"let a\"."
987   (let ((saved-point (point))
988         (search-expr
989          (cond ((null count)
990                 '(typescript--re-search-backward-inner regexp bound 1))
991                ((< count 0)
992                 '(typescript--re-search-forward-inner regexp bound (- count)))
993                ((> count 0)
994                 '(typescript--re-search-backward-inner regexp bound count)))))
995     (condition-case err
996         (eval search-expr)
997       (search-failed
998        (goto-char saved-point)
999        (unless noerror
1000          (error (error-message-string err)))))))
1001
1002 (defun typescript--forward-expression ()
1003   "Move forward over a whole typescript expression.
1004 This function doesn't move over expressions continued across
1005 lines."
1006   (cl-loop
1007    do (progn
1008         (forward-comment most-positive-fixnum)
1009         (cl-loop until (or (eolp)
1010                         (progn
1011                           (forward-comment most-positive-fixnum)
1012                           (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
1013               do (forward-sexp)))
1014    while (and (eq (char-after) ?\n)
1015               (save-excursion
1016                 (forward-char)
1017                 (typescript--continued-expression-p)))))
1018
1019 (defun typescript--forward-function-decl ()
1020   "Move forward over a typescript function declaration.
1021 This puts point at the 'function' keyword.
1022
1023 If this is a syntactically-correct non-expression function,
1024 return the name of the function, or t if the name could not be
1025 determined.  Otherwise, return nil."
1026   (cl-assert (looking-at "\\_<function\\_>"))
1027   (let ((name t))
1028     (forward-word)
1029     (forward-comment most-positive-fixnum)
1030     (when (looking-at typescript--name-re)
1031       (setq name (match-string-no-properties 0))
1032       (goto-char (match-end 0)))
1033     (forward-comment most-positive-fixnum)
1034     (and (eq (char-after) ?\( )
1035          (ignore-errors (forward-list) t)
1036          (progn (forward-comment most-positive-fixnum)
1037                 (and (eq (char-after) ?{)
1038                      name)))))
1039
1040 (defun typescript--function-prologue-beginning (&optional pos)
1041   "Return the start of the typescript function prologue containing POS.
1042 A function prologue is everything from start of the definition up
1043 to and including the opening brace.  POS defaults to point.
1044 If POS is not in a function prologue, return nil."
1045   (let (prologue-begin)
1046     (save-excursion
1047       (if pos
1048           (goto-char pos)
1049         (setq pos (point)))
1050
1051       (when (save-excursion
1052               (forward-line 0)
1053               (or (looking-at typescript--function-heading-2-re)
1054                   (looking-at typescript--function-heading-3-re)))
1055
1056         (setq prologue-begin (match-beginning 1))
1057         (when (<= prologue-begin pos)
1058           (goto-char (match-end 0))))
1059
1060       (skip-syntax-backward "w_")
1061       (and (or (looking-at "\\_<function\\_>")
1062                (typescript--re-search-backward "\\_<function\\_>" nil t))
1063
1064            (save-match-data (goto-char (match-beginning 0))
1065                             (typescript--forward-function-decl))
1066
1067            (<= pos (point))
1068            (or prologue-begin (match-beginning 0))))))
1069
1070 (defun typescript--beginning-of-defun-raw ()
1071   "Helper function for `typescript-beginning-of-defun'.
1072 Go to previous defun-beginning and return the parse state for it,
1073 or nil if we went all the way back to bob and don't find
1074 anything."
1075   (typescript--ensure-cache)
1076   (let (pstate)
1077     (while (and (setq pstate (typescript--backward-pstate))
1078                 (not (eq 'function (typescript--pitem-type (car pstate))))))
1079     (and (not (bobp)) pstate)))
1080
1081 (defun typescript--pstate-is-toplevel-defun (pstate)
1082   "Helper function for `typescript--beginning-of-defun-nested'.
1083 If PSTATE represents a non-empty top-level defun, return the
1084 top-most pitem.  Otherwise, return nil."
1085   (cl-loop for pitem in pstate
1086         with func-depth = 0
1087         with func-pitem
1088         if (eq 'function (typescript--pitem-type pitem))
1089         do (cl-incf func-depth)
1090         and do (setq func-pitem pitem)
1091         finally return (if (eq func-depth 1) func-pitem)))
1092
1093 (defun typescript--beginning-of-defun-nested ()
1094   "Helper function for `typescript--beginning-of-defun'.
1095 Return the pitem of the function we went to the beginning of."
1096   (or
1097    ;; Look for the smallest function that encloses point...
1098    (cl-loop for pitem in (typescript--parse-state-at-point)
1099          if (and (eq 'function (typescript--pitem-type pitem))
1100                  (typescript--inside-pitem-p pitem))
1101          do (goto-char (typescript--pitem-h-begin pitem))
1102          and return pitem)
1103
1104    ;; ...and if that isn't found, look for the previous top-level
1105    ;; defun
1106    (cl-loop for pstate = (typescript--backward-pstate)
1107          while pstate
1108          if (typescript--pstate-is-toplevel-defun pstate)
1109          do (goto-char (typescript--pitem-h-begin it))
1110          and return it)))
1111
1112 (defun typescript--beginning-of-defun-flat ()
1113   "Helper function for `typescript-beginning-of-defun'."
1114   (let ((pstate (typescript--beginning-of-defun-raw)))
1115     (when pstate
1116       (goto-char (typescript--pitem-h-begin (car pstate))))))
1117
1118 (defun typescript-beginning-of-defun (&optional arg)
1119   "Value of `beginning-of-defun-function' for `typescript-mode'."
1120   (setq arg (or arg 1))
1121   (while (and (not (eobp)) (< arg 0))
1122     (cl-incf arg)
1123     (when (and (not typescript-flat-functions)
1124                (or (eq (typescript-syntactic-context) 'function)
1125                    (typescript--function-prologue-beginning)))
1126       (typescript-end-of-defun))
1127
1128     (if (typescript--re-search-forward
1129          "\\_<function\\_>" nil t)
1130         (goto-char (typescript--function-prologue-beginning))
1131       (goto-char (point-max))))
1132
1133   (while (> arg 0)
1134     (cl-decf arg)
1135     ;; If we're just past the end of a function, the user probably wants
1136     ;; to go to the beginning of *that* function
1137     (when (eq (char-before) ?})
1138       (backward-char))
1139
1140     (let ((prologue-begin (typescript--function-prologue-beginning)))
1141       (cond ((and prologue-begin (< prologue-begin (point)))
1142              (goto-char prologue-begin))
1143
1144             (typescript-flat-functions
1145              (typescript--beginning-of-defun-flat))
1146             (t
1147              (typescript--beginning-of-defun-nested))))))
1148
1149 (defun typescript--flush-caches (&optional beg ignored)
1150   "Flush the `typescript-mode' syntax cache after position BEG.
1151 BEG defaults to `point-min', meaning to flush the entire cache."
1152   (interactive)
1153   (setq beg (or beg (save-restriction (widen) (point-min))))
1154   (setq typescript--cache-end (min typescript--cache-end beg)))
1155
1156 (defmacro typescript--debug (&rest arguments)
1157   ;; `(message ,@arguments)
1158   )
1159
1160 (defun typescript--ensure-cache--pop-if-ended (open-items paren-depth)
1161   (let ((top-item (car open-items)))
1162     (when (<= paren-depth (typescript--pitem-paren-depth top-item))
1163       (cl-assert (not (get-text-property (1- (point)) 'typescript-pend)))
1164       (put-text-property (1- (point)) (point) 'typescript--pend top-item)
1165       (setf (typescript--pitem-b-end top-item) (point))
1166       (setq open-items
1167             ;; open-items must contain at least two items for this to
1168             ;; work, but because we push a dummy item to start with,
1169             ;; that assumption holds.
1170             (cons (typescript--pitem-add-child (cl-second open-items) top-item)
1171                   (cddr open-items)))))
1172   open-items)
1173
1174 (defmacro typescript--ensure-cache--update-parse ()
1175   "Helper function for `typescript--ensure-cache'.
1176 Update parsing information up to point, referring to parse,
1177 prev-parse-point, goal-point, and open-items bound lexically in
1178 the body of `typescript--ensure-cache'."
1179   `(progn
1180      (setq goal-point (point))
1181      (goto-char prev-parse-point)
1182      (while (progn
1183               (setq open-items (typescript--ensure-cache--pop-if-ended
1184                                 open-items (car parse)))
1185               ;; Make sure parse-partial-sexp doesn't stop because we *entered*
1186               ;; the given depth -- i.e., make sure we're deeper than the target
1187               ;; depth.
1188               (cl-assert (> (nth 0 parse)
1189                          (typescript--pitem-paren-depth (car open-items))))
1190               (setq parse (parse-partial-sexp
1191                            prev-parse-point goal-point
1192                            (typescript--pitem-paren-depth (car open-items))
1193                            nil parse))
1194
1195 ;;              (let ((overlay (make-overlay prev-parse-point (point))))
1196 ;;                (overlay-put overlay 'face '(:background "red"))
1197 ;;                (unwind-protect
1198 ;;                     (progn
1199 ;;                       (typescript--debug "parsed: %S" parse)
1200 ;;                       (sit-for 1))
1201 ;;                  (delete-overlay overlay)))
1202
1203               (setq prev-parse-point (point))
1204               (< (point) goal-point)))
1205
1206      (setq open-items (typescript--ensure-cache--pop-if-ended
1207                        open-items (car parse)))))
1208
1209 (defun typescript--show-cache-at-point ()
1210   (interactive)
1211   (require 'pp)
1212   (let ((prop (get-text-property (point) 'typescript--pstate)))
1213     (with-output-to-temp-buffer "*Help*"
1214       (pp prop))))
1215
1216 (defun typescript--split-name (string)
1217   "Split a typescript name into its dot-separated parts.
1218 This also removes any prototype parts from the split name
1219 \(unless the name is just \"prototype\" to start with)."
1220   (let ((name (save-match-data
1221                 (split-string string "\\." t))))
1222     (unless (and (= (length name) 1)
1223                  (equal (car name) "prototype"))
1224
1225       (setq name (remove "prototype" name)))))
1226
1227 (defvar typescript--guess-function-name-start nil)
1228
1229 (defun typescript--guess-function-name (position)
1230   "Guess the name of the typescript function at POSITION.
1231 POSITION should be just after the end of the word \"function\".
1232 Return the name of the function, or nil if the name could not be
1233 guessed.
1234
1235 This function clobbers match data.  If we find the preamble
1236 begins earlier than expected while guessing the function name,
1237 set `typescript--guess-function-name-start' to that position; otherwise,
1238 set that variable to nil."
1239   (setq typescript--guess-function-name-start nil)
1240   (save-excursion
1241     (goto-char position)
1242     (forward-line 0)
1243     (cond
1244      ((looking-at typescript--function-heading-3-re)
1245       (and (eq (match-end 0) position)
1246            (setq typescript--guess-function-name-start (match-beginning 1))
1247            (match-string-no-properties 1)))
1248
1249      ((looking-at typescript--function-heading-2-re)
1250       (and (eq (match-end 0) position)
1251            (setq typescript--guess-function-name-start (match-beginning 1))
1252            (match-string-no-properties 1))))))
1253
1254 (defun typescript--clear-stale-cache ()
1255   ;; Clear any endings that occur after point
1256   (let (end-prop)
1257     (save-excursion
1258       (while (setq end-prop (typescript--forward-text-property
1259                              'typescript--pend))
1260         (setf (typescript--pitem-b-end end-prop) nil))))
1261
1262   ;; Remove any cache properties after this point
1263   (remove-text-properties (point) (point-max)
1264                           '(typescript--pstate t typescript--pend t)))
1265
1266 (defun typescript--ensure-cache (&optional limit)
1267   "Ensures brace cache is valid up to the character before LIMIT.
1268 LIMIT defaults to point."
1269   (setq limit (or limit (point)))
1270   (when (< typescript--cache-end limit)
1271
1272     (c-save-buffer-state
1273         (open-items
1274          orig-match-start
1275          orig-match-end
1276          orig-depth
1277          parse
1278          prev-parse-point
1279          name
1280          case-fold-search
1281          filtered-class-styles
1282          new-item
1283          goal-point
1284          end-prop)
1285
1286       ;; Figure out which class styles we need to look for
1287       (setq filtered-class-styles
1288             (cl-loop for style in typescript--class-styles
1289                   if (memq (plist-get style :framework)
1290                            typescript-enabled-frameworks)
1291                   collect style))
1292
1293       (save-excursion
1294         (save-restriction
1295           (widen)
1296
1297           ;; Find last known good position
1298           (goto-char typescript--cache-end)
1299           (unless (bobp)
1300             (setq open-items (get-text-property
1301                               (1- (point)) 'typescript--pstate))
1302
1303             (unless open-items
1304               (goto-char (previous-single-property-change
1305                           (point) 'typescript--pstate nil (point-min)))
1306
1307               (unless (bobp)
1308                 (setq open-items (get-text-property (1- (point))
1309                                                     'typescript--pstate))
1310                 (cl-assert open-items))))
1311
1312           (unless open-items
1313             ;; Make a placeholder for the top-level definition
1314             (setq open-items (list typescript--initial-pitem)))
1315
1316           (setq parse (syntax-ppss))
1317           (setq prev-parse-point (point))
1318
1319           (typescript--clear-stale-cache)
1320
1321           (narrow-to-region (point-min) limit)
1322
1323           (cl-loop while (re-search-forward typescript--quick-match-re-func nil t)
1324                 for orig-match-start = (goto-char (match-beginning 0))
1325                 for orig-match-end = (match-end 0)
1326                 do (typescript--ensure-cache--update-parse)
1327                 for orig-depth = (nth 0 parse)
1328
1329                 ;; Each of these conditions should return non-nil if
1330                 ;; we should add a new item and leave point at the end
1331                 ;; of the new item's header (h-end in the
1332                 ;; typescript--pitem diagram). This point is the one
1333                 ;; after the last character we need to unambiguously
1334                 ;; detect this construct. If one of these evaluates to
1335                 ;; nil, the location of the point is ignored.
1336                 if (cond
1337                     ;; In comment or string
1338                     ((nth 8 parse) nil)
1339
1340                     ;; Regular function declaration
1341                     ((and (looking-at "\\_<function\\_>")
1342                           (setq name (typescript--forward-function-decl)))
1343
1344                      (when (eq name t)
1345                        (setq name (typescript--guess-function-name orig-match-end))
1346                        (if name
1347                            (when typescript--guess-function-name-start
1348                              (setq orig-match-start
1349                                    typescript--guess-function-name-start))
1350
1351                          (setq name t)))
1352
1353                      (cl-assert (eq (char-after) ?{))
1354                      (forward-char)
1355                      (make-typescript--pitem
1356                       :paren-depth orig-depth
1357                       :h-begin orig-match-start
1358                       :type 'function
1359                       :name (if (eq name t)
1360                                 name
1361                               (typescript--split-name name))))
1362
1363                     ;; "Prototype function" declaration
1364                     ((looking-at typescript--plain-method-re)
1365                      (goto-char (match-beginning 3))
1366                      (when (save-match-data
1367                              (typescript--forward-function-decl))
1368                        (forward-char)
1369                        (make-typescript--pitem
1370                         :paren-depth orig-depth
1371                         :h-begin orig-match-start
1372                         :type 'function
1373                         :name (nconc (typescript--split-name
1374                                       (match-string-no-properties 1))
1375                                      (list (match-string-no-properties 2))))))
1376
1377                     ;; Class definition
1378                     ((cl-loop with syntactic-context =
1379                            (typescript--syntactic-context-from-pstate open-items)
1380                            for class-style in filtered-class-styles
1381                            if (and (memq syntactic-context
1382                                          (plist-get class-style :contexts))
1383                                    (looking-at (plist-get class-style
1384                                                           :class-decl)))
1385                            do (goto-char (match-end 0))
1386                            and return
1387                            (make-typescript--pitem
1388                             :paren-depth orig-depth
1389                             :h-begin orig-match-start
1390                             :type class-style
1391                             :name (typescript--split-name
1392                                    (match-string-no-properties 1))))))
1393
1394                 do (typescript--ensure-cache--update-parse)
1395                 and do (push it open-items)
1396                 and do (put-text-property
1397                         (1- (point)) (point) 'typescript--pstate open-items)
1398                 else do (goto-char orig-match-end))
1399
1400           (goto-char limit)
1401           (typescript--ensure-cache--update-parse)
1402           (setq typescript--cache-end limit)
1403           (setq typescript--last-parse-pos limit)
1404           (setq typescript--state-at-last-parse-pos open-items))))))
1405
1406 (defun typescript--end-of-defun-flat ()
1407   "Helper function for `typescript-end-of-defun'."
1408   (cl-loop while (typescript--re-search-forward "}" nil t)
1409         do (typescript--ensure-cache)
1410         if (get-text-property (1- (point)) 'typescript--pend)
1411         if (eq 'function (typescript--pitem-type it))
1412         return t
1413         finally do (goto-char (point-max))))
1414
1415 (defun typescript--end-of-defun-nested ()
1416   "Helper function for `typescript-end-of-defun'."
1417   (let* (pitem
1418          (this-end (save-excursion
1419                      (and (setq pitem (typescript--beginning-of-defun-nested))
1420                           (typescript--pitem-goto-h-end pitem)
1421                           (progn (backward-char)
1422                                  (forward-list)
1423                                  (point)))))
1424          found)
1425
1426     (if (and this-end (< (point) this-end))
1427         ;; We're already inside a function; just go to its end.
1428         (goto-char this-end)
1429
1430       ;; Otherwise, go to the end of the next function...
1431       (while (and (typescript--re-search-forward "\\_<function\\_>" nil t)
1432                   (not (setq found (progn
1433                                      (goto-char (match-beginning 0))
1434                                      (typescript--forward-function-decl))))))
1435
1436       (if found (forward-list)
1437         ;; ... or eob.
1438         (goto-char (point-max))))))
1439
1440 (defun typescript-end-of-defun (&optional arg)
1441   "Value of `end-of-defun-function' for `typescript-mode'."
1442   (setq arg (or arg 1))
1443   (while (and (not (bobp)) (< arg 0))
1444     (cl-incf arg)
1445     (typescript-beginning-of-defun)
1446     (typescript-beginning-of-defun)
1447     (unless (bobp)
1448       (typescript-end-of-defun)))
1449
1450   (while (> arg 0)
1451     (cl-decf arg)
1452     ;; look for function backward. if we're inside it, go to that
1453     ;; function's end. otherwise, search for the next function's end and
1454     ;; go there
1455     (if typescript-flat-functions
1456         (typescript--end-of-defun-flat)
1457
1458       ;; if we're doing nested functions, see whether we're in the
1459       ;; prologue. If we are, go to the end of the function; otherwise,
1460       ;; call typescript--end-of-defun-nested to do the real work
1461       (let ((prologue-begin (typescript--function-prologue-beginning)))
1462         (cond ((and prologue-begin (<= prologue-begin (point)))
1463                (goto-char prologue-begin)
1464                (re-search-forward "\\_<function")
1465                (goto-char (match-beginning 0))
1466                (typescript--forward-function-decl)
1467                (forward-list))
1468
1469               (t (typescript--end-of-defun-nested)))))))
1470
1471 (defun typescript--backward-syntactic-ws (&optional lim)
1472   "Simple implementation of `c-backward-syntactic-ws' for `typescript-mode'."
1473   (save-restriction
1474     (when lim (narrow-to-region lim (point-max)))
1475
1476     (let ((pos (point)))
1477       (while (progn (forward-comment most-negative-fixnum)
1478                     (/= (point)
1479                         (prog1
1480                             pos
1481                           (setq pos (point)))))))))
1482
1483 (defun typescript--forward-syntactic-ws (&optional lim)
1484   "Simple implementation of `c-forward-syntactic-ws' for `typescript-mode'."
1485   (save-restriction
1486     (when lim (narrow-to-region (point-min) lim))
1487     (let ((pos (point)))
1488       (while (progn
1489                (forward-comment most-positive-fixnum)
1490                (/= (point)
1491                    (prog1
1492                        pos
1493                      (setq pos (point)))))))))
1494
1495 ;; Like (up-list -1), but only considers lists that end nearby"
1496 (defun typescript--up-nearby-list ()
1497   (save-restriction
1498     ;; Look at a very small region so our compuation time doesn't
1499     ;; explode in pathological cases.
1500     (narrow-to-region (max (point-min) (- (point) 500)) (point))
1501     (up-list -1)))
1502
1503 (defun typescript--inside-param-list-p ()
1504   "Return non-nil iff point is in a function parameter list."
1505   (ignore-errors
1506     (save-excursion
1507       (typescript--up-nearby-list)
1508       (and (looking-at "(")
1509            (progn (forward-symbol -1)
1510                   (or (looking-at "function")
1511                       (progn (forward-symbol -1)
1512                              (looking-at "function"))))))))
1513
1514 (defun typescript--inside-dojo-class-list-p ()
1515   "Return non-nil iff point is in a Dojo multiple-inheritance class block."
1516   (ignore-errors
1517     (save-excursion
1518       (typescript--up-nearby-list)
1519       (let ((list-begin (point)))
1520         (forward-line 0)
1521         (and (looking-at typescript--dojo-class-decl-re)
1522              (goto-char (match-end 0))
1523              (looking-at "\"\\s-*,\\s-*\\[")
1524              (eq (match-end 0) (1+ list-begin)))))))
1525
1526 (defun typescript--syntax-begin-function ()
1527   (when (< typescript--cache-end (point))
1528     (goto-char (max (point-min) typescript--cache-end)))
1529
1530   (let ((pitem))
1531     (while (and (setq pitem (car (typescript--backward-pstate)))
1532                 (not (eq 0 (typescript--pitem-paren-depth pitem)))))
1533
1534     (when pitem
1535       (goto-char (typescript--pitem-h-begin pitem )))))
1536
1537 (defun typescript--move-to-end-of-plain-string ()
1538   "If the point is in a plain string, move to the end of it.
1539
1540 Otherwise, don't move.  A plain string is a string which is not a
1541 template string.  The point is considered to be \"in\" a string if
1542 it is on the delimiters of the string, or any point inside.
1543
1544 Returns point if the end of the string was found, or nil if the
1545 end of the string was not found."
1546   (let ((end-position
1547          (save-excursion
1548            (let* ((syntax (syntax-ppss))
1549                   (str-terminator (nth 3 syntax))
1550                   ;; The 8th element will also be set if we are in a comment. So we
1551                   ;; check str-terminator to protect against that.
1552                   (string-start (and str-terminator
1553                                      (nth 8 syntax))))
1554              (if (and string-start
1555                       (not (eq str-terminator ?`)))
1556                  ;; We may already be at the end of the string.
1557                  (if (and (eq (char-after) str-terminator)
1558                           (not (eq (char-before) ?\\)))
1559                      (point)
1560                    ;; We just search forward and then check if the hit we get has a
1561                    ;; string-start equal to ours.
1562                    (cl-loop while (re-search-forward
1563                                 (concat "\\(?:[^\\]\\|^\\)\\(" (string str-terminator) "\\)")
1564                                 nil t)
1565                          if (eq string-start
1566                                 (save-excursion (nth 8 (syntax-ppss (match-beginning 1)))))
1567                          return (match-beginning 1)))
1568                ;; If we are on the start delimiter then the value of syntax-ppss will look
1569                ;; like we're not in a string at all, but this function considers the
1570                ;; start delimiter to be "in" the string. We take care of this here.
1571                (when (memq (char-after) '(?' ?\"))
1572                  (forward-char)
1573                  (typescript--move-to-end-of-plain-string)))))))
1574     (when end-position
1575       (goto-char end-position))))
1576
1577 ;;; Font Lock
1578 (defun typescript--make-framework-matcher (framework &rest regexps)
1579   "Helper function for building `typescript--font-lock-keywords'.
1580 Create a byte-compiled function for matching a concatenation of
1581 REGEXPS, but only if FRAMEWORK is in `typescript-enabled-frameworks'."
1582   (setq regexps (apply #'concat regexps))
1583   (byte-compile
1584    `(lambda (limit)
1585       (when (memq (quote ,framework) typescript-enabled-frameworks)
1586         (re-search-forward ,regexps limit t)))))
1587
1588 (defvar typescript--tmp-location nil)
1589 (make-variable-buffer-local 'typescript--tmp-location)
1590
1591 (defun typescript--forward-destructuring-spec (&optional func)
1592   "Move forward over a typescript destructuring spec.
1593 If FUNC is supplied, call it with no arguments before every
1594 variable name in the spec.  Return true iff this was actually a
1595 spec.  FUNC must preserve the match data."
1596   (cl-case (char-after)
1597     (?\[
1598      (forward-char)
1599      (while
1600          (progn
1601            (forward-comment most-positive-fixnum)
1602            (cond ((memq (char-after) '(?\[ ?\{))
1603                   (typescript--forward-destructuring-spec func))
1604
1605                  ((eq (char-after) ?,)
1606                   (forward-char)
1607                   t)
1608
1609                  ((looking-at typescript--name-re)
1610                   (and func (funcall func))
1611                   (goto-char (match-end 0))
1612                   t))))
1613      (when (eq (char-after) ?\])
1614        (forward-char)
1615        t))
1616
1617     (?\{
1618      (forward-char)
1619      (forward-comment most-positive-fixnum)
1620      (while
1621          (when (looking-at typescript--objfield-re)
1622            (goto-char (match-end 0))
1623            (forward-comment most-positive-fixnum)
1624            (and (cond ((memq (char-after) '(?\[ ?\{))
1625                        (typescript--forward-destructuring-spec func))
1626                       ((looking-at typescript--name-re)
1627                        (and func (funcall func))
1628                        (goto-char (match-end 0))
1629                        t))
1630                 (progn (forward-comment most-positive-fixnum)
1631                        (when (eq (char-after) ?\,)
1632                          (forward-char)
1633                          (forward-comment most-positive-fixnum)
1634                          t)))))
1635      (when (eq (char-after) ?\})
1636        (forward-char)
1637        t))))
1638
1639 (defun typescript--variable-decl-matcher (limit)
1640   "Font-lock matcher for variable names in a variable declaration.
1641 This is a cc-mode-style matcher that *always* fails, from the
1642 point of view of font-lock.  It applies highlighting directly with
1643 `font-lock-apply-highlight'."
1644   (condition-case nil
1645       (save-restriction
1646         (narrow-to-region (point-min) limit)
1647
1648         (let ((first t))
1649           (forward-comment most-positive-fixnum)
1650           (while
1651               (and (or first
1652                        (when (eq (char-after) ?,)
1653                          (forward-char)
1654                          (forward-comment most-positive-fixnum)
1655                          t))
1656                    (cond ((looking-at typescript--name-re)
1657                           (font-lock-apply-highlight
1658                            '(0 font-lock-variable-name-face))
1659                           (goto-char (match-end 0)))
1660
1661                          ((save-excursion
1662                             (typescript--forward-destructuring-spec))
1663
1664                           (typescript--forward-destructuring-spec
1665                            (lambda ()
1666                              (font-lock-apply-highlight
1667                               '(0 font-lock-variable-name-face)))))))
1668
1669             (forward-comment most-positive-fixnum)
1670             (when (eq (char-after) ?=)
1671               (forward-char)
1672               (typescript--forward-expression)
1673               (forward-comment most-positive-fixnum))
1674
1675             (setq first nil))))
1676
1677     ;; Conditions to handle
1678     (scan-error nil)
1679     (end-of-buffer nil))
1680
1681   ;; Matcher always "fails"
1682   nil)
1683
1684 (defun typescript--in-documentation-comment-p ()
1685   "Reports whether point is inside a documentation comment."
1686   (let ((parse (syntax-ppss)))
1687     (and
1688      (nth 4 parse) ;; Inside a comment ...
1689      (save-match-data
1690        (save-excursion
1691          (goto-char (nth 8 parse))
1692          (looking-at "/\\*\\*")))))) ;; ... which starts with /**
1693
1694 (defun typescript--documentation-font-lock-helper (re limit)
1695   "This is a helper macro that determines whether jsdoc highlighting is to be applied,
1696 and searches for the next token to be highlighted."
1697   (cl-loop while (re-search-forward re limit t)
1698         if (typescript--in-documentation-comment-p)
1699         return (point)))
1700
1701 (defun typescript--jsdoc-param-matcher (limit)
1702   "Font-lock mode matcher that finds jsdoc parameter tags in documentation."
1703   (typescript--documentation-font-lock-helper typescript-jsdoc-param-tag-regexp limit))
1704
1705 (defun typescript--jsdoc-typed-tag-matcher (limit)
1706   "Font-lock mode matcher that finds jsdoc typed tags in documentation."
1707   (typescript--documentation-font-lock-helper typescript-jsdoc-typed-tag-regexp limit))
1708
1709 (defun typescript--jsdoc-arg-tag-matcher (limit)
1710   "Font-lock mode matcher that finds jsdoc tags that take one argument in documentation."
1711   (typescript--documentation-font-lock-helper typescript-jsdoc-arg-tag-regexp limit))
1712
1713 (defun typescript--jsdoc-empty-tag-matcher (limit)
1714   "Font-lock mode matcher that finds jsdoc tags without argument in documentation."
1715   (typescript--documentation-font-lock-helper typescript-jsdoc-empty-tag-regexp limit))
1716
1717 (defun typescript--typedoc-link-matcher (limit)
1718   "Font-lock mode matcher that finds typedoc links in documentation."
1719   (typescript--documentation-font-lock-helper typescript-typedoc-link-tag-regexp limit))
1720
1721 (defun typescript--typedoc-literal-markup-matcher (limit)
1722   "Font-lock mode matcher that finds typedoc literal markup in documentation."
1723   (typescript--documentation-font-lock-helper typescript-typedoc-literal-markup-regexp limit))
1724
1725 (defun typescript--tslint-flag-matcher (limit)
1726   "Font-lock mode matcher that finds tslint flags in comments."
1727   (cl-loop while (re-search-forward typescript-tslint-flag-regexp limit t)
1728         if (nth 4 (syntax-ppss (match-beginning 1)))
1729         return (point)))
1730
1731 (defconst typescript--font-lock-keywords-3
1732   `(
1733     ,@typescript--font-lock-keywords-2
1734
1735     (typescript--jsdoc-param-matcher (1 'typescript-jsdoc-tag t t)
1736                                      (2 'typescript-jsdoc-type t t)
1737                                      (3 'typescript-jsdoc-value t t))
1738
1739     (typescript--jsdoc-typed-tag-matcher (1 'typescript-jsdoc-tag t t)
1740                                          (2 'typescript-jsdoc-type t t))
1741
1742     (typescript--jsdoc-arg-tag-matcher (1 'typescript-jsdoc-tag t t)
1743                                        (2 'typescript-jsdoc-value t t))
1744
1745     (typescript--jsdoc-empty-tag-matcher (1 'typescript-jsdoc-tag t t))
1746
1747     (typescript--typedoc-link-matcher (0 'typescript-jsdoc-value t))
1748
1749     (typescript--typedoc-literal-markup-matcher
1750      (0 'typescript-jsdoc-value t))
1751
1752     (typescript--tslint-flag-matcher
1753      (1 font-lock-preprocessor-face t))
1754
1755     ("\\.\\(prototype\\)\\_>"
1756      (1 font-lock-constant-face))
1757
1758     (,(rx symbol-start "class" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1759      (1 font-lock-type-face))
1760
1761     (,(rx symbol-start "extends" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1762      (1 font-lock-type-face))
1763
1764     (,(rx symbol-start "implements" (+ space))
1765      (,(rx symbol-start (+ (syntax word))) nil nil (0 font-lock-type-face)))
1766
1767     (,(rx symbol-start "interface" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1768      (1 font-lock-type-face))
1769
1770     (,(rx symbol-start "type" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1771      (1 font-lock-type-face))
1772
1773     (,(rx symbol-start "enum" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1774      (1 font-lock-type-face))
1775
1776     ;; Highlights class being declared, in parts
1777     (typescript--class-decl-matcher
1778      ,(concat "\\(" typescript--name-re "\\)\\(?:\\.\\|.*$\\)")
1779      (goto-char (match-beginning 1))
1780      nil
1781      (1 font-lock-type-face))
1782
1783     ;; Highlights parent class, in parts, if available
1784     (typescript--class-decl-matcher
1785      ,(concat "\\(" typescript--name-re "\\)\\(?:\\.\\|.*$\\)")
1786      (if (match-beginning 2)
1787          (progn
1788            (setq typescript--tmp-location (match-end 2))
1789            (goto-char typescript--tmp-location)
1790            (insert "=")
1791            (goto-char (match-beginning 2)))
1792        (setq typescript--tmp-location nil)
1793        (goto-char (point-at-eol)))
1794      (when typescript--tmp-location
1795        (save-excursion
1796          (goto-char typescript--tmp-location)
1797          (delete-char 1)))
1798      (1 font-lock-type-face))
1799
1800     ;; Highlights parent class
1801     (typescript--class-decl-matcher
1802      (2 font-lock-type-face nil t))
1803
1804     ;; Dojo needs its own matcher to override the string highlighting
1805     (,(typescript--make-framework-matcher
1806        'dojo
1807        "^\\s-*dojo\\.declare\\s-*(\""
1808        "\\(" typescript--dotted-name-re "\\)"
1809        "\\(?:\"\\s-*,\\s-*\\(" typescript--dotted-name-re "\\)\\)?")
1810      (1 font-lock-type-face t)
1811      (2 font-lock-type-face nil t))
1812
1813     ;; Match Dojo base classes. Of course Mojo has to be different
1814     ;; from everything else under the sun...
1815     (,(typescript--make-framework-matcher
1816        'dojo
1817        "^\\s-*dojo\\.declare\\s-*(\""
1818        "\\(" typescript--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
1819      ,(concat "[[,]\\s-*\\(" typescript--dotted-name-re "\\)\\s-*"
1820               "\\(?:\\].*$\\)?")
1821      (backward-char)
1822      (end-of-line)
1823      (1 font-lock-type-face))
1824
1825     ;; continued Dojo base-class list
1826     (,(typescript--make-framework-matcher
1827        'dojo
1828        "^\\s-*" typescript--dotted-name-re "\\s-*[],]")
1829      ,(concat "\\(" typescript--dotted-name-re "\\)"
1830               "\\s-*\\(?:\\].*$\\)?")
1831      (if (save-excursion (backward-char)
1832                          (typescript--inside-dojo-class-list-p))
1833          (forward-symbol -1)
1834        (end-of-line))
1835      (end-of-line)
1836      (1 font-lock-type-face))
1837
1838     ;; variable declarations
1839     ,(list
1840       (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" typescript--basic-type-re)
1841       (list #'typescript--variable-decl-matcher nil nil nil))
1842
1843     ;; class instantiation
1844     ,(list
1845       (concat "\\_<new\\_>\\s-+\\(" typescript--dotted-name-re "\\)")
1846       (list 1 'font-lock-type-face))
1847
1848     ;; instanceof
1849     ,(list
1850       (concat "\\_<instanceof\\_>\\s-+\\(" typescript--dotted-name-re "\\)")
1851       (list 1 'font-lock-type-face))
1852
1853     ;; formal parameters
1854     ,(list
1855       (concat
1856        "\\_<function\\_>\\(\\s-+" typescript--name-re "\\)?\\s-*\\(<.*>\\)?\\s-*(\\s-*"
1857        typescript--name-start-re)
1858       (list (concat "\\(" typescript--name-re "\\)\\(\\s-*).*\\)?")
1859             '(backward-char)
1860             '(end-of-line)
1861             '(1 font-lock-variable-name-face)))
1862
1863     ;; continued formal parameter list
1864     ,(list
1865       (concat
1866        "^\\s-*" typescript--name-re "\\s-*[,)]")
1867       (list typescript--name-re
1868             '(if (save-excursion (backward-char)
1869                                  (typescript--inside-param-list-p))
1870                  (forward-symbol -1)
1871                (end-of-line))
1872             '(end-of-line)
1873             '(0 font-lock-variable-name-face))))
1874   "Level three font lock for `typescript-mode'.")
1875
1876 (defun typescript--flyspell-mode-predicate ()
1877   "A custom predicate to help `flyspell-prog-mode' determine whether a word should be checked."
1878   ;; We depend on fontification for our results. font-lock-ensure is defined on
1879   ;; Emacs 25 and over. Earlier versions use font-lock-fontify-buffer.
1880   (if (fboundp 'font-lock-ensure)
1881       (font-lock-ensure)
1882     (font-lock-fontify-buffer))
1883   (and
1884    ;; Check with the default method that flyspell provides.
1885    (flyspell-generic-progmode-verify)
1886
1887    ;;
1888    ;; And eliminate cases specific to our mode we don't want to have
1889    ;; spell-checked.
1890    ;;
1891
1892    ;; Don't check the module names in import statements.
1893    (save-excursion
1894      (not (let* ((parse (syntax-ppss (1- (point))))
1895                  (string-start-pos (and (nth 3 parse)
1896                                         (nth 8 parse))))
1897             (and string-start-pos
1898                  (save-match-data
1899                    ;; Move to back to the start of the string, then past any ws
1900                    ;; and then past any non-ws to see if we have "from" or "import".
1901                    (goto-char string-start-pos)
1902                    (typescript--backward-syntactic-ws)
1903                    (skip-syntax-backward "^-" (point-at-bol))
1904                    (looking-at "from\\|import\\s-"))))))))
1905
1906 (defun typescript--inside-pitem-p (pitem)
1907   "Return whether point is inside the given pitem's header or body."
1908   (typescript--ensure-cache)
1909   (cl-assert (typescript--pitem-h-begin pitem))
1910   (cl-assert (typescript--pitem-paren-depth pitem))
1911
1912   (and (> (point) (typescript--pitem-h-begin pitem))
1913        (or (null (typescript--pitem-b-end pitem))
1914            (> (typescript--pitem-b-end pitem) (point)))))
1915
1916 (defun typescript--parse-state-at-point ()
1917   "Parse the typescript program state at point.
1918 Return a list of `typescript--pitem' instances that apply to point, most
1919 specific first.  In the worst case, the current toplevel instance
1920 will be returned."
1921   (save-excursion
1922     (save-restriction
1923       (widen)
1924       (typescript--ensure-cache)
1925       (let* ((bound (if (eobp) (point) (1+ (point))))
1926              (pstate (or (save-excursion
1927                            (typescript--backward-pstate))
1928                          (list typescript--initial-pitem))))
1929
1930         ;; Loop until we either hit a pitem at BOB or pitem ends after
1931         ;; point (or at point if we're at eob)
1932         (cl-loop for pitem = (car pstate)
1933               until (or (eq (typescript--pitem-type pitem)
1934                             'toplevel)
1935                         (typescript--inside-pitem-p pitem))
1936               do (pop pstate))
1937
1938         pstate))))
1939
1940 (defun typescript--syntactic-context-from-pstate (pstate)
1941   "Return the typescript syntactic context corresponding to PSTATE."
1942   (let ((type (typescript--pitem-type (car pstate))))
1943     (cond ((memq type '(function macro))
1944            type)
1945           ((consp type)
1946            'class)
1947           (t 'toplevel))))
1948
1949 (defun typescript-syntactic-context ()
1950   "Return the typescript syntactic context at point.
1951 When called interatively, also display a message with that
1952 context."
1953   (interactive)
1954   (let* ((syntactic-context (typescript--syntactic-context-from-pstate
1955                              (typescript--parse-state-at-point))))
1956
1957     (when (called-interactively-p 'interactive)
1958       (message "Syntactic context: %s" syntactic-context))
1959
1960    syntactic-context))
1961
1962 (defun typescript--class-decl-matcher (limit)
1963   "Font lock function used by `typescript-mode'.
1964 This performs fontification according to `typescript--class-styles'."
1965   (cl-loop initially (typescript--ensure-cache limit)
1966         while (re-search-forward typescript--quick-match-re limit t)
1967         for orig-end = (match-end 0)
1968         do (goto-char (match-beginning 0))
1969         if (cl-loop for style in typescript--class-styles
1970                  for decl-re = (plist-get style :class-decl)
1971                  if (and (memq (plist-get style :framework)
1972                                typescript-enabled-frameworks)
1973                          (memq (typescript-syntactic-context)
1974                                (plist-get style :contexts))
1975                          decl-re
1976                          (looking-at decl-re))
1977                  do (goto-char (match-end 0))
1978                  and return t)
1979         return t
1980         else do (goto-char orig-end)))
1981
1982 (defconst typescript--font-lock-keywords-4
1983   `(
1984     ;; highlights that override previous levels
1985     ;;
1986
1987     ;; special highlight for `this' keyword
1988     ("\\(this\\)\\."
1989      (1 'typescript-this-face))
1990
1991     (,typescript--access-modifier-re (1 'typescript-access-modifier-face))
1992     (,typescript--basic-type-re (1 'typescript-primitive-face))
1993
1994     ;; generics support
1995     ,(list
1996       (concat typescript--name-re "\\s-*" "<\\s-*" typescript--name-start-re)
1997       (list (concat "\\(" typescript--name-re "\\)\\(\\s-*>[^<]*\\)?")
1998             '(backward-char)
1999             '(end-of-line)
2000             '(1 font-lock-type-face)))
2001
2002     ;; type-highlighting in variable/parameter declarations
2003     ;; supports a small variety of common declarations:
2004     ;; - let a: SomeType;
2005     ;; - private b: SomeType;
2006     ;; - private someFunc(var: SomeType) {
2007     ;; - private array: SomeType[]
2008     ;; - private generic: SomeType<Foo>
2009     ;; - private genericArray: SomeType<Foo>[]
2010     ;; - function testFunc(): SomeType<> {
2011     ;; TODO: namespaced classes!
2012     ,(list
2013       (concat ":\\s-\\(" typescript--type-name-re "\\)\\(<" typescript--type-name-re ">\\)?\\(\[\]\\)?\\([,;]\\)?\\s-*{?")
2014       '(1 'font-lock-type-face))
2015
2016     ;; type-casts
2017     ,(list
2018       (concat "<\\(" typescript--type-name-re "\\)>")
2019       '(1 'font-lock-type-face))
2020
2021     ;; highlights that append to previous levels
2022     ;;
2023     ,@typescript--font-lock-keywords-3
2024
2025     (,typescript--decorator-re (1 font-lock-function-name-face))
2026     (,typescript--function-call-re (1 font-lock-function-name-face))
2027     (,typescript--builtin-re (1 font-lock-type-face))
2028
2029     ;; arrow function
2030     ("\\(=>\\)"
2031      (1 font-lock-keyword-face)))
2032   "Level four font lock for `typescript-mode'.")
2033
2034 (defconst typescript--font-lock-keywords
2035   '(typescript--font-lock-keywords-4 typescript--font-lock-keywords-1
2036                                    typescript--font-lock-keywords-2
2037                                    typescript--font-lock-keywords-3
2038                                    typescript--font-lock-keywords-4)
2039   "Font lock keywords for `typescript-mode'.  See `font-lock-keywords'.")
2040
2041 ;;; Propertize
2042
2043 ;;
2044 ;; The propertize code was adapted from:
2045 ;; https://github.com/emacs-mirror/emacs/blob/489d6466372f488adc53897435fff290394b62f7/lisp/progmodes/js.el
2046 ;;
2047
2048 (defconst typescript--syntax-propertize-regexp-regexp
2049   (rx
2050    ;; Start of regexp.
2051    "/"
2052    (0+ (or
2053         ;; Match characters outside of a character class.
2054         (not (any ?\[ ?/ ?\\))
2055         ;; Match backslash quoted characters.
2056         (and "\\" not-newline)
2057         ;; Match character class.
2058         (and
2059          "["
2060          (0+ (or
2061               (not (any ?\] ?\\))
2062               (and "\\" not-newline)))
2063          "]")))
2064    (group (zero-or-one "/")))
2065   "Regular expression matching a JavaScript regexp literal.")
2066
2067 (defun typescript-syntax-propertize-regexp (end)
2068   (let ((ppss (syntax-ppss)))
2069     (when (eq (nth 3 ppss) ?/)
2070       ;; A /.../ regexp.
2071       (goto-char (nth 8 ppss))
2072       (when (looking-at typescript--syntax-propertize-regexp-regexp)
2073         ;; Don't touch text after END.
2074         (when (> end (match-end 1))
2075           (setq end (match-end 1)))
2076         (put-text-property (match-beginning 1) end
2077                            'syntax-table (string-to-syntax "\"/"))
2078         (goto-char end)))))
2079
2080 (defun typescript-syntax-propertize (start end)
2081   ;; JavaScript allows immediate regular expression objects, written /.../.
2082   (funcall
2083    (syntax-propertize-rules
2084     ;; Distinguish /-division from /-regexp chars (and from /-comment-starter).
2085     ;; FIXME: Allow regexps after infix ops like + ...
2086     ;; https://developer.mozilla.org/en/JavaScript/Reference/Operators
2087     ;; We can probably just add +, -, <, >, %, ^, ~, ?, : at which
2088     ;; point I think only * and / would be missing which could also be added,
2089     ;; but need care to avoid affecting the // and */ comment markers.
2090     ("\\(?:^\\|[=([{,:;|&!]\\|\\_<return\\_>\\)\\(?:[ \t]\\)*\\(/\\)[^/*]"
2091      (1 (ignore
2092          (forward-char -1)
2093          (when (or (not (memq (char-after (match-beginning 0)) '(?\s ?\t)))
2094                    ;; If the / is at the beginning of line, we have to check
2095                    ;; the end of the previous text.
2096                    (save-excursion
2097                      (goto-char (match-beginning 0))
2098                      (forward-comment (- (point)))
2099                      (memq (char-before)
2100                            (eval-when-compile (append "=({[,:;" '(nil))))))
2101            (put-text-property (match-beginning 1) (match-end 1)
2102                               'syntax-table (string-to-syntax "\"/"))
2103            (typescript-syntax-propertize-regexp end)))))
2104     ;; Hash-bang at beginning of buffer.
2105     ("\\`\\(#\\)!" (1 "< b")))
2106    start end))
2107
2108 ;;; Indentation
2109
2110 (defconst typescript--possibly-braceless-keyword-re
2111   (typescript--regexp-opt-symbol
2112    '("catch" "do" "else" "finally" "for" "if" "try" "while" "with"))
2113   "Regexp matching keywords optionally followed by an opening brace.")
2114
2115 (defconst typescript--indent-keyword-re
2116   (typescript--regexp-opt-symbol '("in" "instanceof"))
2117   "Regexp matching keywords that affect indentation of continued expressions.")
2118
2119 (defconst typescript--indent-operator-re
2120   (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|" typescript--indent-keyword-re)
2121   "Regexp matching operators that affect indentation of continued expressions.")
2122
2123 ;;
2124 ;; We purposely do not allow the plus symbol as a prefix here, as this
2125 ;; regex is used to check number literal in type annotations, and TS
2126 ;; does not allow to use a plus symbol to prefix numbers there: you
2127 ;; can use 1, but not +1 in a type annotation.
2128 ;;
2129 ;; This is meant to match NaN, floats, decimals, the two infinities
2130 ;; and numbers recorded in binary, octal and hex.
2131 ;;
2132 ;; This regular expression was derived from:
2133 ;; https://stackoverflow.com/a/30987109/
2134 ;;
2135 (defconst typescript--number-literal-re
2136   "\\(?:NaN\\|-?\\(?:0[Bb][01]+\\|0[Oo][0-7]+\\|0[Xx][0-9a-fA-F]+\\|Infinity\\|\\(?:[[:digit:]]*\\.[[:digit:]]+\\|[[:digit:]]+\\)\\(?:[Ee][+-]?[[:digit:]]+\\)?\\)\\)"
2137   "Regexp that matches number literals.")
2138
2139 (defconst typescript--reserved-start-keywords
2140   '("const" "export" "function" "let" "var")
2141   "These keywords cannot be variable or type names and start a new sentence.
2142 Note that the \"import\" keyword can be a type import since TS2.9, so it might
2143 not start a sentence!")
2144
2145 (defconst typescript--reserved-start-keywords-re
2146   (typescript--regexp-opt-symbol '("const" "export" "function" "let" "var"))
2147   "A regular expression matching `typescript--reserved-start-keywords'.")
2148
2149 (defconst typescript--type-vs-ternary-re
2150   (concat "[?]\\|" (typescript--regexp-opt-symbol
2151                     (append typescript--reserved-start-keywords
2152                             '("as" "class" "interface" "private" "public" "readonly"))))
2153   "Keywords/Symbols that help tell apart colon for types vs ternary operators.")
2154
2155 (defun typescript--search-backward-matching-angle-bracket-inner (depth)
2156   "Auxiliary function for `typescript--search-backward-matching-angle-bracket'.
2157 DEPTH indicates how nested we think we are: it increases when we cross closing
2158 brackets, and decreases when we cross opening brackets."
2159   ;; We look backwards for a "<" that would correspond to the ">" we started
2160   ;; from.  However, there is no guarantee that it exists, since our ">" could
2161   ;; be a greater-than operation.  Some symbols will make it clear that we are
2162   ;; *not* in a type annotation, so we can return nil.  Otherwise, we keep
2163   ;; looking for the matching one.
2164   (or (<= depth 0)
2165       (and
2166        ;; If we cross over a reserved start keyword, we abandon hope of finding
2167        ;; a matching angle bracket.  This prevents extreme recursion depths.
2168        (typescript--re-search-backward (concat "[<>]\\|" typescript--reserved-start-keywords-re) nil t)
2169        (cl-case (char-after)
2170          (?< (typescript--search-backward-matching-angle-bracket-inner (- depth 1)))
2171          (?> (typescript--search-backward-matching-angle-bracket-inner (+ depth 1)))))))
2172
2173 (defun typescript--search-backward-matching-angle-bracket ()
2174   "Search for matching \"<\" preceding a starting \">\".
2175 DEPTH indicates how nested we think we are.  Assumes the starting position is
2176 right before the closing \">\".  Returns nil when a match was not found,
2177 otherwise returns t and the current position is right before the matching
2178 \"<\"."
2179   (typescript--search-backward-matching-angle-bracket-inner 1))
2180
2181 (defun typescript--re-search-backward-ignoring-angle-brackets ()
2182   "Search backwards, jumping over text within angle brackets.
2183 Searches specifically for any of \"=\", \"}\", and \"type\"."
2184   (and
2185    (typescript--re-search-backward "[>=}]\\|\\_<type\\_>" nil t)
2186    (or (not (looking-at ">"))
2187        (and
2188         (typescript--search-backward-matching-angle-bracket)
2189         (typescript--re-search-backward-ignoring-angle-brackets)))))
2190
2191 (defun typescript--looking-at-operator-p ()
2192   "Return non-nil if point is on a typescript operator, other than a comma."
2193   (save-match-data
2194     (and (looking-at typescript--indent-operator-re)
2195          (or (not (looking-at ":"))
2196              (save-excursion
2197                (backward-sexp)
2198                (and
2199                 (typescript--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
2200                 (looking-at "?"))))
2201          ;; Do not identify forward slashes appearing in a "list" as
2202          ;; an operator. The lists are: arrays, or lists of
2203          ;; arguments. In this context, they must be part of regular
2204          ;; expressions, and not math operators.
2205          (not (and (looking-at "/")
2206                    (save-excursion
2207                      (typescript--backward-syntactic-ws)
2208                      (memq (char-before) '(?, ?\[ ?\()))))
2209          ;; Do not identify methods, or fields, that are named "in" or
2210          ;; "instanceof" as being operator keywords.
2211          (not (and
2212                (looking-at typescript--indent-keyword-re)
2213                (save-excursion
2214                  (typescript--backward-syntactic-ws)
2215                  (memq (char-before) '(?, ?{ ?} ?\;)))))
2216          ;; Do not identify the symbol > if it is likely part of a type argument
2217          ;; T<A>, but identify it if it is likely a greater-than symbol. This is
2218          ;; a hard problem in the absence of semicolons, see:
2219          ;; https://github.com/ananthakumaran/typescript.el/issues/81
2220          (not (and
2221                (looking-at ">")
2222                (save-excursion
2223                  (and
2224                   (typescript--search-backward-matching-angle-bracket)
2225                   ;; If we made it here, we found a candidate matching opening
2226                   ;; angle bracket. We still need to guess whether it actually
2227                   ;; is one, and not a spurious less-than operator!
2228
2229                   ;; Look backwards for the first of:
2230                   ;; - one of the symbols: = :
2231                   ;; - or a TypeScript keyword
2232                   ;; Depending on what comes first, we can make an educated
2233                   ;; guess on the nature of our ">" of interest.
2234                   (typescript--re-search-backward (concat "[=:]\\|" typescript--keyword-re) nil t)
2235                   (or
2236                    ;; If the previous keyword is "as", definitely a type.
2237                    (looking-at "\\_<as\\_>")
2238                    ;; Same goes for type imports.
2239                    (looking-at "\\_<import\\_>")
2240                    ;; A colon could be either a type symbol, or a ternary
2241                    ;; operator, try to guess which.
2242                    (and (looking-at ":")
2243                         (typescript--re-search-backward typescript--type-vs-ternary-re nil t)
2244                         (not (looking-at "?")))
2245                    ;; This final check lets us distinguish between a
2246                    ;; 2-argument type "t < a , b > ..." and a use of the ","
2247                    ;; operator between two comparisons "t < a , b > ...".
2248                    ;; Looking back a little more lets us guess.
2249                    (and (looking-at "=")
2250                         (typescript--re-search-backward-ignoring-angle-brackets)
2251                         (looking-at "\\_<type\\_>")))))))
2252          (not (and
2253                (looking-at "*")
2254                ;; Generator method (possibly using computed property).
2255                (looking-at (concat "\\* *\\(?:\\[\\|" typescript--name-re
2256                                    " *(\\)"))
2257                (save-excursion
2258                  (typescript--backward-syntactic-ws)
2259                  ;; We might misindent some expressions that would
2260                  ;; return NaN anyway.  Shouldn't be a problem.
2261                  (memq (char-before) '(?, ?} ?{ ?\;))))))))
2262
2263
2264 (defun typescript--continued-expression-p ()
2265   "Return non-nil if the current line continues an expression."
2266   (save-excursion
2267     (back-to-indentation)
2268     (let ((list-start (nth 1 (syntax-ppss))))
2269       (and
2270        ;; This not clause is there to eliminate degenerate cases where we have
2271        ;; something that looks like a continued expression but we are in fact at
2272        ;; the beginning of the expression. Example: in `if (a) { .q(1)` when the
2273        ;; point is on the dot, the expression that follows looks like a member
2274        ;; expression but the object on which it is a member is missing. If we
2275        ;; naively treat this as a continued expression, we run into trouble
2276        ;; later. (An infinite loop.)
2277        (not (and list-start
2278                  (save-excursion
2279                    (typescript--backward-syntactic-ws)
2280                    (backward-char)
2281                    (eq (point) list-start))))
2282        ;; Don't identify the spread syntax or rest operator as a "continuation".
2283        (not (looking-at "\\.\\.\\."))
2284        (or (typescript--looking-at-operator-p)
2285            (and (progn
2286                   (typescript--backward-syntactic-ws)
2287                   (or (bobp) (backward-char))
2288                   (and (> (point) (point-min))
2289                        (save-excursion (backward-char) (not (looking-at "[/*]/")))
2290                        (typescript--looking-at-operator-p)
2291                        (and (progn (backward-char)
2292                                    (not (looking-at "++\\|--\\|/[/*]"))))))))))))
2293
2294 (cl-defun typescript--compute-member-expression-indent ()
2295   "Determine the indent of a member expression.
2296
2297 This function must be called with point located at the dot that
2298 starts the member expression.
2299 "
2300   ;; Find the line that has the object from which we are getting thismember.
2301   ;; And set an indent relative to that.
2302   (while (looking-at "\\.")
2303     (typescript--backward-syntactic-ws)
2304     (while (eq (char-before) ?\;)
2305       (backward-char))
2306     (while (memq (char-before) '(?\] ?} ?\) ?>))
2307       (if (not (eq (char-before) ?>))
2308           (backward-list)
2309         (backward-char)
2310         (typescript--backward-over-generic-parameter-list))
2311       (typescript--backward-syntactic-ws))
2312     (if (looking-back typescript--dotted-name-re nil)
2313         (back-to-indentation)
2314       (typescript--forward-syntactic-ws)))
2315   (+ (current-column) typescript-indent-level))
2316
2317 (defun typescript--end-of-do-while-loop-p ()
2318   "Return non-nil if point is on the \"while\" of a do-while statement.
2319 Otherwise, return nil.  A braceless do-while statement spanning
2320 several lines requires that the start of the loop is indented to
2321 the same column as the current line."
2322   (interactive)
2323   (save-excursion
2324     (save-match-data
2325       (when (looking-at "\\s-*\\_<while\\_>")
2326         (if (save-excursion
2327               (skip-chars-backward "[ \t\n]*}")
2328               (looking-at "[ \t\n]*}"))
2329             (save-excursion
2330               (backward-list) (forward-symbol -1) (looking-at "\\_<do\\_>"))
2331           (typescript--re-search-backward "\\_<do\\_>" (point-at-bol) t)
2332           (or (looking-at "\\_<do\\_>")
2333               (let ((saved-indent (current-indentation)))
2334                 (while (and (typescript--re-search-backward "^\\s-*\\_<" nil t)
2335                             (/= (current-indentation) saved-indent)))
2336                 (and (looking-at "\\s-*\\_<do\\_>")
2337                      (not (typescript--re-search-forward
2338                            "\\_<while\\_>" (point-at-eol) t))
2339                      (= (current-indentation) saved-indent)))))))))
2340
2341
2342 (defun typescript--ctrl-statement-indentation ()
2343   "Helper function for `typescript--proper-indentation'.
2344 Return the proper indentation of the current line if it starts
2345 the body of a control statement without braces; otherwise, return
2346 nil."
2347   (save-excursion
2348     (back-to-indentation)
2349     (when (save-excursion
2350             (and (not (eq (point-at-bol) (point-min)))
2351                  (not (looking-at "[{]"))
2352                  (progn
2353                    (typescript--re-search-backward "[[:graph:]]" nil t)
2354                    (or (eobp) (forward-char))
2355                    (when (= (char-before) ?\)) (backward-list))
2356                    (skip-syntax-backward " ")
2357                    (skip-syntax-backward "w_")
2358                    (and
2359                     (looking-at typescript--possibly-braceless-keyword-re)
2360                     ;; If preceded by period, it's a method call.
2361                     (not (= (char-before) ?.))))
2362                  (not (typescript--end-of-do-while-loop-p))))
2363       (save-excursion
2364         (goto-char (match-beginning 0))
2365         (+ (current-indentation) typescript-indent-level)))))
2366
2367 (defun typescript--get-c-offset (symbol anchor)
2368   (let ((c-offsets-alist
2369          (list (cons 'c typescript-comment-lineup-func))))
2370     (c-get-syntactic-indentation (list (cons symbol anchor)))))
2371
2372 (defun typescript--backward-over-generic-parameter-list ()
2373   "Search backward for the start of a generic's parameter list and move to it.
2374
2375 This is a utility function for
2376 `typescript--backward-to-parameter-list'.
2377
2378 This function must be called with the point placed on the final >
2379 of the generic's parameter list.  It will scan backwards to find
2380 the start.  If successful, it will move the point to the start of
2381 the list.  If not, it does not move the point.
2382
2383 Returns nil on failure, or the position to which the point was
2384 moved on success."
2385   (when (eq (char-after) ?>)
2386     (let ((depth 1))
2387       (cl-loop named search-loop
2388             while (> depth 0)
2389             do (progn
2390                  (unless (re-search-backward "[<>]" nil t)
2391                    (cl-return-from search-loop nil))
2392                  (cond
2393                   ((looking-at ">")
2394                    (unless (eq (char-before) ?=)
2395                      (setq depth (1+ depth))))
2396                   ((looking-at "<") (setq depth (1- depth)))))
2397             finally return (point)))))
2398
2399 (defun typescript--backward-to-parameter-list ()
2400   "Search backward for the end of a parameter list and move to it.
2401
2402 This is a utility function for `typescript--proper-indentation'.
2403
2404 This function must be called with the point placed before an
2405 opening curly brace.  It will try to skip over the type
2406 annotation that would mark the return value of a function and
2407 move to the end of the parameter list.  If it is unsuccessful, it
2408 does not move the point. \"Unsuccessful\" here also means that
2409 the position at which we started did not in fact mark the
2410 beginning of a function. The curly brace belonged to some other
2411 syntactic construct than a function.
2412
2413 Returns nil on failure, or the position to which the point was
2414 moved on success."
2415   (let ((location
2416          (or
2417           ;; This handles the case of a function with return type annotation.
2418           (save-excursion
2419             (cl-loop named search-loop
2420                   do
2421                   (typescript--backward-syntactic-ws)
2422                   ;; Check whether we are at "):".
2423                   (when (and (eq (char-before) ?\:)
2424                              (progn
2425                                (backward-char)
2426                                (skip-syntax-backward " ")
2427                                (eq (char-before) ?\))))
2428                     ;; Success! This the end of the parameter list.
2429                     (cl-return-from search-loop (point)))
2430                   ;; If we recognize a structure that belongs in a return type annotation,
2431                   ;; skip back over it, or fail.
2432                   (cond
2433                    ;; Arrow of a function definition, or typeguard (eg. foo is SomeClass)
2434                    ((looking-back "=>\\|is" (- (point) 2))
2435                     (backward-char 2))
2436                    ;; End of the parameters list of a generic.
2437                    ((eq (char-before) ?>)
2438                     (backward-char)
2439                     (typescript--backward-over-generic-parameter-list))
2440                    ;; Union of types, or a dot in a dotted name.
2441                    ((memq (char-before) '(?| ?.))
2442                     (backward-char))
2443                    ((or
2444                      ;; End-delimiter of a delimited construct, for constructs
2445                      ;; not handled above.
2446                      (memq (char-before) '(?\) ?} ?\" ?\]))
2447                      ;; This is also dealing with dotted names. This may come
2448                      ;; into play if a jump back moves over an entire dotted
2449                      ;; name at once.
2450                      ;;
2451                      ;; The earlier test for dotted names comes into play if the
2452                      ;; logic moves over one part of a dotted name at a time (which
2453                      ;; is what `backward-sexp` normally does).
2454                      (and (looking-back typescript--dotted-name-re nil)
2455                           ;; We don't want the loop to walk over constructs like switch (...) or for (...), etc.
2456                           (not (save-excursion
2457                                  (backward-word)
2458                                  (looking-at "\\_<\\(switch\\|if\\|while\\|until\\|for\\)\\_>\\(?:\\s-\\|\n\\)*(")))))
2459                     (condition-case nil
2460                         (backward-sexp)
2461                       (scan-error (cl-return-from search-loop nil))))
2462                    ((looking-back typescript--number-literal-re
2463                                   ;; We limit the search back to the previous space or end of line (if possible)
2464                                   ;; to prevent the search from going over the whole buffer.
2465                                   (save-excursion (re-search-backward "\\(?:\\s-\\|\n\\)" nil t)) t)
2466                     (goto-char (match-beginning 0)))
2467                    ;; Otherwise, we failed to find a location.
2468                    (t
2469                     (cl-return-from search-loop nil)))))
2470           ;; This handles the case of a function without return type annotation.
2471           (progn
2472             (typescript--backward-syntactic-ws)
2473             (when (eq (char-before) ?\))
2474               (point))))))
2475     (when location
2476       (goto-char location))))
2477
2478 (defun typescript--proper-indentation (parse-status)
2479   "Return the proper indentation for the current line."
2480   (save-excursion
2481     (back-to-indentation)
2482     (let ((member-expr-p (looking-at "\\.")))
2483       (cond ((nth 4 parse-status) ;; Inside a comment.
2484              (typescript--get-c-offset 'c (nth 8 parse-status)))
2485             ((nth 8 parse-status) 0) ;; Inside a string.
2486             ((typescript--ctrl-statement-indentation)) ;; Control statements.
2487             ((eq (char-after) ?#) 0) ;; Looking at a pragma.
2488             ;; Inside a list of things. Note that in the TS contents, the curly braces
2489             ;; marking code blocks are "list of things".
2490             ((nth 1 parse-status)
2491              (let ((indent-start (point))
2492                    (same-indent-p (looking-at "[]})]"))
2493                    (switch-keyword-p (looking-at "\\_<default\\_>\\|\\_<case\\_>[^:]"))
2494                    (continued-expr-p (typescript--continued-expression-p))
2495                    (list-start (nth 1 parse-status)))
2496                (goto-char list-start)
2497                (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
2498                    (progn
2499                      (skip-syntax-backward " ")
2500                      (cond
2501                       ((or (typescript--backward-to-parameter-list)
2502                            (eq (char-before) ?\)))
2503                        ;; Take the curly brace as marking off the body of a function.
2504                        ;; In that case, we want the code that follows to see the indentation
2505                        ;; that was in effect at the beginning of the function declaration, and thus
2506                        ;; we want to move back over the list of function parameters.
2507                        (condition-case nil
2508                            (backward-list)
2509                          (error nil)))
2510                       ((looking-back "," nil)
2511                        ;; If we get here, we have a comma, spaces and an opening curly brace. (And
2512                        ;; (point) is just after the comma.) We don't want to move from the current position
2513                        ;; so that object literals in parameter lists are properly indented.
2514                        nil)
2515                       (t
2516                        ;; In all other cases, we don't want to move from the curly brace.
2517                        (goto-char list-start)))
2518                      (back-to-indentation)
2519                      (let* ((in-switch-p (unless same-indent-p
2520                                            (looking-at "\\_<switch\\_>")))
2521                             (same-indent-p (or same-indent-p
2522                                                (and switch-keyword-p
2523                                                     in-switch-p)))
2524                             (indent
2525                              (cond (same-indent-p
2526                                     (current-column))
2527                                    (continued-expr-p
2528                                     (if (not member-expr-p)
2529                                         (+ (current-column) (* 2 typescript-indent-level)
2530                                            typescript-expr-indent-offset)
2531                                       (goto-char indent-start)
2532                                       (typescript--compute-member-expression-indent)))
2533                                    (t
2534                                     (+ (current-column) typescript-indent-level)))))
2535                        (if (and in-switch-p typescript-indent-switch-clauses)
2536                            (+ indent typescript-indent-level)
2537                          indent)))
2538                  (unless same-indent-p
2539                    (forward-char)
2540                    (skip-chars-forward " \t"))
2541                  (if continued-expr-p
2542                      (if (not member-expr-p)
2543                          (progn (back-to-indentation)
2544                                 (+ (current-column) typescript-indent-level
2545                                    typescript-expr-indent-offset))
2546                        (goto-char indent-start)
2547                        (typescript--compute-member-expression-indent))
2548                    (current-column)))))
2549
2550             ((typescript--continued-expression-p) ;; Inside a continued expression.
2551              (if member-expr-p
2552                  (typescript--compute-member-expression-indent)
2553                (+ typescript-indent-level typescript-expr-indent-offset)))
2554             (t 0)))))
2555
2556 (defun typescript-indent-line ()
2557   "Indent the current line as typescript."
2558   (interactive)
2559   (save-restriction
2560     (widen)
2561     (let* ((parse-status
2562             (save-excursion (syntax-ppss (point-at-bol))))
2563            (offset (- (current-column) (current-indentation))))
2564       (indent-line-to (typescript--proper-indentation parse-status))
2565       (when (> offset 0) (move-to-column (+ offset (current-indentation)))))))
2566
2567 ;;; Filling
2568
2569 (defun typescript-c-fill-paragraph (&optional justify)
2570   "Fill the paragraph with `c-fill-paragraph'."
2571   (interactive "*P")
2572   ;; Dynamically replace functions using the lexically scoped cl-letf.
2573   ;; See below for more details:
2574   ;; http://endlessparentheses.com/understanding-letf-and-how-it-replaces-flet.html
2575   (cl-letf (((symbol-function 'c-forward-sws)
2576              (lambda  (&optional limit)
2577                (typescript--forward-syntactic-ws limit)))
2578             ((symbol-function 'c-backward-sws)
2579              (lambda  (&optional limit)
2580                (typescript--backward-syntactic-ws limit))))
2581     (let ((fill-paragraph-function 'c-fill-paragraph))
2582       (c-fill-paragraph justify))))
2583
2584 ;; We maintain a cache of semantic information, i.e., the classes and
2585 ;; functions we've encountered so far. In order to avoid having to
2586 ;; re-parse the buffer on every change, we cache the parse state at
2587 ;; each interesting point in the buffer. Each parse state is a
2588 ;; modified copy of the previous one, or in the case of the first
2589 ;; parse state, the empty state.
2590 ;;
2591 ;; The parse state itself is just a stack of typescript--pitem
2592 ;; instances. It starts off containing one element that is never
2593 ;; closed, that is initially typescript--initial-pitem.
2594 ;;
2595
2596
2597 (defun typescript--pitem-format (pitem)
2598   (let ((name (typescript--pitem-name pitem))
2599         (type (typescript--pitem-type pitem)))
2600
2601     (format "name:%S type:%S"
2602             name
2603             (if (atom type)
2604                 type
2605               (plist-get type :name)))))
2606
2607 (defun typescript--make-merged-item (item child name-parts)
2608   "Helper function for `typescript--splice-into-items'.
2609 Return a new item that is the result of merging CHILD into
2610 ITEM.  NAME-PARTS is a list of parts of the name of CHILD
2611 that we haven't consumed yet."
2612   (typescript--debug "typescript--make-merged-item: {%s} into {%s}"
2613                    (typescript--pitem-format child)
2614                    (typescript--pitem-format item))
2615
2616   ;; If the item we're merging into isn't a class, make it into one
2617   (unless (consp (typescript--pitem-type item))
2618     (typescript--debug "typescript--make-merged-item: changing dest into class")
2619     (setq item (make-typescript--pitem
2620                 :children (list item)
2621
2622                 ;; Use the child's class-style if it's available
2623                 :type (if (atom (typescript--pitem-type child))
2624                           typescript--dummy-class-style
2625                   (typescript--pitem-type child))
2626
2627                 :name (typescript--pitem-strname item))))
2628
2629   ;; Now we can merge either a function or a class into a class
2630   (cons (cond
2631          ((cdr name-parts)
2632           (typescript--debug "typescript--make-merged-item: recursing")
2633           ;; if we have more name-parts to go before we get to the
2634           ;; bottom of the class hierarchy, call the merger
2635           ;; recursively
2636           (typescript--splice-into-items (car item) child
2637                                        (cdr name-parts)))
2638
2639          ((atom (typescript--pitem-type child))
2640           (typescript--debug "typescript--make-merged-item: straight merge")
2641           ;; Not merging a class, but something else, so just prepend
2642           ;; it
2643           (cons child (car item)))
2644
2645          (t
2646           ;; Otherwise, merge the new child's items into those
2647           ;; of the new class
2648           (typescript--debug "typescript--make-merged-item: merging class contents")
2649           (append (car child) (car item))))
2650         (cdr item)))
2651
2652 (defun typescript--pitem-strname (pitem)
2653   "Last part of the name of PITEM, as a string or symbol."
2654   (let ((name (typescript--pitem-name pitem)))
2655     (if (consp name)
2656         (car (last name))
2657       name)))
2658
2659 (defun typescript--splice-into-items (items child name-parts)
2660   "Splice CHILD into the `typescript--pitem' ITEMS at NAME-PARTS.
2661 If a class doesn't exist in the tree, create it.  Return
2662 the new items list.  NAME-PARTS is a list of strings given
2663 the broken-down class name of the item to insert."
2664
2665   (let ((top-name (car name-parts))
2666         (item-ptr items)
2667         new-items last-new-item new-cons item)
2668
2669     (typescript--debug "typescript--splice-into-items: name-parts: %S items:%S"
2670              name-parts
2671              (mapcar #'typescript--pitem-name items))
2672
2673     (cl-assert (stringp top-name))
2674     (cl-assert (> (length top-name) 0))
2675
2676     ;; If top-name isn't found in items, then we build a copy of items
2677     ;; and throw it away. But that's okay, since most of the time, we
2678     ;; *will* find an instance.
2679
2680     (while (and item-ptr
2681                 (cond ((equal (typescript--pitem-strname (car item-ptr)) top-name)
2682                        ;; Okay, we found an entry with the right name. Splice
2683                        ;; the merged item into the list...
2684                        (setq new-cons (cons (typescript--make-merged-item
2685                                              (car item-ptr) child
2686                                              name-parts)
2687                                             (cdr item-ptr)))
2688
2689                        (if last-new-item
2690                            (setcdr last-new-item new-cons)
2691                          (setq new-items new-cons))
2692
2693                        ;; ...and terminate the loop
2694                        nil)
2695
2696                       (t
2697                        ;; Otherwise, copy the current cons and move onto the
2698                        ;; text. This is tricky; we keep track of the tail of
2699                        ;; the list that begins with new-items in
2700                        ;; last-new-item.
2701                        (setq new-cons (cons (car item-ptr) nil))
2702                        (if last-new-item
2703                            (setcdr last-new-item new-cons)
2704                          (setq new-items new-cons))
2705                        (setq last-new-item new-cons)
2706
2707                        ;; Go to the next cell in items
2708                        (setq item-ptr (cdr item-ptr))))))
2709
2710     (if item-ptr
2711         ;; Yay! We stopped because we found something, not because
2712         ;; we ran out of items to search. Just return the new
2713         ;; list.
2714         (progn
2715           (typescript--debug "search succeeded: %S" name-parts)
2716           new-items)
2717
2718       ;; We didn't find anything. If the child is a class and we don't
2719       ;; have any classes to drill down into, just push that class;
2720       ;; otherwise, make a fake class and carry on.
2721       (typescript--debug "search failed: %S" name-parts)
2722       (cons (if (cdr name-parts)
2723                 ;; We have name-parts left to process. Make a fake
2724                 ;; class for this particular part...
2725                 (make-typescript--pitem
2726                  ;; ...and recursively digest the rest of the name
2727                  :children (typescript--splice-into-items
2728                             nil child (cdr name-parts))
2729                  :type typescript--dummy-class-style
2730                  :name top-name)
2731
2732               ;; Otherwise, this is the only name we have, so stick
2733               ;; the item on the front of the list
2734               child)
2735             items))))
2736
2737 (defun typescript--pitem-add-child (pitem child)
2738   "Copy `typescript--pitem' PITEM, and push CHILD onto its list of children."
2739   (cl-assert (integerp (typescript--pitem-h-begin child)))
2740   (cl-assert (if (consp (typescript--pitem-name child))
2741               (cl-loop for part in (typescript--pitem-name child)
2742                     always (stringp part))
2743             t))
2744
2745   ;; This trick works because we know (based on our cl-defstructs) that
2746   ;; the child list is always the first element, and so the second
2747   ;; element and beyond can be shared when we make our "copy".
2748   (cons
2749
2750    (let ((name (typescript--pitem-name child))
2751          (type (typescript--pitem-type child)))
2752
2753      (cond ((cdr-safe name) ; true if a list of at least two elements
2754             ;; Use slow path because we need class lookup
2755             (typescript--splice-into-items (car pitem) child name))
2756
2757            ((and (consp type)
2758                  (plist-get type :prototype))
2759
2760             ;; Use slow path because we need class merging. We know
2761             ;; name is a list here because down in
2762             ;; `typescript--ensure-cache', we made sure to only add
2763             ;; class entries with lists for :name
2764             (cl-assert (consp name))
2765             (typescript--splice-into-items (car pitem) child name))
2766
2767            (t
2768             ;; Fast path
2769             (cons child (car pitem)))))
2770
2771    (cdr pitem)))
2772
2773 ;;; compilation-mode support
2774
2775 ;; tsc supports formatting errors in two general ways: plain and
2776 ;; pretty. ("Plain" is our term for "not pretty".) In tsc versions
2777 ;; prior to 2.7, the plain and pretty formats both used the same
2778 ;; format for references into files. `typescript-tsc-error-regexp`
2779 ;; covers both plain and pretty for those versions.
2780 ;;
2781 ;; Version 2.7 changed the pretty format so as to format source code
2782 ;; references differently. This required the introduction of
2783 ;; `typescript-tsc-pretty-error-regexp`. The format of plain error
2784 ;; messages did not change. So from that version onwards,
2785 ;; `typescript-tsc-error-regexp` covers plain error messages and
2786 ;; `typescript-tsc-pretty-error-regexp` covers pretty error messages.
2787
2788 ;; handle plain compiler-errors like the following when doing M-x compile<ret>tsc<ret>
2789 ;;
2790 ;; greeter.ts(24,9): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
2791 ;; greeter.ts(30,12): error TS2339: Property 'indexOf' does not exist on type 'number'.
2792 (defconst typescript-tsc-error-regexp
2793   (concat
2794    "^[[:blank:]]*"
2795    "\\([^(\r\n)]+\\)(\\([0-9]+\\),\\([0-9]+\\)):[[:blank:]]+"
2796    "error [[:alnum:]]+: [^\r\n]+$")
2797   "Regexp to match errors generated by tsc.")
2798
2799 ;; handle pretty compiler-errors like the following when doing M-x compile<ret>tsc<ret>
2800 ;; test.ts:2:7 - error TS2322: Type '2' is not assignable to type 'string'.
2801 (defconst typescript-tsc-pretty-error-regexp
2802   (concat
2803    "^[[:blank:]]*"
2804    "\\([^(\r\n)]+\\):\\([0-9]+\\):\\([0-9]+\\) - [[:blank:]]*"
2805    "error [[:alnum:]]+: [^\r\n]+$")
2806   "Regexp to match errors generated by tsc.")
2807
2808 ;;
2809 ;; Should handle output like:
2810 ;; src/modules/authenticator.ts[1, 83]: ' should be "
2811 ;; (quotemarks) src/modules/authenticator.ts[2, 26]: ' should be "
2812 ;; ERROR: (quotemarks) src/modules/authenticator.ts[2, 26]: ' should be "
2813 ;; WARNING: src/modules/authenticator.ts[2, 26]: ' should be "
2814 ;;
2815 ;; "(quotemarks)" it the rule name. It is produced when using the
2816 ;; "verbose" formatter. The "verbose" formatter is identical to the
2817 ;; default ("prose") formatter, except for the additional rule name.
2818 ;;
2819 ;; "ERROR:" and "WARNING:" are the severity. This was added in tslint
2820 ;; 5.0. Prior versions have no notion of severity and simply omit this
2821 ;; part.
2822 ;;
2823 (defconst typescript-tslint-report-regexp
2824   (concat
2825    "^[[:blank:]]*"
2826    ;; severity ("type" in Emacs' parlance)
2827    "\\(?:\\(?:ERROR\\|\\(WARNING\\)\\):[[:blank:]]+\\)?"
2828    ;; rule name
2829    "\\((.*)[[:blank:]]+\\)?"
2830    ;; filename
2831    "\\([^(\r\n)]+\\)"
2832    "\\["
2833    ;; line
2834    "\\([[:digit:]]+\\)"
2835    ", "
2836    ;; column
2837    "\\([[:digit:]]+\\)"
2838    "\\]: "
2839    ;; message
2840    ".*$")
2841   "Regexp to match reports generated by tslint.")
2842
2843 (defconst typescript-nglint-error-regexp
2844   (concat
2845    ;; severity ("type" in Emacs' parlance)
2846    "ERROR:[[:blank:]]+"
2847
2848    ;; filename
2849    "\\([^(\r\n)]+\\)"
2850    ":"
2851    ;; line
2852    "\\([[:digit:]]+\\)"
2853    ":"
2854    ;; column
2855    "\\([[:digit:]]+\\)"
2856
2857    " - "
2858    ;; message
2859    ".*$"))
2860
2861 (defconst typescript-nglint-warning-regexp
2862   (concat
2863    ;; severity ("type" in Emacs' parlance)
2864    "WARNING:[[:blank:]]+"
2865
2866    ;; filename
2867    "\\([^(\r\n)]+\\)"
2868    ":"
2869    ;; line
2870    "\\([[:digit:]]+\\)"
2871    ":"
2872    ;; column
2873    "\\([[:digit:]]+\\)"
2874
2875    " - "
2876    ;; message
2877    ".*$"))
2878
2879 (dolist
2880     (regexp
2881      `((typescript-tsc
2882         ,typescript-tsc-error-regexp
2883         1 2 3 2)
2884
2885        (typescript-tsc-pretty
2886         ,typescript-tsc-pretty-error-regexp
2887         1 2 3 2)
2888
2889        (typescript-tslint
2890         ,typescript-tslint-report-regexp
2891         3 4 5 (1))
2892
2893        (typescript-nglint-error
2894         ,typescript-nglint-error-regexp
2895         1 2 3 2)
2896
2897        (typescript-nglint-warning
2898         ,typescript-nglint-warning-regexp
2899         1 2 3 1)))
2900   (add-to-list 'compilation-error-regexp-alist-alist regexp)
2901   (add-to-list 'compilation-error-regexp-alist (car regexp)))
2902
2903 ;;; Main Function
2904
2905 ;;;###autoload
2906 (define-derived-mode typescript-mode prog-mode "typescript"
2907   "Major mode for editing typescript.
2908
2909 Key bindings:
2910
2911 \\{typescript-mode-map}"
2912
2913   :group 'typescript
2914   :syntax-table typescript-mode-syntax-table
2915
2916   (setq-local indent-line-function 'typescript-indent-line)
2917   (setq-local beginning-of-defun-function 'typescript-beginning-of-defun)
2918   (setq-local end-of-defun-function 'typescript-end-of-defun)
2919   (setq-local open-paren-in-column-0-is-defun-start nil)
2920   (setq-local font-lock-defaults (list typescript--font-lock-keywords))
2921   (setq-local syntax-propertize-function #'typescript-syntax-propertize)
2922   (setq-local parse-sexp-ignore-comments t)
2923   (setq-local parse-sexp-lookup-properties t)
2924
2925   ;; Comments
2926   (setq-local comment-start "// ")
2927   (setq-local comment-end "")
2928   (setq-local fill-paragraph-function 'typescript-c-fill-paragraph)
2929
2930   ;; Parse cache
2931   (add-hook 'before-change-functions #'typescript--flush-caches t t)
2932
2933   ;; Frameworks
2934   (typescript--update-quick-match-re)
2935
2936   ;; for filling, pretend we're cc-mode
2937   (setq c-comment-prefix-regexp "//+\\|\\**"
2938         c-paragraph-start "$"
2939         c-paragraph-separate "$"
2940         c-block-comment-prefix "* "
2941         c-line-comment-starter "//"
2942         c-comment-start-regexp "/[*/]\\|\\s!"
2943         comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
2944
2945   (setq-local electric-indent-chars
2946               (append "{}():;," electric-indent-chars))
2947   (setq-local electric-layout-rules
2948               '((?\; . after) (?\{ . after) (?\} . before)))
2949
2950   (let ((c-buffer-is-cc-mode t))
2951     ;; FIXME: These are normally set by `c-basic-common-init'.  Should
2952     ;; we call it instead?  (Bug#6071)
2953     (make-local-variable 'paragraph-start)
2954     (make-local-variable 'paragraph-separate)
2955     (make-local-variable 'paragraph-ignore-fill-prefix)
2956     (make-local-variable 'adaptive-fill-mode)
2957     (make-local-variable 'adaptive-fill-regexp)
2958     (c-setup-paragraph-variables))
2959
2960   (add-hook 'post-self-insert-hook
2961             #'typescript--post-self-insert-function)
2962
2963   (setq-local syntax-begin-function #'typescript--syntax-begin-function))
2964
2965 ;; Set our custom predicate for flyspell prog mode
2966 (put 'typescript-mode 'flyspell-mode-predicate
2967      'typescript--flyspell-mode-predicate)
2968
2969 ;;;###autoload
2970 (eval-after-load 'folding
2971   '(when (fboundp 'folding-add-to-marks-list)
2972      (folding-add-to-marks-list 'typescript-mode "// {{{" "// }}}" )))
2973
2974 ;;;###autoload
2975 (add-to-list 'auto-mode-alist '("\\.ts$" . typescript-mode))
2976
2977 (provide 'typescript-mode)
2978
2979 ;;; typescript-mode.el ends here