diff options
| author | Fabián Ezequiel Gallina | 2015-04-05 23:58:13 -0300 |
|---|---|---|
| committer | Fabián Ezequiel Gallina | 2015-04-05 23:58:13 -0300 |
| commit | deea36f0ece7b1b14afe2a833b6a0f66d59f4459 (patch) | |
| tree | e3e41e0a5a03b0ea83b293c0c65835be038d56d4 | |
| parent | 7514b24b6a512d85b762c603e9e0107d2c8a52f1 (diff) | |
| download | emacs-deea36f0ece7b1b14afe2a833b6a0f66d59f4459.tar.gz emacs-deea36f0ece7b1b14afe2a833b6a0f66d59f4459.zip | |
python.el: Enhance docstring detection following PEP-257.
* lisp/progmodes/python.el (python-docstring-at-p): Remove function.
(python-info-assignment-statement-p): New function.
(python-info-assignment-continuation-line-p): Use it.
(python-info-docstring-p): New function.
(python-font-lock-syntactic-face-function)
(python-fill-string): Use it.
* test/automated/python-tests.el (python-info-assignment-statement-p-1)
(python-info-assignment-statement-p-2)
(python-info-assignment-statement-p-3, python-info-docstring-p-1)
(python-info-docstring-p-2, python-info-docstring-p-3)
(python-info-docstring-p-4, python-info-docstring-p-5)
(python-info-docstring-p-6): New tests.
| -rw-r--r-- | lisp/ChangeLog | 11 | ||||
| -rw-r--r-- | lisp/progmodes/python.el | 96 | ||||
| -rw-r--r-- | test/ChangeLog | 9 | ||||
| -rw-r--r-- | test/automated/python-tests.el | 173 |
4 files changed, 262 insertions, 27 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 8ea7f4a3cc8..c7d4dd5bac0 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,14 @@ | |||
| 1 | 2015-04-06 Fabián Ezequiel Gallina <fgallina@gnu.org> | ||
| 2 | |||
| 3 | python.el: Enhance docstring detection following PEP-257. | ||
| 4 | |||
| 5 | * progmodes/python.el (python-docstring-at-p): Remove function. | ||
| 6 | (python-info-assignment-statement-p): New function. | ||
| 7 | (python-info-assignment-continuation-line-p): Use it. | ||
| 8 | (python-info-docstring-p): New function. | ||
| 9 | (python-font-lock-syntactic-face-function) | ||
| 10 | (python-fill-string): Use it. | ||
| 11 | |||
| 1 | 2015-04-05 Eli Zaretskii <eliz@gnu.org> | 12 | 2015-04-05 Eli Zaretskii <eliz@gnu.org> |
| 2 | 13 | ||
| 3 | * ses.el (ses-sym-rowcol): Move up, before the first use, to avoid | 14 | * ses.el (ses-sym-rowcol): Move up, before the first use, to avoid |
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 67b44aa1bbe..f402ad83cca 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el | |||
| @@ -482,19 +482,10 @@ The type returned can be `comment', `string' or `paren'." | |||
| 482 | 'python-info-ppss-comment-or-string-p | 482 | 'python-info-ppss-comment-or-string-p |
| 483 | #'python-syntax-comment-or-string-p "24.3") | 483 | #'python-syntax-comment-or-string-p "24.3") |
| 484 | 484 | ||
| 485 | (defun python-docstring-at-p (pos) | ||
| 486 | "Check to see if there is a docstring at POS." | ||
| 487 | (save-excursion | ||
| 488 | (goto-char pos) | ||
| 489 | (if (looking-at-p "'''\\|\"\"\"") | ||
| 490 | (progn | ||
| 491 | (python-nav-backward-statement) | ||
| 492 | (looking-at "\\`\\|class \\|def ")) | ||
| 493 | nil))) | ||
| 494 | |||
| 495 | (defun python-font-lock-syntactic-face-function (state) | 485 | (defun python-font-lock-syntactic-face-function (state) |
| 486 | "Return syntactic face given STATE." | ||
| 496 | (if (nth 3 state) | 487 | (if (nth 3 state) |
| 497 | (if (python-docstring-at-p (nth 8 state)) | 488 | (if (python-info-docstring-p state) |
| 498 | font-lock-doc-face | 489 | font-lock-doc-face |
| 499 | font-lock-string-face) | 490 | font-lock-string-face) |
| 500 | font-lock-comment-face)) | 491 | font-lock-comment-face)) |
| @@ -3587,17 +3578,12 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'." | |||
| 3587 | (`pep-257 (and multi-line-p (cons nil 2))) | 3578 | (`pep-257 (and multi-line-p (cons nil 2))) |
| 3588 | (`pep-257-nn (and multi-line-p (cons nil 1))) | 3579 | (`pep-257-nn (and multi-line-p (cons nil 1))) |
| 3589 | (`symmetric (and multi-line-p (cons 1 1))))) | 3580 | (`symmetric (and multi-line-p (cons 1 1))))) |
| 3590 | (docstring-p (save-excursion | ||
| 3591 | ;; Consider docstrings those strings which | ||
| 3592 | ;; start on a line by themselves. | ||
| 3593 | (python-nav-beginning-of-statement) | ||
| 3594 | (and (= (point) str-start-pos)))) | ||
| 3595 | (fill-paragraph-function)) | 3581 | (fill-paragraph-function)) |
| 3596 | (save-restriction | 3582 | (save-restriction |
| 3597 | (narrow-to-region str-start-pos str-end-pos) | 3583 | (narrow-to-region str-start-pos str-end-pos) |
| 3598 | (fill-paragraph justify)) | 3584 | (fill-paragraph justify)) |
| 3599 | (save-excursion | 3585 | (save-excursion |
| 3600 | (when (and docstring-p python-fill-docstring-style) | 3586 | (when (and (python-info-docstring-p) python-fill-docstring-style) |
| 3601 | ;; Add the number of newlines indicated by the selected style | 3587 | ;; Add the number of newlines indicated by the selected style |
| 3602 | ;; at the start of the docstring. | 3588 | ;; at the start of the docstring. |
| 3603 | (goto-char (+ str-start-pos num-quotes)) | 3589 | (goto-char (+ str-start-pos num-quotes)) |
| @@ -4423,23 +4409,40 @@ where the continued line ends." | |||
| 4423 | (when (looking-at (python-rx block-start)) | 4409 | (when (looking-at (python-rx block-start)) |
| 4424 | (point-marker))))) | 4410 | (point-marker))))) |
| 4425 | 4411 | ||
| 4412 | (defun python-info-assignment-statement-p (&optional current-line-only) | ||
| 4413 | "Check if current line is an assignment. | ||
| 4414 | With argument CURRENT-LINE-ONLY is non-nil, don't follow any | ||
| 4415 | continuations, just check the if current line is an assignment." | ||
| 4416 | (save-excursion | ||
| 4417 | (let ((found nil)) | ||
| 4418 | (if current-line-only | ||
| 4419 | (back-to-indentation) | ||
| 4420 | (python-nav-beginning-of-statement)) | ||
| 4421 | (while (and | ||
| 4422 | (re-search-forward (python-rx not-simple-operator | ||
| 4423 | assignment-operator | ||
| 4424 | (group not-simple-operator)) | ||
| 4425 | (line-end-position) t) | ||
| 4426 | (not found)) | ||
| 4427 | (save-excursion | ||
| 4428 | ;; The assignment operator should not be inside a string. | ||
| 4429 | (backward-char (length (match-string-no-properties 1))) | ||
| 4430 | (setq found (not (python-syntax-context-type))))) | ||
| 4431 | (when found | ||
| 4432 | (skip-syntax-forward " ") | ||
| 4433 | (point-marker))))) | ||
| 4434 | |||
| 4435 | ;; TODO: rename to clarify this is only for the first continuation | ||
| 4436 | ;; line or remove it and move its body to `python-indent-context'. | ||
| 4426 | (defun python-info-assignment-continuation-line-p () | 4437 | (defun python-info-assignment-continuation-line-p () |
| 4427 | "Check if current line is a continuation of an assignment. | 4438 | "Check if current line is the first continuation of an assignment. |
| 4428 | When current line is continuation of another with an assignment | 4439 | When current line is continuation of another with an assignment |
| 4429 | return the point of the first non-blank character after the | 4440 | return the point of the first non-blank character after the |
| 4430 | operator." | 4441 | operator." |
| 4431 | (save-excursion | 4442 | (save-excursion |
| 4432 | (when (python-info-continuation-line-p) | 4443 | (when (python-info-continuation-line-p) |
| 4433 | (forward-line -1) | 4444 | (forward-line -1) |
| 4434 | (back-to-indentation) | 4445 | (python-info-assignment-statement-p t)))) |
| 4435 | (when (and (not (looking-at (python-rx block-start))) | ||
| 4436 | (and (re-search-forward (python-rx not-simple-operator | ||
| 4437 | assignment-operator | ||
| 4438 | not-simple-operator) | ||
| 4439 | (line-end-position) t) | ||
| 4440 | (not (python-syntax-context-type)))) | ||
| 4441 | (skip-syntax-forward "\s") | ||
| 4442 | (point-marker))))) | ||
| 4443 | 4446 | ||
| 4444 | (defun python-info-looking-at-beginning-of-defun (&optional syntax-ppss) | 4447 | (defun python-info-looking-at-beginning-of-defun (&optional syntax-ppss) |
| 4445 | "Check if point is at `beginning-of-defun' using SYNTAX-PPSS." | 4448 | "Check if point is at `beginning-of-defun' using SYNTAX-PPSS." |
| @@ -4464,6 +4467,45 @@ operator." | |||
| 4464 | (* whitespace) line-end)) | 4467 | (* whitespace) line-end)) |
| 4465 | (string-equal "" (match-string-no-properties 1)))) | 4468 | (string-equal "" (match-string-no-properties 1)))) |
| 4466 | 4469 | ||
| 4470 | (defun python-info-docstring-p (&optional syntax-ppss) | ||
| 4471 | "Return non-nil if point is in a docstring. | ||
| 4472 | When optional argument SYNTAX-PPSS is given, use that instead of | ||
| 4473 | point's current `syntax-ppss'." | ||
| 4474 | ;;; https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring | ||
| 4475 | (save-excursion | ||
| 4476 | (when (and syntax-ppss (python-syntax-context 'string syntax-ppss)) | ||
| 4477 | (goto-char (nth 8 syntax-ppss))) | ||
| 4478 | (python-nav-beginning-of-statement) | ||
| 4479 | (let ((counter 1) | ||
| 4480 | (indentation (current-indentation)) | ||
| 4481 | (backward-sexp-point) | ||
| 4482 | (re (concat "[uU]?[rR]?" | ||
| 4483 | (python-rx string-delimiter)))) | ||
| 4484 | (when (and | ||
| 4485 | (not (python-info-assignment-statement-p)) | ||
| 4486 | (looking-at-p re) | ||
| 4487 | ;; Allow up to two consecutive docstrings only. | ||
| 4488 | (>= | ||
| 4489 | 2 | ||
| 4490 | (progn | ||
| 4491 | (while (save-excursion | ||
| 4492 | (python-nav-backward-sexp) | ||
| 4493 | (setq backward-sexp-point (point)) | ||
| 4494 | (and (= indentation (current-indentation)) | ||
| 4495 | (looking-at-p | ||
| 4496 | (concat "[uU]?[rR]?" | ||
| 4497 | (python-rx string-delimiter))))) | ||
| 4498 | ;; Previous sexp was a string, restore point. | ||
| 4499 | (goto-char backward-sexp-point) | ||
| 4500 | (cl-incf counter)) | ||
| 4501 | counter))) | ||
| 4502 | (python-util-forward-comment -1) | ||
| 4503 | (python-nav-beginning-of-statement) | ||
| 4504 | (cond ((bobp)) | ||
| 4505 | ((python-info-assignment-statement-p) t) | ||
| 4506 | ((python-info-looking-at-beginning-of-defun)) | ||
| 4507 | (t nil)))))) | ||
| 4508 | |||
| 4467 | (defun python-info-encoding-from-cookie () | 4509 | (defun python-info-encoding-from-cookie () |
| 4468 | "Detect current buffer's encoding from its coding cookie. | 4510 | "Detect current buffer's encoding from its coding cookie. |
| 4469 | Returns the encoding as a symbol." | 4511 | Returns the encoding as a symbol." |
diff --git a/test/ChangeLog b/test/ChangeLog index f7bec2ee119..813f5dd42b7 100644 --- a/test/ChangeLog +++ b/test/ChangeLog | |||
| @@ -1,3 +1,12 @@ | |||
| 1 | 2015-04-06 Fabián Ezequiel Gallina <fgallina@gnu.org> | ||
| 2 | |||
| 3 | * automated/python-tests.el (python-info-assignment-statement-p-1) | ||
| 4 | (python-info-assignment-statement-p-2) | ||
| 5 | (python-info-assignment-statement-p-3, python-info-docstring-p-1) | ||
| 6 | (python-info-docstring-p-2, python-info-docstring-p-3) | ||
| 7 | (python-info-docstring-p-4, python-info-docstring-p-5) | ||
| 8 | (python-info-docstring-p-6): New tests. | ||
| 9 | |||
| 1 | 2015-04-01 Artur Malabarba <bruce.connor.am@gmail.com> | 10 | 2015-04-01 Artur Malabarba <bruce.connor.am@gmail.com> |
| 2 | 11 | ||
| 3 | * automated/package-test.el: Avoid async while testing. | 12 | * automated/package-test.el: Avoid async while testing. |
diff --git a/test/automated/python-tests.el b/test/automated/python-tests.el index b377a26f77a..22c111fc04a 100644 --- a/test/automated/python-tests.el +++ b/test/automated/python-tests.el | |||
| @@ -4273,6 +4273,49 @@ def foo(a, | |||
| 4273 | (python-tests-look-at "c):") | 4273 | (python-tests-look-at "c):") |
| 4274 | (should (not (python-info-block-continuation-line-p))))) | 4274 | (should (not (python-info-block-continuation-line-p))))) |
| 4275 | 4275 | ||
| 4276 | (ert-deftest python-info-assignment-statement-p-1 () | ||
| 4277 | (python-tests-with-temp-buffer | ||
| 4278 | " | ||
| 4279 | data = foo(), bar() \\\\ | ||
| 4280 | baz(), 4 \\\\ | ||
| 4281 | 5, 6 | ||
| 4282 | " | ||
| 4283 | (python-tests-look-at "data = foo(), bar()") | ||
| 4284 | (should (python-info-assignment-statement-p)) | ||
| 4285 | (should (python-info-assignment-statement-p t)) | ||
| 4286 | (python-tests-look-at "baz(), 4") | ||
| 4287 | (should (python-info-assignment-statement-p)) | ||
| 4288 | (should (not (python-info-assignment-statement-p t))) | ||
| 4289 | (python-tests-look-at "5, 6") | ||
| 4290 | (should (python-info-assignment-statement-p)) | ||
| 4291 | (should (not (python-info-assignment-statement-p t))))) | ||
| 4292 | |||
| 4293 | (ert-deftest python-info-assignment-statement-p-2 () | ||
| 4294 | (python-tests-with-temp-buffer | ||
| 4295 | " | ||
| 4296 | data = (foo(), bar() | ||
| 4297 | baz(), 4 | ||
| 4298 | 5, 6) | ||
| 4299 | " | ||
| 4300 | (python-tests-look-at "data = (foo(), bar()") | ||
| 4301 | (should (python-info-assignment-statement-p)) | ||
| 4302 | (should (python-info-assignment-statement-p t)) | ||
| 4303 | (python-tests-look-at "baz(), 4") | ||
| 4304 | (should (python-info-assignment-statement-p)) | ||
| 4305 | (should (not (python-info-assignment-statement-p t))) | ||
| 4306 | (python-tests-look-at "5, 6)") | ||
| 4307 | (should (python-info-assignment-statement-p)) | ||
| 4308 | (should (not (python-info-assignment-statement-p t))))) | ||
| 4309 | |||
| 4310 | (ert-deftest python-info-assignment-statement-p-3 () | ||
| 4311 | (python-tests-with-temp-buffer | ||
| 4312 | " | ||
| 4313 | data '=' 42 | ||
| 4314 | " | ||
| 4315 | (python-tests-look-at "data '=' 42") | ||
| 4316 | (should (not (python-info-assignment-statement-p))) | ||
| 4317 | (should (not (python-info-assignment-statement-p t))))) | ||
| 4318 | |||
| 4276 | (ert-deftest python-info-assignment-continuation-line-p-1 () | 4319 | (ert-deftest python-info-assignment-continuation-line-p-1 () |
| 4277 | (python-tests-with-temp-buffer | 4320 | (python-tests-with-temp-buffer |
| 4278 | " | 4321 | " |
| @@ -4360,6 +4403,136 @@ foo = True # another comment | |||
| 4360 | (forward-line 1) | 4403 | (forward-line 1) |
| 4361 | (should (python-info-current-line-empty-p)))) | 4404 | (should (python-info-current-line-empty-p)))) |
| 4362 | 4405 | ||
| 4406 | (ert-deftest python-info-docstring-p-1 () | ||
| 4407 | "Test module docstring detection." | ||
| 4408 | (python-tests-with-temp-buffer | ||
| 4409 | "# -*- coding: utf-8 -*- | ||
| 4410 | #!/usr/bin/python | ||
| 4411 | |||
| 4412 | ''' | ||
| 4413 | Module Docstring Django style. | ||
| 4414 | ''' | ||
| 4415 | u'''Additional module docstring.''' | ||
| 4416 | '''Not a module docstring.''' | ||
| 4417 | " | ||
| 4418 | (python-tests-look-at "Module Docstring Django style.") | ||
| 4419 | (should (python-info-docstring-p)) | ||
| 4420 | (python-tests-look-at "u'''Additional module docstring.'''") | ||
| 4421 | (should (python-info-docstring-p)) | ||
| 4422 | (python-tests-look-at "'''Not a module docstring.'''") | ||
| 4423 | (should (not (python-info-docstring-p))))) | ||
| 4424 | |||
| 4425 | (ert-deftest python-info-docstring-p-2 () | ||
| 4426 | "Test variable docstring detection." | ||
| 4427 | (python-tests-with-temp-buffer | ||
| 4428 | " | ||
| 4429 | variable = 42 | ||
| 4430 | U'''Variable docstring.''' | ||
| 4431 | '''Additional variable docstring.''' | ||
| 4432 | '''Not a variable docstring.''' | ||
| 4433 | " | ||
| 4434 | (python-tests-look-at "Variable docstring.") | ||
| 4435 | (should (python-info-docstring-p)) | ||
| 4436 | (python-tests-look-at "u'''Additional variable docstring.'''") | ||
| 4437 | (should (python-info-docstring-p)) | ||
| 4438 | (python-tests-look-at "'''Not a variable docstring.'''") | ||
| 4439 | (should (not (python-info-docstring-p))))) | ||
| 4440 | |||
| 4441 | (ert-deftest python-info-docstring-p-3 () | ||
| 4442 | "Test function docstring detection." | ||
| 4443 | (python-tests-with-temp-buffer | ||
| 4444 | " | ||
| 4445 | def func(a, b): | ||
| 4446 | r''' | ||
| 4447 | Function docstring. | ||
| 4448 | |||
| 4449 | onetwo style. | ||
| 4450 | ''' | ||
| 4451 | R'''Additional function docstring.''' | ||
| 4452 | '''Not a function docstring.''' | ||
| 4453 | return a + b | ||
| 4454 | " | ||
| 4455 | (python-tests-look-at "Function docstring.") | ||
| 4456 | (should (python-info-docstring-p)) | ||
| 4457 | (python-tests-look-at "R'''Additional function docstring.'''") | ||
| 4458 | (should (python-info-docstring-p)) | ||
| 4459 | (python-tests-look-at "'''Not a function docstring.'''") | ||
| 4460 | (should (not (python-info-docstring-p))))) | ||
| 4461 | |||
| 4462 | (ert-deftest python-info-docstring-p-4 () | ||
| 4463 | "Test class docstring detection." | ||
| 4464 | (python-tests-with-temp-buffer | ||
| 4465 | " | ||
| 4466 | class Class: | ||
| 4467 | ur''' | ||
| 4468 | Class docstring. | ||
| 4469 | |||
| 4470 | symmetric style. | ||
| 4471 | ''' | ||
| 4472 | uR''' | ||
| 4473 | Additional class docstring. | ||
| 4474 | ''' | ||
| 4475 | '''Not a class docstring.''' | ||
| 4476 | pass | ||
| 4477 | " | ||
| 4478 | (python-tests-look-at "Class docstring.") | ||
| 4479 | (should (python-info-docstring-p)) | ||
| 4480 | (python-tests-look-at "uR'''") ;; Additional class docstring | ||
| 4481 | (should (python-info-docstring-p)) | ||
| 4482 | (python-tests-look-at "'''Not a class docstring.'''") | ||
| 4483 | (should (not (python-info-docstring-p))))) | ||
| 4484 | |||
| 4485 | (ert-deftest python-info-docstring-p-5 () | ||
| 4486 | "Test class attribute docstring detection." | ||
| 4487 | (python-tests-with-temp-buffer | ||
| 4488 | " | ||
| 4489 | class Class: | ||
| 4490 | attribute = 42 | ||
| 4491 | Ur''' | ||
| 4492 | Class attribute docstring. | ||
| 4493 | |||
| 4494 | pep-257 style. | ||
| 4495 | |||
| 4496 | ''' | ||
| 4497 | UR''' | ||
| 4498 | Additional class attribute docstring. | ||
| 4499 | ''' | ||
| 4500 | '''Not a class attribute docstring.''' | ||
| 4501 | pass | ||
| 4502 | " | ||
| 4503 | (python-tests-look-at "Class attribute docstring.") | ||
| 4504 | (should (python-info-docstring-p)) | ||
| 4505 | (python-tests-look-at "UR'''") ;; Additional class attr docstring | ||
| 4506 | (should (python-info-docstring-p)) | ||
| 4507 | (python-tests-look-at "'''Not a class attribute docstring.'''") | ||
| 4508 | (should (not (python-info-docstring-p))))) | ||
| 4509 | |||
| 4510 | (ert-deftest python-info-docstring-p-6 () | ||
| 4511 | "Test class method docstring detection." | ||
| 4512 | (python-tests-with-temp-buffer | ||
| 4513 | " | ||
| 4514 | class Class: | ||
| 4515 | |||
| 4516 | def __init__(self, a, b): | ||
| 4517 | self.a = a | ||
| 4518 | self.b = b | ||
| 4519 | |||
| 4520 | def __call__(self): | ||
| 4521 | '''Method docstring. | ||
| 4522 | |||
| 4523 | pep-257-nn style. | ||
| 4524 | ''' | ||
| 4525 | '''Additional method docstring.''' | ||
| 4526 | '''Not a method docstring.''' | ||
| 4527 | return self.a + self.b | ||
| 4528 | " | ||
| 4529 | (python-tests-look-at "Method docstring.") | ||
| 4530 | (should (python-info-docstring-p)) | ||
| 4531 | (python-tests-look-at "'''Additional method docstring.'''") | ||
| 4532 | (should (python-info-docstring-p)) | ||
| 4533 | (python-tests-look-at "'''Not a method docstring.'''") | ||
| 4534 | (should (not (python-info-docstring-p))))) | ||
| 4535 | |||
| 4363 | (ert-deftest python-info-encoding-from-cookie-1 () | 4536 | (ert-deftest python-info-encoding-from-cookie-1 () |
| 4364 | "Should detect it on first line." | 4537 | "Should detect it on first line." |
| 4365 | (python-tests-with-temp-buffer | 4538 | (python-tests-with-temp-buffer |