final tt updates
[emacs-init.git] / elpa / typescript-mode-20200430.1232 / typescript-mode.el
diff --git a/elpa/typescript-mode-20200430.1232/typescript-mode.el b/elpa/typescript-mode-20200430.1232/typescript-mode.el
new file mode 100644 (file)
index 0000000..09add95
--- /dev/null
@@ -0,0 +1,2979 @@
+;;; typescript-mode.el --- Major mode for editing typescript
+
+;; -----------------------------------------------------------------------------------
+;;     TypeScript support for Emacs
+;;     Unmodified original sourve available at http://www.karllandstrom.se/downloads/emacs/javascript.el
+;;     Copyright (c) 2008 Free Software Foundation
+;;     Portions Copyright (C) Microsoft Open Technologies, Inc. All rights reserved.
+;;
+;;     This program is free software: you can redistribute it and/or modify
+;;     it under the terms of the GNU General Public License as published by
+;;     the Free Software Foundation, either version 3 of the License, or
+;;     (at your option) any later version.
+;;
+;;     This program is distributed in the hope that it will be useful,
+;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;     GNU General Public License for more details.
+;;
+;;     You should have received a copy of the GNU General Public License
+;;     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; -------------------------------------------------------------------------------------------
+
+;; URL: http://github.com/ananthakumaran/typescript.el
+;; Version: 0.1
+;; Keywords: typescript languages
+;; Package-Requires: ((emacs "24.3"))
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; This is based on Karl Landstrom's barebones typescript-mode.  This
+;; is much more robust and works with cc-mode's comment filling
+;; (mostly).
+;; The modifications to the original javascript.el mode mainly consisted in
+;; replacing "javascript" with "typescript"
+;;
+;; The main features of this typescript mode are syntactic
+;; highlighting (enabled with `font-lock-mode' or
+;; `global-font-lock-mode'), automatic indentation and filling of
+;; comments.
+;;
+;;
+;; General Remarks:
+;;
+;; XXX: This mode assumes that block comments are not nested inside block
+;; XXX: comments
+;;
+;; Exported names start with "typescript-"; private names start with
+;; "typescript--".
+
+;;; Code:
+
+(eval-and-compile
+  (require 'compile)
+  (require 'cc-mode)
+  (require 'font-lock)
+  (require 'rx)
+  (require 'newcomment))
+
+(eval-when-compile
+  (require 'cl-lib))
+
+;;; Constants
+
+(defconst typescript--type-name-re "\\(?:[A-Z][A-Za-z0-9]+\\.\\)\\{0,\\}\\(?:[A-Z][A-Za-z0-9]+\\)"
+  "Regexp matching a conventional TypeScript type-name.  Must start with upper-case letter!")
+
+(defconst typescript--name-start-re "[a-zA-Z_$]"
+  "Regexp matching the start of a typescript identifier, without grouping.")
+
+(defconst typescript--name-re (concat typescript--name-start-re
+                              "\\(?:\\s_\\|\\sw\\)*")
+  "Regexp matching a typescript identifier, without grouping.")
+
+(defconst typescript--objfield-re (concat typescript--name-re ":")
+  "Regexp matching the start of a typescript object field.")
+
+(defconst typescript--dotted-name-re
+  (concat typescript--name-re "\\(?:\\." typescript--name-re "\\)*")
+  "Regexp matching a dot-separated sequence of typescript names.")
+
+(defconst typescript--plain-method-re
+  (concat "^\\s-*?\\(" typescript--dotted-name-re "\\)\\.prototype"
+          "\\.\\(" typescript--name-re "\\)\\s-*?=\\s-*?\\(function\\)\\_>")
+  "Regexp matching an explicit typescript prototype \"method\" declaration.
+Group 1 is a (possibly-dotted) class name, group 2 is a method name,
+and group 3 is the 'function' keyword.")
+
+(defconst typescript--plain-class-re
+  (concat "^\\s-*\\(" typescript--dotted-name-re "\\)\\.prototype"
+          "\\s-*=\\s-*{")
+  "Regexp matching a typescript explicit prototype \"class\" declaration.
+An example of this is \"Class.prototype = { method1: ...}\".")
+
+(defconst typescript--module-declaration-re
+  "^\\s-*\\(?:declare\\|\\(?:export\\(?:\\s-+default\\)?\\)\\)?"
+  "Regexp matching ambient declaration modifier or export declaration.")
+
+;; var NewClass = BaseClass.extend(
+(defconst typescript--mp-class-decl-re
+  (concat "^\\s-*var\\s-+"
+          "\\(" typescript--name-re "\\)"
+          "\\s-*=\\s-*"
+          "\\(" typescript--dotted-name-re
+          "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$"))
+
+;; var NewClass = Class.create()
+(defconst typescript--prototype-obsolete-class-decl-re
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" typescript--dotted-name-re "\\)"
+          "\\s-*=\\s-*Class\\.create()"))
+
+(defconst typescript--prototype-objextend-class-decl-re-1
+  (concat "^\\s-*Object\\.extend\\s-*("
+          "\\(" typescript--dotted-name-re "\\)"
+          "\\s-*,\\s-*{"))
+
+(defconst typescript--prototype-objextend-class-decl-re-2
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" typescript--dotted-name-re "\\)"
+          "\\s-*=\\s-*Object\\.extend\\s-*\("))
+
+;; var NewClass = Class.create({
+(defconst typescript--prototype-class-decl-re
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" typescript--name-re "\\)"
+          "\\s-*=\\s-*Class\\.create\\s-*(\\s-*"
+          "\\(?:\\(" typescript--dotted-name-re "\\)\\s-*,\\s-*\\)?{?"))
+
+;; Parent class name(s) (yes, multiple inheritance in typescript) are
+;; matched with dedicated font-lock matchers
+(defconst typescript--dojo-class-decl-re
+  (concat "^\\s-*dojo\\.declare\\s-*(\"\\(" typescript--dotted-name-re "\\)"))
+
+(defconst typescript--exttypescript-class-decl-re-1
+  (concat "^\\s-*Ext\\.extend\\s-*("
+          "\\s-*\\(" typescript--dotted-name-re "\\)"
+          "\\s-*,\\s-*\\(" typescript--dotted-name-re "\\)")
+  "Regexp matching an ExtTYPESCRIPT class declaration (style 1).")
+
+(defconst typescript--exttypescript-class-decl-re-2
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" typescript--name-re "\\)"
+          "\\s-*=\\s-*Ext\\.extend\\s-*(\\s-*"
+          "\\(" typescript--dotted-name-re "\\)")
+  "Regexp matching an ExtTYPESCRIPT class declaration (style 2).")
+
+(defconst typescript--mochikit-class-re
+  (concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
+          "\\(" typescript--dotted-name-re "\\)")
+  "Regexp matching a MochiKit class declaration.")
+
+(defconst typescript--dummy-class-style
+  '(:name "[Automatically Generated Class]"))
+
+(defconst typescript--class-styles
+  `((:name            "Plain"
+     :class-decl      ,typescript--plain-class-re
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       typescript)
+
+    (:name            "MochiKit"
+     :class-decl      ,typescript--mochikit-class-re
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       mochikit)
+
+    (:name            "Prototype (Obsolete)"
+     :class-decl      ,typescript--prototype-obsolete-class-decl-re
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Prototype (Modern)"
+     :class-decl      ,typescript--prototype-class-decl-re
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Prototype (Object.extend)"
+     :class-decl      ,typescript--prototype-objextend-class-decl-re-1
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Prototype (Object.extend) 2"
+     :class-decl      ,typescript--prototype-objextend-class-decl-re-2
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Dojo"
+     :class-decl      ,typescript--dojo-class-decl-re
+     :contexts        (toplevel)
+     :framework       dojo)
+
+    (:name            "ExtTYPESCRIPT (style 1)"
+     :class-decl      ,typescript--exttypescript-class-decl-re-1
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       exttypescript)
+
+    (:name            "ExtTYPESCRIPT (style 2)"
+     :class-decl      ,typescript--exttypescript-class-decl-re-2
+     :contexts        (toplevel)
+     :framework       exttypescript)
+
+    (:name            "Merrill Press"
+     :class-decl      ,typescript--mp-class-decl-re
+     :contexts        (toplevel)
+     :framework       merrillpress))
+
+  "List of typescript class definition styles.
+
+A class definition style is a plist with the following keys:
+
+:name is a human-readable name of the class type
+
+:class-decl is a regular expression giving the start of the
+class.  Its first group must match the name of its class.  If there
+is a parent class, the second group should match, and it should be
+the name of the class.
+
+If :prototype is present and non-nil, the parser will merge
+declarations for this constructs with others at the same lexical
+level that have the same name.  Otherwise, multiple definitions
+will create multiple top-level entries.  Don't use :prototype
+unnecessarily: it has an associated cost in performance.
+
+If :strip-prototype is present and non-nil, then if the class
+name as matched contains")
+
+(defconst typescript--available-frameworks
+  (cl-loop with available-frameworks
+        for style in typescript--class-styles
+        for framework = (plist-get style :framework)
+        unless (memq framework available-frameworks)
+        collect framework into available-frameworks
+        finally return available-frameworks)
+  "List of available typescript frameworks symbols.")
+
+(defconst typescript--function-heading-1-re
+  (concat
+   typescript--module-declaration-re
+   "\\s-*function\\s-+\\(" typescript--name-re "\\)")
+  "Regexp matching the start of a typescript function header.
+Match group 1 is the name of the function.")
+
+(defconst typescript--function-heading-2-re
+  (concat
+   "^\\s-*\\(" typescript--name-re "\\)\\s-*:\\s-*function\\_>")
+  "Regexp matching the start of a function entry in an associative array.
+Match group 1 is the name of the function.")
+
+(defconst typescript--function-heading-3-re
+  (concat
+   "^\\s-*\\(?:var\\s-+\\)?\\(" typescript--dotted-name-re "\\)"
+   "\\s-*=\\s-*function\\_>")
+  "Regexp matching a line in the typescript form \"var MUMBLE = function\".
+Match group 1 is MUMBLE.")
+
+(defun typescript--regexp-opt-symbol (list)
+  "Like `regexp-opt', but surround the result with `\\\\_<' and `\\\\_>'."
+  (concat "\\_<" (regexp-opt list t) "\\_>"))
+
+(defconst typescript--keyword-re
+  (typescript--regexp-opt-symbol
+   '("abstract" "any" "as" "async" "await" "boolean" "bigint" "break" "case" "catch" "class" "const"
+     "constructor" "continue" "declare" "default" "delete" "do" "else"
+     "enum" "export" "extends" "extern" "false" "finally" "for"
+     "function" "from" "get" "goto" "if" "implements" "import" "in" "instanceof"
+     "interface" "keyof" "let" "module" "namespace" "never" "new" "null" "number" "object" "of"
+     "private" "protected" "public" "readonly" "return" "set" "static" "string"
+     "super" "switch"  "this" "throw" "true"
+     "try" "type" "typeof" "unknown" "var" "void"
+     "while")) ; yield is handled separately
+  "Regexp matching any typescript keyword.")
+
+(defconst typescript--basic-type-re
+  (typescript--regexp-opt-symbol
+   '("any" "bool" "boolean" "bigint" "never" "number" "string" "unknown" "void"))
+  "Regular expression matching any predefined type in typescript.")
+
+(defconst typescript--access-modifier-re
+  (typescript--regexp-opt-symbol
+   '("private" "protected" "public" "readonly" "static" "extends" "implements"))
+  "Regular expression matching access modifiers.")
+
+(defconst typescript--decorator-re
+  (concat "\\(@" typescript--name-re "\\)"))
+
+(defconst typescript--constant-re
+  (typescript--regexp-opt-symbol '("false" "null" "undefined"
+                                 "Infinity" "NaN"
+                                 "true" "arguments" "this"))
+  "Regular expression matching any future reserved words in typescript.")
+
+(defconst typescript--builtin-re
+  (typescript--regexp-opt-symbol
+   '("console"))
+  "Regular expression matching builtins.")
+
+(defconst typescript--function-call-re "\\(\\(?:\\w\\|\\s_\\)+\\)\\(<.+>\\)?\s*("
+  "Regular expression matching function calls.")
+
+(defconst typescript--font-lock-keywords-1
+  (list
+   "\\_<import\\_>"
+   (list typescript--function-heading-1-re 1 font-lock-function-name-face)
+   (list typescript--function-heading-2-re 1 font-lock-function-name-face))
+  "Level one font lock keywords for `typescript-mode'.")
+
+(defconst typescript--font-lock-keywords-2
+  (append typescript--font-lock-keywords-1
+          (list (list typescript--keyword-re 1 font-lock-keyword-face)
+                (list "\\_<for\\_>"
+                      "\\s-+\\(each\\)\\_>" nil nil
+                      (list 1 'font-lock-keyword-face))
+                (cons "\\_<yield\\(\\*\\|\\_>\\)" 'font-lock-keyword-face)
+                (cons typescript--basic-type-re font-lock-type-face)
+                (cons typescript--constant-re font-lock-constant-face)))
+  "Level two font lock keywords for `typescript-mode'.")
+
+;; typescript--pitem is the basic building block of the lexical
+;; database. When one refers to a real part of the buffer, the region
+;; of text to which it refers is split into a conceptual header and
+;; body. Consider the (very short) block described by a hypothetical
+;; typescript--pitem:
+;;
+;;   function foo(a,b,c) { return 42; }
+;;   ^                    ^            ^
+;;   |                    |            |
+;;   +- h-begin           +- h-end     +- b-end
+;;
+;; (Remember that these are buffer positions, and therefore point
+;; between characters, not at them. An arrow drawn to a character
+;; indicates the corresponding position is between that character and
+;; the one immediately preceding it.)
+;;
+;; The header is the region of text [h-begin, h-end], and is
+;; the text needed to unambiguously recognize the start of the
+;; construct. If the entire header is not present, the construct is
+;; not recognized at all. No other pitems may be nested inside the
+;; header.
+;;
+;; The body is the region [h-end, b-end]. It may contain nested
+;; typescript--pitem instances. The body of a pitem may be empty: in
+;; that case, b-end is equal to header-end.
+;;
+;; The three points obey the following relationship:
+;;
+;;   h-begin < h-end <= b-end
+;;
+;; We put a text property in the buffer on the character *before*
+;; h-end, and if we see it, on the character *before* b-end.
+;;
+;; The text property for h-end, typescript--pstate, is actually a list
+;; of all typescript--pitem instances open after the marked character.
+;;
+;; The text property for b-end, typescript--pend, is simply the
+;; typescript--pitem that ends after the marked character. (Because
+;; pitems always end when the paren-depth drops below a critical
+;; value, and because we can only drop one level per character, only
+;; one pitem may end at a given character.)
+;;
+;; In the structure below, we only store h-begin and (sometimes)
+;; b-end. We can trivially and quickly find h-end by going to h-begin
+;; and searching for an typescript--pstate text property. Since no other
+;; typescript--pitem instances can be nested inside the header of a
+;; pitem, the location after the character with this text property
+;; must be h-end.
+;;
+;; typescript--pitem instances are never modified (with the exception
+;; of the b-end field). Instead, modified copies are added at subseqnce parse points.
+;; (The exception for b-end and its caveats is described below.)
+;;
+
+(cl-defstruct (typescript--pitem (:type list))
+  ;; IMPORTANT: Do not alter the position of fields within the list.
+  ;; Various bits of code depend on their positions, particularly
+  ;; anything that manipulates the list of children.
+
+  ;; List of children inside this pitem's body
+  (children nil :read-only t)
+
+  ;; When we reach this paren depth after h-end, the pitem ends
+  (paren-depth nil :read-only t)
+
+  ;; Symbol or class-style plist if this is a class
+  (type nil :read-only t)
+
+  ;; See above
+  (h-begin nil :read-only t)
+
+  ;; List of strings giving the parts of the name of this pitem (e.g.,
+  ;; '("MyClass" "myMethod"), or t if this pitem is anonymous
+  (name nil :read-only t)
+
+  ;; THIS FIELD IS MUTATED, and its value is shared by all copies of
+  ;; this pitem: when we copy-and-modify pitem instances, we share
+  ;; their tail structures, so all the copies actually have the same
+  ;; terminating cons cell. We modify that shared cons cell directly.
+  ;;
+  ;; The field value is either a number (buffer location) or nil if
+  ;; unknown.
+  ;;
+  ;; If the field's value is greater than `typescript--cache-end', the
+  ;; value is stale and must be treated as if it were nil. Conversely,
+  ;; if this field is nil, it is guaranteed that this pitem is open up
+  ;; to at least `typescript--cache-end'. (This property is handy when
+  ;; computing whether we're inside a given pitem.)
+  ;;
+  (b-end nil))
+
+;; The pitem we start parsing with.
+(defconst typescript--initial-pitem
+  (make-typescript--pitem
+   :paren-depth most-negative-fixnum
+   :type 'toplevel))
+
+;; When we say "jsdoc" here, we mean "jsdoc 3". There exist multiple dialects of
+;; "jsdoc documentation".
+
+;; Note that all typedoc/jsdoc regexp by themselves would match occurrences that appear outside
+;; documentation comments. The logic that uses these regexps must guard against it.
+(defconst typescript-typedoc-link-tag-regexp
+  "\\[\\[.*?\\]\\]"
+  "Matches a typedoc link.")
+
+(defconst typescript-typedoc-literal-markup-regexp
+  "\\(`+\\).*?\\1"
+  "Matches a typedoc keyword markup.")
+
+(defconst typescript-jsdoc-before-tag-regexp
+  "\\(?:^\\s-*\\*+\\|/\\*\\*\\)\\s-*"
+  "Matches everything we allow before the @ of a jsdoc tag.")
+
+;; This was taken from js2-mode.
+(defconst typescript-jsdoc-param-tag-regexp
+  (concat typescript-jsdoc-before-tag-regexp
+          "\\(@"
+          (regexp-opt
+           '("arg"
+             "argument"
+             "param"
+             "prop"
+             "property"
+             "typedef"))
+          "\\)"
+          "\\s-*\\({[^}]+}\\)?"         ; optional type
+          "\\s-*\\[?\\([[:alnum:]_$\.]+\\)?\\]?"  ; name
+          "\\_>")
+  "Matches jsdoc tags with optional type and optional param name.")
+
+;; This was taken from js2-mode.
+;; and extended with tags in http://usejsdoc.org/
+(defconst typescript-jsdoc-typed-tag-regexp
+  (concat typescript-jsdoc-before-tag-regexp
+          "\\(@"
+          (regexp-opt
+           '("enum"
+             "extends"
+             "field"
+             "id"
+             "implements"
+             "lends"
+             "mods"
+             "requires"
+             "return"
+             "returns"
+             "throw"
+             "throws"
+             "type"
+             "yield"
+             "yields"))
+          "\\)\\s-*\\({[^}]+}\\)?")
+  "Matches jsdoc tags with optional type.")
+
+;; This was taken from js2-mode.
+;; and extended with tags in http://usejsdoc.org/
+(defconst typescript-jsdoc-arg-tag-regexp
+  (concat typescript-jsdoc-before-tag-regexp
+          "\\(@"
+          (regexp-opt
+           '("access"
+             "alias"
+             "augments"
+             "base"
+             "borrows"
+             "bug"
+             "callback"
+             "config"
+             "default"
+             "define"
+             "emits"
+             "exception"
+             "extends"
+             "external"
+             "fires"
+             "func"
+             "function"
+             "host"
+             "kind"
+             "listens"
+             "member"
+             "memberof"
+             "method"
+             "mixes"
+             "module"
+             "name"
+             "namespace"
+             "requires"
+             "since"
+             "suppress"
+             "this"
+             "throws"
+             "var"
+             "variation"
+             "version"))
+          "\\)\\s-+\\([^ \t]+\\)")
+  "Matches jsdoc tags with a single argument.")
+
+;; This was taken from js2-mode
+;; and extended with tags in http://usejsdoc.org/
+(defconst typescript-jsdoc-empty-tag-regexp
+  (concat typescript-jsdoc-before-tag-regexp
+          "\\(@"
+          (regexp-opt
+           '("abstract"
+             "addon"
+             "async"
+             "author"
+             "class"
+             "classdesc"
+             "const"
+             "constant"
+             "constructor"
+             "constructs"
+             "copyright"
+             "default"
+             "defaultvalue"
+             "deprecated"
+             "desc"
+             "description"
+             "event"
+             "example"
+             "exec"
+             "export"
+             "exports"
+             "file"
+             "fileoverview"
+             "final"
+             "func"
+             "function"
+             "generator"
+             "global"
+             "hidden"
+             "hideconstructor"
+             "ignore"
+             "implicitcast"
+             "inheritdoc"
+             "inner"
+             "instance"
+             "interface"
+             "license"
+             "method"
+             "mixin"
+             "noalias"
+             "noshadow"
+             "notypecheck"
+             "override"
+             "overview"
+             "owner"
+             "package"
+             "preserve"
+             "preservetry"
+             "private"
+             "protected"
+             "public"
+             "readonly"
+             "static"
+             "summary"
+             "supported"
+             "todo"
+             "tutorial"
+             "virtual"))
+          "\\)\\s-*")
+  "Matches empty jsdoc tags.")
+
+;; Note that this regexp by itself would match tslint flags that appear inside
+;; strings. The logic using this regexp must guard against it.
+(defconst typescript-tslint-flag-regexp
+  "\\(?://\\|/\\*\\)\\s-*\\(tslint:.*?\\)\\(?:\\*/\\|$\\)"
+  "Matches tslint flags.")
+
+;;; Faces
+
+(defface typescript-jsdoc-tag
+  '((t :foreground "SlateGray"))
+  "Face used to highlight @whatever tags in jsdoc comments."
+  :group 'typescript)
+
+(defface typescript-jsdoc-type
+  '((t :foreground "SteelBlue"))
+  "Face used to highlight {FooBar} types in jsdoc comments."
+  :group 'typescript)
+
+(defface typescript-jsdoc-value
+  '((t :foreground "gold4"))
+  "Face used to highlight tag values in jsdoc comments."
+  :group 'typescript)
+
+(defface typescript-access-modifier-face
+  '((t (:inherit font-lock-keyword-face)))
+  "Face used to highlight access modifiers."
+  :group 'typescript)
+
+(defface typescript-this-face
+  '((t (:inherit font-lock-keyword-face)))
+  "Face used to highlight 'this' keyword."
+  :group 'typescript)
+
+(defface typescript-primitive-face
+  '((t (:inherit font-lock-keyword-face)))
+  "Face used to highlight builtin types."
+  :group 'typescript)
+
+;;; User Customization
+
+(defgroup typescript nil
+  "Customization variables for typescript mode."
+  :tag "typescript"
+  :group 'languages)
+
+(defcustom typescript-indent-level 4
+  "Number of spaces for each indentation step in `typescript-mode'."
+  :type 'integer
+  :safe 'integerp
+  :group 'typescript)
+;;;###autoload(put 'typescript-indent-level 'safe-local-variable #'integerp)
+
+(defcustom typescript-expr-indent-offset 0
+  "Number of additional spaces used for indentation of continued expressions.
+The value must be no less than minus `typescript-indent-level'."
+  :type 'integer
+  :safe 'integerp
+  :group 'typescript)
+
+(defcustom typescript-indent-switch-clauses t
+  "Enable indenting of switch case and default clauses to
+replicate tsserver behaviour. Indent level is taken to be
+`typescript-indent-level'."
+  :type 'boolean
+  :group 'typescript)
+
+(defcustom typescript-auto-indent-flag t
+  "Whether to automatically indent when typing punctuation characters.
+If non-nil, the characters {}();,: also indent the current line
+in typescript mode."
+  :type 'boolean
+  :group 'typescript)
+
+(defcustom typescript-flat-functions nil
+  "Treat nested functions as top-level functions in `typescript-mode'.
+This applies to function movement, marking, and so on."
+  :type 'boolean
+  :group 'typescript)
+
+(defcustom typescript-comment-lineup-func #'c-lineup-C-comments
+  "Lineup function for `cc-mode-style', for C comments in `typescript-mode'."
+  :type 'function
+  :group 'typescript)
+
+(defcustom typescript-enabled-frameworks typescript--available-frameworks
+  "Frameworks recognized by `typescript-mode'.
+To improve performance, you may turn off some frameworks you
+seldom use, either globally or on a per-buffer basis."
+  :type (cons 'set (mapcar (lambda (x)
+                             (list 'const x))
+                           typescript--available-frameworks))
+  :group 'typescript)
+
+(defcustom typescript-mode-hook nil
+  "*Hook called by `typescript-mode'."
+  :type 'hook
+  :group 'typescript)
+
+(defcustom typescript-autoconvert-to-template-flag nil
+  "Non-nil means automatically convert plain strings to templates.
+
+When the flag is non-nil the `typescript-autoconvert-to-template'
+is called whenever a plain string delimiter is typed in the buffer."
+  :type 'boolean
+  :group 'typescript)
+
+;;; Public utilities
+
+(defun typescript-convert-to-template ()
+  "Convert the string at point to a template string."
+  (interactive)
+  (save-restriction
+    (widen)
+    (save-excursion
+      (let* ((syntax (syntax-ppss))
+             (str-terminator (nth 3 syntax))
+             (string-start (or (and str-terminator (nth 8 syntax))
+                               ;; We have to consider the case that we're on the start delimiter of a string.
+                               ;; We tentatively take (point) as string-start. If it turns out we're
+                               ;; wrong, then typescript--move-to-end-of-plain-string will fail anway,
+                               ;; and we won't use the bogus value.
+                               (progn
+                                 (forward-char)
+                                 (point)))))
+        (when (typescript--move-to-end-of-plain-string)
+          (let ((end-start (or (nth 8 (syntax-ppss)) -1)))
+            (undo-boundary)
+            (when (=  end-start string-start)
+              (delete-char 1)
+              (insert "`")))
+          (goto-char string-start)
+          (delete-char 1)
+          (insert "`"))))))
+
+(defun typescript-autoconvert-to-template ()
+  "Automatically convert a plain string to a teplate string, if needed.
+
+This function is meant to be automatically invoked when the user
+enters plain string delimiters.  It checks whether the character
+before point is the end of a string.  If it is, then it checks
+whether the string contains ${...}.  If it does, then it converts
+the string from a plain string to a template."
+  (interactive)
+  (save-restriction
+    (widen)
+    (save-excursion
+      (backward-char)
+      (when (and (memq (char-after) '(?' ?\"))
+                 (not (eq (char-before) ?\\)))
+        (let* ((string-start (nth 8 (syntax-ppss))))
+          (when (and string-start
+                     (save-excursion
+                       (re-search-backward "\\${.*?}" string-start t)))
+            (typescript-convert-to-template)))))))
+
+;;; KeyMap
+
+(defvar typescript-mode-map
+  (let ((keymap (make-sparse-keymap)))
+    (define-key keymap (kbd "C-c '") #'typescript-convert-to-template)
+    keymap)
+  "Keymap for `typescript-mode'.")
+
+(defun typescript--post-self-insert-function ()
+  (when (and (derived-mode-p 'typescript-mode)
+             typescript-autoconvert-to-template-flag
+             (or (eq last-command-event ?\')
+                 (eq last-command-event ?\")))
+    (typescript-autoconvert-to-template)))
+
+;;; Syntax table and parsing
+
+(defvar typescript-mode-syntax-table
+  (let ((table (make-syntax-table)))
+    (c-populate-syntax-table table)
+    (modify-syntax-entry ?$ "_" table)
+    (modify-syntax-entry ?` "\"" table)
+    table)
+  "Syntax table for `typescript-mode'.")
+
+(defvar typescript--quick-match-re nil
+  "Autogenerated regexp used by `typescript-mode' to match buffer constructs.")
+
+(defvar typescript--quick-match-re-func nil
+  "Autogenerated regexp used by `typescript-mode' to match constructs and functions.")
+
+(make-variable-buffer-local 'typescript--quick-match-re)
+(make-variable-buffer-local 'typescript--quick-match-re-func)
+
+(defvar typescript--cache-end 1
+  "Last valid buffer position for the `typescript-mode' function cache.")
+(make-variable-buffer-local 'typescript--cache-end)
+
+(defvar typescript--last-parse-pos nil
+  "Latest parse position reached by `typescript--ensure-cache'.")
+(make-variable-buffer-local 'typescript--last-parse-pos)
+
+(defvar typescript--state-at-last-parse-pos nil
+  "Parse state at `typescript--last-parse-pos'.")
+(make-variable-buffer-local 'typescript--state-at-last-parse-pos)
+
+(defun typescript--flatten-list (list)
+  (cl-loop for item in list
+        nconc (cond ((consp item)
+                     (typescript--flatten-list item))
+                    (item (list item)))))
+
+(defun typescript--maybe-join (prefix separator suffix &rest list)
+  "Helper function for `typescript--update-quick-match-re'.
+If LIST contains any element that is not nil, return its non-nil
+elements, separated by SEPARATOR, prefixed by PREFIX, and ended
+with SUFFIX as with `concat'.  Otherwise, if LIST is empty, return
+nil.  If any element in LIST is itself a list, flatten that
+element."
+  (setq list (typescript--flatten-list list))
+  (when list
+    (concat prefix (mapconcat #'identity list separator) suffix)))
+
+(defun typescript--update-quick-match-re ()
+  "Internal function used by `typescript-mode' for caching buffer constructs.
+This updates `typescript--quick-match-re', based on the current set of
+enabled frameworks."
+  (setq typescript--quick-match-re
+        (typescript--maybe-join
+         "^[ \t]*\\(?:" "\\|" "\\)"
+
+         ;; #define mumble
+         "#define[ \t]+[a-zA-Z_]"
+
+         (when (memq 'exttypescript typescript-enabled-frameworks)
+           "Ext\\.extend")
+
+         (when (memq 'prototype typescript-enabled-frameworks)
+           "Object\\.extend")
+
+          ;; var mumble = THING (
+         (typescript--maybe-join
+          "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
+          "\\|"
+          "\\)[ \t]*\("
+
+          (when (memq 'prototype typescript-enabled-frameworks)
+                    "Class\\.create")
+
+          (when (memq 'exttypescript typescript-enabled-frameworks)
+            "Ext\\.extend")
+
+          (when (memq 'merrillpress typescript-enabled-frameworks)
+            "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))
+
+         (when (memq 'dojo typescript-enabled-frameworks)
+           "dojo\\.declare[ \t]*\(")
+
+         (when (memq 'mochikit typescript-enabled-frameworks)
+           "MochiKit\\.Base\\.update[ \t]*\(")
+
+         ;; mumble.prototypeTHING
+         (typescript--maybe-join
+          "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"
+
+          (when (memq 'typescript typescript-enabled-frameworks)
+            '( ;; foo.prototype.bar = function(
+              "\\.[a-zA-Z_$0-9]+[ \t]*=[ \t]*function[ \t]*\("
+
+              ;; mumble.prototype = {
+              "[ \t]*=[ \t]*{")))))
+
+  (setq typescript--quick-match-re-func
+        (concat "function\\|" typescript--quick-match-re)))
+
+(defun typescript--forward-text-property (propname)
+  "Move over the next value of PROPNAME in the buffer.
+If found, return that value and leave point after the character
+having that value; otherwise, return nil and leave point at EOB."
+  (let ((next-value (get-text-property (point) propname)))
+    (if next-value
+        (forward-char)
+
+      (goto-char (next-single-property-change
+                  (point) propname nil (point-max)))
+      (unless (eobp)
+        (setq next-value (get-text-property (point) propname))
+        (forward-char)))
+
+    next-value))
+
+(defun typescript--backward-text-property (propname)
+  "Move over the previous value of PROPNAME in the buffer.
+If found, return that value and leave point just before the
+character that has that value, otherwise return nil and leave
+point at BOB."
+    (unless (bobp)
+      (let ((prev-value (get-text-property (1- (point)) propname)))
+        (if prev-value
+            (backward-char)
+
+          (goto-char (previous-single-property-change
+                      (point) propname nil (point-min)))
+
+          (unless (bobp)
+            (backward-char)
+            (setq prev-value (get-text-property (point) propname))))
+
+        prev-value)))
+
+(defsubst typescript--forward-pstate ()
+  (typescript--forward-text-property 'typescript--pstate))
+
+(defsubst typescript--backward-pstate ()
+  (typescript--backward-text-property 'typescript--pstate))
+
+(defun typescript--pitem-goto-h-end (pitem)
+  (goto-char (typescript--pitem-h-begin pitem))
+  (typescript--forward-pstate))
+
+(defun typescript--re-search-forward-inner (regexp &optional bound count)
+  "Helper function for `typescript--re-search-forward'."
+  (let ((parse)
+        str-terminator)
+    (while (> count 0)
+      (re-search-forward regexp bound)
+      (setq parse (syntax-ppss))
+      (cond ((setq str-terminator (nth 3 parse))
+             (when (eq str-terminator t)
+               (setq str-terminator ?/))
+             (re-search-forward
+              (concat "\\([^\\]\\|^\\)" (string str-terminator))
+              (save-excursion (end-of-line) (point)) t))
+            ((nth 7 parse)
+             (forward-line))
+            ((or (nth 4 parse)
+                 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
+             (re-search-forward "\\*/"))
+            (t
+             (setq count (1- count))))))
+  (point))
+
+
+(defun typescript--re-search-forward (regexp &optional bound noerror count)
+  "Search forward, ignoring strings and comments.
+This function invokes `re-search-forward', but treats the buffer
+as if strings and comments have been removed."
+  (let ((saved-point (point))
+        (search-expr
+         (cond ((null count)
+                '(typescript--re-search-forward-inner regexp bound 1))
+               ((< count 0)
+                '(typescript--re-search-backward-inner regexp bound (- count)))
+               ((> count 0)
+                '(typescript--re-search-forward-inner regexp bound count)))))
+    (condition-case err
+        (eval search-expr)
+      (search-failed
+       (goto-char saved-point)
+       (unless noerror
+         (error (error-message-string err)))))))
+
+
+(defun typescript--re-search-backward-inner (regexp &optional bound count)
+  "Auxiliary function for `typescript--re-search-backward'."
+  (let ((parse))
+    (while (> count 0)
+      (re-search-backward regexp bound)
+      (when (and (> (point) (point-min))
+                 (save-excursion (backward-char) (looking-at "/[/*]")))
+        (forward-char))
+      (setq parse (syntax-ppss))
+      (cond
+       ;; If we are in a comment or a string, jump back to the start
+       ;; of the comment or string.
+       ((nth 8 parse)
+        (goto-char (nth 8 parse)))
+       ((and (eq (char-before) ?/) (eq (char-after) ?*))
+        (re-search-backward "/\\*"))
+       (t
+        (setq count (1- count))))))
+  (point))
+
+
+(defun typescript--re-search-backward (regexp &optional bound noerror count)
+  "Search backward, ignoring strings, and comments.
+
+This function invokes `re-search-backward' but treats the buffer
+as if strings and comments have been removed.
+
+IMPORTANT NOTE: searching for \"\\n\" with this function to find
+line breaks will generally not work, because the final newline of
+a one-line comment is considered to be part of the comment and
+will be skipped.  Take the following code:
+
+  let a = 1;
+  let b = 2; // Foo
+  let c = 3;
+
+If the point is in the last line, searching back for \"\\n\" will
+skip over the line with \"let b\". The newline found will be the
+one at the end of the line with \"let a\"."
+  (let ((saved-point (point))
+        (search-expr
+         (cond ((null count)
+                '(typescript--re-search-backward-inner regexp bound 1))
+               ((< count 0)
+                '(typescript--re-search-forward-inner regexp bound (- count)))
+               ((> count 0)
+                '(typescript--re-search-backward-inner regexp bound count)))))
+    (condition-case err
+        (eval search-expr)
+      (search-failed
+       (goto-char saved-point)
+       (unless noerror
+         (error (error-message-string err)))))))
+
+(defun typescript--forward-expression ()
+  "Move forward over a whole typescript expression.
+This function doesn't move over expressions continued across
+lines."
+  (cl-loop
+   do (progn
+        (forward-comment most-positive-fixnum)
+        (cl-loop until (or (eolp)
+                        (progn
+                          (forward-comment most-positive-fixnum)
+                          (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
+              do (forward-sexp)))
+   while (and (eq (char-after) ?\n)
+              (save-excursion
+                (forward-char)
+                (typescript--continued-expression-p)))))
+
+(defun typescript--forward-function-decl ()
+  "Move forward over a typescript function declaration.
+This puts point at the 'function' keyword.
+
+If this is a syntactically-correct non-expression function,
+return the name of the function, or t if the name could not be
+determined.  Otherwise, return nil."
+  (cl-assert (looking-at "\\_<function\\_>"))
+  (let ((name t))
+    (forward-word)
+    (forward-comment most-positive-fixnum)
+    (when (looking-at typescript--name-re)
+      (setq name (match-string-no-properties 0))
+      (goto-char (match-end 0)))
+    (forward-comment most-positive-fixnum)
+    (and (eq (char-after) ?\( )
+         (ignore-errors (forward-list) t)
+         (progn (forward-comment most-positive-fixnum)
+                (and (eq (char-after) ?{)
+                     name)))))
+
+(defun typescript--function-prologue-beginning (&optional pos)
+  "Return the start of the typescript function prologue containing POS.
+A function prologue is everything from start of the definition up
+to and including the opening brace.  POS defaults to point.
+If POS is not in a function prologue, return nil."
+  (let (prologue-begin)
+    (save-excursion
+      (if pos
+          (goto-char pos)
+        (setq pos (point)))
+
+      (when (save-excursion
+              (forward-line 0)
+              (or (looking-at typescript--function-heading-2-re)
+                  (looking-at typescript--function-heading-3-re)))
+
+        (setq prologue-begin (match-beginning 1))
+        (when (<= prologue-begin pos)
+          (goto-char (match-end 0))))
+
+      (skip-syntax-backward "w_")
+      (and (or (looking-at "\\_<function\\_>")
+               (typescript--re-search-backward "\\_<function\\_>" nil t))
+
+           (save-match-data (goto-char (match-beginning 0))
+                            (typescript--forward-function-decl))
+
+           (<= pos (point))
+           (or prologue-begin (match-beginning 0))))))
+
+(defun typescript--beginning-of-defun-raw ()
+  "Helper function for `typescript-beginning-of-defun'.
+Go to previous defun-beginning and return the parse state for it,
+or nil if we went all the way back to bob and don't find
+anything."
+  (typescript--ensure-cache)
+  (let (pstate)
+    (while (and (setq pstate (typescript--backward-pstate))
+                (not (eq 'function (typescript--pitem-type (car pstate))))))
+    (and (not (bobp)) pstate)))
+
+(defun typescript--pstate-is-toplevel-defun (pstate)
+  "Helper function for `typescript--beginning-of-defun-nested'.
+If PSTATE represents a non-empty top-level defun, return the
+top-most pitem.  Otherwise, return nil."
+  (cl-loop for pitem in pstate
+        with func-depth = 0
+        with func-pitem
+        if (eq 'function (typescript--pitem-type pitem))
+        do (cl-incf func-depth)
+        and do (setq func-pitem pitem)
+        finally return (if (eq func-depth 1) func-pitem)))
+
+(defun typescript--beginning-of-defun-nested ()
+  "Helper function for `typescript--beginning-of-defun'.
+Return the pitem of the function we went to the beginning of."
+  (or
+   ;; Look for the smallest function that encloses point...
+   (cl-loop for pitem in (typescript--parse-state-at-point)
+         if (and (eq 'function (typescript--pitem-type pitem))
+                 (typescript--inside-pitem-p pitem))
+         do (goto-char (typescript--pitem-h-begin pitem))
+         and return pitem)
+
+   ;; ...and if that isn't found, look for the previous top-level
+   ;; defun
+   (cl-loop for pstate = (typescript--backward-pstate)
+         while pstate
+         if (typescript--pstate-is-toplevel-defun pstate)
+         do (goto-char (typescript--pitem-h-begin it))
+         and return it)))
+
+(defun typescript--beginning-of-defun-flat ()
+  "Helper function for `typescript-beginning-of-defun'."
+  (let ((pstate (typescript--beginning-of-defun-raw)))
+    (when pstate
+      (goto-char (typescript--pitem-h-begin (car pstate))))))
+
+(defun typescript-beginning-of-defun (&optional arg)
+  "Value of `beginning-of-defun-function' for `typescript-mode'."
+  (setq arg (or arg 1))
+  (while (and (not (eobp)) (< arg 0))
+    (cl-incf arg)
+    (when (and (not typescript-flat-functions)
+               (or (eq (typescript-syntactic-context) 'function)
+                   (typescript--function-prologue-beginning)))
+      (typescript-end-of-defun))
+
+    (if (typescript--re-search-forward
+         "\\_<function\\_>" nil t)
+        (goto-char (typescript--function-prologue-beginning))
+      (goto-char (point-max))))
+
+  (while (> arg 0)
+    (cl-decf arg)
+    ;; If we're just past the end of a function, the user probably wants
+    ;; to go to the beginning of *that* function
+    (when (eq (char-before) ?})
+      (backward-char))
+
+    (let ((prologue-begin (typescript--function-prologue-beginning)))
+      (cond ((and prologue-begin (< prologue-begin (point)))
+             (goto-char prologue-begin))
+
+            (typescript-flat-functions
+             (typescript--beginning-of-defun-flat))
+            (t
+             (typescript--beginning-of-defun-nested))))))
+
+(defun typescript--flush-caches (&optional beg ignored)
+  "Flush the `typescript-mode' syntax cache after position BEG.
+BEG defaults to `point-min', meaning to flush the entire cache."
+  (interactive)
+  (setq beg (or beg (save-restriction (widen) (point-min))))
+  (setq typescript--cache-end (min typescript--cache-end beg)))
+
+(defmacro typescript--debug (&rest arguments)
+  ;; `(message ,@arguments)
+  )
+
+(defun typescript--ensure-cache--pop-if-ended (open-items paren-depth)
+  (let ((top-item (car open-items)))
+    (when (<= paren-depth (typescript--pitem-paren-depth top-item))
+      (cl-assert (not (get-text-property (1- (point)) 'typescript-pend)))
+      (put-text-property (1- (point)) (point) 'typescript--pend top-item)
+      (setf (typescript--pitem-b-end top-item) (point))
+      (setq open-items
+            ;; open-items must contain at least two items for this to
+            ;; work, but because we push a dummy item to start with,
+            ;; that assumption holds.
+            (cons (typescript--pitem-add-child (cl-second open-items) top-item)
+                  (cddr open-items)))))
+  open-items)
+
+(defmacro typescript--ensure-cache--update-parse ()
+  "Helper function for `typescript--ensure-cache'.
+Update parsing information up to point, referring to parse,
+prev-parse-point, goal-point, and open-items bound lexically in
+the body of `typescript--ensure-cache'."
+  `(progn
+     (setq goal-point (point))
+     (goto-char prev-parse-point)
+     (while (progn
+              (setq open-items (typescript--ensure-cache--pop-if-ended
+                                open-items (car parse)))
+              ;; Make sure parse-partial-sexp doesn't stop because we *entered*
+              ;; the given depth -- i.e., make sure we're deeper than the target
+              ;; depth.
+              (cl-assert (> (nth 0 parse)
+                         (typescript--pitem-paren-depth (car open-items))))
+              (setq parse (parse-partial-sexp
+                           prev-parse-point goal-point
+                           (typescript--pitem-paren-depth (car open-items))
+                           nil parse))
+
+;;              (let ((overlay (make-overlay prev-parse-point (point))))
+;;                (overlay-put overlay 'face '(:background "red"))
+;;                (unwind-protect
+;;                     (progn
+;;                       (typescript--debug "parsed: %S" parse)
+;;                       (sit-for 1))
+;;                  (delete-overlay overlay)))
+
+              (setq prev-parse-point (point))
+              (< (point) goal-point)))
+
+     (setq open-items (typescript--ensure-cache--pop-if-ended
+                       open-items (car parse)))))
+
+(defun typescript--show-cache-at-point ()
+  (interactive)
+  (require 'pp)
+  (let ((prop (get-text-property (point) 'typescript--pstate)))
+    (with-output-to-temp-buffer "*Help*"
+      (pp prop))))
+
+(defun typescript--split-name (string)
+  "Split a typescript name into its dot-separated parts.
+This also removes any prototype parts from the split name
+\(unless the name is just \"prototype\" to start with)."
+  (let ((name (save-match-data
+                (split-string string "\\." t))))
+    (unless (and (= (length name) 1)
+                 (equal (car name) "prototype"))
+
+      (setq name (remove "prototype" name)))))
+
+(defvar typescript--guess-function-name-start nil)
+
+(defun typescript--guess-function-name (position)
+  "Guess the name of the typescript function at POSITION.
+POSITION should be just after the end of the word \"function\".
+Return the name of the function, or nil if the name could not be
+guessed.
+
+This function clobbers match data.  If we find the preamble
+begins earlier than expected while guessing the function name,
+set `typescript--guess-function-name-start' to that position; otherwise,
+set that variable to nil."
+  (setq typescript--guess-function-name-start nil)
+  (save-excursion
+    (goto-char position)
+    (forward-line 0)
+    (cond
+     ((looking-at typescript--function-heading-3-re)
+      (and (eq (match-end 0) position)
+           (setq typescript--guess-function-name-start (match-beginning 1))
+           (match-string-no-properties 1)))
+
+     ((looking-at typescript--function-heading-2-re)
+      (and (eq (match-end 0) position)
+           (setq typescript--guess-function-name-start (match-beginning 1))
+           (match-string-no-properties 1))))))
+
+(defun typescript--clear-stale-cache ()
+  ;; Clear any endings that occur after point
+  (let (end-prop)
+    (save-excursion
+      (while (setq end-prop (typescript--forward-text-property
+                             'typescript--pend))
+        (setf (typescript--pitem-b-end end-prop) nil))))
+
+  ;; Remove any cache properties after this point
+  (remove-text-properties (point) (point-max)
+                          '(typescript--pstate t typescript--pend t)))
+
+(defun typescript--ensure-cache (&optional limit)
+  "Ensures brace cache is valid up to the character before LIMIT.
+LIMIT defaults to point."
+  (setq limit (or limit (point)))
+  (when (< typescript--cache-end limit)
+
+    (c-save-buffer-state
+        (open-items
+         orig-match-start
+         orig-match-end
+         orig-depth
+         parse
+         prev-parse-point
+         name
+         case-fold-search
+         filtered-class-styles
+         new-item
+         goal-point
+         end-prop)
+
+      ;; Figure out which class styles we need to look for
+      (setq filtered-class-styles
+            (cl-loop for style in typescript--class-styles
+                  if (memq (plist-get style :framework)
+                           typescript-enabled-frameworks)
+                  collect style))
+
+      (save-excursion
+        (save-restriction
+          (widen)
+
+          ;; Find last known good position
+          (goto-char typescript--cache-end)
+          (unless (bobp)
+            (setq open-items (get-text-property
+                              (1- (point)) 'typescript--pstate))
+
+            (unless open-items
+              (goto-char (previous-single-property-change
+                          (point) 'typescript--pstate nil (point-min)))
+
+              (unless (bobp)
+                (setq open-items (get-text-property (1- (point))
+                                                    'typescript--pstate))
+                (cl-assert open-items))))
+
+          (unless open-items
+            ;; Make a placeholder for the top-level definition
+            (setq open-items (list typescript--initial-pitem)))
+
+          (setq parse (syntax-ppss))
+          (setq prev-parse-point (point))
+
+          (typescript--clear-stale-cache)
+
+          (narrow-to-region (point-min) limit)
+
+          (cl-loop while (re-search-forward typescript--quick-match-re-func nil t)
+                for orig-match-start = (goto-char (match-beginning 0))
+                for orig-match-end = (match-end 0)
+                do (typescript--ensure-cache--update-parse)
+                for orig-depth = (nth 0 parse)
+
+                ;; Each of these conditions should return non-nil if
+                ;; we should add a new item and leave point at the end
+                ;; of the new item's header (h-end in the
+                ;; typescript--pitem diagram). This point is the one
+                ;; after the last character we need to unambiguously
+                ;; detect this construct. If one of these evaluates to
+                ;; nil, the location of the point is ignored.
+                if (cond
+                    ;; In comment or string
+                    ((nth 8 parse) nil)
+
+                    ;; Regular function declaration
+                    ((and (looking-at "\\_<function\\_>")
+                          (setq name (typescript--forward-function-decl)))
+
+                     (when (eq name t)
+                       (setq name (typescript--guess-function-name orig-match-end))
+                       (if name
+                           (when typescript--guess-function-name-start
+                             (setq orig-match-start
+                                   typescript--guess-function-name-start))
+
+                         (setq name t)))
+
+                     (cl-assert (eq (char-after) ?{))
+                     (forward-char)
+                     (make-typescript--pitem
+                      :paren-depth orig-depth
+                      :h-begin orig-match-start
+                      :type 'function
+                      :name (if (eq name t)
+                                name
+                              (typescript--split-name name))))
+
+                    ;; "Prototype function" declaration
+                    ((looking-at typescript--plain-method-re)
+                     (goto-char (match-beginning 3))
+                     (when (save-match-data
+                             (typescript--forward-function-decl))
+                       (forward-char)
+                       (make-typescript--pitem
+                        :paren-depth orig-depth
+                        :h-begin orig-match-start
+                        :type 'function
+                        :name (nconc (typescript--split-name
+                                      (match-string-no-properties 1))
+                                     (list (match-string-no-properties 2))))))
+
+                    ;; Class definition
+                    ((cl-loop with syntactic-context =
+                           (typescript--syntactic-context-from-pstate open-items)
+                           for class-style in filtered-class-styles
+                           if (and (memq syntactic-context
+                                         (plist-get class-style :contexts))
+                                   (looking-at (plist-get class-style
+                                                          :class-decl)))
+                           do (goto-char (match-end 0))
+                           and return
+                           (make-typescript--pitem
+                            :paren-depth orig-depth
+                            :h-begin orig-match-start
+                            :type class-style
+                            :name (typescript--split-name
+                                   (match-string-no-properties 1))))))
+
+                do (typescript--ensure-cache--update-parse)
+                and do (push it open-items)
+                and do (put-text-property
+                        (1- (point)) (point) 'typescript--pstate open-items)
+                else do (goto-char orig-match-end))
+
+          (goto-char limit)
+          (typescript--ensure-cache--update-parse)
+          (setq typescript--cache-end limit)
+          (setq typescript--last-parse-pos limit)
+          (setq typescript--state-at-last-parse-pos open-items))))))
+
+(defun typescript--end-of-defun-flat ()
+  "Helper function for `typescript-end-of-defun'."
+  (cl-loop while (typescript--re-search-forward "}" nil t)
+        do (typescript--ensure-cache)
+        if (get-text-property (1- (point)) 'typescript--pend)
+        if (eq 'function (typescript--pitem-type it))
+        return t
+        finally do (goto-char (point-max))))
+
+(defun typescript--end-of-defun-nested ()
+  "Helper function for `typescript-end-of-defun'."
+  (let* (pitem
+         (this-end (save-excursion
+                     (and (setq pitem (typescript--beginning-of-defun-nested))
+                          (typescript--pitem-goto-h-end pitem)
+                          (progn (backward-char)
+                                 (forward-list)
+                                 (point)))))
+         found)
+
+    (if (and this-end (< (point) this-end))
+        ;; We're already inside a function; just go to its end.
+        (goto-char this-end)
+
+      ;; Otherwise, go to the end of the next function...
+      (while (and (typescript--re-search-forward "\\_<function\\_>" nil t)
+                  (not (setq found (progn
+                                     (goto-char (match-beginning 0))
+                                     (typescript--forward-function-decl))))))
+
+      (if found (forward-list)
+        ;; ... or eob.
+        (goto-char (point-max))))))
+
+(defun typescript-end-of-defun (&optional arg)
+  "Value of `end-of-defun-function' for `typescript-mode'."
+  (setq arg (or arg 1))
+  (while (and (not (bobp)) (< arg 0))
+    (cl-incf arg)
+    (typescript-beginning-of-defun)
+    (typescript-beginning-of-defun)
+    (unless (bobp)
+      (typescript-end-of-defun)))
+
+  (while (> arg 0)
+    (cl-decf arg)
+    ;; look for function backward. if we're inside it, go to that
+    ;; function's end. otherwise, search for the next function's end and
+    ;; go there
+    (if typescript-flat-functions
+        (typescript--end-of-defun-flat)
+
+      ;; if we're doing nested functions, see whether we're in the
+      ;; prologue. If we are, go to the end of the function; otherwise,
+      ;; call typescript--end-of-defun-nested to do the real work
+      (let ((prologue-begin (typescript--function-prologue-beginning)))
+        (cond ((and prologue-begin (<= prologue-begin (point)))
+               (goto-char prologue-begin)
+               (re-search-forward "\\_<function")
+               (goto-char (match-beginning 0))
+               (typescript--forward-function-decl)
+               (forward-list))
+
+              (t (typescript--end-of-defun-nested)))))))
+
+(defun typescript--backward-syntactic-ws (&optional lim)
+  "Simple implementation of `c-backward-syntactic-ws' for `typescript-mode'."
+  (save-restriction
+    (when lim (narrow-to-region lim (point-max)))
+
+    (let ((pos (point)))
+      (while (progn (forward-comment most-negative-fixnum)
+                    (/= (point)
+                        (prog1
+                            pos
+                          (setq pos (point)))))))))
+
+(defun typescript--forward-syntactic-ws (&optional lim)
+  "Simple implementation of `c-forward-syntactic-ws' for `typescript-mode'."
+  (save-restriction
+    (when lim (narrow-to-region (point-min) lim))
+    (let ((pos (point)))
+      (while (progn
+               (forward-comment most-positive-fixnum)
+               (/= (point)
+                   (prog1
+                       pos
+                     (setq pos (point)))))))))
+
+;; Like (up-list -1), but only considers lists that end nearby"
+(defun typescript--up-nearby-list ()
+  (save-restriction
+    ;; Look at a very small region so our compuation time doesn't
+    ;; explode in pathological cases.
+    (narrow-to-region (max (point-min) (- (point) 500)) (point))
+    (up-list -1)))
+
+(defun typescript--inside-param-list-p ()
+  "Return non-nil iff point is in a function parameter list."
+  (ignore-errors
+    (save-excursion
+      (typescript--up-nearby-list)
+      (and (looking-at "(")
+           (progn (forward-symbol -1)
+                  (or (looking-at "function")
+                      (progn (forward-symbol -1)
+                             (looking-at "function"))))))))
+
+(defun typescript--inside-dojo-class-list-p ()
+  "Return non-nil iff point is in a Dojo multiple-inheritance class block."
+  (ignore-errors
+    (save-excursion
+      (typescript--up-nearby-list)
+      (let ((list-begin (point)))
+        (forward-line 0)
+        (and (looking-at typescript--dojo-class-decl-re)
+             (goto-char (match-end 0))
+             (looking-at "\"\\s-*,\\s-*\\[")
+             (eq (match-end 0) (1+ list-begin)))))))
+
+(defun typescript--syntax-begin-function ()
+  (when (< typescript--cache-end (point))
+    (goto-char (max (point-min) typescript--cache-end)))
+
+  (let ((pitem))
+    (while (and (setq pitem (car (typescript--backward-pstate)))
+                (not (eq 0 (typescript--pitem-paren-depth pitem)))))
+
+    (when pitem
+      (goto-char (typescript--pitem-h-begin pitem )))))
+
+(defun typescript--move-to-end-of-plain-string ()
+  "If the point is in a plain string, move to the end of it.
+
+Otherwise, don't move.  A plain string is a string which is not a
+template string.  The point is considered to be \"in\" a string if
+it is on the delimiters of the string, or any point inside.
+
+Returns point if the end of the string was found, or nil if the
+end of the string was not found."
+  (let ((end-position
+         (save-excursion
+           (let* ((syntax (syntax-ppss))
+                  (str-terminator (nth 3 syntax))
+                  ;; The 8th element will also be set if we are in a comment. So we
+                  ;; check str-terminator to protect against that.
+                  (string-start (and str-terminator
+                                     (nth 8 syntax))))
+             (if (and string-start
+                      (not (eq str-terminator ?`)))
+                 ;; We may already be at the end of the string.
+                 (if (and (eq (char-after) str-terminator)
+                          (not (eq (char-before) ?\\)))
+                     (point)
+                   ;; We just search forward and then check if the hit we get has a
+                   ;; string-start equal to ours.
+                   (cl-loop while (re-search-forward
+                                (concat "\\(?:[^\\]\\|^\\)\\(" (string str-terminator) "\\)")
+                                nil t)
+                         if (eq string-start
+                                (save-excursion (nth 8 (syntax-ppss (match-beginning 1)))))
+                         return (match-beginning 1)))
+               ;; If we are on the start delimiter then the value of syntax-ppss will look
+               ;; like we're not in a string at all, but this function considers the
+               ;; start delimiter to be "in" the string. We take care of this here.
+               (when (memq (char-after) '(?' ?\"))
+                 (forward-char)
+                 (typescript--move-to-end-of-plain-string)))))))
+    (when end-position
+      (goto-char end-position))))
+
+;;; Font Lock
+(defun typescript--make-framework-matcher (framework &rest regexps)
+  "Helper function for building `typescript--font-lock-keywords'.
+Create a byte-compiled function for matching a concatenation of
+REGEXPS, but only if FRAMEWORK is in `typescript-enabled-frameworks'."
+  (setq regexps (apply #'concat regexps))
+  (byte-compile
+   `(lambda (limit)
+      (when (memq (quote ,framework) typescript-enabled-frameworks)
+        (re-search-forward ,regexps limit t)))))
+
+(defvar typescript--tmp-location nil)
+(make-variable-buffer-local 'typescript--tmp-location)
+
+(defun typescript--forward-destructuring-spec (&optional func)
+  "Move forward over a typescript destructuring spec.
+If FUNC is supplied, call it with no arguments before every
+variable name in the spec.  Return true iff this was actually a
+spec.  FUNC must preserve the match data."
+  (cl-case (char-after)
+    (?\[
+     (forward-char)
+     (while
+         (progn
+           (forward-comment most-positive-fixnum)
+           (cond ((memq (char-after) '(?\[ ?\{))
+                  (typescript--forward-destructuring-spec func))
+
+                 ((eq (char-after) ?,)
+                  (forward-char)
+                  t)
+
+                 ((looking-at typescript--name-re)
+                  (and func (funcall func))
+                  (goto-char (match-end 0))
+                  t))))
+     (when (eq (char-after) ?\])
+       (forward-char)
+       t))
+
+    (?\{
+     (forward-char)
+     (forward-comment most-positive-fixnum)
+     (while
+         (when (looking-at typescript--objfield-re)
+           (goto-char (match-end 0))
+           (forward-comment most-positive-fixnum)
+           (and (cond ((memq (char-after) '(?\[ ?\{))
+                       (typescript--forward-destructuring-spec func))
+                      ((looking-at typescript--name-re)
+                       (and func (funcall func))
+                       (goto-char (match-end 0))
+                       t))
+                (progn (forward-comment most-positive-fixnum)
+                       (when (eq (char-after) ?\,)
+                         (forward-char)
+                         (forward-comment most-positive-fixnum)
+                         t)))))
+     (when (eq (char-after) ?\})
+       (forward-char)
+       t))))
+
+(defun typescript--variable-decl-matcher (limit)
+  "Font-lock matcher for variable names in a variable declaration.
+This is a cc-mode-style matcher that *always* fails, from the
+point of view of font-lock.  It applies highlighting directly with
+`font-lock-apply-highlight'."
+  (condition-case nil
+      (save-restriction
+        (narrow-to-region (point-min) limit)
+
+        (let ((first t))
+          (forward-comment most-positive-fixnum)
+          (while
+              (and (or first
+                       (when (eq (char-after) ?,)
+                         (forward-char)
+                         (forward-comment most-positive-fixnum)
+                         t))
+                   (cond ((looking-at typescript--name-re)
+                          (font-lock-apply-highlight
+                           '(0 font-lock-variable-name-face))
+                          (goto-char (match-end 0)))
+
+                         ((save-excursion
+                            (typescript--forward-destructuring-spec))
+
+                          (typescript--forward-destructuring-spec
+                           (lambda ()
+                             (font-lock-apply-highlight
+                              '(0 font-lock-variable-name-face)))))))
+
+            (forward-comment most-positive-fixnum)
+            (when (eq (char-after) ?=)
+              (forward-char)
+              (typescript--forward-expression)
+              (forward-comment most-positive-fixnum))
+
+            (setq first nil))))
+
+    ;; Conditions to handle
+    (scan-error nil)
+    (end-of-buffer nil))
+
+  ;; Matcher always "fails"
+  nil)
+
+(defun typescript--in-documentation-comment-p ()
+  "Reports whether point is inside a documentation comment."
+  (let ((parse (syntax-ppss)))
+    (and
+     (nth 4 parse) ;; Inside a comment ...
+     (save-match-data
+       (save-excursion
+         (goto-char (nth 8 parse))
+         (looking-at "/\\*\\*")))))) ;; ... which starts with /**
+
+(defun typescript--documentation-font-lock-helper (re limit)
+  "This is a helper macro that determines whether jsdoc highlighting is to be applied,
+and searches for the next token to be highlighted."
+  (cl-loop while (re-search-forward re limit t)
+        if (typescript--in-documentation-comment-p)
+        return (point)))
+
+(defun typescript--jsdoc-param-matcher (limit)
+  "Font-lock mode matcher that finds jsdoc parameter tags in documentation."
+  (typescript--documentation-font-lock-helper typescript-jsdoc-param-tag-regexp limit))
+
+(defun typescript--jsdoc-typed-tag-matcher (limit)
+  "Font-lock mode matcher that finds jsdoc typed tags in documentation."
+  (typescript--documentation-font-lock-helper typescript-jsdoc-typed-tag-regexp limit))
+
+(defun typescript--jsdoc-arg-tag-matcher (limit)
+  "Font-lock mode matcher that finds jsdoc tags that take one argument in documentation."
+  (typescript--documentation-font-lock-helper typescript-jsdoc-arg-tag-regexp limit))
+
+(defun typescript--jsdoc-empty-tag-matcher (limit)
+  "Font-lock mode matcher that finds jsdoc tags without argument in documentation."
+  (typescript--documentation-font-lock-helper typescript-jsdoc-empty-tag-regexp limit))
+
+(defun typescript--typedoc-link-matcher (limit)
+  "Font-lock mode matcher that finds typedoc links in documentation."
+  (typescript--documentation-font-lock-helper typescript-typedoc-link-tag-regexp limit))
+
+(defun typescript--typedoc-literal-markup-matcher (limit)
+  "Font-lock mode matcher that finds typedoc literal markup in documentation."
+  (typescript--documentation-font-lock-helper typescript-typedoc-literal-markup-regexp limit))
+
+(defun typescript--tslint-flag-matcher (limit)
+  "Font-lock mode matcher that finds tslint flags in comments."
+  (cl-loop while (re-search-forward typescript-tslint-flag-regexp limit t)
+        if (nth 4 (syntax-ppss (match-beginning 1)))
+        return (point)))
+
+(defconst typescript--font-lock-keywords-3
+  `(
+    ,@typescript--font-lock-keywords-2
+
+    (typescript--jsdoc-param-matcher (1 'typescript-jsdoc-tag t t)
+                                     (2 'typescript-jsdoc-type t t)
+                                     (3 'typescript-jsdoc-value t t))
+
+    (typescript--jsdoc-typed-tag-matcher (1 'typescript-jsdoc-tag t t)
+                                         (2 'typescript-jsdoc-type t t))
+
+    (typescript--jsdoc-arg-tag-matcher (1 'typescript-jsdoc-tag t t)
+                                       (2 'typescript-jsdoc-value t t))
+
+    (typescript--jsdoc-empty-tag-matcher (1 'typescript-jsdoc-tag t t))
+
+    (typescript--typedoc-link-matcher (0 'typescript-jsdoc-value t))
+
+    (typescript--typedoc-literal-markup-matcher
+     (0 'typescript-jsdoc-value t))
+
+    (typescript--tslint-flag-matcher
+     (1 font-lock-preprocessor-face t))
+
+    ("\\.\\(prototype\\)\\_>"
+     (1 font-lock-constant-face))
+
+    (,(rx symbol-start "class" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
+     (1 font-lock-type-face))
+
+    (,(rx symbol-start "extends" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
+     (1 font-lock-type-face))
+
+    (,(rx symbol-start "implements" (+ space))
+     (,(rx symbol-start (+ (syntax word))) nil nil (0 font-lock-type-face)))
+
+    (,(rx symbol-start "interface" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
+     (1 font-lock-type-face))
+
+    (,(rx symbol-start "type" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
+     (1 font-lock-type-face))
+
+    (,(rx symbol-start "enum" (+ space) (group (+ (or (syntax word) (syntax symbol)))))
+     (1 font-lock-type-face))
+
+    ;; Highlights class being declared, in parts
+    (typescript--class-decl-matcher
+     ,(concat "\\(" typescript--name-re "\\)\\(?:\\.\\|.*$\\)")
+     (goto-char (match-beginning 1))
+     nil
+     (1 font-lock-type-face))
+
+    ;; Highlights parent class, in parts, if available
+    (typescript--class-decl-matcher
+     ,(concat "\\(" typescript--name-re "\\)\\(?:\\.\\|.*$\\)")
+     (if (match-beginning 2)
+         (progn
+           (setq typescript--tmp-location (match-end 2))
+           (goto-char typescript--tmp-location)
+           (insert "=")
+           (goto-char (match-beginning 2)))
+       (setq typescript--tmp-location nil)
+       (goto-char (point-at-eol)))
+     (when typescript--tmp-location
+       (save-excursion
+         (goto-char typescript--tmp-location)
+         (delete-char 1)))
+     (1 font-lock-type-face))
+
+    ;; Highlights parent class
+    (typescript--class-decl-matcher
+     (2 font-lock-type-face nil t))
+
+    ;; Dojo needs its own matcher to override the string highlighting
+    (,(typescript--make-framework-matcher
+       'dojo
+       "^\\s-*dojo\\.declare\\s-*(\""
+       "\\(" typescript--dotted-name-re "\\)"
+       "\\(?:\"\\s-*,\\s-*\\(" typescript--dotted-name-re "\\)\\)?")
+     (1 font-lock-type-face t)
+     (2 font-lock-type-face nil t))
+
+    ;; Match Dojo base classes. Of course Mojo has to be different
+    ;; from everything else under the sun...
+    (,(typescript--make-framework-matcher
+       'dojo
+       "^\\s-*dojo\\.declare\\s-*(\""
+       "\\(" typescript--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
+     ,(concat "[[,]\\s-*\\(" typescript--dotted-name-re "\\)\\s-*"
+              "\\(?:\\].*$\\)?")
+     (backward-char)
+     (end-of-line)
+     (1 font-lock-type-face))
+
+    ;; continued Dojo base-class list
+    (,(typescript--make-framework-matcher
+       'dojo
+       "^\\s-*" typescript--dotted-name-re "\\s-*[],]")
+     ,(concat "\\(" typescript--dotted-name-re "\\)"
+              "\\s-*\\(?:\\].*$\\)?")
+     (if (save-excursion (backward-char)
+                         (typescript--inside-dojo-class-list-p))
+         (forward-symbol -1)
+       (end-of-line))
+     (end-of-line)
+     (1 font-lock-type-face))
+
+    ;; variable declarations
+    ,(list
+      (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" typescript--basic-type-re)
+      (list #'typescript--variable-decl-matcher nil nil nil))
+
+    ;; class instantiation
+    ,(list
+      (concat "\\_<new\\_>\\s-+\\(" typescript--dotted-name-re "\\)")
+      (list 1 'font-lock-type-face))
+
+    ;; instanceof
+    ,(list
+      (concat "\\_<instanceof\\_>\\s-+\\(" typescript--dotted-name-re "\\)")
+      (list 1 'font-lock-type-face))
+
+    ;; formal parameters
+    ,(list
+      (concat
+       "\\_<function\\_>\\(\\s-+" typescript--name-re "\\)?\\s-*\\(<.*>\\)?\\s-*(\\s-*"
+       typescript--name-start-re)
+      (list (concat "\\(" typescript--name-re "\\)\\(\\s-*).*\\)?")
+            '(backward-char)
+            '(end-of-line)
+            '(1 font-lock-variable-name-face)))
+
+    ;; continued formal parameter list
+    ,(list
+      (concat
+       "^\\s-*" typescript--name-re "\\s-*[,)]")
+      (list typescript--name-re
+            '(if (save-excursion (backward-char)
+                                 (typescript--inside-param-list-p))
+                 (forward-symbol -1)
+               (end-of-line))
+            '(end-of-line)
+            '(0 font-lock-variable-name-face))))
+  "Level three font lock for `typescript-mode'.")
+
+(defun typescript--flyspell-mode-predicate ()
+  "A custom predicate to help `flyspell-prog-mode' determine whether a word should be checked."
+  ;; We depend on fontification for our results. font-lock-ensure is defined on
+  ;; Emacs 25 and over. Earlier versions use font-lock-fontify-buffer.
+  (if (fboundp 'font-lock-ensure)
+      (font-lock-ensure)
+    (font-lock-fontify-buffer))
+  (and
+   ;; Check with the default method that flyspell provides.
+   (flyspell-generic-progmode-verify)
+
+   ;;
+   ;; And eliminate cases specific to our mode we don't want to have
+   ;; spell-checked.
+   ;;
+
+   ;; Don't check the module names in import statements.
+   (save-excursion
+     (not (let* ((parse (syntax-ppss (1- (point))))
+                 (string-start-pos (and (nth 3 parse)
+                                        (nth 8 parse))))
+            (and string-start-pos
+                 (save-match-data
+                   ;; Move to back to the start of the string, then past any ws
+                   ;; and then past any non-ws to see if we have "from" or "import".
+                   (goto-char string-start-pos)
+                   (typescript--backward-syntactic-ws)
+                   (skip-syntax-backward "^-" (point-at-bol))
+                   (looking-at "from\\|import\\s-"))))))))
+
+(defun typescript--inside-pitem-p (pitem)
+  "Return whether point is inside the given pitem's header or body."
+  (typescript--ensure-cache)
+  (cl-assert (typescript--pitem-h-begin pitem))
+  (cl-assert (typescript--pitem-paren-depth pitem))
+
+  (and (> (point) (typescript--pitem-h-begin pitem))
+       (or (null (typescript--pitem-b-end pitem))
+           (> (typescript--pitem-b-end pitem) (point)))))
+
+(defun typescript--parse-state-at-point ()
+  "Parse the typescript program state at point.
+Return a list of `typescript--pitem' instances that apply to point, most
+specific first.  In the worst case, the current toplevel instance
+will be returned."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (typescript--ensure-cache)
+      (let* ((bound (if (eobp) (point) (1+ (point))))
+             (pstate (or (save-excursion
+                           (typescript--backward-pstate))
+                         (list typescript--initial-pitem))))
+
+        ;; Loop until we either hit a pitem at BOB or pitem ends after
+        ;; point (or at point if we're at eob)
+        (cl-loop for pitem = (car pstate)
+              until (or (eq (typescript--pitem-type pitem)
+                            'toplevel)
+                        (typescript--inside-pitem-p pitem))
+              do (pop pstate))
+
+        pstate))))
+
+(defun typescript--syntactic-context-from-pstate (pstate)
+  "Return the typescript syntactic context corresponding to PSTATE."
+  (let ((type (typescript--pitem-type (car pstate))))
+    (cond ((memq type '(function macro))
+           type)
+          ((consp type)
+           'class)
+          (t 'toplevel))))
+
+(defun typescript-syntactic-context ()
+  "Return the typescript syntactic context at point.
+When called interatively, also display a message with that
+context."
+  (interactive)
+  (let* ((syntactic-context (typescript--syntactic-context-from-pstate
+                             (typescript--parse-state-at-point))))
+
+    (when (called-interactively-p 'interactive)
+      (message "Syntactic context: %s" syntactic-context))
+
+   syntactic-context))
+
+(defun typescript--class-decl-matcher (limit)
+  "Font lock function used by `typescript-mode'.
+This performs fontification according to `typescript--class-styles'."
+  (cl-loop initially (typescript--ensure-cache limit)
+        while (re-search-forward typescript--quick-match-re limit t)
+        for orig-end = (match-end 0)
+        do (goto-char (match-beginning 0))
+        if (cl-loop for style in typescript--class-styles
+                 for decl-re = (plist-get style :class-decl)
+                 if (and (memq (plist-get style :framework)
+                               typescript-enabled-frameworks)
+                         (memq (typescript-syntactic-context)
+                               (plist-get style :contexts))
+                         decl-re
+                         (looking-at decl-re))
+                 do (goto-char (match-end 0))
+                 and return t)
+        return t
+        else do (goto-char orig-end)))
+
+(defconst typescript--font-lock-keywords-4
+  `(
+    ;; highlights that override previous levels
+    ;;
+
+    ;; special highlight for `this' keyword
+    ("\\(this\\)\\."
+     (1 'typescript-this-face))
+
+    (,typescript--access-modifier-re (1 'typescript-access-modifier-face))
+    (,typescript--basic-type-re (1 'typescript-primitive-face))
+
+    ;; generics support
+    ,(list
+      (concat typescript--name-re "\\s-*" "<\\s-*" typescript--name-start-re)
+      (list (concat "\\(" typescript--name-re "\\)\\(\\s-*>[^<]*\\)?")
+            '(backward-char)
+            '(end-of-line)
+            '(1 font-lock-type-face)))
+
+    ;; type-highlighting in variable/parameter declarations
+    ;; supports a small variety of common declarations:
+    ;; - let a: SomeType;
+    ;; - private b: SomeType;
+    ;; - private someFunc(var: SomeType) {
+    ;; - private array: SomeType[]
+    ;; - private generic: SomeType<Foo>
+    ;; - private genericArray: SomeType<Foo>[]
+    ;; - function testFunc(): SomeType<> {
+    ;; TODO: namespaced classes!
+    ,(list
+      (concat ":\\s-\\(" typescript--type-name-re "\\)\\(<" typescript--type-name-re ">\\)?\\(\[\]\\)?\\([,;]\\)?\\s-*{?")
+      '(1 'font-lock-type-face))
+
+    ;; type-casts
+    ,(list
+      (concat "<\\(" typescript--type-name-re "\\)>")
+      '(1 'font-lock-type-face))
+
+    ;; highlights that append to previous levels
+    ;;
+    ,@typescript--font-lock-keywords-3
+
+    (,typescript--decorator-re (1 font-lock-function-name-face))
+    (,typescript--function-call-re (1 font-lock-function-name-face))
+    (,typescript--builtin-re (1 font-lock-type-face))
+
+    ;; arrow function
+    ("\\(=>\\)"
+     (1 font-lock-keyword-face)))
+  "Level four font lock for `typescript-mode'.")
+
+(defconst typescript--font-lock-keywords
+  '(typescript--font-lock-keywords-4 typescript--font-lock-keywords-1
+                                   typescript--font-lock-keywords-2
+                                   typescript--font-lock-keywords-3
+                                   typescript--font-lock-keywords-4)
+  "Font lock keywords for `typescript-mode'.  See `font-lock-keywords'.")
+
+;;; Propertize
+
+;;
+;; The propertize code was adapted from:
+;; https://github.com/emacs-mirror/emacs/blob/489d6466372f488adc53897435fff290394b62f7/lisp/progmodes/js.el
+;;
+
+(defconst typescript--syntax-propertize-regexp-regexp
+  (rx
+   ;; Start of regexp.
+   "/"
+   (0+ (or
+        ;; Match characters outside of a character class.
+        (not (any ?\[ ?/ ?\\))
+        ;; Match backslash quoted characters.
+        (and "\\" not-newline)
+        ;; Match character class.
+        (and
+         "["
+         (0+ (or
+              (not (any ?\] ?\\))
+              (and "\\" not-newline)))
+         "]")))
+   (group (zero-or-one "/")))
+  "Regular expression matching a JavaScript regexp literal.")
+
+(defun typescript-syntax-propertize-regexp (end)
+  (let ((ppss (syntax-ppss)))
+    (when (eq (nth 3 ppss) ?/)
+      ;; A /.../ regexp.
+      (goto-char (nth 8 ppss))
+      (when (looking-at typescript--syntax-propertize-regexp-regexp)
+        ;; Don't touch text after END.
+        (when (> end (match-end 1))
+          (setq end (match-end 1)))
+        (put-text-property (match-beginning 1) end
+                           'syntax-table (string-to-syntax "\"/"))
+        (goto-char end)))))
+
+(defun typescript-syntax-propertize (start end)
+  ;; JavaScript allows immediate regular expression objects, written /.../.
+  (funcall
+   (syntax-propertize-rules
+    ;; Distinguish /-division from /-regexp chars (and from /-comment-starter).
+    ;; FIXME: Allow regexps after infix ops like + ...
+    ;; https://developer.mozilla.org/en/JavaScript/Reference/Operators
+    ;; We can probably just add +, -, <, >, %, ^, ~, ?, : at which
+    ;; point I think only * and / would be missing which could also be added,
+    ;; but need care to avoid affecting the // and */ comment markers.
+    ("\\(?:^\\|[=([{,:;|&!]\\|\\_<return\\_>\\)\\(?:[ \t]\\)*\\(/\\)[^/*]"
+     (1 (ignore
+        (forward-char -1)
+         (when (or (not (memq (char-after (match-beginning 0)) '(?\s ?\t)))
+                   ;; If the / is at the beginning of line, we have to check
+                   ;; the end of the previous text.
+                   (save-excursion
+                     (goto-char (match-beginning 0))
+                     (forward-comment (- (point)))
+                     (memq (char-before)
+                           (eval-when-compile (append "=({[,:;" '(nil))))))
+           (put-text-property (match-beginning 1) (match-end 1)
+                              'syntax-table (string-to-syntax "\"/"))
+           (typescript-syntax-propertize-regexp end)))))
+    ;; Hash-bang at beginning of buffer.
+    ("\\`\\(#\\)!" (1 "< b")))
+   start end))
+
+;;; Indentation
+
+(defconst typescript--possibly-braceless-keyword-re
+  (typescript--regexp-opt-symbol
+   '("catch" "do" "else" "finally" "for" "if" "try" "while" "with"))
+  "Regexp matching keywords optionally followed by an opening brace.")
+
+(defconst typescript--indent-keyword-re
+  (typescript--regexp-opt-symbol '("in" "instanceof"))
+  "Regexp matching keywords that affect indentation of continued expressions.")
+
+(defconst typescript--indent-operator-re
+  (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|" typescript--indent-keyword-re)
+  "Regexp matching operators that affect indentation of continued expressions.")
+
+;;
+;; We purposely do not allow the plus symbol as a prefix here, as this
+;; regex is used to check number literal in type annotations, and TS
+;; does not allow to use a plus symbol to prefix numbers there: you
+;; can use 1, but not +1 in a type annotation.
+;;
+;; This is meant to match NaN, floats, decimals, the two infinities
+;; and numbers recorded in binary, octal and hex.
+;;
+;; This regular expression was derived from:
+;; https://stackoverflow.com/a/30987109/
+;;
+(defconst typescript--number-literal-re
+  "\\(?:NaN\\|-?\\(?:0[Bb][01]+\\|0[Oo][0-7]+\\|0[Xx][0-9a-fA-F]+\\|Infinity\\|\\(?:[[:digit:]]*\\.[[:digit:]]+\\|[[:digit:]]+\\)\\(?:[Ee][+-]?[[:digit:]]+\\)?\\)\\)"
+  "Regexp that matches number literals.")
+
+(defconst typescript--reserved-start-keywords
+  '("const" "export" "function" "let" "var")
+  "These keywords cannot be variable or type names and start a new sentence.
+Note that the \"import\" keyword can be a type import since TS2.9, so it might
+not start a sentence!")
+
+(defconst typescript--reserved-start-keywords-re
+  (typescript--regexp-opt-symbol '("const" "export" "function" "let" "var"))
+  "A regular expression matching `typescript--reserved-start-keywords'.")
+
+(defconst typescript--type-vs-ternary-re
+  (concat "[?]\\|" (typescript--regexp-opt-symbol
+                    (append typescript--reserved-start-keywords
+                            '("as" "class" "interface" "private" "public" "readonly"))))
+  "Keywords/Symbols that help tell apart colon for types vs ternary operators.")
+
+(defun typescript--search-backward-matching-angle-bracket-inner (depth)
+  "Auxiliary function for `typescript--search-backward-matching-angle-bracket'.
+DEPTH indicates how nested we think we are: it increases when we cross closing
+brackets, and decreases when we cross opening brackets."
+  ;; We look backwards for a "<" that would correspond to the ">" we started
+  ;; from.  However, there is no guarantee that it exists, since our ">" could
+  ;; be a greater-than operation.  Some symbols will make it clear that we are
+  ;; *not* in a type annotation, so we can return nil.  Otherwise, we keep
+  ;; looking for the matching one.
+  (or (<= depth 0)
+      (and
+       ;; If we cross over a reserved start keyword, we abandon hope of finding
+       ;; a matching angle bracket.  This prevents extreme recursion depths.
+       (typescript--re-search-backward (concat "[<>]\\|" typescript--reserved-start-keywords-re) nil t)
+       (cl-case (char-after)
+         (?< (typescript--search-backward-matching-angle-bracket-inner (- depth 1)))
+         (?> (typescript--search-backward-matching-angle-bracket-inner (+ depth 1)))))))
+
+(defun typescript--search-backward-matching-angle-bracket ()
+  "Search for matching \"<\" preceding a starting \">\".
+DEPTH indicates how nested we think we are.  Assumes the starting position is
+right before the closing \">\".  Returns nil when a match was not found,
+otherwise returns t and the current position is right before the matching
+\"<\"."
+  (typescript--search-backward-matching-angle-bracket-inner 1))
+
+(defun typescript--re-search-backward-ignoring-angle-brackets ()
+  "Search backwards, jumping over text within angle brackets.
+Searches specifically for any of \"=\", \"}\", and \"type\"."
+  (and
+   (typescript--re-search-backward "[>=}]\\|\\_<type\\_>" nil t)
+   (or (not (looking-at ">"))
+       (and
+        (typescript--search-backward-matching-angle-bracket)
+        (typescript--re-search-backward-ignoring-angle-brackets)))))
+
+(defun typescript--looking-at-operator-p ()
+  "Return non-nil if point is on a typescript operator, other than a comma."
+  (save-match-data
+    (and (looking-at typescript--indent-operator-re)
+         (or (not (looking-at ":"))
+             (save-excursion
+               (backward-sexp)
+               (and
+                (typescript--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
+                (looking-at "?"))))
+         ;; Do not identify forward slashes appearing in a "list" as
+         ;; an operator. The lists are: arrays, or lists of
+         ;; arguments. In this context, they must be part of regular
+         ;; expressions, and not math operators.
+         (not (and (looking-at "/")
+                   (save-excursion
+                     (typescript--backward-syntactic-ws)
+                     (memq (char-before) '(?, ?\[ ?\()))))
+         ;; Do not identify methods, or fields, that are named "in" or
+         ;; "instanceof" as being operator keywords.
+         (not (and
+               (looking-at typescript--indent-keyword-re)
+               (save-excursion
+                 (typescript--backward-syntactic-ws)
+                 (memq (char-before) '(?, ?{ ?} ?\;)))))
+         ;; Do not identify the symbol > if it is likely part of a type argument
+         ;; T<A>, but identify it if it is likely a greater-than symbol. This is
+         ;; a hard problem in the absence of semicolons, see:
+         ;; https://github.com/ananthakumaran/typescript.el/issues/81
+         (not (and
+               (looking-at ">")
+               (save-excursion
+                 (and
+                  (typescript--search-backward-matching-angle-bracket)
+                  ;; If we made it here, we found a candidate matching opening
+                  ;; angle bracket. We still need to guess whether it actually
+                  ;; is one, and not a spurious less-than operator!
+
+                  ;; Look backwards for the first of:
+                  ;; - one of the symbols: = :
+                  ;; - or a TypeScript keyword
+                  ;; Depending on what comes first, we can make an educated
+                  ;; guess on the nature of our ">" of interest.
+                  (typescript--re-search-backward (concat "[=:]\\|" typescript--keyword-re) nil t)
+                  (or
+                   ;; If the previous keyword is "as", definitely a type.
+                   (looking-at "\\_<as\\_>")
+                   ;; Same goes for type imports.
+                   (looking-at "\\_<import\\_>")
+                   ;; A colon could be either a type symbol, or a ternary
+                   ;; operator, try to guess which.
+                   (and (looking-at ":")
+                        (typescript--re-search-backward typescript--type-vs-ternary-re nil t)
+                        (not (looking-at "?")))
+                   ;; This final check lets us distinguish between a
+                   ;; 2-argument type "t < a , b > ..." and a use of the ","
+                   ;; operator between two comparisons "t < a , b > ...".
+                   ;; Looking back a little more lets us guess.
+                   (and (looking-at "=")
+                        (typescript--re-search-backward-ignoring-angle-brackets)
+                        (looking-at "\\_<type\\_>")))))))
+         (not (and
+               (looking-at "*")
+               ;; Generator method (possibly using computed property).
+               (looking-at (concat "\\* *\\(?:\\[\\|" typescript--name-re
+                                   " *(\\)"))
+               (save-excursion
+                 (typescript--backward-syntactic-ws)
+                 ;; We might misindent some expressions that would
+                 ;; return NaN anyway.  Shouldn't be a problem.
+                 (memq (char-before) '(?, ?} ?{ ?\;))))))))
+
+
+(defun typescript--continued-expression-p ()
+  "Return non-nil if the current line continues an expression."
+  (save-excursion
+    (back-to-indentation)
+    (let ((list-start (nth 1 (syntax-ppss))))
+      (and
+       ;; This not clause is there to eliminate degenerate cases where we have
+       ;; something that looks like a continued expression but we are in fact at
+       ;; the beginning of the expression. Example: in `if (a) { .q(1)` when the
+       ;; point is on the dot, the expression that follows looks like a member
+       ;; expression but the object on which it is a member is missing. If we
+       ;; naively treat this as a continued expression, we run into trouble
+       ;; later. (An infinite loop.)
+       (not (and list-start
+                 (save-excursion
+                   (typescript--backward-syntactic-ws)
+                   (backward-char)
+                   (eq (point) list-start))))
+       ;; Don't identify the spread syntax or rest operator as a "continuation".
+       (not (looking-at "\\.\\.\\."))
+       (or (typescript--looking-at-operator-p)
+           (and (progn
+                  (typescript--backward-syntactic-ws)
+                  (or (bobp) (backward-char))
+                  (and (> (point) (point-min))
+                       (save-excursion (backward-char) (not (looking-at "[/*]/")))
+                       (typescript--looking-at-operator-p)
+                       (and (progn (backward-char)
+                                   (not (looking-at "++\\|--\\|/[/*]"))))))))))))
+
+(cl-defun typescript--compute-member-expression-indent ()
+  "Determine the indent of a member expression.
+
+This function must be called with point located at the dot that
+starts the member expression.
+"
+  ;; Find the line that has the object from which we are getting thismember.
+  ;; And set an indent relative to that.
+  (while (looking-at "\\.")
+    (typescript--backward-syntactic-ws)
+    (while (eq (char-before) ?\;)
+      (backward-char))
+    (while (memq (char-before) '(?\] ?} ?\) ?>))
+      (if (not (eq (char-before) ?>))
+          (backward-list)
+        (backward-char)
+        (typescript--backward-over-generic-parameter-list))
+      (typescript--backward-syntactic-ws))
+    (if (looking-back typescript--dotted-name-re nil)
+        (back-to-indentation)
+      (typescript--forward-syntactic-ws)))
+  (+ (current-column) typescript-indent-level))
+
+(defun typescript--end-of-do-while-loop-p ()
+  "Return non-nil if point is on the \"while\" of a do-while statement.
+Otherwise, return nil.  A braceless do-while statement spanning
+several lines requires that the start of the loop is indented to
+the same column as the current line."
+  (interactive)
+  (save-excursion
+    (save-match-data
+      (when (looking-at "\\s-*\\_<while\\_>")
+       (if (save-excursion
+             (skip-chars-backward "[ \t\n]*}")
+             (looking-at "[ \t\n]*}"))
+           (save-excursion
+             (backward-list) (forward-symbol -1) (looking-at "\\_<do\\_>"))
+         (typescript--re-search-backward "\\_<do\\_>" (point-at-bol) t)
+         (or (looking-at "\\_<do\\_>")
+             (let ((saved-indent (current-indentation)))
+               (while (and (typescript--re-search-backward "^\\s-*\\_<" nil t)
+                           (/= (current-indentation) saved-indent)))
+               (and (looking-at "\\s-*\\_<do\\_>")
+                    (not (typescript--re-search-forward
+                          "\\_<while\\_>" (point-at-eol) t))
+                    (= (current-indentation) saved-indent)))))))))
+
+
+(defun typescript--ctrl-statement-indentation ()
+  "Helper function for `typescript--proper-indentation'.
+Return the proper indentation of the current line if it starts
+the body of a control statement without braces; otherwise, return
+nil."
+  (save-excursion
+    (back-to-indentation)
+    (when (save-excursion
+            (and (not (eq (point-at-bol) (point-min)))
+                 (not (looking-at "[{]"))
+                 (progn
+                   (typescript--re-search-backward "[[:graph:]]" nil t)
+                   (or (eobp) (forward-char))
+                   (when (= (char-before) ?\)) (backward-list))
+                   (skip-syntax-backward " ")
+                   (skip-syntax-backward "w_")
+                   (and
+                    (looking-at typescript--possibly-braceless-keyword-re)
+                    ;; If preceded by period, it's a method call.
+                    (not (= (char-before) ?.))))
+                 (not (typescript--end-of-do-while-loop-p))))
+      (save-excursion
+        (goto-char (match-beginning 0))
+        (+ (current-indentation) typescript-indent-level)))))
+
+(defun typescript--get-c-offset (symbol anchor)
+  (let ((c-offsets-alist
+         (list (cons 'c typescript-comment-lineup-func))))
+    (c-get-syntactic-indentation (list (cons symbol anchor)))))
+
+(defun typescript--backward-over-generic-parameter-list ()
+  "Search backward for the start of a generic's parameter list and move to it.
+
+This is a utility function for
+`typescript--backward-to-parameter-list'.
+
+This function must be called with the point placed on the final >
+of the generic's parameter list.  It will scan backwards to find
+the start.  If successful, it will move the point to the start of
+the list.  If not, it does not move the point.
+
+Returns nil on failure, or the position to which the point was
+moved on success."
+  (when (eq (char-after) ?>)
+    (let ((depth 1))
+      (cl-loop named search-loop
+            while (> depth 0)
+            do (progn
+                 (unless (re-search-backward "[<>]" nil t)
+                   (cl-return-from search-loop nil))
+                 (cond
+                  ((looking-at ">")
+                   (unless (eq (char-before) ?=)
+                     (setq depth (1+ depth))))
+                  ((looking-at "<") (setq depth (1- depth)))))
+            finally return (point)))))
+
+(defun typescript--backward-to-parameter-list ()
+  "Search backward for the end of a parameter list and move to it.
+
+This is a utility function for `typescript--proper-indentation'.
+
+This function must be called with the point placed before an
+opening curly brace.  It will try to skip over the type
+annotation that would mark the return value of a function and
+move to the end of the parameter list.  If it is unsuccessful, it
+does not move the point. \"Unsuccessful\" here also means that
+the position at which we started did not in fact mark the
+beginning of a function. The curly brace belonged to some other
+syntactic construct than a function.
+
+Returns nil on failure, or the position to which the point was
+moved on success."
+  (let ((location
+         (or
+          ;; This handles the case of a function with return type annotation.
+          (save-excursion
+            (cl-loop named search-loop
+                  do
+                  (typescript--backward-syntactic-ws)
+                  ;; Check whether we are at "):".
+                  (when (and (eq (char-before) ?\:)
+                             (progn
+                               (backward-char)
+                               (skip-syntax-backward " ")
+                               (eq (char-before) ?\))))
+                    ;; Success! This the end of the parameter list.
+                    (cl-return-from search-loop (point)))
+                  ;; If we recognize a structure that belongs in a return type annotation,
+                  ;; skip back over it, or fail.
+                  (cond
+                   ;; Arrow of a function definition, or typeguard (eg. foo is SomeClass)
+                   ((looking-back "=>\\|is" (- (point) 2))
+                    (backward-char 2))
+                   ;; End of the parameters list of a generic.
+                   ((eq (char-before) ?>)
+                    (backward-char)
+                    (typescript--backward-over-generic-parameter-list))
+                   ;; Union of types, or a dot in a dotted name.
+                   ((memq (char-before) '(?| ?.))
+                    (backward-char))
+                   ((or
+                     ;; End-delimiter of a delimited construct, for constructs
+                     ;; not handled above.
+                     (memq (char-before) '(?\) ?} ?\" ?\]))
+                     ;; This is also dealing with dotted names. This may come
+                     ;; into play if a jump back moves over an entire dotted
+                     ;; name at once.
+                     ;;
+                     ;; The earlier test for dotted names comes into play if the
+                     ;; logic moves over one part of a dotted name at a time (which
+                     ;; is what `backward-sexp` normally does).
+                     (and (looking-back typescript--dotted-name-re nil)
+                          ;; We don't want the loop to walk over constructs like switch (...) or for (...), etc.
+                          (not (save-excursion
+                                 (backward-word)
+                                 (looking-at "\\_<\\(switch\\|if\\|while\\|until\\|for\\)\\_>\\(?:\\s-\\|\n\\)*(")))))
+                    (condition-case nil
+                        (backward-sexp)
+                      (scan-error (cl-return-from search-loop nil))))
+                   ((looking-back typescript--number-literal-re
+                                  ;; We limit the search back to the previous space or end of line (if possible)
+                                  ;; to prevent the search from going over the whole buffer.
+                                  (save-excursion (re-search-backward "\\(?:\\s-\\|\n\\)" nil t)) t)
+                    (goto-char (match-beginning 0)))
+                   ;; Otherwise, we failed to find a location.
+                   (t
+                    (cl-return-from search-loop nil)))))
+          ;; This handles the case of a function without return type annotation.
+          (progn
+            (typescript--backward-syntactic-ws)
+            (when (eq (char-before) ?\))
+              (point))))))
+    (when location
+      (goto-char location))))
+
+(defun typescript--proper-indentation (parse-status)
+  "Return the proper indentation for the current line."
+  (save-excursion
+    (back-to-indentation)
+    (let ((member-expr-p (looking-at "\\.")))
+      (cond ((nth 4 parse-status) ;; Inside a comment.
+             (typescript--get-c-offset 'c (nth 8 parse-status)))
+            ((nth 8 parse-status) 0) ;; Inside a string.
+            ((typescript--ctrl-statement-indentation)) ;; Control statements.
+            ((eq (char-after) ?#) 0) ;; Looking at a pragma.
+            ;; Inside a list of things. Note that in the TS contents, the curly braces
+            ;; marking code blocks are "list of things".
+            ((nth 1 parse-status)
+             (let ((indent-start (point))
+                   (same-indent-p (looking-at "[]})]"))
+                   (switch-keyword-p (looking-at "\\_<default\\_>\\|\\_<case\\_>[^:]"))
+                   (continued-expr-p (typescript--continued-expression-p))
+                   (list-start (nth 1 parse-status)))
+               (goto-char list-start)
+               (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
+                   (progn
+                     (skip-syntax-backward " ")
+                     (cond
+                      ((or (typescript--backward-to-parameter-list)
+                           (eq (char-before) ?\)))
+                       ;; Take the curly brace as marking off the body of a function.
+                       ;; In that case, we want the code that follows to see the indentation
+                       ;; that was in effect at the beginning of the function declaration, and thus
+                       ;; we want to move back over the list of function parameters.
+                       (condition-case nil
+                           (backward-list)
+                         (error nil)))
+                      ((looking-back "," nil)
+                       ;; If we get here, we have a comma, spaces and an opening curly brace. (And
+                       ;; (point) is just after the comma.) We don't want to move from the current position
+                       ;; so that object literals in parameter lists are properly indented.
+                       nil)
+                      (t
+                       ;; In all other cases, we don't want to move from the curly brace.
+                       (goto-char list-start)))
+                     (back-to-indentation)
+                     (let* ((in-switch-p (unless same-indent-p
+                                           (looking-at "\\_<switch\\_>")))
+                            (same-indent-p (or same-indent-p
+                                               (and switch-keyword-p
+                                                    in-switch-p)))
+                            (indent
+                             (cond (same-indent-p
+                                    (current-column))
+                                   (continued-expr-p
+                                    (if (not member-expr-p)
+                                        (+ (current-column) (* 2 typescript-indent-level)
+                                           typescript-expr-indent-offset)
+                                      (goto-char indent-start)
+                                      (typescript--compute-member-expression-indent)))
+                                   (t
+                                    (+ (current-column) typescript-indent-level)))))
+                       (if (and in-switch-p typescript-indent-switch-clauses)
+                           (+ indent typescript-indent-level)
+                         indent)))
+                 (unless same-indent-p
+                   (forward-char)
+                   (skip-chars-forward " \t"))
+                 (if continued-expr-p
+                     (if (not member-expr-p)
+                         (progn (back-to-indentation)
+                                (+ (current-column) typescript-indent-level
+                                   typescript-expr-indent-offset))
+                       (goto-char indent-start)
+                       (typescript--compute-member-expression-indent))
+                   (current-column)))))
+
+            ((typescript--continued-expression-p) ;; Inside a continued expression.
+             (if member-expr-p
+                 (typescript--compute-member-expression-indent)
+               (+ typescript-indent-level typescript-expr-indent-offset)))
+            (t 0)))))
+
+(defun typescript-indent-line ()
+  "Indent the current line as typescript."
+  (interactive)
+  (save-restriction
+    (widen)
+    (let* ((parse-status
+            (save-excursion (syntax-ppss (point-at-bol))))
+           (offset (- (current-column) (current-indentation))))
+      (indent-line-to (typescript--proper-indentation parse-status))
+      (when (> offset 0) (move-to-column (+ offset (current-indentation)))))))
+
+;;; Filling
+
+(defun typescript-c-fill-paragraph (&optional justify)
+  "Fill the paragraph with `c-fill-paragraph'."
+  (interactive "*P")
+  ;; Dynamically replace functions using the lexically scoped cl-letf.
+  ;; See below for more details:
+  ;; http://endlessparentheses.com/understanding-letf-and-how-it-replaces-flet.html
+  (cl-letf (((symbol-function 'c-forward-sws)
+             (lambda  (&optional limit)
+               (typescript--forward-syntactic-ws limit)))
+            ((symbol-function 'c-backward-sws)
+             (lambda  (&optional limit)
+               (typescript--backward-syntactic-ws limit))))
+    (let ((fill-paragraph-function 'c-fill-paragraph))
+      (c-fill-paragraph justify))))
+
+;; We maintain a cache of semantic information, i.e., the classes and
+;; functions we've encountered so far. In order to avoid having to
+;; re-parse the buffer on every change, we cache the parse state at
+;; each interesting point in the buffer. Each parse state is a
+;; modified copy of the previous one, or in the case of the first
+;; parse state, the empty state.
+;;
+;; The parse state itself is just a stack of typescript--pitem
+;; instances. It starts off containing one element that is never
+;; closed, that is initially typescript--initial-pitem.
+;;
+
+
+(defun typescript--pitem-format (pitem)
+  (let ((name (typescript--pitem-name pitem))
+        (type (typescript--pitem-type pitem)))
+
+    (format "name:%S type:%S"
+            name
+            (if (atom type)
+                type
+              (plist-get type :name)))))
+
+(defun typescript--make-merged-item (item child name-parts)
+  "Helper function for `typescript--splice-into-items'.
+Return a new item that is the result of merging CHILD into
+ITEM.  NAME-PARTS is a list of parts of the name of CHILD
+that we haven't consumed yet."
+  (typescript--debug "typescript--make-merged-item: {%s} into {%s}"
+                   (typescript--pitem-format child)
+                   (typescript--pitem-format item))
+
+  ;; If the item we're merging into isn't a class, make it into one
+  (unless (consp (typescript--pitem-type item))
+    (typescript--debug "typescript--make-merged-item: changing dest into class")
+    (setq item (make-typescript--pitem
+                :children (list item)
+
+                ;; Use the child's class-style if it's available
+                :type (if (atom (typescript--pitem-type child))
+                          typescript--dummy-class-style
+                  (typescript--pitem-type child))
+
+                :name (typescript--pitem-strname item))))
+
+  ;; Now we can merge either a function or a class into a class
+  (cons (cond
+         ((cdr name-parts)
+          (typescript--debug "typescript--make-merged-item: recursing")
+          ;; if we have more name-parts to go before we get to the
+          ;; bottom of the class hierarchy, call the merger
+          ;; recursively
+          (typescript--splice-into-items (car item) child
+                                       (cdr name-parts)))
+
+         ((atom (typescript--pitem-type child))
+          (typescript--debug "typescript--make-merged-item: straight merge")
+          ;; Not merging a class, but something else, so just prepend
+          ;; it
+          (cons child (car item)))
+
+         (t
+          ;; Otherwise, merge the new child's items into those
+          ;; of the new class
+          (typescript--debug "typescript--make-merged-item: merging class contents")
+          (append (car child) (car item))))
+        (cdr item)))
+
+(defun typescript--pitem-strname (pitem)
+  "Last part of the name of PITEM, as a string or symbol."
+  (let ((name (typescript--pitem-name pitem)))
+    (if (consp name)
+        (car (last name))
+      name)))
+
+(defun typescript--splice-into-items (items child name-parts)
+  "Splice CHILD into the `typescript--pitem' ITEMS at NAME-PARTS.
+If a class doesn't exist in the tree, create it.  Return
+the new items list.  NAME-PARTS is a list of strings given
+the broken-down class name of the item to insert."
+
+  (let ((top-name (car name-parts))
+        (item-ptr items)
+        new-items last-new-item new-cons item)
+
+    (typescript--debug "typescript--splice-into-items: name-parts: %S items:%S"
+             name-parts
+             (mapcar #'typescript--pitem-name items))
+
+    (cl-assert (stringp top-name))
+    (cl-assert (> (length top-name) 0))
+
+    ;; If top-name isn't found in items, then we build a copy of items
+    ;; and throw it away. But that's okay, since most of the time, we
+    ;; *will* find an instance.
+
+    (while (and item-ptr
+                (cond ((equal (typescript--pitem-strname (car item-ptr)) top-name)
+                       ;; Okay, we found an entry with the right name. Splice
+                       ;; the merged item into the list...
+                       (setq new-cons (cons (typescript--make-merged-item
+                                             (car item-ptr) child
+                                             name-parts)
+                                            (cdr item-ptr)))
+
+                       (if last-new-item
+                           (setcdr last-new-item new-cons)
+                         (setq new-items new-cons))
+
+                       ;; ...and terminate the loop
+                       nil)
+
+                      (t
+                       ;; Otherwise, copy the current cons and move onto the
+                       ;; text. This is tricky; we keep track of the tail of
+                       ;; the list that begins with new-items in
+                       ;; last-new-item.
+                       (setq new-cons (cons (car item-ptr) nil))
+                       (if last-new-item
+                           (setcdr last-new-item new-cons)
+                         (setq new-items new-cons))
+                       (setq last-new-item new-cons)
+
+                       ;; Go to the next cell in items
+                       (setq item-ptr (cdr item-ptr))))))
+
+    (if item-ptr
+        ;; Yay! We stopped because we found something, not because
+        ;; we ran out of items to search. Just return the new
+        ;; list.
+        (progn
+          (typescript--debug "search succeeded: %S" name-parts)
+          new-items)
+
+      ;; We didn't find anything. If the child is a class and we don't
+      ;; have any classes to drill down into, just push that class;
+      ;; otherwise, make a fake class and carry on.
+      (typescript--debug "search failed: %S" name-parts)
+      (cons (if (cdr name-parts)
+                ;; We have name-parts left to process. Make a fake
+                ;; class for this particular part...
+                (make-typescript--pitem
+                 ;; ...and recursively digest the rest of the name
+                 :children (typescript--splice-into-items
+                            nil child (cdr name-parts))
+                 :type typescript--dummy-class-style
+                 :name top-name)
+
+              ;; Otherwise, this is the only name we have, so stick
+              ;; the item on the front of the list
+              child)
+            items))))
+
+(defun typescript--pitem-add-child (pitem child)
+  "Copy `typescript--pitem' PITEM, and push CHILD onto its list of children."
+  (cl-assert (integerp (typescript--pitem-h-begin child)))
+  (cl-assert (if (consp (typescript--pitem-name child))
+              (cl-loop for part in (typescript--pitem-name child)
+                    always (stringp part))
+            t))
+
+  ;; This trick works because we know (based on our cl-defstructs) that
+  ;; the child list is always the first element, and so the second
+  ;; element and beyond can be shared when we make our "copy".
+  (cons
+
+   (let ((name (typescript--pitem-name child))
+         (type (typescript--pitem-type child)))
+
+     (cond ((cdr-safe name) ; true if a list of at least two elements
+            ;; Use slow path because we need class lookup
+            (typescript--splice-into-items (car pitem) child name))
+
+           ((and (consp type)
+                 (plist-get type :prototype))
+
+            ;; Use slow path because we need class merging. We know
+            ;; name is a list here because down in
+            ;; `typescript--ensure-cache', we made sure to only add
+            ;; class entries with lists for :name
+            (cl-assert (consp name))
+            (typescript--splice-into-items (car pitem) child name))
+
+           (t
+            ;; Fast path
+            (cons child (car pitem)))))
+
+   (cdr pitem)))
+
+;;; compilation-mode support
+
+;; tsc supports formatting errors in two general ways: plain and
+;; pretty. ("Plain" is our term for "not pretty".) In tsc versions
+;; prior to 2.7, the plain and pretty formats both used the same
+;; format for references into files. `typescript-tsc-error-regexp`
+;; covers both plain and pretty for those versions.
+;;
+;; Version 2.7 changed the pretty format so as to format source code
+;; references differently. This required the introduction of
+;; `typescript-tsc-pretty-error-regexp`. The format of plain error
+;; messages did not change. So from that version onwards,
+;; `typescript-tsc-error-regexp` covers plain error messages and
+;; `typescript-tsc-pretty-error-regexp` covers pretty error messages.
+
+;; handle plain compiler-errors like the following when doing M-x compile<ret>tsc<ret>
+;;
+;; greeter.ts(24,9): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+;; greeter.ts(30,12): error TS2339: Property 'indexOf' does not exist on type 'number'.
+(defconst typescript-tsc-error-regexp
+  (concat
+   "^[[:blank:]]*"
+   "\\([^(\r\n)]+\\)(\\([0-9]+\\),\\([0-9]+\\)):[[:blank:]]+"
+   "error [[:alnum:]]+: [^\r\n]+$")
+  "Regexp to match errors generated by tsc.")
+
+;; handle pretty compiler-errors like the following when doing M-x compile<ret>tsc<ret>
+;; test.ts:2:7 - error TS2322: Type '2' is not assignable to type 'string'.
+(defconst typescript-tsc-pretty-error-regexp
+  (concat
+   "^[[:blank:]]*"
+   "\\([^(\r\n)]+\\):\\([0-9]+\\):\\([0-9]+\\) - [[:blank:]]*"
+   "error [[:alnum:]]+: [^\r\n]+$")
+  "Regexp to match errors generated by tsc.")
+
+;;
+;; Should handle output like:
+;; src/modules/authenticator.ts[1, 83]: ' should be "
+;; (quotemarks) src/modules/authenticator.ts[2, 26]: ' should be "
+;; ERROR: (quotemarks) src/modules/authenticator.ts[2, 26]: ' should be "
+;; WARNING: src/modules/authenticator.ts[2, 26]: ' should be "
+;;
+;; "(quotemarks)" it the rule name. It is produced when using the
+;; "verbose" formatter. The "verbose" formatter is identical to the
+;; default ("prose") formatter, except for the additional rule name.
+;;
+;; "ERROR:" and "WARNING:" are the severity. This was added in tslint
+;; 5.0. Prior versions have no notion of severity and simply omit this
+;; part.
+;;
+(defconst typescript-tslint-report-regexp
+  (concat
+   "^[[:blank:]]*"
+   ;; severity ("type" in Emacs' parlance)
+   "\\(?:\\(?:ERROR\\|\\(WARNING\\)\\):[[:blank:]]+\\)?"
+   ;; rule name
+   "\\((.*)[[:blank:]]+\\)?"
+   ;; filename
+   "\\([^(\r\n)]+\\)"
+   "\\["
+   ;; line
+   "\\([[:digit:]]+\\)"
+   ", "
+   ;; column
+   "\\([[:digit:]]+\\)"
+   "\\]: "
+   ;; message
+   ".*$")
+  "Regexp to match reports generated by tslint.")
+
+(defconst typescript-nglint-error-regexp
+  (concat
+   ;; severity ("type" in Emacs' parlance)
+   "ERROR:[[:blank:]]+"
+
+   ;; filename
+   "\\([^(\r\n)]+\\)"
+   ":"
+   ;; line
+   "\\([[:digit:]]+\\)"
+   ":"
+   ;; column
+   "\\([[:digit:]]+\\)"
+
+   " - "
+   ;; message
+   ".*$"))
+
+(defconst typescript-nglint-warning-regexp
+  (concat
+   ;; severity ("type" in Emacs' parlance)
+   "WARNING:[[:blank:]]+"
+
+   ;; filename
+   "\\([^(\r\n)]+\\)"
+   ":"
+   ;; line
+   "\\([[:digit:]]+\\)"
+   ":"
+   ;; column
+   "\\([[:digit:]]+\\)"
+
+   " - "
+   ;; message
+   ".*$"))
+
+(dolist
+    (regexp
+     `((typescript-tsc
+        ,typescript-tsc-error-regexp
+        1 2 3 2)
+
+       (typescript-tsc-pretty
+        ,typescript-tsc-pretty-error-regexp
+        1 2 3 2)
+
+       (typescript-tslint
+        ,typescript-tslint-report-regexp
+        3 4 5 (1))
+
+       (typescript-nglint-error
+        ,typescript-nglint-error-regexp
+        1 2 3 2)
+
+       (typescript-nglint-warning
+        ,typescript-nglint-warning-regexp
+        1 2 3 1)))
+  (add-to-list 'compilation-error-regexp-alist-alist regexp)
+  (add-to-list 'compilation-error-regexp-alist (car regexp)))
+
+;;; Main Function
+
+;;;###autoload
+(define-derived-mode typescript-mode prog-mode "typescript"
+  "Major mode for editing typescript.
+
+Key bindings:
+
+\\{typescript-mode-map}"
+
+  :group 'typescript
+  :syntax-table typescript-mode-syntax-table
+
+  (setq-local indent-line-function 'typescript-indent-line)
+  (setq-local beginning-of-defun-function 'typescript-beginning-of-defun)
+  (setq-local end-of-defun-function 'typescript-end-of-defun)
+  (setq-local open-paren-in-column-0-is-defun-start nil)
+  (setq-local font-lock-defaults (list typescript--font-lock-keywords))
+  (setq-local syntax-propertize-function #'typescript-syntax-propertize)
+  (setq-local parse-sexp-ignore-comments t)
+  (setq-local parse-sexp-lookup-properties t)
+
+  ;; Comments
+  (setq-local comment-start "// ")
+  (setq-local comment-end "")
+  (setq-local fill-paragraph-function 'typescript-c-fill-paragraph)
+
+  ;; Parse cache
+  (add-hook 'before-change-functions #'typescript--flush-caches t t)
+
+  ;; Frameworks
+  (typescript--update-quick-match-re)
+
+  ;; for filling, pretend we're cc-mode
+  (setq c-comment-prefix-regexp "//+\\|\\**"
+        c-paragraph-start "$"
+        c-paragraph-separate "$"
+        c-block-comment-prefix "* "
+        c-line-comment-starter "//"
+        c-comment-start-regexp "/[*/]\\|\\s!"
+        comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
+
+  (setq-local electric-indent-chars
+             (append "{}():;," electric-indent-chars))
+  (setq-local electric-layout-rules
+             '((?\; . after) (?\{ . after) (?\} . before)))
+
+  (let ((c-buffer-is-cc-mode t))
+    ;; FIXME: These are normally set by `c-basic-common-init'.  Should
+    ;; we call it instead?  (Bug#6071)
+    (make-local-variable 'paragraph-start)
+    (make-local-variable 'paragraph-separate)
+    (make-local-variable 'paragraph-ignore-fill-prefix)
+    (make-local-variable 'adaptive-fill-mode)
+    (make-local-variable 'adaptive-fill-regexp)
+    (c-setup-paragraph-variables))
+
+  (add-hook 'post-self-insert-hook
+            #'typescript--post-self-insert-function)
+
+  (setq-local syntax-begin-function #'typescript--syntax-begin-function))
+
+;; Set our custom predicate for flyspell prog mode
+(put 'typescript-mode 'flyspell-mode-predicate
+     'typescript--flyspell-mode-predicate)
+
+;;;###autoload
+(eval-after-load 'folding
+  '(when (fboundp 'folding-add-to-marks-list)
+     (folding-add-to-marks-list 'typescript-mode "// {{{" "// }}}" )))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.ts$" . typescript-mode))
+
+(provide 'typescript-mode)
+
+;;; typescript-mode.el ends here