]> code.delx.au - gnu-emacs-elpa/blobdiff - js2-mode.el
Merge branch 'master' into emacs24
[gnu-emacs-elpa] / js2-mode.el
index 1f8634b73ca39ef3b94a47851d0dce1382517c53..64ad3a5b6bbdaa554a52f9b79d64a032e8fed008 100644 (file)
@@ -233,27 +233,24 @@ js2-mode also binds `js2-bounce-indent-backwards' to Shift-Tab."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-consistent-level-indent-inner-bracket-p t
-  "Non-nil to make indentation level inner bracket consistent,
-regardless of the beginning bracket position."
-  :group 'js2-mode
-  :type 'boolean)
-(js2-mark-safe-local 'js2-consistent-level-indent-inner-bracket-p 'booleanp)
+(defcustom js2-pretty-multiline-declarations t
+  "Non-nil to line up multiline declarations vertically:
 
-(defcustom js2-pretty-multiline-decl-indentation-p t
-  "Non-nil to line up multiline declarations vertically. See the
-function `js2-multiline-decl-indentation' for details."
-  :group 'js2-mode
-  :type 'boolean)
-(js2-mark-safe-local 'js2-pretty-multiline-decl-indentation-p 'booleanp)
+  var a = 10,
+      b = 20,
+      c = 30;
+
+If the value is not `all', and the first assigned value in
+declaration is a function/array/object literal spanning several
+lines, it won't be indented additionally:
 
-(defcustom js2-always-indent-assigned-expr-in-decls-p nil
-  "If both `js2-pretty-multiline-decl-indentation-p' and this are non-nil,
-always additionally indent function expression or array/object literal
-assigned in a declaration, even when only one var is declared."
+  var o = {                   var bar = 2,
+    foo: 3          vs.           o = {
+  },                                foo: 3
+      bar = 2;                    };"
   :group 'js2-mode
-  :type 'boolean)
-(js2-mark-safe-local 'js2-always-indent-assigned-expr-in-decls-p 'booleanp)
+  :type 'symbol)
+(js2-mark-safe-local 'js2-pretty-multiline-declarations 'symbolp)
 
 (defcustom js2-indent-on-enter-key nil
   "Non-nil to have Enter/Return key indent the line.
@@ -292,6 +289,13 @@ If `js2-dynamic-idle-timer-adjust' is 0 or negative,
   :type 'boolean
   :group 'js2-mode)
 
+(defcustom js2-concat-multiline-strings t
+  "Non-nil to automatically turn a newline in mid-string into a
+string concatenation.  When `eol', the '+' will be inserted at the
+end of the line, otherwise, at the beginning of the next line."
+  :type '(choice (const t) (const eol) (const nil))
+  :group 'js2-mode)
+
 (defcustom js2-mode-squeeze-spaces t
   "Non-nil to normalize whitespace when filling in comments.
 Multiple runs of spaces are converted to a single space."
@@ -349,14 +353,6 @@ parenthesizing the expression, e.g. if ((a = b)) ..."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-strict-cond-assign-warning t
-  "Non-nil to warn about expressions like if (a = b).
-This often should have been '==' instead of '='.  If the warning
-is enabled, you can suppress it on a per-expression basis by
-parenthesizing the expression, e.g. if ((a = b)) ..."
-  :type 'boolean
-  :group 'js2-mode)
-
 (defcustom js2-strict-var-redeclaration-warning t
   "Non-nil to warn about redeclaring variables in a script or function."
   :type 'boolean
@@ -1720,6 +1716,9 @@ the correct number of ARGS must be provided."
 (js2-msg "msg.assn.create.strict"
          "Assignment to undeclared variable %s")
 
+(js2-msg "msg.undeclared.variable"  ; added by js2-mode
+         "Undeclared variable or function '%s'")
+
 (js2-msg "msg.ref.undefined.prop"
          "Reference to undefined property '%s'")
 
@@ -6487,7 +6486,7 @@ it is considered declared."
                     (member name js2-default-externs)
                     (member name js2-additional-externs)
                     (js2-get-defining-scope scope name))
-          (js2-report-warning "Undeclared variable" nil pos (- end pos)))))
+          (js2-report-warning "msg.undeclared.variable" name pos (- end pos)))))
     (setq js2-recorded-identifiers nil)))
 
 ;;; IMenu support
@@ -6605,7 +6604,8 @@ the parent function, look up its qname, then prepend a copy of it to the chain."
 (defun js2-record-imenu-functions (node &optional var)
   "Record function definitions for imenu.
 NODE is a function node or an object literal.
-VAR, if non-nil, is the expression that NODE is being assigned to."
+VAR, if non-nil, is the expression that NODE is being assigned to.
+When passed arguments of wrong type, does nothing."
   (when js2-parse-ide-mode
     (let ((fun-p (js2-function-node-p node))
           qname left fname-node pos)
@@ -6721,9 +6721,9 @@ For instance, processing a nested scope requires a parent function node."
     (dolist (entry entries)
       ;; function node goes first
       (destructuring-bind (current-fn &rest (&whole chain head &rest)) entry
-        ;; examine its defining scope;
-        ;; if top-level/external, keep as-is
-        (if (js2-node-top-level-decl-p head)
+        ;; Examine head's defining scope:
+        ;; Pre-processed chain, or top-level/external, keep as-is.
+        (if (or (stringp head) (js2-node-top-level-decl-p head))
             (push chain result)
           (when (js2-this-node-p head)
             (setq chain (cdr chain))) ; discard this-node
@@ -8254,10 +8254,7 @@ Returns the parsed `js2-var-decl-node' expression node."
       (when (js2-match-token js2-ASSIGN)
         (setq init (js2-parse-assign-expr)
               end (js2-node-end init))
-        (if (and js2-parse-ide-mode
-                 (or (js2-object-node-p init)
-                     (js2-function-node-p init)))
-            (js2-record-imenu-functions init name)))
+        (js2-record-imenu-functions init name))
       (when name
         (js2-set-face nbeg nend (if (js2-function-node-p init)
                                     'font-lock-function-name-face
@@ -8415,9 +8412,7 @@ If NODE is non-nil, it is the AST node associated with the symbol."
                                        :right right))
         (when js2-parse-ide-mode
           (js2-highlight-assign-targets pn left right)
-          (if (or (js2-function-node-p right)
-                  (js2-object-node-p right))
-              (js2-record-imenu-functions right left)))
+          (js2-record-imenu-functions right left))
         ;; do this last so ide checks above can use absolute positions
         (js2-node-add-children pn left right))
       pn)))
@@ -9632,21 +9627,7 @@ indented to the same column as the current line."
 
 (defun js2-multiline-decl-indentation ()
   "Returns the declaration indentation column if the current line belongs
-to a multiline declaration statement.  All declarations are lined up vertically:
-
-var a = 10,
-    b = 20,
-    c = 30;
-
-Note that if `js2-always-indent-assigned-expr-in-decls-p' is nil, and the first
-assigned expression is a function or array/object literal, it will be indented
-differently:
-
-var o = {                               var bar = 2,
-  foo: 3                                    o = {
-},                                            foo: 3
-    bar = 2;                                };
-"
+to a multiline declaration statement.  See `js2-pretty-multiline-declarations'."
   (let (forward-sexp-function ; use Lisp version
         at-opening-bracket)
     (save-excursion
@@ -9719,8 +9700,9 @@ In particular, return the buffer position of the first `for' kwd."
             ;; so we'll just guess at it.
             (if (and (> end (point)) ; not empty literal
                      (re-search-forward "[^,]]* \\(for\\) " end t)
-                     ;; not inside a string literal
-                     (not (nth 3 (parse-partial-sexp bracket (point)))))
+                     ;; not inside comment or string literal
+                     (let ((state (parse-partial-sexp bracket (point))))
+                       (not (or (nth 3 state) (nth 4 state)))))
                 (match-beginning 1))))))))
 
 (defun js2-array-comp-indentation (parse-status for-kwd)
@@ -9742,13 +9724,14 @@ In particular, return the buffer position of the first `for' kwd."
     (let ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
           (same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
           (continued-expr-p (js2-continued-expression-p))
-          (declaration-indent (and js2-pretty-multiline-decl-indentation-p
+          (declaration-indent (and js2-pretty-multiline-declarations
                                    (js2-multiline-decl-indentation)))
           (bracket (nth 1 parse-status))
           beg)
       (cond
        ;; indent array comprehension continuation lines specially
        ((and bracket
+             (>= js2-language-version 170)
              (not (js2-same-line bracket))
              (setq beg (js2-indent-in-array-comp parse-status))
              (>= (point) (save-excursion
@@ -9767,25 +9750,19 @@ In particular, return the buffer position of the first `for' kwd."
         (goto-char bracket)
         (cond
          ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)")
-          (let ((p (parse-partial-sexp (point-at-bol) (point))))
-            (when (save-excursion (skip-chars-backward " \t)")
-                                  (looking-at ")"))
-              (backward-list))
-            (if (and (nth 1 p)
-                     (not js2-consistent-level-indent-inner-bracket-p))
-                (progn (goto-char (1+ (nth 1 p)))
-                       (skip-chars-forward " \t"))
-              (back-to-indentation)
-              (when (and js2-pretty-multiline-decl-indentation-p
-                         js2-always-indent-assigned-expr-in-decls-p
-                         (looking-at js2-declaration-keyword-re))
-                (goto-char (1+ (match-end 0)))))
-            (cond (same-indent-p
-                   (current-column))
-                  (continued-expr-p
-                   (+ (current-column) (* 2 js2-basic-offset)))
-                  (t
-                   (+ (current-column) js2-basic-offset)))))
+          (when (save-excursion (skip-chars-backward " \t)")
+                                (looking-at ")"))
+            (backward-list))
+          (back-to-indentation)
+          (and (eq js2-pretty-multiline-declarations 'all)
+               (looking-at js2-declaration-keyword-re)
+               (goto-char (1+ (match-end 0))))
+          (cond (same-indent-p
+                 (current-column))
+                (continued-expr-p
+                 (+ (current-column) (* 2 js2-basic-offset)))
+                (t
+                 (+ (current-column) js2-basic-offset))))
          (t
           (unless same-indent-p
             (forward-char)
@@ -10131,8 +10108,8 @@ highlighting features of `js2-mode'."
             (* js2-idle-timer-delay
                (/ (point-max) js2-dynamic-idle-timer-adjust))))
   (setq js2-mode-buffer-dirty-p t
-        js2-mode-parsing nil
-        js2-highlight-level 0)  ; no syntax highlighting
+        js2-mode-parsing nil)
+  (set (make-local-variable 'js2-highlight-level) 0) ; no syntax highlighting
   (add-hook 'after-change-functions #'js2-minor-mode-edit nil t)
   (add-hook 'change-major-mode-hook #'js2-minor-mode-exit nil t)
   (js2-reparse))
@@ -10148,22 +10125,116 @@ highlighting features of `js2-mode'."
   (js2-remove-overlays)
   (setq js2-mode-ast nil))
 
-(defun js2-display-error-list ()
+(defvar js2-source-buffer nil "Linked source buffer for diagnostics view")
+(make-variable-buffer-local 'js2-source-buffer)
+
+(defun* js2-display-error-list ()
   "Display a navigable buffer listing parse errors/warnings."
   (interactive)
-  (if (not (js2-have-errors-p))
-      (message "No errors")
-    (let ((srcbuf (current-buffer))
-          (errbuf (get-buffer-create "*js-lint*"))
-          (errs (js2-errors-and-warnings)))
-      (setq errs (sort errs (lambda (e1 e2)
-                              (funcall '< (second e1) (second e2)))))
+  (unless (js2-have-errors-p)
+    (message "No errors")
+    (return-from js2-display-error-list))
+  (labels ((annotate-list
+            (lst type)
+            "Add diagnostic TYPE and line number to errs list"
+            (mapcar (lambda (err)
+                      (append err (list type
+                                        (line-number-at-pos (nth 1 err)))))
+                    lst)))
+    (let* ((srcbuf (current-buffer))
+           (errbuf (get-buffer-create "*js-lint*"))
+           (errors (annotate-list
+                    (when js2-mode-ast (js2-ast-root-errors js2-mode-ast))
+                    'js2-error))  ; must be a valid face name
+           (warnings (annotate-list
+                      (when js2-mode-ast (js2-ast-root-warnings js2-mode-ast))
+                      'js2-warning))  ; must be a valid face name
+           (all-errs (sort (append errors warnings)
+                           (lambda (e1 e2)
+                             (funcall '< (nth 1 e1) (nth 1 e2))))))
       (with-current-buffer errbuf
         (let ((inhibit-read-only t))
           (erase-buffer)
-          (dolist (err errs)
-            (insert (format "%s\n" err)))
-          (pop-to-buffer errbuf))))))
+          (dolist (err all-errs)
+            (destructuring-bind (msg-key beg end type line) err
+              (insert-text-button
+               (format "line %d: %s" line (js2-get-msg msg-key))
+               'face type
+               'follow-link "\C-m"
+               'action 'js2-error-buffer-jump
+               'js2-msg (js2-get-msg msg-key)
+               'js2-pos beg)
+              (insert "\n"))))
+        (js2-error-buffer-mode)
+        (setq js2-source-buffer srcbuf)
+        (pop-to-buffer errbuf)
+        (goto-char (point-min))
+        (unless (eobp)
+          (js2-error-buffer-view))))))
+
+(defvar js2-error-buffer-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "n" #'js2-error-buffer-next)
+    (define-key map "p" #'js2-error-buffer-prev)
+    (define-key map (kbd "RET") #'js2-error-buffer-jump)
+    (define-key map "o" #'js2-error-buffer-view)
+    (define-key map "q" #'js2-error-buffer-quit)
+    map)
+  "Keymap used for js2 diagnostics buffers.")
+
+(defun js2-error-buffer-mode ()
+  "Major mode for js2 diagnostics buffers.
+Selecting an error will jump it to the corresponding source-buffer error.
+\\{js2-error-buffer-mode-map}"
+  (interactive)
+  (setq major-mode 'js2-error-buffer-mode
+        mode-name "JS Lint Diagnostics")
+  (use-local-map js2-error-buffer-mode-map)
+  (setq truncate-lines t)
+  (set-buffer-modified-p nil)
+  (setq buffer-read-only t)
+  (run-hooks 'js2-error-buffer-mode-hook))
+
+(defun js2-error-buffer-next ()
+  "Move to next error and view it."
+  (interactive)
+  (when (zerop (forward-line 1))
+    (js2-error-buffer-view)))
+
+(defun js2-error-buffer-prev ()
+  "Move to previous error and view it."
+  (interactive)
+  (when (zerop (forward-line -1))
+    (js2-error-buffer-view)))
+
+(defun js2-error-buffer-quit ()
+  "Kill the current buffer."
+  (interactive)
+  (kill-buffer))
+
+(defun js2-error-buffer-jump (&rest ignored)
+  "Jump cursor to current error in source buffer."
+  (interactive)
+  (when (js2-error-buffer-view)
+    (pop-to-buffer js2-source-buffer)))
+
+(defun js2-error-buffer-view ()
+  "Scroll source buffer to show error at current line."
+  (interactive)
+  (cond
+   ((not (eq major-mode 'js2-error-buffer-mode))
+    (message "Not in a js2 errors buffer"))
+   ((not (buffer-live-p js2-source-buffer))
+    (message "Source buffer has been killed"))
+   ((not (wholenump (get-text-property (point) 'js2-pos)))
+    (message "There does not seem to be an error here"))
+   (t
+    (let ((pos (get-text-property (point) 'js2-pos))
+          (msg (get-text-property (point) 'js2-msg)))
+      (save-selected-window
+        (pop-to-buffer js2-source-buffer)
+        (goto-char pos)
+        (message msg))))))
 
 ;;;###autoload
 (define-derived-mode js2-mode prog-mode "Javascript-IDE"
@@ -10253,7 +10324,6 @@ highlighting features of `js2-mode'."
     (delete-overlay js2-mode-node-overlay)
     (setq js2-mode-node-overlay nil))
   (js2-remove-overlays)
-  (setq next-error-function nil)
   (setq js2-mode-ast nil)
   (remove-hook 'change-major-mode-hook #'js2-mode-exit t)
   (remove-from-invisibility-spec '(js2-outline . t))
@@ -10480,9 +10550,9 @@ This ensures that the counts and `next-error' are correct."
 (defun js2-echo-error (old-point new-point)
   "Called by point-motion hooks."
   (let ((msg (get-text-property new-point 'help-echo)))
-    (if (and msg (or (not (current-message))
-                     (string= (current-message) "Quit")))
-        (message msg))))
+    (when (and (stringp msg) (or (not (current-message))
+                                 (string= (current-message) "Quit")))
+      (message msg))))
 
 (defalias #'js2-echo-help #'js2-echo-error)
 
@@ -10492,7 +10562,9 @@ This ensures that the counts and `next-error' are correct."
     (cond
      ;; Check if we're inside a string.
      ((nth 3 parse-status)
-      (js2-mode-split-string parse-status))
+      (if js2-concat-multiline-strings
+          (js2-mode-split-string parse-status)
+        (insert "\n")))
      ;; Check if inside a block comment.
      ((nth 4 parse-status)
       (js2-mode-extend-comment))
@@ -10506,6 +10578,7 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
          (quote-char (nth 3 parse-status))
          (quote-string (string quote-char))
          (string-beg (nth 8 parse-status))
+         (at-eol (eq js2-concat-multiline-strings 'eol))
          (indent (or
                   (save-excursion
                     (back-to-indentation)
@@ -10516,9 +10589,15 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
                     (if (looking-back "\\+\\s-+")
                         (goto-char (match-beginning 0)))
                     (current-column)))))
-    (insert quote-char "\n")
+    (insert quote-char)
+    (if at-eol
+        (insert " +\n")
+      (insert "\n"))
+    ;; FIXME: This does not match the behavior of `js2-indent-line'.
     (indent-to indent)
-    (insert "+ " quote-string)
+    (unless at-eol
+      (insert "+ "))
+    (insert quote-string)
     (when (eolp)
       (insert quote-string)
       (backward-char 1))))
@@ -10986,10 +11065,12 @@ move backward across N balanced expressions."
   (or (js2-errors) (js2-warnings)))
 
 (defun js2-errors-and-warnings ()
-  "Return a copy of the concatenated errors and warnings lists."
-  (and js2-mode-ast
-       (append (js2-ast-root-errors js2-mode-ast)
-               (copy-sequence (js2-ast-root-warnings js2-mode-ast)))))
+  "Return a copy of the concatenated errors and warnings lists.
+They are appended:  first the errors, then the warnings.
+Entries are of the form (MSG BEG END)."
+  (when js2-mode-ast
+    (append (js2-ast-root-errors js2-mode-ast)
+            (copy-sequence (js2-ast-root-warnings js2-mode-ast)))))
 
 (defun js2-next-error (&optional arg reset)
   "Move to next parse error.