--- /dev/null
+;;; js2-imenu-extras.el --- Imenu support for additional constructs\r
+\r
+;; Copyright (C) 2012-2013 Free Software Foundation, Inc.\r
+\r
+;; Author: Dmitry Gutov <dgutov@yandex.ru>\r
+;; Keywords: languages, javascript, imenu\r
+\r
+;; This file is part of GNU Emacs.\r
+\r
+;; GNU Emacs is free software: you can redistribute it and/or modify\r
+;; it under the terms of the GNU General Public License as published by\r
+;; the Free Software Foundation, either version 3 of the License, or\r
+;; (at your option) any later version.\r
+\r
+;; GNU Emacs is distributed in the hope that it will be useful,\r
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+;; GNU General Public License for more details.\r
+\r
+;; You should have received a copy of the GNU General Public License\r
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+;;; Commentary:\r
+\r
+;; This package adds Imenu support for additional framework constructs and\r
+;; structural patterns to `js2-mode'.\r
+\r
+;; Usage:\r
+\r
+;; (add-hook 'js2-mode-hook 'js2-imenu-extras-mode)\r
+\r
+;; To customize how it works:\r
+;; M-x customize-group RET js2-imenu RET\r
+\r
+(eval-when-compile\r
+ (require 'cl))\r
+\r
+(require 'js2-mode)\r
+\r
+(defconst js2-imenu-extension-styles\r
+ `((:framework jquery\r
+ :call-re "\\_<\\(?:jQuery\\|\\$\\|_\\)\\.extend\\s-*("\r
+ :recorder js2-imenu-record-jquery-extend)\r
+\r
+ (:framework jquery-ui\r
+ :call-re "^\\s-*\\(?:jQuery\\|\\$\\)\\.widget\\s-*("\r
+ :recorder js2-imenu-record-string-declare)\r
+\r
+ (:framework dojo\r
+ :call-re "^\\s-*dojo.declare\\s-*("\r
+ :recorder js2-imenu-record-string-declare)\r
+\r
+ (:framework backbone\r
+ :call-re ,(concat "\\_<" js2-mode-identifier-re "\\.extend\\s-*(")\r
+ :recorder js2-imenu-record-backbone-extend))\r
+ "List of JavaScript class definition or extension styles.\r
+\r
+:framework is a valid value in `js2-imenu-enabled-frameworks'.\r
+\r
+:call-re is a regular expression that has no capturing groups.\r
+\r
+:recorder is a function name that will be called when the regular\r
+expression matches some text in the buffer. When it's called, point will be\r
+at the end of the match. The function must keep the point position.")\r
+\r
+(defconst js2-imenu-available-frameworks\r
+ (mapcar (lambda (style) (plist-get style :framework)) js2-imenu-extension-styles)\r
+ "List of available JavaScript framework symbols.")\r
+\r
+(defcustom js2-imenu-enabled-frameworks js2-imenu-available-frameworks\r
+ "Frameworks to be recognized by `js2-mode'."\r
+ :type (cons 'set (mapcar (lambda (x) (list 'const x))\r
+ js2-imenu-available-frameworks))\r
+ :group 'js2-imenu)\r
+\r
+(defcustom js2-imenu-show-other-functions t\r
+ "Non-nil to show functions not recognized by other mechanisms,\r
+in a shared namespace."\r
+ :type 'boolean\r
+ :group 'js2-imenu)\r
+\r
+(defcustom js2-imenu-other-functions-ns "?"\r
+ "Namespace name to use for other functions."\r
+ :type 'string\r
+ :group 'js2-imenu)\r
+\r
+(defcustom js2-imenu-show-module-pattern t\r
+ "Non-nil to recognize the module pattern:\r
+\r
+var foobs = (function(a) {\r
+ return {fib: function() {}, fub: function() {}};\r
+})(b);\r
+\r
+We record the returned hash as belonging to the named module, and\r
+prefix any functions defined inside the IIFE with the module name."\r
+ :type 'boolean\r
+ :group 'js2-imenu)\r
+\r
+;;;###autoload\r
+(defun js2-imenu-extras-setup ()\r
+ (when js2-imenu-enabled-frameworks\r
+ (add-hook 'js2-post-parse-callbacks 'js2-imenu-record-declarations t t))\r
+ (when (or js2-imenu-show-other-functions js2-imenu-show-module-pattern)\r
+ (add-hook 'js2-post-parse-callbacks 'js2-imenu-walk-ast t t)))\r
+\r
+(defun js2-imenu-extras-remove ()\r
+ (remove-hook 'js2-post-parse-callbacks 'js2-imenu-record-declarations t)\r
+ (remove-hook 'js2-post-parse-callbacks 'js2-imenu-walk-ast t))\r
+\r
+(defun js2-imenu-record-declarations ()\r
+ (let* ((styles (loop for style in js2-imenu-extension-styles\r
+ when (memq (plist-get style :framework)\r
+ js2-imenu-enabled-frameworks)\r
+ collect style))\r
+ (re (mapconcat (lambda (style)\r
+ (concat "\\(" (plist-get style :call-re) "\\)"))\r
+ styles "\\|")))\r
+ (goto-char (point-min))\r
+ (while (js2-re-search-forward re nil t)\r
+ (loop for i from 0 to (1- (length styles))\r
+ when (match-beginning (1+ i))\r
+ return (funcall (plist-get (nth i styles) :recorder))))))\r
+\r
+(defun js2-imenu-record-jquery-extend ()\r
+ (let ((pred (lambda (subject)\r
+ (and\r
+ (js2-prop-get-node-p subject)\r
+ (string= (js2-name-node-name (js2-prop-get-node-right subject))\r
+ "prototype")))))\r
+ (js2-imenu-record-extend-first-arg (1- (point)) pred\r
+ 'js2-compute-nested-prop-get)))\r
+\r
+(defun js2-imenu-record-string-declare ()\r
+ (js2-imenu-record-extend-first-arg\r
+ (1- (point)) 'js2-string-node-p\r
+ (lambda (node) (split-string (js2-string-node-value node) "\\." t))))\r
+\r
+(defun js2-imenu-record-extend-first-arg (point pred qname-fn)\r
+ (let* ((node (js2-node-at-point point))\r
+ (args (js2-call-node-args node))\r
+ (subject (first args)))\r
+ (when (funcall pred subject)\r
+ (loop for arg in (cdr args)\r
+ when (js2-object-node-p arg)\r
+ do (js2-record-object-literal\r
+ arg (funcall qname-fn subject) (js2-node-abs-pos arg))))))\r
+\r
+(defun js2-imenu-record-backbone-extend ()\r
+ (let* ((node (js2-node-at-point (1- (point))))\r
+ (args (js2-call-node-args node))\r
+ (methods (first args))\r
+ (parent (js2-node-parent node)))\r
+ (when (js2-object-node-p methods)\r
+ (let ((subject (cond ((js2-var-init-node-p parent)\r
+ (js2-var-init-node-target parent))\r
+ ((js2-assign-node-p parent)\r
+ (js2-assign-node-left parent)))))\r
+ (when subject\r
+ (js2-record-object-literal methods\r
+ (js2-compute-nested-prop-get subject)\r
+ (js2-node-abs-pos methods)))))))\r
+\r
+(defun js2-imenu-walk-ast ()\r
+ (js2-visit-ast\r
+ js2-mode-ast\r
+ (lambda (node end-p)\r
+ (unless end-p\r
+ (cond\r
+ ((and js2-imenu-show-other-functions\r
+ (js2-object-prop-node-p node))\r
+ (js2-imenu-record-orphan-function node))\r
+ ((and js2-imenu-show-module-pattern\r
+ (js2-assign-node-p node))\r
+ (js2-imenu-record-module-pattern node)))\r
+ t))))\r
+\r
+(defun js2-imenu-record-orphan-function (node)\r
+ "Record orphan function when it's the value of NODE.\r
+NODE must be `js2-object-prop-node'."\r
+ (when (js2-function-node-p (js2-object-prop-node-right node))\r
+ (let ((fn-node (js2-object-prop-node-right node)))\r
+ (unless (and js2-imenu-function-map\r
+ (gethash fn-node js2-imenu-function-map))\r
+ (let ((key-node (js2-object-prop-node-left node)))\r
+ (js2-record-imenu-entry fn-node\r
+ (list js2-imenu-other-functions-ns\r
+ (js2-prop-node-name key-node))\r
+ (js2-node-abs-pos key-node)))))))\r
+\r
+(defun js2-imenu-record-module-pattern (node)\r
+ "Recognize and record module pattern use instance.\r
+NODE must be `js2-assign-node'."\r
+ (let ((init (js2-assign-node-right node)))\r
+ (when (js2-call-node-p init)\r
+ (let ((target (js2-assign-node-left node))\r
+ (callt (js2-call-node-target init)))\r
+ ;; Just basic call form: (function() {...})();\r
+ ;; TODO: Handle variations without duplicating `js2-wrapper-function-p'?\r
+ (when (and (js2-paren-node-p callt)\r
+ (js2-function-node-p (js2-paren-node-expr callt)))\r
+ (let* ((fn (js2-paren-node-expr callt))\r
+ (blk (js2-function-node-body fn))\r
+ (ret (car (last (js2-block-node-kids blk)))))\r
+ (when (and (js2-return-node-p ret)\r
+ (js2-object-node-p (js2-return-node-retval ret)))\r
+ ;; TODO: Map function names when revealing module pattern is used.\r
+ (let ((retval (js2-return-node-retval ret))\r
+ (target-qname (js2-compute-nested-prop-get target)))\r
+ (js2-record-object-literal retval target-qname\r
+ (js2-node-abs-pos retval))\r
+ (js2-record-imenu-entry fn target-qname\r
+ (js2-node-abs-pos target))))))))))\r
+\r
+;;;###autoload\r
+(define-minor-mode js2-imenu-extras-mode\r
+ "Toggle Imenu support for frameworks and structural patterns."\r
+ :lighter ""\r
+ (if js2-imenu-extras-mode\r
+ (js2-imenu-extras-setup)\r
+ (js2-imenu-extras-remove)))\r
+\r
+(provide 'js2-imenu-extras)\r
;; Dmitry Gutov <dgutov@yandex.ru>
;; URL: https://github.com/mooz/js2-mode/
;; http://code.google.com/p/js2-mode/
-;; Version: 20130307
+;; Version: 20130510
;; Keywords: languages, javascript
;; Package-Requires: ((emacs "24.1"))
;; (add-hook 'js-mode-hook 'js2-minor-mode)
+;; You may also want to hook it in for shell scripts running via node.js:
+
+;; (add-to-list 'interpreter-mode-alist '("node" . js2-mode))
+
;; To customize how it works:
;; M-x customize-group RET js2-mode RET
DocumentRange Range RangeException
;; W3C XML
- XPathResult XMLHttpRequest))
+ XPathResult XMLHttpRequest
+
+ ;; console object. Provided by at least Chrome and Firefox.
+ console))
"Browser externs.
You can cause these to be included or excluded with the custom
variable `js2-include-browser-externs'.")
"Node.js externs.
Set `js2-include-node-externs' to t to include them.")
+(defvar js2-typed-array-externs
+ (mapcar 'symbol-name
+ '(ArrayBuffer Uint8ClampedArray DataView
+ Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array
+ Float32Array Float64Array))
+ "Khronos typed array externs. Available in most modern browsers and
+in node.js >= 0.6. If `js2-include-node-externs' or `js2-include-browser-externs'
+are enabled, these will also be included.")
+
;;; Variables
(defun js2-mark-safe-local (name pred)
externs appropriate for the specific file, perhaps based on its path.
These should go in `js2-additional-externs', which is buffer-local.
+Third, you can use JSLint's global declaration, as long as
+`js2-include-jslint-globals' is non-nil, which see.
+
Finally, you can add a function to `js2-post-parse-callbacks',
which is called after parsing completes, and `js2-mode-ast' is bound to
the root of the parse tree. At this stage you can set up an AST
:type 'boolean
:group 'js2-mode)
-(defcustom js2-auto-insert-catch-block t
- "Non-nil to insert matching catch block on open-curly after `try'."
+(defcustom js2-include-jslint-globals t
+ "Non-nil to include the identifiers from JSLint global
+declaration (see http://www.jslint.com/lint.html#global) in the
+buffer-local externs list. See `js2-additional-externs' for more
+information."
:type 'boolean
:group 'js2-mode)
(let ((expr (js2-expr-stmt-node-expr node)))
(or (js2-node-has-side-effects expr)
(when (js2-string-node-p expr)
- (string= "use strict" (js2-string-node-value expr))))))
+ (member (js2-string-node-value expr) '("use strict" "use asm"))))))
((= tt js2-COMMA)
(js2-node-has-side-effects (js2-infix-node-right node)))
((or (= tt js2-AND)
'js2-external-variable))))
(setq js2-recorded-identifiers nil)))
+(defun js2-set-default-externs ()
+ "Set the value of `js2-default-externs' based on the various
+`js2-include-?-externs' variables."
+ (setq js2-default-externs
+ (append js2-ecma-262-externs
+ (if js2-include-browser-externs js2-browser-externs)
+ (if js2-include-rhino-externs js2-rhino-externs)
+ (if js2-include-node-externs js2-node-externs)
+ (if (or js2-include-browser-externs js2-include-node-externs)
+ js2-typed-array-externs))))
+
+(defun js2-apply-jslint-globals ()
+ (setq js2-additional-externs
+ (nconc (js2-get-jslint-globals)
+ js2-additional-externs)))
+
+(defun js2-get-jslint-globals ()
+ (loop for node in (js2-ast-root-comments js2-mode-ast)
+ when (and (eq 'block (js2-comment-node-format node))
+ (save-excursion
+ (goto-char (js2-node-abs-pos node))
+ (looking-at "/\\*global ")))
+ append (js2-get-jslint-globals-in
+ (match-end 0)
+ (js2-node-abs-end node))))
+
+(defun js2-get-jslint-globals-in (beg end)
+ (let (res)
+ (save-excursion
+ (goto-char beg)
+ (while (re-search-forward js2-mode-identifier-re end t)
+ (let ((match (match-string 0)))
+ (unless (member match '("true" "false"))
+ (push match res)))))
+ (nreverse res)))
+
;;; IMenu support
;; We currently only support imenu, but eventually should support speedbar and
;; Give extensions a chance to muck with things before highlighting starts.
(let ((js2-additional-externs js2-additional-externs))
(save-excursion
- (dolist (callback js2-post-parse-callbacks)
- (funcall callback)))
+ (run-hooks 'js2-post-parse-callbacks))
(js2-highlight-undeclared-vars))
root))
(js2-backward-sws)
(or (eq (char-before) ?,)
(and (not (eq (char-before) ?\;))
- (and
- (prog2 (skip-syntax-backward ".")
- (looking-at js2-indent-operator-re)
- (js2-backward-sws))
- (not (eq (char-before) ?\;))))
+ (prog2 (skip-syntax-backward ".")
+ (looking-at js2-indent-operator-re)
+ (js2-backward-sws))
+ (not (eq (char-before) ?\;)))
(js2-same-line pos)))))
(condition-case err
(backward-sexp)
(set (make-local-variable 'max-lisp-eval-depth)
(max max-lisp-eval-depth 3000))
(setq next-error-function #'js2-next-error)
- (setq js2-default-externs
- (append js2-ecma-262-externs
- (if js2-include-browser-externs js2-browser-externs)
- (if js2-include-rhino-externs js2-rhino-externs)
- (if js2-include-node-externs js2-node-externs)))
+ (js2-set-default-externs)
;; Experiment: make reparse-delay longer for longer files.
(if (plusp js2-dynamic-idle-timer-adjust)
(setq js2-idle-timer-delay
(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)
+ (when js2-include-jslint-globals
+ (add-hook 'js2-post-parse-callbacks 'js2-apply-jslint-globals nil t))
(js2-reparse))
(defun js2-minor-mode-exit ()
(delete-overlay js2-mode-node-overlay)
(setq js2-mode-node-overlay nil))
(js2-remove-overlays)
+ (remove-hook 'js2-post-parse-callbacks 'js2-apply-jslint-globals t)
(setq js2-mode-ast nil))
(defvar js2-source-buffer nil "Linked source buffer for diagnostics view")
(make-local-variable 'adaptive-fill-regexp)
(c-setup-paragraph-variables))
- (setq js2-default-externs
- (append js2-ecma-262-externs
- (if js2-include-browser-externs js2-browser-externs)
- (if js2-include-rhino-externs js2-rhino-externs)
- (if js2-include-node-externs js2-node-externs)))
-
(setq font-lock-defaults '(nil t))
;; Experiment: make reparse-delay longer for longer files.
js2-mode-comments-hidden nil
js2-mode-buffer-dirty-p t
js2-mode-parsing nil)
+
+ (js2-set-default-externs)
+
+ (when js2-include-jslint-globals
+ (add-hook 'js2-post-parse-callbacks 'js2-apply-jslint-globals nil t))
+
(js2-reparse))
(defun js2-mode-exit ()