-
Notifications
You must be signed in to change notification settings - Fork 13
/
php-cs-fixer.el
262 lines (237 loc) · 10.5 KB
/
php-cs-fixer.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
;;; php-cs-fixer.el --- The php-cs-fixer wrapper
;;; License:
;; Copyright 2015 OVYA (Renée Costes Group). All rights reserved.
;; Use of this source code is governed by a BSD-style
;; license that can be found in the LICENSE file.
;;; Author: Philippe Ivaldi for OVYA
;; Source: Some pieces of code are copied from go-mode.el https://github.com/dominikh/go-mode.el
;; Version: 2.0.0
;; Keywords: languages php
;; Package-Requires: ((cl-lib "0.5"))
;; URL: https://github.com/OVYA/php-cs-fixer
;;
;;; Commentary:
;; This file is not part of GNU Emacs.
;; See the file README.org for further information.
;;; Code:
(require 'cl-lib)
;;;###autoload
(defgroup php-cs-fixer nil
"The php-cs-fixer wrapper group."
:tag "PHP"
:prefix "php-cs-fixer-"
:group 'languages
:link '(url-link :tag "Source code repository" "https://github.com/OVYA/php-cs-fixer")
:link '(url-link :tag "Executable dependency" "https://github.com/FriendsOfPHP/PHP-CS-Fixer"))
(defcustom php-cs-fixer-command "php-cs-fixer"
"The php-cs-fixer command."
:type 'string
:group 'php-cs-fixer)
(defcustom php-cs-fixer-config-option nil
"The php-cs-fixer config option.
If not nil `php-cs-rules-level-part-options`
and `php-cs-rules-fixer-part-options` are not used."
:type 'string
:group 'php-cs-fixer)
(defcustom php-cs-fixer-rules-level-part-options '("@Symfony")
"The php-cs-fixer --rules base part options."
:type '(repeat
(choice
;; (const :tag "Not set" :value nil)
(const :value "@DoctrineAnnotation")
(const :value "@PHP54Migration")
(const :value "@PHP56Migration:risky")
(const :value "@PHP70Migration")
(const :value "@PHP70Migration:risky")
(const :value "@PHP71Migration")
(const :value "@PHP71Migration:risky")
(const :value "@PHP73Migration")
(const :value "@PHP74Migration")
(const :value "@PHP74Migration:risky")
(const :value "@PHP80Migration")
(const :value "@PHP80Migration:risky")
(const :value "@PHP81Migration")
(const :value "@PHPUnit30Migration:risky")
(const :value "@PHPUnit32Migration:risky")
(const :value "@PHPUnit35Migration:risky")
(const :value "@PHPUnit43Migration:risky")
(const :value "@PHPUnit48Migration:risky")
(const :value "@PHPUnit50Migration:risky")
(const :value "@PHPUnit52Migration:risky")
(const :value "@PHPUnit54Migration:risky")
(const :value "@PHPUnit55Migration:risky")
(const :value "@PHPUnit56Migration:risky")
(const :value "@PHPUnit57Migration:risky")
(const :value "@PHPUnit60Migration:risky")
(const :value "@PHPUnit75Migration:risky")
(const :value "@PHPUnit84Migration:risky")
(const :value "@PSR1")
(const :value "@PSR12")
(const :value "@PSR12:risky")
(const :value "@PSR2")
(const :value "@PhpCsFixer")
(const :value "@PhpCsFixer:risky")
(const :value "@Symfony")
(const :value "@Symfony:risky")))
:group 'php-cs-fixer)
(defcustom php-cs-fixer-rules-fixer-part-options
'("multiline_whitespace_before_semicolons" "concat_space")
"The php-cs-fixer --rules part options.
These options are not part of `php-cs-fixer-rules-level-part-options`."
:type '(repeat string)
:group 'php-cs-fixer)
;; Copy of go--goto-line from https://github.com/dominikh/go-mode.el
(defun php-cs-fixer--goto-line (line)
"Private goto line to LINE."
(goto-char (point-min))
(forward-line (1- line)))
(defun php-cs-fixer--delete-whole-line (&optional arg)
"Delete the current line without putting it in the `kill-ring`.
Derived from the function `kill-whole-line'.
ARG is defined as for that function."
(delete-region
(progn (forward-line 0) (point))
(progn (forward-line (or arg 0)) (point))))
;; Derivated of go--apply-rcs-patch from https://github.com/dominikh/go-mode.el
(defun php-cs-fixer--apply-rcs-patch (patch-buffer)
"Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
(let ((target-buffer (current-buffer))
;; Relative offset between buffer line numbers and line numbers
;; in patch.
;;
;; Line numbers in the patch are based on the source file, so
;; we have to keep an offset when making changes to the
;; buffer.
;;
;; Appending lines decrements the offset (possibly making it
;; negative), deleting lines increments it. This order
;; simplifies the forward-line invocations.
(line-offset 0))
(save-excursion
(with-current-buffer patch-buffer
(goto-char (point-min))
(while (not (eobp))
(unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
(error "Invalid rcs patch or internal error in php-cs-fixer--apply-rcs-patch"))
(forward-line)
(let ((action (match-string 1))
(from (string-to-number (match-string 2)))
(len (string-to-number (match-string 3))))
(cond
((equal action "a")
(let ((start (point)))
(forward-line len)
(let ((text (buffer-substring start (point))))
(with-current-buffer target-buffer
(cl-decf line-offset len)
(goto-char (point-min))
(forward-line (- from len line-offset))
(insert text)))))
((equal action "d")
(with-current-buffer target-buffer
(php-cs-fixer--goto-line (- from line-offset))
(cl-incf line-offset len)
(php-cs-fixer--delete-whole-line len)))
(t
(error "Invalid rcs patch or internal error in php-cs-fixer--apply-rcs-patch")))))))))
(defun php-cs-fixer--kill-error-buffer (errbuf)
"Private function that kill the error buffer ERRBUF."
(let ((win (get-buffer-window errbuf)))
(if win
(quit-window t win)
(kill-buffer errbuf))))
(defun php-cs-fixer--build-rules-options ()
"Private method to build the --rules options."
(if php-cs-fixer-config-option ""
(let ((base-opts
(concat
(if php-cs-fixer-rules-level-part-options
(mapconcat 'identity php-cs-fixer-rules-level-part-options ",")
nil)))
(other-opts (if php-cs-fixer-rules-fixer-part-options (concat "," (mapconcat 'identity php-cs-fixer-rules-fixer-part-options ",")) nil)))
(concat
"--rules=" base-opts
(if other-opts other-opts "")))))
(defvar php-cs-fixer-command-not-found-msg "Command php-cs-fixer not found.
Fix this issue removing the Emacs package php-cs-fixer or installing the program php-cs-fixer")
(defvar php-cs-fixer-command-bad-version-msg "Command php-cs-fixer version not supported.
Fix this issue removing the Emacs package php-cs-fixer or updating the program php-cs-fixer to version 2.*")
(defvar php-cs-fixer-is-command-ok-var nil)
(defun php-cs-fixer--is-command-ok ()
"Private Method.
Return t if the command `php-cs-fixer-command`
is available and supported by this package, return nil otherwise.
The test is done at first call and the same result will returns
for the next calls."
(if php-cs-fixer-is-command-ok-var
(= 1 php-cs-fixer-is-command-ok-var)
(progn
(message "Testing php-cs-fixer existence and version...")
(setq php-cs-fixer-is-command-ok-var 0)
(if (executable-find php-cs-fixer-command)
(if (string-match ".+ [2-3].[0-9]+.*"
(shell-command-to-string
(concat php-cs-fixer-command " --version")))
(progn (setq php-cs-fixer-is-command-ok-var 1) t)
(progn
(warn php-cs-fixer-command-bad-version-msg)
nil))
(progn (warn php-cs-fixer-command-not-found-msg) nil)))))
;;;###autoload
(defun php-cs-fixer-fix ()
"Formats the current PHP buffer according to the PHP-CS-Fixer tool."
(interactive)
(when (php-cs-fixer--is-command-ok)
(let ((tmpfile (make-temp-file "PHP-CS-Fixer" nil ".php"))
(patchbuf (get-buffer-create "*PHP-CS-Fixer patch*"))
(errbuf (get-buffer-create "*PHP-CS-Fixer Errors*"))
(coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8)
(errorp nil))
(save-restriction
(widen)
(if errbuf
(with-current-buffer errbuf
(setq buffer-read-only nil)
(erase-buffer)))
(with-current-buffer patchbuf
(erase-buffer))
(write-region nil nil tmpfile)
;; We're using errbuf for the mixed stdout and stderr output. This
;; is not an issue because php-cs-fixer -q does not produce any stdout
;; output in case of success.
(if (and (zerop (call-process "php" nil errbuf nil "-l" tmpfile))
(zerop (call-process php-cs-fixer-command
nil errbuf nil
"fix"
(if php-cs-fixer-config-option
(concat "--config=" (shell-quote-argument php-cs-fixer-config-option))
(php-cs-fixer--build-rules-options))
"--using-cache=no"
"--quiet"
tmpfile)))
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
(message "Buffer is already php-cs-fixed")
(progn
(php-cs-fixer--apply-rcs-patch patchbuf)
(message "Applied php-cs-fixer")))
(progn
(pop-to-buffer errbuf nil t)
(setq errorp t))))
(unless errorp (php-cs-fixer--kill-error-buffer errbuf))
(kill-buffer patchbuf)
(delete-file tmpfile))))
;;;###autoload
(defun php-cs-fixer-before-save ()
"Used to automatically fix the file saving the buffer.
Add this to .emacs to run php-cs-fix on the current buffer when saving:
(add-hook \\='before-save-hook \\='php-cs-fixer-before-save)."
(interactive)
(when (and
buffer-file-name
(string= (file-name-extension buffer-file-name) "php")
(or (not (boundp 'geben-temporary-file-directory))
(not (string-match geben-temporary-file-directory (file-name-directory buffer-file-name)))))
(php-cs-fixer-fix)))
(provide 'php-cs-fixer)
;;; php-cs-fixer.el ends here