1 ;;; typescript-mode.el --- Major mode for editing typescript
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.
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.
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.
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 ;; -------------------------------------------------------------------------------------------
23 ;; URL: http://github.com/ananthakumaran/typescript.el
25 ;; Keywords: typescript languages
26 ;; Package-Requires: ((emacs "24.3"))
28 ;; This file is not part of GNU Emacs.
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
35 ;; The modifications to the original javascript.el mode mainly consisted in
36 ;; replacing "javascript" with "typescript"
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
46 ;; XXX: This mode assumes that block comments are not nested inside block
49 ;; Exported names start with "typescript-"; private names start with
59 (require 'newcomment))
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!")
69 (defconst typescript--name-start-re "[a-zA-Z_$]"
70 "Regexp matching the start of a typescript identifier, without grouping.")
72 (defconst typescript--name-re (concat typescript--name-start-re
73 "\\(?:\\s_\\|\\sw\\)*")
74 "Regexp matching a typescript identifier, without grouping.")
76 (defconst typescript--objfield-re (concat typescript--name-re ":")
77 "Regexp matching the start of a typescript object field.")
79 (defconst typescript--dotted-name-re
80 (concat typescript--name-re "\\(?:\\." typescript--name-re "\\)*")
81 "Regexp matching a dot-separated sequence of typescript names.")
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.")
90 (defconst typescript--plain-class-re
91 (concat "^\\s-*\\(" typescript--dotted-name-re "\\)\\.prototype"
93 "Regexp matching a typescript explicit prototype \"class\" declaration.
94 An example of this is \"Class.prototype = { method1: ...}\".")
96 (defconst typescript--module-declaration-re
97 "^\\s-*\\(?:declare\\|\\(?:export\\(?:\\s-+default\\)?\\)\\)?"
98 "Regexp matching ambient declaration modifier or export declaration.")
100 ;; var NewClass = BaseClass.extend(
101 (defconst typescript--mp-class-decl-re
102 (concat "^\\s-*var\\s-+"
103 "\\(" typescript--name-re "\\)"
105 "\\(" typescript--dotted-name-re
106 "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$"))
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()"))
114 (defconst typescript--prototype-objextend-class-decl-re-1
115 (concat "^\\s-*Object\\.extend\\s-*("
116 "\\(" typescript--dotted-name-re "\\)"
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-*\("))
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-*\\)?{?"))
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 "\\)"))
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).")
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).")
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.")
154 (defconst typescript--dummy-class-style
155 '(:name "[Automatically Generated Class]"))
157 (defconst typescript--class-styles
159 :class-decl ,typescript--plain-class-re
162 :framework typescript)
165 :class-decl ,typescript--mochikit-class-re
170 (:name "Prototype (Obsolete)"
171 :class-decl ,typescript--prototype-obsolete-class-decl-re
173 :framework prototype)
175 (:name "Prototype (Modern)"
176 :class-decl ,typescript--prototype-class-decl-re
178 :framework prototype)
180 (:name "Prototype (Object.extend)"
181 :class-decl ,typescript--prototype-objextend-class-decl-re-1
184 :framework prototype)
186 (:name "Prototype (Object.extend) 2"
187 :class-decl ,typescript--prototype-objextend-class-decl-re-2
190 :framework prototype)
193 :class-decl ,typescript--dojo-class-decl-re
197 (:name "ExtTYPESCRIPT (style 1)"
198 :class-decl ,typescript--exttypescript-class-decl-re-1
201 :framework exttypescript)
203 (:name "ExtTYPESCRIPT (style 2)"
204 :class-decl ,typescript--exttypescript-class-decl-re-2
206 :framework exttypescript)
208 (:name "Merrill Press"
209 :class-decl ,typescript--mp-class-decl-re
211 :framework merrillpress))
213 "List of typescript class definition styles.
215 A class definition style is a plist with the following keys:
217 :name is a human-readable name of the class type
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.
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.
230 If :strip-prototype is present and non-nil, then if the class
231 name as matched contains")
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.")
242 (defconst typescript--function-heading-1-re
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.")
249 (defconst typescript--function-heading-2-re
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.")
255 (defconst typescript--function-heading-3-re
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.")
262 (defun typescript--regexp-opt-symbol (list)
263 "Like `regexp-opt', but surround the result with `\\\\_<' and `\\\\_>'."
264 (concat "\\_<" (regexp-opt list t) "\\_>"))
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.")
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.")
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.")
289 (defconst typescript--decorator-re
290 (concat "\\(@" typescript--name-re "\\)"))
292 (defconst typescript--constant-re
293 (typescript--regexp-opt-symbol '("false" "null" "undefined"
295 "true" "arguments" "this"))
296 "Regular expression matching any future reserved words in typescript.")
298 (defconst typescript--builtin-re
299 (typescript--regexp-opt-symbol
301 "Regular expression matching builtins.")
303 (defconst typescript--function-call-re "\\(\\(?:\\w\\|\\s_\\)+\\)\\(<.+>\\)?\s*("
304 "Regular expression matching function calls.")
306 (defconst typescript--font-lock-keywords-1
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'.")
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)
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'.")
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:
330 ;; function foo(a,b,c) { return 42; }
333 ;; +- h-begin +- h-end +- b-end
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.)
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
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.
350 ;; The three points obey the following relationship:
352 ;; h-begin < h-end <= b-end
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.
357 ;; The text property for h-end, typescript--pstate, is actually a list
358 ;; of all typescript--pitem instances open after the marked character.
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.)
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
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.)
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.
383 ;; List of children inside this pitem's body
384 (children nil :read-only t)
386 ;; When we reach this paren depth after h-end, the pitem ends
387 (paren-depth nil :read-only t)
389 ;; Symbol or class-style plist if this is a class
390 (type nil :read-only t)
393 (h-begin nil :read-only t)
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)
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.
404 ;; The field value is either a number (buffer location) or nil if
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.)
415 ;; The pitem we start parsing with.
416 (defconst typescript--initial-pitem
417 (make-typescript--pitem
418 :paren-depth most-negative-fixnum
421 ;; When we say "jsdoc" here, we mean "jsdoc 3". There exist multiple dialects of
422 ;; "jsdoc documentation".
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
428 "Matches a typedoc link.")
430 (defconst typescript-typedoc-literal-markup-regexp
432 "Matches a typedoc keyword markup.")
434 (defconst typescript-jsdoc-before-tag-regexp
435 "\\(?:^\\s-*\\*+\\|/\\*\\*\\)\\s-*"
436 "Matches everything we allow before the @ of a jsdoc tag.")
438 ;; This was taken from js2-mode.
439 (defconst typescript-jsdoc-param-tag-regexp
440 (concat typescript-jsdoc-before-tag-regexp
450 "\\s-*\\({[^}]+}\\)?" ; optional type
451 "\\s-*\\[?\\([[:alnum:]_$\.]+\\)?\\]?" ; name
453 "Matches jsdoc tags with optional type and optional param name.")
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
476 "\\)\\s-*\\({[^}]+}\\)?")
477 "Matches jsdoc tags with optional type.")
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
520 "\\)\\s-+\\([^ \t]+\\)")
521 "Matches jsdoc tags with a single argument.")
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
588 "Matches empty jsdoc tags.")
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.")
598 (defface typescript-jsdoc-tag
599 '((t :foreground "SlateGray"))
600 "Face used to highlight @whatever tags in jsdoc comments."
603 (defface typescript-jsdoc-type
604 '((t :foreground "SteelBlue"))
605 "Face used to highlight {FooBar} types in jsdoc comments."
608 (defface typescript-jsdoc-value
609 '((t :foreground "gold4"))
610 "Face used to highlight tag values in jsdoc comments."
613 (defface typescript-access-modifier-face
614 '((t (:inherit font-lock-keyword-face)))
615 "Face used to highlight access modifiers."
618 (defface typescript-this-face
619 '((t (:inherit font-lock-keyword-face)))
620 "Face used to highlight 'this' keyword."
623 (defface typescript-primitive-face
624 '((t (:inherit font-lock-keyword-face)))
625 "Face used to highlight builtin types."
628 ;;; User Customization
630 (defgroup typescript nil
631 "Customization variables for typescript mode."
635 (defcustom typescript-indent-level 4
636 "Number of spaces for each indentation step in `typescript-mode'."
640 ;;;###autoload(put 'typescript-indent-level 'safe-local-variable #'integerp)
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'."
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'."
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
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."
669 (defcustom typescript-comment-lineup-func #'c-lineup-C-comments
670 "Lineup function for `cc-mode-style', for C comments in `typescript-mode'."
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)
680 typescript--available-frameworks))
683 (defcustom typescript-mode-hook nil
684 "*Hook called by `typescript-mode'."
688 (defcustom typescript-autoconvert-to-template-flag nil
689 "Non-nil means automatically convert plain strings to templates.
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."
698 (defun typescript-convert-to-template ()
699 "Convert the string at point to a template string."
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.
714 (when (typescript--move-to-end-of-plain-string)
715 (let ((end-start (or (nth 8 (syntax-ppss)) -1)))
717 (when (= end-start string-start)
720 (goto-char string-start)
724 (defun typescript-autoconvert-to-template ()
725 "Automatically convert a plain string to a teplate string, if needed.
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."
737 (when (and (memq (char-after) '(?' ?\"))
738 (not (eq (char-before) ?\\)))
739 (let* ((string-start (nth 8 (syntax-ppss))))
740 (when (and string-start
742 (re-search-backward "\\${.*?}" string-start t)))
743 (typescript-convert-to-template)))))))
747 (defvar typescript-mode-map
748 (let ((keymap (make-sparse-keymap)))
749 (define-key keymap (kbd "C-c '") #'typescript-convert-to-template)
751 "Keymap for `typescript-mode'.")
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)))
760 ;;; Syntax table and parsing
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)
768 "Syntax table for `typescript-mode'.")
770 (defvar typescript--quick-match-re nil
771 "Autogenerated regexp used by `typescript-mode' to match buffer constructs.")
773 (defvar typescript--quick-match-re-func nil
774 "Autogenerated regexp used by `typescript-mode' to match constructs and functions.")
776 (make-variable-buffer-local 'typescript--quick-match-re)
777 (make-variable-buffer-local 'typescript--quick-match-re-func)
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)
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)
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)
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)))))
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
804 (setq list (typescript--flatten-list list))
806 (concat prefix (mapconcat #'identity list separator) suffix)))
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
812 (setq typescript--quick-match-re
813 (typescript--maybe-join
814 "^[ \t]*\\(?:" "\\|" "\\)"
817 "#define[ \t]+[a-zA-Z_]"
819 (when (memq 'exttypescript typescript-enabled-frameworks)
822 (when (memq 'prototype typescript-enabled-frameworks)
825 ;; var mumble = THING (
826 (typescript--maybe-join
827 "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
831 (when (memq 'prototype typescript-enabled-frameworks)
834 (when (memq 'exttypescript typescript-enabled-frameworks)
837 (when (memq 'merrillpress typescript-enabled-frameworks)
838 "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))
840 (when (memq 'dojo typescript-enabled-frameworks)
841 "dojo\\.declare[ \t]*\(")
843 (when (memq 'mochikit typescript-enabled-frameworks)
844 "MochiKit\\.Base\\.update[ \t]*\(")
846 ;; mumble.prototypeTHING
847 (typescript--maybe-join
848 "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"
850 (when (memq 'typescript typescript-enabled-frameworks)
851 '( ;; foo.prototype.bar = function(
852 "\\.[a-zA-Z_$0-9]+[ \t]*=[ \t]*function[ \t]*\("
854 ;; mumble.prototype = {
855 "[ \t]*=[ \t]*{")))))
857 (setq typescript--quick-match-re-func
858 (concat "function\\|" typescript--quick-match-re)))
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)))
868 (goto-char (next-single-property-change
869 (point) propname nil (point-max)))
871 (setq next-value (get-text-property (point) propname))
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
882 (let ((prev-value (get-text-property (1- (point)) propname)))
886 (goto-char (previous-single-property-change
887 (point) propname nil (point-min)))
891 (setq prev-value (get-text-property (point) propname))))
895 (defsubst typescript--forward-pstate ()
896 (typescript--forward-text-property 'typescript--pstate))
898 (defsubst typescript--backward-pstate ()
899 (typescript--backward-text-property 'typescript--pstate))
901 (defun typescript--pitem-goto-h-end (pitem)
902 (goto-char (typescript--pitem-h-begin pitem))
903 (typescript--forward-pstate))
905 (defun typescript--re-search-forward-inner (regexp &optional bound count)
906 "Helper function for `typescript--re-search-forward'."
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 ?/))
916 (concat "\\([^\\]\\|^\\)" (string str-terminator))
917 (save-excursion (end-of-line) (point)) t))
921 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
922 (re-search-forward "\\*/"))
924 (setq count (1- count))))))
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))
935 '(typescript--re-search-forward-inner regexp bound 1))
937 '(typescript--re-search-backward-inner regexp bound (- count)))
939 '(typescript--re-search-forward-inner regexp bound count)))))
943 (goto-char saved-point)
945 (error (error-message-string err)))))))
948 (defun typescript--re-search-backward-inner (regexp &optional bound count)
949 "Auxiliary function for `typescript--re-search-backward'."
952 (re-search-backward regexp bound)
953 (when (and (> (point) (point-min))
954 (save-excursion (backward-char) (looking-at "/[/*]")))
956 (setq parse (syntax-ppss))
958 ;; If we are in a comment or a string, jump back to the start
959 ;; of the comment or string.
961 (goto-char (nth 8 parse)))
962 ((and (eq (char-before) ?/) (eq (char-after) ?*))
963 (re-search-backward "/\\*"))
965 (setq count (1- count))))))
969 (defun typescript--re-search-backward (regexp &optional bound noerror count)
970 "Search backward, ignoring strings, and comments.
972 This function invokes `re-search-backward' but treats the buffer
973 as if strings and comments have been removed.
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:
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))
990 '(typescript--re-search-backward-inner regexp bound 1))
992 '(typescript--re-search-forward-inner regexp bound (- count)))
994 '(typescript--re-search-backward-inner regexp bound count)))))
998 (goto-char saved-point)
1000 (error (error-message-string err)))))))
1002 (defun typescript--forward-expression ()
1003 "Move forward over a whole typescript expression.
1004 This function doesn't move over expressions continued across
1008 (forward-comment most-positive-fixnum)
1009 (cl-loop until (or (eolp)
1011 (forward-comment most-positive-fixnum)
1012 (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
1014 while (and (eq (char-after) ?\n)
1017 (typescript--continued-expression-p)))))
1019 (defun typescript--forward-function-decl ()
1020 "Move forward over a typescript function declaration.
1021 This puts point at the 'function' keyword.
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\\_>"))
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) ?{)
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)
1051 (when (save-excursion
1053 (or (looking-at typescript--function-heading-2-re)
1054 (looking-at typescript--function-heading-3-re)))
1056 (setq prologue-begin (match-beginning 1))
1057 (when (<= prologue-begin pos)
1058 (goto-char (match-end 0))))
1060 (skip-syntax-backward "w_")
1061 (and (or (looking-at "\\_<function\\_>")
1062 (typescript--re-search-backward "\\_<function\\_>" nil t))
1064 (save-match-data (goto-char (match-beginning 0))
1065 (typescript--forward-function-decl))
1068 (or prologue-begin (match-beginning 0))))))
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
1075 (typescript--ensure-cache)
1077 (while (and (setq pstate (typescript--backward-pstate))
1078 (not (eq 'function (typescript--pitem-type (car pstate))))))
1079 (and (not (bobp)) pstate)))
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
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)))
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."
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))
1104 ;; ...and if that isn't found, look for the previous top-level
1106 (cl-loop for pstate = (typescript--backward-pstate)
1108 if (typescript--pstate-is-toplevel-defun pstate)
1109 do (goto-char (typescript--pitem-h-begin it))
1112 (defun typescript--beginning-of-defun-flat ()
1113 "Helper function for `typescript-beginning-of-defun'."
1114 (let ((pstate (typescript--beginning-of-defun-raw)))
1116 (goto-char (typescript--pitem-h-begin (car pstate))))))
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))
1123 (when (and (not typescript-flat-functions)
1124 (or (eq (typescript-syntactic-context) 'function)
1125 (typescript--function-prologue-beginning)))
1126 (typescript-end-of-defun))
1128 (if (typescript--re-search-forward
1129 "\\_<function\\_>" nil t)
1130 (goto-char (typescript--function-prologue-beginning))
1131 (goto-char (point-max))))
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) ?})
1140 (let ((prologue-begin (typescript--function-prologue-beginning)))
1141 (cond ((and prologue-begin (< prologue-begin (point)))
1142 (goto-char prologue-begin))
1144 (typescript-flat-functions
1145 (typescript--beginning-of-defun-flat))
1147 (typescript--beginning-of-defun-nested))))))
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."
1153 (setq beg (or beg (save-restriction (widen) (point-min))))
1154 (setq typescript--cache-end (min typescript--cache-end beg)))
1156 (defmacro typescript--debug (&rest arguments)
1157 ;; `(message ,@arguments)
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))
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)))))
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'."
1180 (setq goal-point (point))
1181 (goto-char prev-parse-point)
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
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))
1195 ;; (let ((overlay (make-overlay prev-parse-point (point))))
1196 ;; (overlay-put overlay 'face '(:background "red"))
1199 ;; (typescript--debug "parsed: %S" parse)
1201 ;; (delete-overlay overlay)))
1203 (setq prev-parse-point (point))
1204 (< (point) goal-point)))
1206 (setq open-items (typescript--ensure-cache--pop-if-ended
1207 open-items (car parse)))))
1209 (defun typescript--show-cache-at-point ()
1212 (let ((prop (get-text-property (point) 'typescript--pstate)))
1213 (with-output-to-temp-buffer "*Help*"
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"))
1225 (setq name (remove "prototype" name)))))
1227 (defvar typescript--guess-function-name-start nil)
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
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)
1241 (goto-char position)
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)))
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))))))
1254 (defun typescript--clear-stale-cache ()
1255 ;; Clear any endings that occur after point
1258 (while (setq end-prop (typescript--forward-text-property
1260 (setf (typescript--pitem-b-end end-prop) nil))))
1262 ;; Remove any cache properties after this point
1263 (remove-text-properties (point) (point-max)
1264 '(typescript--pstate t typescript--pend t)))
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)
1272 (c-save-buffer-state
1281 filtered-class-styles
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)
1297 ;; Find last known good position
1298 (goto-char typescript--cache-end)
1300 (setq open-items (get-text-property
1301 (1- (point)) 'typescript--pstate))
1304 (goto-char (previous-single-property-change
1305 (point) 'typescript--pstate nil (point-min)))
1308 (setq open-items (get-text-property (1- (point))
1309 'typescript--pstate))
1310 (cl-assert open-items))))
1313 ;; Make a placeholder for the top-level definition
1314 (setq open-items (list typescript--initial-pitem)))
1316 (setq parse (syntax-ppss))
1317 (setq prev-parse-point (point))
1319 (typescript--clear-stale-cache)
1321 (narrow-to-region (point-min) limit)
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)
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.
1337 ;; In comment or string
1340 ;; Regular function declaration
1341 ((and (looking-at "\\_<function\\_>")
1342 (setq name (typescript--forward-function-decl)))
1345 (setq name (typescript--guess-function-name orig-match-end))
1347 (when typescript--guess-function-name-start
1348 (setq orig-match-start
1349 typescript--guess-function-name-start))
1353 (cl-assert (eq (char-after) ?{))
1355 (make-typescript--pitem
1356 :paren-depth orig-depth
1357 :h-begin orig-match-start
1359 :name (if (eq name t)
1361 (typescript--split-name name))))
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))
1369 (make-typescript--pitem
1370 :paren-depth orig-depth
1371 :h-begin orig-match-start
1373 :name (nconc (typescript--split-name
1374 (match-string-no-properties 1))
1375 (list (match-string-no-properties 2))))))
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
1385 do (goto-char (match-end 0))
1387 (make-typescript--pitem
1388 :paren-depth orig-depth
1389 :h-begin orig-match-start
1391 :name (typescript--split-name
1392 (match-string-no-properties 1))))))
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))
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))))))
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))
1413 finally do (goto-char (point-max))))
1415 (defun typescript--end-of-defun-nested ()
1416 "Helper function for `typescript-end-of-defun'."
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)
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)
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))))))
1436 (if found (forward-list)
1438 (goto-char (point-max))))))
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))
1445 (typescript-beginning-of-defun)
1446 (typescript-beginning-of-defun)
1448 (typescript-end-of-defun)))
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
1455 (if typescript-flat-functions
1456 (typescript--end-of-defun-flat)
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)
1469 (t (typescript--end-of-defun-nested)))))))
1471 (defun typescript--backward-syntactic-ws (&optional lim)
1472 "Simple implementation of `c-backward-syntactic-ws' for `typescript-mode'."
1474 (when lim (narrow-to-region lim (point-max)))
1476 (let ((pos (point)))
1477 (while (progn (forward-comment most-negative-fixnum)
1481 (setq pos (point)))))))))
1483 (defun typescript--forward-syntactic-ws (&optional lim)
1484 "Simple implementation of `c-forward-syntactic-ws' for `typescript-mode'."
1486 (when lim (narrow-to-region (point-min) lim))
1487 (let ((pos (point)))
1489 (forward-comment most-positive-fixnum)
1493 (setq pos (point)))))))))
1495 ;; Like (up-list -1), but only considers lists that end nearby"
1496 (defun typescript--up-nearby-list ()
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))
1503 (defun typescript--inside-param-list-p ()
1504 "Return non-nil iff point is in a function parameter list."
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"))))))))
1514 (defun typescript--inside-dojo-class-list-p ()
1515 "Return non-nil iff point is in a Dojo multiple-inheritance class block."
1518 (typescript--up-nearby-list)
1519 (let ((list-begin (point)))
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)))))))
1526 (defun typescript--syntax-begin-function ()
1527 (when (< typescript--cache-end (point))
1528 (goto-char (max (point-min) typescript--cache-end)))
1531 (while (and (setq pitem (car (typescript--backward-pstate)))
1532 (not (eq 0 (typescript--pitem-paren-depth pitem)))))
1535 (goto-char (typescript--pitem-h-begin pitem )))))
1537 (defun typescript--move-to-end-of-plain-string ()
1538 "If the point is in a plain string, move to the end of it.
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.
1544 Returns point if the end of the string was found, or nil if the
1545 end of the string was not found."
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
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) ?\\)))
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) "\\)")
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) '(?' ?\"))
1573 (typescript--move-to-end-of-plain-string)))))))
1575 (goto-char end-position))))
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))
1585 (when (memq (quote ,framework) typescript-enabled-frameworks)
1586 (re-search-forward ,regexps limit t)))))
1588 (defvar typescript--tmp-location nil)
1589 (make-variable-buffer-local 'typescript--tmp-location)
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)
1601 (forward-comment most-positive-fixnum)
1602 (cond ((memq (char-after) '(?\[ ?\{))
1603 (typescript--forward-destructuring-spec func))
1605 ((eq (char-after) ?,)
1609 ((looking-at typescript--name-re)
1610 (and func (funcall func))
1611 (goto-char (match-end 0))
1613 (when (eq (char-after) ?\])
1619 (forward-comment most-positive-fixnum)
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))
1630 (progn (forward-comment most-positive-fixnum)
1631 (when (eq (char-after) ?\,)
1633 (forward-comment most-positive-fixnum)
1635 (when (eq (char-after) ?\})
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'."
1646 (narrow-to-region (point-min) limit)
1649 (forward-comment most-positive-fixnum)
1652 (when (eq (char-after) ?,)
1654 (forward-comment most-positive-fixnum)
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)))
1662 (typescript--forward-destructuring-spec))
1664 (typescript--forward-destructuring-spec
1666 (font-lock-apply-highlight
1667 '(0 font-lock-variable-name-face)))))))
1669 (forward-comment most-positive-fixnum)
1670 (when (eq (char-after) ?=)
1672 (typescript--forward-expression)
1673 (forward-comment most-positive-fixnum))
1677 ;; Conditions to handle
1679 (end-of-buffer nil))
1681 ;; Matcher always "fails"
1684 (defun typescript--in-documentation-comment-p ()
1685 "Reports whether point is inside a documentation comment."
1686 (let ((parse (syntax-ppss)))
1688 (nth 4 parse) ;; Inside a comment ...
1691 (goto-char (nth 8 parse))
1692 (looking-at "/\\*\\*")))))) ;; ... which starts with /**
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)
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))
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))
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))
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))
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))
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))
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)))
1731 (defconst typescript--font-lock-keywords-3
1733 ,@typescript--font-lock-keywords-2
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))
1739 (typescript--jsdoc-typed-tag-matcher (1 'typescript-jsdoc-tag t t)
1740 (2 'typescript-jsdoc-type t t))
1742 (typescript--jsdoc-arg-tag-matcher (1 'typescript-jsdoc-tag t t)
1743 (2 'typescript-jsdoc-value t t))
1745 (typescript--jsdoc-empty-tag-matcher (1 'typescript-jsdoc-tag t t))
1747 (typescript--typedoc-link-matcher (0 'typescript-jsdoc-value t))
1749 (typescript--typedoc-literal-markup-matcher
1750 (0 'typescript-jsdoc-value t))
1752 (typescript--tslint-flag-matcher
1753 (1 font-lock-preprocessor-face t))
1755 ("\\.\\(prototype\\)\\_>"
1756 (1 font-lock-constant-face))
1758 (,(rx symbol-start "class" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1759 (1 font-lock-type-face))
1761 (,(rx symbol-start "extends" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1762 (1 font-lock-type-face))
1764 (,(rx symbol-start "implements" (+ space))
1765 (,(rx symbol-start (+ (syntax word))) nil nil (0 font-lock-type-face)))
1767 (,(rx symbol-start "interface" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1768 (1 font-lock-type-face))
1770 (,(rx symbol-start "type" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1771 (1 font-lock-type-face))
1773 (,(rx symbol-start "enum" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
1774 (1 font-lock-type-face))
1776 ;; Highlights class being declared, in parts
1777 (typescript--class-decl-matcher
1778 ,(concat "\\(" typescript--name-re "\\)\\(?:\\.\\|.*$\\)")
1779 (goto-char (match-beginning 1))
1781 (1 font-lock-type-face))
1783 ;; Highlights parent class, in parts, if available
1784 (typescript--class-decl-matcher
1785 ,(concat "\\(" typescript--name-re "\\)\\(?:\\.\\|.*$\\)")
1786 (if (match-beginning 2)
1788 (setq typescript--tmp-location (match-end 2))
1789 (goto-char typescript--tmp-location)
1791 (goto-char (match-beginning 2)))
1792 (setq typescript--tmp-location nil)
1793 (goto-char (point-at-eol)))
1794 (when typescript--tmp-location
1796 (goto-char typescript--tmp-location)
1798 (1 font-lock-type-face))
1800 ;; Highlights parent class
1801 (typescript--class-decl-matcher
1802 (2 font-lock-type-face nil t))
1804 ;; Dojo needs its own matcher to override the string highlighting
1805 (,(typescript--make-framework-matcher
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))
1813 ;; Match Dojo base classes. Of course Mojo has to be different
1814 ;; from everything else under the sun...
1815 (,(typescript--make-framework-matcher
1817 "^\\s-*dojo\\.declare\\s-*(\""
1818 "\\(" typescript--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
1819 ,(concat "[[,]\\s-*\\(" typescript--dotted-name-re "\\)\\s-*"
1823 (1 font-lock-type-face))
1825 ;; continued Dojo base-class list
1826 (,(typescript--make-framework-matcher
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))
1836 (1 font-lock-type-face))
1838 ;; variable declarations
1840 (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" typescript--basic-type-re)
1841 (list #'typescript--variable-decl-matcher nil nil nil))
1843 ;; class instantiation
1845 (concat "\\_<new\\_>\\s-+\\(" typescript--dotted-name-re "\\)")
1846 (list 1 'font-lock-type-face))
1850 (concat "\\_<instanceof\\_>\\s-+\\(" typescript--dotted-name-re "\\)")
1851 (list 1 'font-lock-type-face))
1853 ;; formal parameters
1856 "\\_<function\\_>\\(\\s-+" typescript--name-re "\\)?\\s-*\\(<.*>\\)?\\s-*(\\s-*"
1857 typescript--name-start-re)
1858 (list (concat "\\(" typescript--name-re "\\)\\(\\s-*).*\\)?")
1861 '(1 font-lock-variable-name-face)))
1863 ;; continued formal parameter list
1866 "^\\s-*" typescript--name-re "\\s-*[,)]")
1867 (list typescript--name-re
1868 '(if (save-excursion (backward-char)
1869 (typescript--inside-param-list-p))
1873 '(0 font-lock-variable-name-face))))
1874 "Level three font lock for `typescript-mode'.")
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)
1882 (font-lock-fontify-buffer))
1884 ;; Check with the default method that flyspell provides.
1885 (flyspell-generic-progmode-verify)
1888 ;; And eliminate cases specific to our mode we don't want to have
1892 ;; Don't check the module names in import statements.
1894 (not (let* ((parse (syntax-ppss (1- (point))))
1895 (string-start-pos (and (nth 3 parse)
1897 (and string-start-pos
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-"))))))))
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))
1912 (and (> (point) (typescript--pitem-h-begin pitem))
1913 (or (null (typescript--pitem-b-end pitem))
1914 (> (typescript--pitem-b-end pitem) (point)))))
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
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))))
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)
1935 (typescript--inside-pitem-p pitem))
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))
1949 (defun typescript-syntactic-context ()
1950 "Return the typescript syntactic context at point.
1951 When called interatively, also display a message with that
1954 (let* ((syntactic-context (typescript--syntactic-context-from-pstate
1955 (typescript--parse-state-at-point))))
1957 (when (called-interactively-p 'interactive)
1958 (message "Syntactic context: %s" syntactic-context))
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))
1976 (looking-at decl-re))
1977 do (goto-char (match-end 0))
1980 else do (goto-char orig-end)))
1982 (defconst typescript--font-lock-keywords-4
1984 ;; highlights that override previous levels
1987 ;; special highlight for `this' keyword
1989 (1 'typescript-this-face))
1991 (,typescript--access-modifier-re (1 'typescript-access-modifier-face))
1992 (,typescript--basic-type-re (1 'typescript-primitive-face))
1996 (concat typescript--name-re "\\s-*" "<\\s-*" typescript--name-start-re)
1997 (list (concat "\\(" typescript--name-re "\\)\\(\\s-*>[^<]*\\)?")
2000 '(1 font-lock-type-face)))
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!
2013 (concat ":\\s-\\(" typescript--type-name-re "\\)\\(<" typescript--type-name-re ">\\)?\\(\[\]\\)?\\([,;]\\)?\\s-*{?")
2014 '(1 'font-lock-type-face))
2018 (concat "<\\(" typescript--type-name-re "\\)>")
2019 '(1 'font-lock-type-face))
2021 ;; highlights that append to previous levels
2023 ,@typescript--font-lock-keywords-3
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))
2031 (1 font-lock-keyword-face)))
2032 "Level four font lock for `typescript-mode'.")
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'.")
2044 ;; The propertize code was adapted from:
2045 ;; https://github.com/emacs-mirror/emacs/blob/489d6466372f488adc53897435fff290394b62f7/lisp/progmodes/js.el
2048 (defconst typescript--syntax-propertize-regexp-regexp
2053 ;; Match characters outside of a character class.
2054 (not (any ?\[ ?/ ?\\))
2055 ;; Match backslash quoted characters.
2056 (and "\\" not-newline)
2057 ;; Match character class.
2062 (and "\\" not-newline)))
2064 (group (zero-or-one "/")))
2065 "Regular expression matching a JavaScript regexp literal.")
2067 (defun typescript-syntax-propertize-regexp (end)
2068 (let ((ppss (syntax-ppss)))
2069 (when (eq (nth 3 ppss) ?/)
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 "\"/"))
2080 (defun typescript-syntax-propertize (start end)
2081 ;; JavaScript allows immediate regular expression objects, written /.../.
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]\\)*\\(/\\)[^/*]"
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.
2097 (goto-char (match-beginning 0))
2098 (forward-comment (- (point)))
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")))
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.")
2115 (defconst typescript--indent-keyword-re
2116 (typescript--regexp-opt-symbol '("in" "instanceof"))
2117 "Regexp matching keywords that affect indentation of continued expressions.")
2119 (defconst typescript--indent-operator-re
2120 (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|" typescript--indent-keyword-re)
2121 "Regexp matching operators that affect indentation of continued expressions.")
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.
2129 ;; This is meant to match NaN, floats, decimals, the two infinities
2130 ;; and numbers recorded in binary, octal and hex.
2132 ;; This regular expression was derived from:
2133 ;; https://stackoverflow.com/a/30987109/
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.")
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!")
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'.")
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.")
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.
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)))))))
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
2179 (typescript--search-backward-matching-angle-bracket-inner 1))
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\"."
2185 (typescript--re-search-backward "[>=}]\\|\\_<type\\_>" nil t)
2186 (or (not (looking-at ">"))
2188 (typescript--search-backward-matching-angle-bracket)
2189 (typescript--re-search-backward-ignoring-angle-brackets)))))
2191 (defun typescript--looking-at-operator-p ()
2192 "Return non-nil if point is on a typescript operator, other than a comma."
2194 (and (looking-at typescript--indent-operator-re)
2195 (or (not (looking-at ":"))
2199 (typescript--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
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 "/")
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.
2212 (looking-at typescript--indent-keyword-re)
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
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!
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)
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\\_>")))))))
2254 ;; Generator method (possibly using computed property).
2255 (looking-at (concat "\\* *\\(?:\\[\\|" typescript--name-re
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) '(?, ?} ?{ ?\;))))))))
2264 (defun typescript--continued-expression-p ()
2265 "Return non-nil if the current line continues an expression."
2267 (back-to-indentation)
2268 (let ((list-start (nth 1 (syntax-ppss))))
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
2279 (typescript--backward-syntactic-ws)
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)
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 "++\\|--\\|/[/*]"))))))))))))
2294 (cl-defun typescript--compute-member-expression-indent ()
2295 "Determine the indent of a member expression.
2297 This function must be called with point located at the dot that
2298 starts the member expression.
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) ?\;)
2306 (while (memq (char-before) '(?\] ?} ?\) ?>))
2307 (if (not (eq (char-before) ?>))
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))
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."
2325 (when (looking-at "\\s-*\\_<while\\_>")
2327 (skip-chars-backward "[ \t\n]*}")
2328 (looking-at "[ \t\n]*}"))
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)))))))))
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
2348 (back-to-indentation)
2349 (when (save-excursion
2350 (and (not (eq (point-at-bol) (point-min)))
2351 (not (looking-at "[{]"))
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_")
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))))
2364 (goto-char (match-beginning 0))
2365 (+ (current-indentation) typescript-indent-level)))))
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)))))
2372 (defun typescript--backward-over-generic-parameter-list ()
2373 "Search backward for the start of a generic's parameter list and move to it.
2375 This is a utility function for
2376 `typescript--backward-to-parameter-list'.
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.
2383 Returns nil on failure, or the position to which the point was
2385 (when (eq (char-after) ?>)
2387 (cl-loop named search-loop
2390 (unless (re-search-backward "[<>]" nil t)
2391 (cl-return-from search-loop nil))
2394 (unless (eq (char-before) ?=)
2395 (setq depth (1+ depth))))
2396 ((looking-at "<") (setq depth (1- depth)))))
2397 finally return (point)))))
2399 (defun typescript--backward-to-parameter-list ()
2400 "Search backward for the end of a parameter list and move to it.
2402 This is a utility function for `typescript--proper-indentation'.
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.
2413 Returns nil on failure, or the position to which the point was
2417 ;; This handles the case of a function with return type annotation.
2419 (cl-loop named search-loop
2421 (typescript--backward-syntactic-ws)
2422 ;; Check whether we are at "):".
2423 (when (and (eq (char-before) ?\:)
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.
2433 ;; Arrow of a function definition, or typeguard (eg. foo is SomeClass)
2434 ((looking-back "=>\\|is" (- (point) 2))
2436 ;; End of the parameters list of a generic.
2437 ((eq (char-before) ?>)
2439 (typescript--backward-over-generic-parameter-list))
2440 ;; Union of types, or a dot in a dotted name.
2441 ((memq (char-before) '(?| ?.))
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
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
2458 (looking-at "\\_<\\(switch\\|if\\|while\\|until\\|for\\)\\_>\\(?:\\s-\\|\n\\)*(")))))
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.
2469 (cl-return-from search-loop nil)))))
2470 ;; This handles the case of a function without return type annotation.
2472 (typescript--backward-syntactic-ws)
2473 (when (eq (char-before) ?\))
2476 (goto-char location))))
2478 (defun typescript--proper-indentation (parse-status)
2479 "Return the proper indentation for the current line."
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-*\\(/[/*]\\|$\\)")
2499 (skip-syntax-backward " ")
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.
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.
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
2525 (cond (same-indent-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)))
2534 (+ (current-column) typescript-indent-level)))))
2535 (if (and in-switch-p typescript-indent-switch-clauses)
2536 (+ indent typescript-indent-level)
2538 (unless same-indent-p
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)))))
2550 ((typescript--continued-expression-p) ;; Inside a continued expression.
2552 (typescript--compute-member-expression-indent)
2553 (+ typescript-indent-level typescript-expr-indent-offset)))
2556 (defun typescript-indent-line ()
2557 "Indent the current line as typescript."
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)))))))
2569 (defun typescript-c-fill-paragraph (&optional justify)
2570 "Fill the paragraph with `c-fill-paragraph'."
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))))
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.
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.
2597 (defun typescript--pitem-format (pitem)
2598 (let ((name (typescript--pitem-name pitem))
2599 (type (typescript--pitem-type pitem)))
2601 (format "name:%S type:%S"
2605 (plist-get type :name)))))
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))
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)
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))
2627 :name (typescript--pitem-strname item))))
2629 ;; Now we can merge either a function or a class into a class
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
2636 (typescript--splice-into-items (car item) child
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
2643 (cons child (car item)))
2646 ;; Otherwise, merge the new child's items into those
2648 (typescript--debug "typescript--make-merged-item: merging class contents")
2649 (append (car child) (car item))))
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)))
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."
2665 (let ((top-name (car name-parts))
2667 new-items last-new-item new-cons item)
2669 (typescript--debug "typescript--splice-into-items: name-parts: %S items:%S"
2671 (mapcar #'typescript--pitem-name items))
2673 (cl-assert (stringp top-name))
2674 (cl-assert (> (length top-name) 0))
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.
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
2690 (setcdr last-new-item new-cons)
2691 (setq new-items new-cons))
2693 ;; ...and terminate the loop
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
2701 (setq new-cons (cons (car item-ptr) nil))
2703 (setcdr last-new-item new-cons)
2704 (setq new-items new-cons))
2705 (setq last-new-item new-cons)
2707 ;; Go to the next cell in items
2708 (setq item-ptr (cdr item-ptr))))))
2711 ;; Yay! We stopped because we found something, not because
2712 ;; we ran out of items to search. Just return the new
2715 (typescript--debug "search succeeded: %S" name-parts)
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
2732 ;; Otherwise, this is the only name we have, so stick
2733 ;; the item on the front of the list
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))
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".
2750 (let ((name (typescript--pitem-name child))
2751 (type (typescript--pitem-type child)))
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))
2758 (plist-get type :prototype))
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))
2769 (cons child (car pitem)))))
2773 ;;; compilation-mode support
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.
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.
2788 ;; handle plain compiler-errors like the following when doing M-x compile<ret>tsc<ret>
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
2795 "\\([^(\r\n)]+\\)(\\([0-9]+\\),\\([0-9]+\\)):[[:blank:]]+"
2796 "error [[:alnum:]]+: [^\r\n]+$")
2797 "Regexp to match errors generated by tsc.")
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
2804 "\\([^(\r\n)]+\\):\\([0-9]+\\):\\([0-9]+\\) - [[:blank:]]*"
2805 "error [[:alnum:]]+: [^\r\n]+$")
2806 "Regexp to match errors generated by tsc.")
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 "
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.
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
2823 (defconst typescript-tslint-report-regexp
2826 ;; severity ("type" in Emacs' parlance)
2827 "\\(?:\\(?:ERROR\\|\\(WARNING\\)\\):[[:blank:]]+\\)?"
2829 "\\((.*)[[:blank:]]+\\)?"
2834 "\\([[:digit:]]+\\)"
2837 "\\([[:digit:]]+\\)"
2841 "Regexp to match reports generated by tslint.")
2843 (defconst typescript-nglint-error-regexp
2845 ;; severity ("type" in Emacs' parlance)
2846 "ERROR:[[:blank:]]+"
2852 "\\([[:digit:]]+\\)"
2855 "\\([[:digit:]]+\\)"
2861 (defconst typescript-nglint-warning-regexp
2863 ;; severity ("type" in Emacs' parlance)
2864 "WARNING:[[:blank:]]+"
2870 "\\([[:digit:]]+\\)"
2873 "\\([[:digit:]]+\\)"
2882 ,typescript-tsc-error-regexp
2885 (typescript-tsc-pretty
2886 ,typescript-tsc-pretty-error-regexp
2890 ,typescript-tslint-report-regexp
2893 (typescript-nglint-error
2894 ,typescript-nglint-error-regexp
2897 (typescript-nglint-warning
2898 ,typescript-nglint-warning-regexp
2900 (add-to-list 'compilation-error-regexp-alist-alist regexp)
2901 (add-to-list 'compilation-error-regexp-alist (car regexp)))
2906 (define-derived-mode typescript-mode prog-mode "typescript"
2907 "Major mode for editing typescript.
2911 \\{typescript-mode-map}"
2914 :syntax-table typescript-mode-syntax-table
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)
2926 (setq-local comment-start "// ")
2927 (setq-local comment-end "")
2928 (setq-local fill-paragraph-function 'typescript-c-fill-paragraph)
2931 (add-hook 'before-change-functions #'typescript--flush-caches t t)
2934 (typescript--update-quick-match-re)
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 *")
2945 (setq-local electric-indent-chars
2946 (append "{}():;," electric-indent-chars))
2947 (setq-local electric-layout-rules
2948 '((?\; . after) (?\{ . after) (?\} . before)))
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))
2960 (add-hook 'post-self-insert-hook
2961 #'typescript--post-self-insert-function)
2963 (setq-local syntax-begin-function #'typescript--syntax-begin-function))
2965 ;; Set our custom predicate for flyspell prog mode
2966 (put 'typescript-mode 'flyspell-mode-predicate
2967 'typescript--flyspell-mode-predicate)
2970 (eval-after-load 'folding
2971 '(when (fboundp 'folding-add-to-marks-list)
2972 (folding-add-to-marks-list 'typescript-mode "// {{{" "// }}}" )))
2975 (add-to-list 'auto-mode-alist '("\\.ts$" . typescript-mode))
2977 (provide 'typescript-mode)
2979 ;;; typescript-mode.el ends here