aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTheodor Thornhill2022-10-11 10:27:55 +0200
committerYuan Fu2022-10-11 23:41:53 -0700
commit45b8204e09b7d399b792bb26c799efe48835c4a7 (patch)
tree1ddae2f1189a13134f9757bd19d8892b64c774ac
parentc4179117afd37a03a7ba322e66b5bd6bae5637bd (diff)
downloademacs-45b8204e09b7d399b792bb26c799efe48835c4a7.tar.gz
emacs-45b8204e09b7d399b792bb26c799efe48835c4a7.zip
Add TypeScript support with tree-sitter
* lisp/progmodes/ts-mode.el (ts-mode): New major mode for TypeScript with support for tree-sitter. It uses the TSX parser, so that we get support for TSX as well as TypeScript. If we cannot find tree-sitter, we default to using js-mode.
-rw-r--r--etc/NEWS7
-rw-r--r--lisp/progmodes/ts-mode.el364
2 files changed, 371 insertions, 0 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 88b1431d6a6..c5a142b500f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2775,6 +2775,13 @@ This is a lightweight variant of 'js-mode' that is used by default
2775when visiting JSON files. 2775when visiting JSON files.
2776 2776
2777 2777
2778** New mode ts-mode'.
2779Support is added for TypeScript, based on the new integration with
2780Tree-Sitter. There's support for font-locking, indentation and
2781navigation. Tree-Sitter is required for this mode to function, but if
2782it is not available, we will default to use 'js-mode'.
2783
2784
2778* Incompatible Lisp Changes in Emacs 29.1 2785* Incompatible Lisp Changes in Emacs 29.1
2779 2786
2780+++ 2787+++
diff --git a/lisp/progmodes/ts-mode.el b/lisp/progmodes/ts-mode.el
new file mode 100644
index 00000000000..99ffe0c0f63
--- /dev/null
+++ b/lisp/progmodes/ts-mode.el
@@ -0,0 +1,364 @@
1;;; ts-mode.el --- tree sitter support for TypeScript -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2022 Free Software Foundation, Inc.
4
5;; Author : Theodor Thornhill <theo@thornhill.no>
6;; Maintainer : Theodor Thornhill <theo@thornhill.no>
7;; Created : October 2022
8;; Keywords : typescript tsx languages tree-sitter
9
10;; This file is part of GNU Emacs.
11
12;; This program is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
16
17;; This program is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with this program. If not, see <http://www.gnu.org/licenses/>.
24
25(require 'treesit)
26(require 'rx)
27(require 'js)
28
29(defcustom ts-mode-indent-offset 2
30 "Number of spaces for each indentation step in `ts-mode'."
31 :type 'integer
32 :safe 'integerp
33 :group 'typescript)
34
35(defvar ts-mode--syntax-table
36 (let ((table (make-syntax-table)))
37 ;; Taken from the cc-langs version
38 (modify-syntax-entry ?_ "_" table)
39 (modify-syntax-entry ?$ "_" table)
40 (modify-syntax-entry ?\\ "\\" table)
41 (modify-syntax-entry ?+ "." table)
42 (modify-syntax-entry ?- "." table)
43 (modify-syntax-entry ?= "." table)
44 (modify-syntax-entry ?% "." table)
45 (modify-syntax-entry ?< "." table)
46 (modify-syntax-entry ?> "." table)
47 (modify-syntax-entry ?& "." table)
48 (modify-syntax-entry ?| "." table)
49 (modify-syntax-entry ?` "\"" table)
50 (modify-syntax-entry ?\240 "." table)
51 table)
52 "Syntax table for `ts-mode'.")
53
54(defvar ts-mode--indent-rules
55 `((tsx
56 ((node-is "}") parent-bol 0)
57 ((node-is ")") parent-bol 0)
58 ((node-is "]") parent-bol 0)
59 ((node-is ">") parent-bol 0)
60 ((node-is ".")
61 parent-bol ,ts-mode-indent-offset)
62 ((parent-is "ternary_expression")
63 parent-bol ,ts-mode-indent-offset)
64 ((parent-is "named_imports")
65 parent-bol ,ts-mode-indent-offset)
66 ((parent-is "statement_block")
67 parent-bol ,ts-mode-indent-offset)
68 ((parent-is "type_arguments")
69 parent-bol ,ts-mode-indent-offset)
70 ((parent-is "variable_declarator")
71 parent-bol ,ts-mode-indent-offset)
72 ((parent-is "arguments")
73 parent-bol ,ts-mode-indent-offset)
74 ((parent-is "array")
75 parent-bol ,ts-mode-indent-offset)
76 ((parent-is "formal_parameters")
77 parent-bol ,ts-mode-indent-offset)
78 ((parent-is "template_substitution")
79 parent-bol ,ts-mode-indent-offset)
80 ((parent-is "object_pattern")
81 parent-bol ,ts-mode-indent-offset)
82 ((parent-is "object")
83 parent-bol ,ts-mode-indent-offset)
84 ((parent-is "object_type")
85 parent-bol ,ts-mode-indent-offset)
86 ((parent-is "enum_body")
87 parent-bol ,ts-mode-indent-offset)
88 ((parent-is "arrow_function")
89 parent-bol ,ts-mode-indent-offset)
90 ((parent-is "parenthesized_expression")
91 parent-bol ,ts-mode-indent-offset)
92
93 ;; TSX
94 ((parent-is "jsx_opening_element")
95 parent ,ts-mode-indent-offset)
96 ((node-is "jsx_closing_element") parent 0)
97 ((parent-is "jsx_element")
98 parent ,ts-mode-indent-offset)
99 ((node-is "/") parent 0)
100 ((parent-is "jsx_self_closing_element")
101 parent ,ts-mode-indent-offset)
102 (no-node parent-bol 0))))
103
104(defvar ts-mode--settings
105 (treesit-font-lock-rules
106 :language 'tsx
107 :override t
108 '(
109 (template_string) @font-lock-string-face
110
111 ((identifier) @font-lock-constant-face
112 (:match "^[A-Z_][A-Z_\\d]*$" @font-lock-constant-face))
113
114 (nested_type_identifier
115 module: (identifier) @font-lock-type-face)
116 (type_identifier) @font-lock-type-face
117 (predefined_type) @font-lock-type-face
118
119 (new_expression
120 constructor: (identifier) @font-lock-type-face)
121
122 (function
123 name: (identifier) @font-lock-function-name-face)
124
125 (function_declaration
126 name: (identifier) @font-lock-function-name-face)
127
128 (method_definition
129 name: (property_identifier) @font-lock-function-name-face)
130
131 (variable_declarator
132 name: (identifier) @font-lock-function-name-face
133 value: [(function) (arrow_function)])
134
135 (variable_declarator
136 name: (array_pattern
137 (identifier)
138 (identifier) @font-lock-function-name-face)
139 value: (array (number) (function)))
140
141 (assignment_expression
142 left: [(identifier) @font-lock-function-name-face
143 (member_expression
144 property: (property_identifier) @font-lock-function-name-face)]
145 right: [(function) (arrow_function)])
146
147 (call_expression
148 function:
149 [(identifier) @font-lock-function-name-face
150 (member_expression
151 property: (property_identifier) @font-lock-function-name-face)])
152
153 (variable_declarator
154 name: (identifier) @font-lock-variable-name-face)
155
156 (enum_declaration (identifier) @font-lock-type-face)
157
158 (enum_body (property_identifier) @font-lock-type-face)
159
160 (enum_assignment name: (property_identifier) @font-lock-type-face)
161
162 (assignment_expression
163 left: [(identifier) @font-lock-variable-name-face
164 (member_expression
165 property: (property_identifier) @font-lock-variable-name-face)])
166
167 (for_in_statement
168 left: (identifier) @font-lock-variable-name-face)
169
170 (arrow_function
171 parameter: (identifier) @font-lock-variable-name-face)
172
173 (arrow_function
174 parameters:
175 [(_ (identifier) @font-lock-variable-name-face)
176 (_ (_ (identifier) @font-lock-variable-name-face))
177 (_ (_ (_ (identifier) @font-lock-variable-name-face)))])
178
179
180 (pair key: (property_identifier) @font-lock-variable-name-face)
181
182 (pair value: (identifier) @font-lock-variable-name-face)
183
184 (pair
185 key: (property_identifier) @font-lock-function-name-face
186 value: [(function) (arrow_function)])
187
188 (property_signature
189 name: (property_identifier) @font-lock-variable-name-face)
190
191 ((shorthand_property_identifier) @font-lock-variable-name-face)
192
193 (pair_pattern
194 key: (property_identifier) @font-lock-variable-name-face)
195
196 ((shorthand_property_identifier_pattern)
197 @font-lock-variable-name-face)
198
199 (array_pattern (identifier) @font-lock-variable-name-face)
200
201 (jsx_opening_element
202 [(nested_identifier (identifier)) (identifier)]
203 @font-lock-function-name-face)
204
205 (jsx_closing_element
206 [(nested_identifier (identifier)) (identifier)]
207 @font-lock-function-name-face)
208
209 (jsx_self_closing_element
210 [(nested_identifier (identifier)) (identifier)]
211 @font-lock-function-name-face)
212
213 (jsx_attribute (property_identifier) @font-lock-constant-face)
214
215 [(this) (super)] @font-lock-keyword-face
216
217 [(true) (false) (null)] @font-lock-constant-face
218 (regex pattern: (regex_pattern)) @font-lock-string-face
219 (number) @font-lock-constant-face
220
221 (string) @font-lock-string-face
222 (template_string) @font-lock-string-face
223
224 (template_substitution
225 ["${" "}"] @font-lock-constant-face)
226
227 ["!"
228 "abstract"
229 "as"
230 "async"
231 "await"
232 "break"
233 "case"
234 "catch"
235 "class"
236 "const"
237 "continue"
238 "debugger"
239 "declare"
240 "default"
241 "delete"
242 "do"
243 "else"
244 "enum"
245 "export"
246 "extends"
247 "finally"
248 "for"
249 "from"
250 "function"
251 "get"
252 "if"
253 "implements"
254 "import"
255 "in"
256 "instanceof"
257 "interface"
258 "keyof"
259 "let"
260 "namespace"
261 "new"
262 "of"
263 "private"
264 "protected"
265 "public"
266 "readonly"
267 "return"
268 "set"
269 "static"
270 "switch"
271 "target"
272 "throw"
273 "try"
274 "type"
275 "typeof"
276 "var"
277 "void"
278 "while"
279 "with"
280 "yield"
281 ] @font-lock-keyword-face
282
283 (comment) @font-lock-comment-face
284 )))
285
286(defvar ts-mode--defun-type-regexp
287 (rx (or "class_declaration"
288 "method_definition"
289 "function_declaration"
290 "lexical_declaration"))
291 "Regular expression that matches type of defun nodes.
292Used in `ts-mode--beginning-of-defun' and friends.")
293
294(defun ts-mode--beginning-of-defun (&optional arg)
295 "Tree-sitter `beginning-of-defun' function.
296ARG is the same as in `beginning-of-defun."
297 (let ((arg (or arg 1)))
298 (if (> arg 0)
299 ;; Go backward.
300 (while (and (> arg 0)
301 (treesit-search-forward-goto
302 ts-mode--defun-type-regexp 'start nil t))
303 (setq arg (1- arg)))
304 ;; Go forward.
305 (while (and (< arg 0)
306 (treesit-search-forward-goto
307 ts-mode--defun-type-regexp 'start))
308 (setq arg (1+ arg))))))
309
310(defun ts-mode--end-of-defun (&optional arg)
311 "Tree-sitter `end-of-defun' function.
312ARG is the same as in `end-of-defun."
313 (let ((arg (or arg 1)))
314 (if (< arg 0)
315 ;; Go backward.
316 (while (and (< arg 0)
317 (treesit-search-forward-goto
318 ts-mode--defun-type-regexp 'end nil t))
319 (setq arg (1+ arg)))
320 ;; Go forward.
321 (while (and (> arg 0)
322 (treesit-search-forward-goto
323 ts-mode--defun-type-regexp 'end))
324 (setq arg (1- arg))))))
325
326;;;###autoload
327(add-to-list 'auto-mode-alist '("\\.ts\\'" . ts-mode))
328
329;;;###autoload
330(add-to-list 'auto-mode-alist '("\\.tsx\\'" . ts-mode))
331
332;;;###autoload
333(define-derived-mode ts-mode prog-mode "TypeScript"
334 "Major mode for editing TypeScript."
335 :group 'typescript
336 :syntax-table ts-mode--syntax-table
337
338 (cond
339 ((and (treesit-can-enable-p)
340 (treesit-language-available-p 'tsx))
341 ;; Comments
342 (setq-local comment-start "// ")
343 (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
344 (setq-local comment-end "")
345
346 (setq-local treesit-simple-indent-rules ts-mode--indent-rules)
347 (setq-local indent-line-function #'treesit-indent)
348
349 (setq-local beginning-of-defun-function #'ts-mode--beginning-of-defun)
350 (setq-local end-of-defun-function #'ts-mode--end-of-defun)
351
352 (unless font-lock-defaults
353 (setq font-lock-defaults '(nil t)))
354
355 (setq-local treesit-font-lock-settings ts-mode--settings)
356
357 (treesit-font-lock-enable))
358 (t
359 (message "Tree sitter for TypeScript isn't available, defaulting to js-mode")
360 (js-mode))))
361
362(provide 'ts-mode)
363
364;;; ts-mode.el ends here