-
-
Notifications
You must be signed in to change notification settings - Fork 27
/
elsa-rules-list.el
258 lines (215 loc) · 11.3 KB
/
elsa-rules-list.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
(require 'dash)
(require 'elsa-explainer)
(require 'elsa-reader)
(require 'elsa-error)
(require 'elsa-check)
(require 'elsa-scope)
(require 'elsa-state)
(require 'elsa-functions)
(defclass elsa-check-if (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-if) form scope state)
(elsa-form-function-call-p form 'if))
(defclass elsa-check-if-useless-condition (elsa-check-if) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-if-useless-condition) form scope state)
(or (elsa-form-function-call-p form 'if)
(elsa-form-function-call-p form 'when)
(elsa-form-function-call-p form 'unless)))
(cl-defmethod elsa-check-check ((_ elsa-check-if-useless-condition) form scope state)
(let ((condition (cadr (oref form sequence))))
(if (not (elsa-type-accept (oref condition type) (elsa-type-nil)))
(elsa-state-add-message state
(elsa-make-warning condition "Condition always evaluates to non-nil."))
(when (elsa-type-accept (elsa-type-nil) (oref condition type))
(elsa-state-add-message state
(elsa-make-warning condition "Condition always evaluates to nil."))))))
(defclass elsa-check-if-useless-then-progn (elsa-check-if) ())
(cl-defmethod elsa-check-check ((_ elsa-check-if-useless-then-progn) form scope state)
(let ((then-body (nth 2 (oref form sequence))))
(when (and (eq (elsa-get-name then-body) 'progn)
(= 2 (length (oref then-body sequence))))
(elsa-state-add-message state
(elsa-make-notice (elsa-car then-body) "Useless `progn' around body of then branch.")))))
(defclass elsa-check-if-useless-else-progn (elsa-check-if) ())
(cl-defmethod elsa-check-check ((_ elsa-check-if-useless-else-progn) form scope state)
(let ((else-body (nth 3 (oref form sequence))))
(when (eq (elsa-get-name else-body) 'progn)
(elsa-state-add-message state
(elsa-make-notice (elsa-car else-body) "Useless `progn' around body of else branch.")))))
(defclass elsa-check-if-to-when (elsa-check-if) ())
(cl-defmethod elsa-check-check ((_ elsa-check-if-to-when) form scope state)
(let ((then-body (nth 2 (oref form sequence)))
(else-body (nth 3 (oref form sequence))))
(unless else-body
(when (eq (elsa-get-name then-body) 'progn)
(elsa-state-add-message state
(elsa-make-notice (elsa-car form) "Rewrite `if' as `when' and unwrap the `progn' which is implicit."))))))
(defclass elsa-check-symbol (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-symbol) form scope state)
(elsa-form-symbol-p form))
(defclass elsa-check-symbol-naming (elsa-check-symbol) ())
(cl-defmethod elsa-check-check ((_ elsa-check-symbol-naming) form scope state)
(let ((name (symbol-name (elsa-get-name form))))
(when (string-match-p ".+_" name)
(elsa-state-add-message state
(elsa-make-notice form "Use lisp-case for naming symbol instead of snake_case.")))
(let ((case-fold-search nil))
(when (string-match-p ".+[a-z][A-Z]" name)
(elsa-state-add-message state
(elsa-make-notice form "Use lisp-case for naming symbol instead of camelCase."))))))
(defclass elsa-check-error-message (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-error-message) form scope state)
(elsa-form-function-call-p form 'error))
(cl-defmethod elsa-check-check ((_ elsa-check-error-message) form scope state)
(let ((error-message (nth 1 (oref form sequence))))
(when (elsa-form-string-p error-message)
(let ((msg (oref error-message sequence)))
(when (and (< 0 (length msg))
(equal (substring msg -1) "."))
(elsa-state-add-message state
(elsa-make-notice (elsa-car form) "Error messages should not end with a period.")))
(let ((case-fold-search nil))
(unless (string-match-p "[A-Z]" msg)
(elsa-state-add-message state
(elsa-make-notice (elsa-car form) "Error messages should start with a capital letter."))))))))
(defclass elsa-check-unbound-variable (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-unbound-variable) form scope state)
(and (elsa-form-symbol-p form)
(not (elsa-form-keyword-p form))))
(cl-defmethod elsa-check-check ((_ elsa-check-unbound-variable) form scope state)
(let* ((name (elsa-get-name form))
(var (elsa-scope-get-var scope name)))
(unless (or (eq name 't)
(eq name 'nil)
var
;; global variable defined in emacs core as a `defvar'
;; FIXME: this actually includes the variables bound
;; during analysis, so this is not the proper way to
;; check.
(get name 'elsa-type-var)
(boundp name))
(elsa-state-add-message state
(elsa-make-error form
"Reference to free variable `%s'." (symbol-name name))))))
(defclass elsa-check-cond-useless-condition (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-cond-useless-condition) form scope state)
(elsa-form-function-call-p form 'cond))
(cl-defmethod elsa-check-check ((_ elsa-check-cond-useless-condition) form scope state)
(let* ((branches (cdr (oref form sequence)))
(total (length branches)))
(-each-indexed branches
(lambda (index branch)
(-when-let* ((sequence (elsa-form-sequence branch))
(first-item (-first-item sequence)))
(if (and (not (elsa-type-accept (oref first-item type) (elsa-type-nil)))
(< index (1- total)))
(elsa-state-add-message state
(elsa-make-warning first-item "Condition always evaluates to non-nil."))
(when (elsa-type-accept (elsa-type-nil) (oref first-item type))
(elsa-state-add-message state
(elsa-make-warning first-item "Condition always evaluates to nil.")))))))))
(defclass elsa-check-lambda-eta-conversion (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-lambda-eta-conversion) form scope state)
(elsa-form-function-call-p form 'lambda))
(cl-defmethod elsa-check-check ((_ elsa-check-lambda-eta-conversion) form scope state)
(let* ((seq (oref form sequence))
(arg-list (elsa-form-sequence (nth 1 seq)))
(body (nthcdr 2 seq)))
(when (= 1 (length body))
(let ((fn-form (car body)))
(when (elsa-form-list-p fn-form)
(let ((fn-args (cdr (elsa-form-sequence fn-form))))
(when (and (= (length arg-list) (length fn-args))
(-all-p
(-lambda ((lambda-arg . fn-arg))
(and (elsa-form-symbol-p fn-arg)
(eq (elsa-get-name lambda-arg)
(elsa-get-name fn-arg))))
(-zip arg-list fn-args)))
(elsa-state-add-message state
(elsa-make-notice (elsa-car form)
"You can eta convert the lambda form and use the function `%s' directly"
(symbol-name (elsa-get-name fn-form)))))))))))
(defclass elsa-check-or-unreachable-code (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-or-unreachable-code) form scope state)
(elsa-form-function-call-p form 'or))
(cl-defmethod elsa-check-check ((_ elsa-check-or-unreachable-code) form scope state)
(let ((args (elsa-cdr form))
(can-be-nil-p t))
(-each args
(lambda (condition)
(if (not can-be-nil-p)
(elsa-state-add-message state
(elsa-make-warning condition
"Unreachable expression %S"
:code "or-unreachable-code"
(elsa-form-print form)))
(if (not (elsa-type-accept (oref condition type) (elsa-type-nil)))
(when can-be-nil-p (setq can-be-nil-p nil))
(when (and (elsa-type-accept (elsa-type-nil) (oref condition type))
can-be-nil-p)
(elsa-state-add-message state
(elsa-make-warning condition
"Condition always evaluates to nil."
:code "or-unreachable-code")))))))))
(defclass elsa-check-unreachable-code (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-unreachable-code) form scope state)
t)
(cl-defmethod elsa-check-check ((_ elsa-check-unreachable-code) form scope state)
(when (trinary-false-p (oref form reachable))
(elsa-state-add-message state
(elsa-make-warning form
"Unreachable expression %S"
:code "unreachable-code"
(elsa-form-print form)))))
(defclass elsa-check-public-functions-have-docstring (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-public-functions-have-docstring) form scope state)
(and (or (elsa-form-function-call-p form 'defun)
(elsa-form-function-call-p form 'defmacro)
(elsa-form-function-call-p form 'cl-defgeneric)
(elsa-form-function-call-p form 'defsubst))
(not (string-match-p "--" (symbol-name (elsa-get-name (elsa-nth 1 form)))))))
(cl-defmethod elsa-check-check ((_ elsa-check-public-functions-have-docstring) form scope state)
(let ((docstring-maybe (elsa-nth 3 form)))
(unless (elsa-form-string-p docstring-maybe)
(elsa-state-add-message state
(elsa-make-notice (elsa-car form) "Public functions should have a docstring.")))))
(defclass elsa-check-function-call (elsa-check) ())
(cl-defmethod elsa-check-should-run ((_ elsa-check-function-call) form scope state)
(elsa-form-function-call-p form))
(defclass elsa-check-useless-type-guard (elsa-check-function-call) ())
(cl-defmethod elsa-check-check ((_ elsa-check-useless-type-guard) form scope state)
"Check if the type guard is useless.
This can happen when the passed in arguent is of a subtype of the
returned narrowing."
(let* ((name (elsa-get-name form))
(arg-type (elsa-get-type (elsa-cadr form)))
(narrow-type (elsa-function-get-narrow-type name)))
(when narrow-type
(let ((could-accept (elsa-type-could-accept narrow-type arg-type)))
(cond
((trinary-true-p could-accept)
(elsa-state-add-message state
(elsa-make-warning (elsa-car form)
(elsa-with-temp-explainer expl
(elsa-explain-and-indent expl
("Function `%s' narrows argument type to `%s' and the argument type is `%s'"
name
(elsa-type-describe narrow-type)
(elsa-type-describe arg-type))
(elsa-explain expl
"Expression always evaluates to true because the argument %s will always be `%s'"
(elsa-form-print (elsa-cadr form))
(elsa-type-describe narrow-type)))
expl)
:code "useless-type-guard")))
((trinary-false-p could-accept)
(elsa-state-add-message state
(elsa-make-warning (elsa-car form)
"Function `%s' narrows argument type to `%s' and the argument type is `%s'.\n Expression always evaluates to false because the argument %s can never be `%s'."
:code "useless-type-guard"
name
(elsa-type-describe narrow-type)
(elsa-type-describe arg-type)
(elsa-form-print (elsa-cadr form))
(elsa-type-describe narrow-type)))))))))
(provide 'elsa-rules-list)