Skip to content

Commit 47e2604

Browse files
authored
Merge pull request #70 from emacs-php/fix/json-false-object
Map JSON false and null to nil in phpstan--parse-json
2 parents c46b6c8 + 3298803 commit 47e2604

5 files changed

Lines changed: 70 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ All notable changes of the `phpstan.el` are documented in this file using the [K
4242
* `phpstan-version` and `phpstan-editor-mode-available-p` now take the whole command line, as returned by `phpstan-get-executable-and-args`. A bare string is still accepted. `phpstan-version` no longer merges STDERR into the version string, which a container runtime pollutes with its progress report.
4343
* Fix `declare-function` forms for `tramp` that quoted the function name and argument list (and misspelled `tramp` as `tamp`), so the byte compiler warned that `tramp-dissect-file-name` might not be defined at runtime.
4444
* Fix a container run erroring when the project has no configuration file. `phpstan-normalize-path` was handed the nil from `phpstan-get-config-file` and passed it to `replace-regexp-in-string`; it now returns nil for a nil path, so the command line simply omits `-c`.
45+
* Fix `phpstan--parse-json` reading JSON `false` (and, on `json-parse-buffer`, `null`) as a truthy symbol. A non-ignorable message (`"ignorable":false`) was treated as ignorable by both backends, so its identifier was shown with the 🪪 prefix and `phpstan-insert-ignore` offered it — even though it cannot be ignored. Both parser paths now map `false`/`null` to nil.
4546

4647
### Removed
4748

phpstan.el

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,18 @@ it returns the value of `SOURCE' as it is."
429429
(when (search-forward-regexp "^{" nil t)
430430
(backward-char 1)
431431
(delete-region (point-min) (point))))
432+
;; Map JSON false and null to nil. PHPStan's `ignorable' is a boolean read
433+
;; in a truthy context, and by default both parsers return a truthy symbol
434+
;; for false (`:false' / `:json-false'); `json-parse-buffer' also returns a
435+
;; truthy `:null'. Without this, a non-ignorable message looks ignorable,
436+
;; and the two parsers disagree on null.
432437
(if (eval-when-compile (and (fboundp 'json-serialize)
433438
(fboundp 'json-parse-buffer)))
434439
(with-no-warnings
435-
(json-parse-buffer :object-type 'plist :array-type 'list))
436-
(let ((json-object-type 'plist) (json-array-type 'list))
440+
(json-parse-buffer :object-type 'plist :array-type 'list
441+
:false-object nil :null-object nil))
442+
(let ((json-object-type 'plist) (json-array-type 'list)
443+
(json-false nil) (json-null nil))
437444
(json-read-object)))))
438445

439446
(defun phpstan--expand-file-name (name)

test/flycheck-phpstan-test.el

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
;;; Code:
2727
(require 'ert)
28+
(require 'cl-lib)
2829
(require 'flycheck-phpstan)
2930

3031
(defconst flycheck-phpstan-test--json
@@ -66,5 +67,24 @@ A swallowed fallback would show a failing PHPStan as a clean buffer."
6667
(should (string-match-p "Bootstrap file"
6768
(flycheck-error-message (car errors))))))
6869

70+
(ert-deftest flycheck-phpstan-test-identifier-only-when-ignorable ()
71+
"The identifier prefix is shown only for an ignorable message.
72+
A non-ignorable message (\"ignorable\":false) reads its flag as nil now, so
73+
it must not look like something `phpstan-insert-ignore' can act on."
74+
(let* ((phpstan-disable-buffer-errors t)
75+
(phpstan-identifier-prefix "ID:")
76+
(flycheck-phpstan-ignore-metadata-list nil)
77+
(output (concat "{\"files\":{\"/x\":{\"messages\":["
78+
"{\"message\":\"parse error\",\"line\":1,\"ignorable\":false,"
79+
"\"identifier\":\"ignore.parseError\"},"
80+
"{\"message\":\"undefined\",\"line\":2,\"ignorable\":true,"
81+
"\"identifier\":\"variable.undefined\"}]}}}"))
82+
(errors (flycheck-phpstan-parse-output output))
83+
(by-line (lambda (n) (car (cl-remove-if-not
84+
(lambda (e) (= n (flycheck-error-line e))) errors)))))
85+
(should-not (string-match-p "ID:" (flycheck-error-message (funcall by-line 1))))
86+
(should (string-match-p "ID:variable.undefined"
87+
(flycheck-error-message (funcall by-line 2))))))
88+
6989
(provide 'flycheck-phpstan-test)
7090
;;; flycheck-phpstan-test.el ends here

test/flymake-phpstan-test.el

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ This is what lets `phpstan-insert-ignore' work from Flymake."
7777
(should (equal '((4 "function.notFound") (7 "constant.notFound"))
7878
phpstan--ignorable-errors)))))
7979

80+
(ert-deftest flymake-phpstan-test-parse-skips-non-ignorable ()
81+
"A non-ignorable message (\"ignorable\":false) is not offered to ignore."
82+
(with-temp-buffer
83+
(let ((source (current-buffer))
84+
(phpstan-disable-buffer-errors nil)
85+
(json (concat "{\"files\":{\"/x\":{\"messages\":["
86+
"{\"message\":\"parse error\",\"line\":1,\"ignorable\":false,"
87+
"\"identifier\":\"ignore.parseError\"},"
88+
"{\"message\":\"undefined\",\"line\":2,\"ignorable\":true,"
89+
"\"identifier\":\"variable.undefined\"}]}}}")))
90+
(flymake-phpstan--parse json source)
91+
(should (equal '((2 "variable.undefined")) phpstan--ignorable-errors)))))
92+
8093
(ert-deftest flymake-phpstan-test-parse-json-with-stderr-prefix ()
8194
"The report is found even when a container prefixes it with progress."
8295
(with-temp-buffer

test/phpstan-test.el

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@
3535
(phpstan--plist-to-alist '(:a 1 :b 2))))
3636
(should (equal nil (phpstan--plist-to-alist nil))))
3737

38+
;;; JSON parsing
39+
40+
(ert-deftest phpstan-test-parse-json-false-is-nil ()
41+
"JSON false and null read as nil, not a truthy symbol.
42+
`ignorable' is read in a truthy context, so `false' must be nil."
43+
(with-temp-buffer
44+
(insert "{\"a\":false,\"b\":null,\"c\":true}")
45+
(let ((data (phpstan--parse-json (current-buffer))))
46+
(should-not (plist-get data :a))
47+
(should-not (plist-get data :b))
48+
(should (eq t (plist-get data :c))))))
49+
50+
(ert-deftest phpstan-test-update-ignorable-skips-non-ignorable ()
51+
"Only ignorable messages feed `phpstan--ignorable-errors'.
52+
A non-ignorable message (\"ignorable\":false) must not be offered to
53+
`phpstan-insert-ignore'."
54+
(with-temp-buffer
55+
(insert (concat "{\"files\":{\"/x\":{\"messages\":["
56+
"{\"message\":\"parse error\",\"line\":1,\"ignorable\":false,"
57+
"\"identifier\":\"ignore.parseError\"},"
58+
"{\"message\":\"undefined\",\"line\":2,\"ignorable\":true,"
59+
"\"identifier\":\"variable.undefined\"}]}}}"))
60+
(let ((errors (phpstan--plist-to-alist
61+
(plist-get (phpstan--parse-json (current-buffer)) :files))))
62+
(phpstan-update-ignorebale-errors-from-json-buffer errors)
63+
(should (equal '((2 "variable.undefined")) phpstan--ignorable-errors)))))
64+
3865
;;; Container runtime detection
3966

4067
(ert-deftest phpstan-test-container-runtime-command ()

0 commit comments

Comments
 (0)