You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

186 lines
5.9 KiB

;;; jscs.el --- Consistent JavaScript editing using JSCS -*- lexical-binding: t; -*-
;; Copyright (C) 2015 papaeye
;; Author: papaeye <papaeye@gmail.com>
;; Keywords: languages, convenience
;; Version: 0.1.0
;; Homepage: https://github.com/papaeye/emacs-jscs
;; Package-Requires: ((emacs "24.1") (langfmt "0.1.0"))
;; 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/>.
;;; Commentary:
;; jscs.el provides consistent JavaScript editing using JSCS.
;;
;; Installation:
;;
;; 1. Install JSCS <http://jscs.info/>.
;;
;; 2. Put jscs.el and langfmt.el <https://github.com/papaeye/go-mode.el>
;; somewhere in your `load-path'.
;;
;; 3. Add the following code into your .emacs:
;;
;; (autoload 'jscs-indent-apply "jscs" nil t)
;; (autoload 'jscs-fix "jscs" nil t)
;; (autoload 'jscs-fix-before-save "jscs" nil t)
;;
;; Usage:
;;
;; To apply JSCS indentation rules to JavaScript modes,
;; add the following code into your .emacs:
;;
;; (with-eval-after-load 'js
;; (add-hook 'js-mode-hook #'jscs-indent-apply))
;;
;; (with-eval-after-load 'js2-mode
;; (add-hook 'js2-mode-hook #'jscs-indent-apply))
;;
;; To run "jscs --fix" interactively, run \\[jscs-fix].
;;
;; To run "jscs --fix" on the current buffer when saving,
;; add the following code into your .emacs:
;;
;; (add-hook 'before-save-hook #'jscs-fix-before-save)
;;; Code:
(require 'json)
(require 'langfmt)
(defvar js-indent-level)
(defvar js2-basic-offset)
(defgroup jscs nil
"Consistent JavaScript editing using JSCS"
:group 'tools)
(defcustom jscs-command "jscs"
"The 'jscs' command."
:type 'string
:group 'jscs)
(defvar jscs--presets-path
(expand-file-name "../lib/node_modules/jscs/presets"
(file-name-directory (executable-find jscs-command))))
(defun jscs--read-jscsrc ()
(let ((dir (locate-dominating-file default-directory ".jscsrc")))
(when dir
(json-read-file (expand-file-name ".jscsrc" dir)))))
(defun jscs--read-preset (name)
(let ((preset (expand-file-name (concat name ".json") jscs--presets-path)))
(if (file-readable-p preset)
(json-read-file preset)
(error "Preset %s is not found" name))))
(defun jscs--config-list (config)
(let ((preset (cdr (assq 'preset config))))
(if (stringp preset)
(cons config (jscs--config-list (jscs--read-preset preset)))
(list config))))
(defun jscs-indent--rule-validate-indentation (config)
(let ((indent (cdr (assq 'validateIndentation config))))
(prog1 indent
(when (listp indent)
(setq indent (cdr (assq 'value indent))))
(cond
((integerp indent)
(cond
((memq major-mode '(js-mode json-mode))
(setq-local js-indent-level indent))
((eq major-mode 'js2-mode)
(setq js2-basic-offset indent)))
(setq indent-tabs-mode nil))
((string= indent "\t")
(setq indent-tabs-mode t))))))
(defun jscs-indent--rule-maximum-line-length (config)
(let ((rule (cdr (assq 'maximumLineLength config)))
tab-size)
(prog1 rule
(when (listp rule)
(setq tab-size (cdr (assq 'tabSize rule)))
(when (integerp tab-size)
(setq tab-width tab-size))))))
(defvar jscs-indent--rule-functions
(list #'jscs-indent--rule-validate-indentation
#'jscs-indent--rule-maximum-line-length))
(defun jscs-indent--apply (config)
(let ((config-list (jscs--config-list config)))
(dolist (func jscs-indent--rule-functions)
(let ((tail config-list)
done)
(while (and (not done) tail)
(setq done (funcall func (car tail)))
(setq tail (cdr tail)))))))
;;;###autoload
(defun jscs-indent-apply ()
(interactive)
(let ((jscsrc (jscs--read-jscsrc)))
(when jscsrc
(jscs-indent--apply jscsrc))))
;;;###autoload (autoload 'jscs-fix "jscs" nil t)
;;;###autoload (autoload 'jscs-fix-before-save "jscs" nil t)
(define-langfmt jscs-fix
"Format the current buffer according to the JSCS tool."
:group 'jscs
:modes '(js-mode js2-mode js3-mode)
:runner #'jscs-fix--runner
:error-filter #'jscs-fix--error-filter)
(defun jscs-fix--runner (tmpfile patchbuf errbuf)
(let ((exit (call-process jscs-command nil errbuf nil
"--fix" "--reporter" "inline" tmpfile)))
(if (= exit 1)
(progn
(message "No configuration found")
(when errbuf
(langfmt-kill-error-buffer errbuf)))
(if (zerop (call-process-region (point-min) (point-max) "diff"
nil patchbuf nil "-n" "-" tmpfile))
(message (if (zerop exit)
"Buffer is already jscs-fixed"
"Could not apply jscs-fix"))
(langfmt-apply-rcs-patch patchbuf)
(message (if (zerop exit)
"Applied jscs-fix"
"Applied jscs-fix partially")))
(when errbuf
(if (zerop exit)
(langfmt-kill-error-buffer errbuf)
(jscs-fix--process-errors (buffer-file-name) tmpfile errbuf))))))
(defun jscs-fix--error-filter (filename tmpfile)
(while (search-forward-regexp
(concat "^\\(?:"
(regexp-quote tmpfile)
"\\): line \\([0-9]+\\), col \\([0-9]+\\), \\(.+\\)")
nil t)
(replace-match (concat (file-name-nondirectory filename)
":" (match-string 1) ":" (match-string 2)
": " (match-string 3))
t t)))
(provide 'jscs)
;;; jscs.el ends here