;;; jscs.el --- Consistent JavaScript editing using JSCS -*- lexical-binding: t; -*- ;; Copyright (C) 2015 papaeye ;; Author: papaeye ;; 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 . ;;; Commentary: ;; jscs.el provides consistent JavaScript editing using JSCS. ;; ;; Installation: ;; ;; 1. Install JSCS . ;; ;; 2. Put jscs.el and langfmt.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