[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master c79d426 06/45: Support ES6 class statements/expressions.
From: |
Dmitry Gutov |
Subject: |
[elpa] master c79d426 06/45: Support ES6 class statements/expressions. |
Date: |
Mon, 02 Feb 2015 03:18:34 +0000 |
branch: master
commit c79d4260a38a5b44cd52dabfdccbff12323870ac
Author: Stephen Hicks <address@hidden>
Commit: Stephen Hicks <address@hidden>
Support ES6 class statements/expressions.
This is specified in ยง14.5 of the draft ES6 spec:
http://people.mozilla.org/~jorendorff/es6-draft.html#sec-class-definitions, and
described via examples at
https://github.com/lukehoban/es6features#enhanced-object-literals. This commit
does not yet add support for the 'static' keyword.
---
js2-mode.el | 142 ++++++++++++++++++++++++++++++++++++++++++++++++------
tests/parser.el | 32 ++++++++++++
2 files changed, 158 insertions(+), 16 deletions(-)
diff --git a/js2-mode.el b/js2-mode.el
index c53c5b6..64b20f8 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -661,8 +661,10 @@ which doesn't seem particularly useful, but Rhino permits
it."
(defvar js2-ENUM 161) ; for "enum" reserved word
(defvar js2-TRIPLEDOT 162) ; for rest parameter
(defvar js2-ARROW 163) ; function arrow (=>)
+(defvar js2-CLASS 164)
+(defvar js2-EXTENDS 165)
-(defconst js2-num-tokens (1+ js2-ARROW))
+(defconst js2-num-tokens (1+ js2-EXTENDS))
(defconst js2-debug-print-trees nil)
@@ -1956,6 +1958,18 @@ the correct number of ARGS must be provided."
"Yield from closing generator")
;; Classes
+(js2-msg "msg.unnamed.class.stmt" ; added by js2-mode
+ "class statement requires a name")
+
+(js2-msg "msg.class.unexpected.comma" ; added by js2-mode
+ "unexpected ',' between class properties")
+
+(js2-msg "msg.missing.extends" ; added by js2-mode
+ "name is required after extends")
+
+(js2-msg "msg.no.brace.class" ; added by js2-mode
+ "missing '{' before class body")
+
(js2-msg "msg.missing.computed.rb" ; added by js2-mode
"missing ']' after computed property expression")
@@ -3494,6 +3508,49 @@ You can tell the quote type by looking at the first
character."
(insert ",")))
(insert "]"))
+(defstruct (js2-class-node
+ (:include js2-node)
+ (:constructor nil)
+ (:constructor make-js2-class-node (&key (type js2-CLASS)
+ (pos js2-ts-cursor)
+ (form 'CLASS_STATEMENT)
+ (name "")
+ extends len elems)))
+ "AST node for an class expression.
+`elems' is a list of `js2-object-prop-node', and `extends' is an
+optional `js2-expr-node'"
+ form ; CLASS_{STATEMENT|EXPRESSION}
+ name ; class name (a `js2-node-name', or nil if anonymous)
+ extends ; class heritage (a `js2-expr-node', or nil if none)
+ elems)
+
+(put 'cl-struct-js2-class-node 'js2-visitor 'js2-visit-class-node)
+(put 'cl-struct-js2-class-node 'js2-printer 'js2-print-class-node)
+
+(defun js2-visit-class-node (n v)
+ (js2-visit-ast (js2-class-node-name n) v)
+ (js2-visit-ast (js2-class-node-extends n) v)
+ (dolist (e (js2-class-node-elems n))
+ (js2-visit-ast e v)))
+
+(defun js2-print-class-node (n i)
+ (let* ((pad (js2-make-pad i))
+ (name (js2-class-node-name n))
+ (extends (js2-class-node-extends n))
+ (elems (js2-class-node-elems n)))
+ (insert pad "class")
+ (when name
+ (insert " ")
+ (js2-print-ast name 0))
+ (when extends
+ (insert " extends ")
+ (js2-print-ast extends))
+ (insert " {")
+ (dolist (elem elems)
+ (insert "\n")
+ (js2-print-ast elem (1+ i)))
+ (insert "\n" pad "}")))
+
(defstruct (js2-object-node
(:include js2-node)
(:constructor nil)
@@ -4660,6 +4717,7 @@ You should use `js2-print-tree' instead of this function."
js2-CALL
js2-CATCH
js2-CATCH_SCOPE
+ js2-CLASS
js2-CONST
js2-CONTINUE
js2-DEBUGGER
@@ -5283,9 +5341,9 @@ into temp buffers."
(defconst js2-keywords
'(break
- case catch const continue
+ case catch class const continue
debugger default delete do
- else enum
+ else enum extends
false finally for function
if in instanceof import
let
@@ -5303,9 +5361,9 @@ into temp buffers."
(let ((table (make-vector js2-num-tokens nil))
(tokens
(list js2-BREAK
- js2-CASE js2-CATCH js2-CONST js2-CONTINUE
+ js2-CASE js2-CATCH js2-CLASS js2-CONST js2-CONTINUE
js2-DEBUGGER js2-DEFAULT js2-DELPROP js2-DO
- js2-ELSE
+ js2-ELSE js2-EXTENDS
js2-FALSE js2-FINALLY js2-FOR js2-FUNCTION
js2-IF js2-IN js2-INSTANCEOF js2-IMPORT
js2-LET
@@ -6576,7 +6634,8 @@ of a simple name. Called before EXPR has a parent node."
"Highlight function properties and external variables."
(let (leftpos name)
;; highlight vars and props assigned function values
- (when (js2-function-node-p right)
+ (when (or (js2-function-node-p right)
+ (js2-class-node-p right))
(cond
;; var foo = function() {...}
((js2-name-node-p left)
@@ -7615,6 +7674,7 @@ node are given relative start positions and correct
lengths."
(let ((parsers (make-vector js2-num-tokens
#'js2-parse-expr-stmt)))
(aset parsers js2-BREAK #'js2-parse-break)
+ (aset parsers js2-CLASS #'js2-parse-class-stmt)
(aset parsers js2-CONST #'js2-parse-const-var)
(aset parsers js2-CONTINUE #'js2-parse-continue)
(aset parsers js2-DEBUGGER #'js2-parse-debugger)
@@ -7660,6 +7720,7 @@ node are given relative start positions and correct
lengths."
js2-LC
js2-ERROR
js2-SEMI
+ js2-CLASS
js2-FUNCTION)
"List of tokens that don't do automatic semicolon insertion.")
@@ -9239,6 +9300,8 @@ array-literals, array comprehensions and regular
expressions."
tt)
(setq tt (js2-current-token-type))
(cond
+ ((= tt js2-CLASS)
+ (js2-parse-class-expr))
((= tt js2-FUNCTION)
(js2-parse-function-expr))
((= tt js2-LB)
@@ -9540,7 +9603,52 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)'
form is allowed."
(js2-node-add-children pn iter obj)
pn))
+(defun js2-parse-class-stmt ()
+ (let ((pos (js2-current-token-beg)))
+ (js2-must-match-name "msg.unnamed.class.stmt")
+ (js2-parse-class pos 'CLASS_STATEMENT (js2-create-name-node t))))
+
+(defun js2-parse-class-expr ()
+ (let ((pos (js2-current-token-beg))
+ name)
+ (when (js2-match-token js2-NAME)
+ (setq name (js2-create-name-node t)))
+ (js2-parse-class pos 'CLASS_EXPRESSION name)))
+
+(defun js2-parse-class (pos form name)
+ ;; class X [extends ...] {
+ (let (pn elems extends)
+ (when name
+ (js2-set-face (js2-node-pos name) (js2-node-end name)
+ 'font-lock-function-name-face 'record))
+ (if (js2-match-token js2-EXTENDS)
+ (if (= (js2-peek-token) js2-LC)
+ (js2-report-error "msg.missing.extends")
+ ;; TODO(sdh): this should be left-hand-side-expr, not assign-expr
+ (setq extends (js2-parse-assign-expr))
+ (if (not extends)
+ (js2-report-error "msg.bad.extends"))))
+ (js2-must-match js2-LC "msg.no.brace.class")
+ (setq elems (js2-parse-object-literal-elems t)
+ pn (make-js2-class-node :pos pos
+ :len (- js2-ts-cursor pos)
+ :form form
+ :name name
+ :extends extends
+ :elems elems))
+ (apply #'js2-node-add-children pn (js2-class-node-elems pn))
+ pn))
+
(defun js2-parse-object-literal ()
+ (let* ((pos (js2-current-token-beg))
+ (elems (js2-parse-object-literal-elems))
+ (result (make-js2-object-node :pos pos
+ :len (- js2-ts-cursor pos)
+ :elems elems)))
+ (apply #'js2-node-add-children result (js2-object-node-elems result))
+ result))
+
+(defun js2-parse-object-literal-elems (&optional class-p)
(let ((pos (js2-current-token-beg))
tt elems result after-comma
(continue t))
@@ -9569,8 +9677,9 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form
is allowed."
((= tt js2-NUMBER)
(setq after-comma nil)
(push (js2-parse-plain-property (make-js2-number-node)) elems))
- ;; trailing comma
- ((= tt js2-RC)
+ ;; break out of loop, trailing comma
+ ((or (= tt js2-RC)
+ (= tt js2-EOF))
(js2-unget-token)
(setq continue nil)
(if after-comma
@@ -9580,15 +9689,16 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)'
form is allowed."
(js2-report-error "msg.bad.prop")
(unless js2-recover-from-parse-errors
(setq continue nil)))) ; end switch
- (if (js2-match-token js2-COMMA)
- (setq after-comma (js2-current-token-end))
- (setq continue nil))) ; end loop
+ ;; handle commas, depending on class-p
+ (let ((comma (js2-match-token js2-COMMA)))
+ (if class-p
+ (if comma
+ (js2-report-error "msg.class.unexpected.comma"))
+ (if comma
+ (setq after-comma (js2-current-token-end))
+ (setq continue nil))))) ; end loop
(js2-must-match js2-RC "msg.no.brace.prop")
- (setq result (make-js2-object-node :pos pos
- :len (- js2-ts-cursor pos)
- :elems (nreverse elems)))
- (apply #'js2-node-add-children result (js2-object-node-elems result))
- result))
+ (nreverse elems)))
(defun js2-parse-named-prop (tt)
"Parse a name, string, or getter/setter object property.
diff --git a/tests/parser.el b/tests/parser.el
index fe66d36..65f3ae4 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -332,6 +332,38 @@ the test."
(js2-deftest-parse octal-number-broken "0o812;"
:syntax-error "0o8" :errors-count 2)
+;;; Classes
+
+(js2-deftest-parse parse-harmony-class-statement
+ "class Foo {\n get bar() { return 42;\n}\n set bar(x) { y = x;\n}\n}")
+
+(js2-deftest-parse parse-harmony-class-statement-without-name-is-not-ok
+ "class {\n get bar() { return 42;\n}\n}"
+ :syntax-error "{")
+
+(js2-deftest-parse parse-harmony-class-expression
+ "var Foo1 = class Foo {\n bar() { return 42;\n}\n};")
+
+(js2-deftest-parse parse-harmony-anonymous-class-expression
+ "var Foo = class {\n set bar(x) { bar = x;\n}\n};")
+
+(js2-deftest-parse parse-harmony-class-with-extends
+ "class Foo extends Bar {\n}")
+
+(js2-deftest-parse parse-harmony-anonymous-class-with-extends
+ "foo.Foo = class extends Bar {\n set bar(x) { bar = x;\n}\n};")
+
+(js2-deftest-parse parse-harmony-class-with-complex-extends
+ "class Foo extends foo[BAR][2].Baz {\n}")
+
+(js2-deftest-parse parse-harmony-class-missing-extended-class-is-not-ok
+ "class Foo extends {\n}"
+ :syntax-error "extends")
+
+(js2-deftest-parse parse-unterminated-class-is-not-okay
+ "class Foo {\n get bar() { return 42;\n}"
+ :syntax-error "}")
+
;;; Scopes
(js2-deftest ast-symbol-table-includes-fn-node "function foo() {}"
- [elpa] master updated (d47c872 -> a1ac1e0), Dmitry Gutov, 2015/02/01
- [elpa] master 6037c0f 04/45: Add support for 'u' and 'y' RegExp flags in ES6 mode., Dmitry Gutov, 2015/02/01
- [elpa] master dad2f12 03/45: Support method definitions in object literals., Dmitry Gutov, 2015/02/01
- [elpa] master 926cb45 05/45: Support ES6 computed property names., Dmitry Gutov, 2015/02/01
- [elpa] master 30249fa 07/45: Support parsing the ES6 'static' keyword., Dmitry Gutov, 2015/02/01
- [elpa] master c79d426 06/45: Support ES6 class statements/expressions.,
Dmitry Gutov <=
- [elpa] master 2b0f5ba 08/45: Support parsing the ES6 'super' keyword., Dmitry Gutov, 2015/02/01
- [elpa] master dd344ff 01/45: Support short-hand object literals {foo, bar} everywhere., Dmitry Gutov, 2015/02/01
- [elpa] master b1f7e6c 11/45: Simplify parsing of destructuring literals, Dmitry Gutov, 2015/02/01
- [elpa] master 68b4abd 02/45: Add ES6 class externs, Dmitry Gutov, 2015/02/01
- [elpa] master ee17084 13/45: js2-parse-plain-property: Disallow numbers as abbreviated props, Dmitry Gutov, 2015/02/01
- [elpa] master b19ea52 12/45: js2-parse-named-prop: Highlight externals in abbreviated props, Dmitry Gutov, 2015/02/01
- [elpa] master 31c49e0 14/45: js2-imenu-extension-styles: Turn into defvar, Dmitry Gutov, 2015/02/01
- [elpa] master 9b77d64 15/45: Implement template strings, ignoring substitutions, Dmitry Gutov, 2015/02/01
- [elpa] master 7b3d590 17/45: No need to explicitly apply face to string nodes, Dmitry Gutov, 2015/02/01
- [elpa] master 3f09ff3 09/45: Fix string literals in parser.el test harness., Dmitry Gutov, 2015/02/01