summaryrefslogtreecommitdiff
path: root/elpa/typescript-mode-20220506.827/typescript-mode.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/typescript-mode-20220506.827/typescript-mode.el')
-rw-r--r--elpa/typescript-mode-20220506.827/typescript-mode.el2988
1 files changed, 2988 insertions, 0 deletions
diff --git a/elpa/typescript-mode-20220506.827/typescript-mode.el b/elpa/typescript-mode-20220506.827/typescript-mode.el
new file mode 100644
index 0000000..220cf75
--- /dev/null
+++ b/elpa/typescript-mode-20220506.827/typescript-mode.el
@@ -0,0 +1,2988 @@
+;;; 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.4
+;; 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" "debugger" "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"
+ "override" "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 (cons typescript--constant-re font-lock-constant-face)
+ (cons typescript--basic-type-re font-lock-type-face)
+ (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)))
+ "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-indent-list-items t
+ "Enable indenting of list items, useful for certain code styles."
+ :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))
+ (when (memq (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)))
+ (when (and (not same-indent-p)
+ typescript-indent-list-items)
+ (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-block-comment-ender-regexp "\\*/"
+ 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