diff options
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | doc/modules/ROOT/pages/projects.adoc | 9 | ||||
| -rw-r--r-- | projectile.el | 97 | ||||
| -rw-r--r-- | test/projectile-test.el | 65 |
4 files changed, 140 insertions, 34 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e33a2..24b0cd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Changes -* `projectile-parse-dirconfig-file' now returns a `projectile-dirconfig' struct (with `keep', `ignore', and `ensure' slots) instead of a positional 3-tuple. External callers should use the accessors (`projectile-dirconfig-keep' etc.) rather than `car'/`cadr'/`caddr'. +* `projectile-parse-dirconfig-file' now returns a `projectile-dirconfig' struct (with `keep', `ignore', `ensure', and `prefixless-ignore' slots) instead of a positional 3-tuple. External callers should use the accessors (`projectile-dirconfig-keep' etc.) rather than `car'/`cadr'/`caddr'. +* Soft-deprecate prefix-less ignore entries in `.projectile'. Lines without a `+'/`-'/`!' prefix are still treated as ignore patterns for backward compatibility, but a one-time warning is now shown for each project that uses them. Set `projectile-warn-on-prefixless-dirconfig-lines' to nil to silence. ### New features diff --git a/doc/modules/ROOT/pages/projects.adoc b/doc/modules/ROOT/pages/projects.adoc index f1f4a5f..24e6e31 100644 --- a/doc/modules/ROOT/pages/projects.adoc +++ b/doc/modules/ROOT/pages/projects.adoc @@ -813,8 +813,13 @@ If you'd like to instruct Projectile to ignore certain files in a project, when indexing it you can do so in the `.projectile` file by adding each path to ignore, where the paths all are relative to the root directory and start with a slash. Everything ignored should be -preceded with a `-` sign. Alternatively, not having any prefix at all -also means to ignore the directory or file pattern that follows. +preceded with a `-` sign. + +NOTE: Lines without any prefix at all are still accepted and treated +as ignore patterns for backward compatibility, but the implicit form +is being phased out and Projectile now warns about it once per +project. Prefer the explicit `-` prefix in new dirconfigs. + Here's an example for a typical Rails application: ---- diff --git a/projectile.el b/projectile.el index 7217397..f600597 100644 --- a/projectile.el +++ b/projectile.el @@ -415,6 +415,17 @@ silent bypass is a frequent source of confusion." :type 'boolean :package-version '(projectile . "2.10.0")) +(defcustom projectile-warn-on-prefixless-dirconfig-lines t + "Whether to warn about deprecated prefix-less ignore entries. +Lines in `.projectile' that start with no `+'/`-'/`!' prefix are +still accepted as ignore patterns for backward compatibility, but +the implicit form is being phased out. When this option is +non-nil, a one-time warning is shown per project that uses any +such line, listing the offending entries." + :group 'projectile + :type 'boolean + :package-version '(projectile . "2.10.0")) + (defcustom projectile-globally-ignored-files (list projectile-tags-file-name projectile-cache-file) "A list of files globally ignored by projectile. @@ -667,6 +678,9 @@ Each value is a cons of (MTIME . PARSED-RESULT).") (defvar projectile--alien-dirconfig-warned-projects (make-hash-table :test 'equal) "Set of project roots already warned about alien indexing skipping the dirconfig.") +(defvar projectile--prefixless-dirconfig-warned-projects (make-hash-table :test 'equal) + "Set of project roots already warned about prefix-less dirconfig entries.") + (defvar projectile-known-projects nil "List of locations where we have previously seen projects. The list of projects is ordered by the time they have been accessed. @@ -2193,9 +2207,11 @@ Unignored files/directories are not included." KEEP is the list of subdirectories to restrict the project to (as returned with a trailing slash). IGNORE and ENSURE are the lists of files or directories to ignore and to forcibly include, -respectively. All slots default to nil, which represents \"no -file present or no entries of this kind\"." - (keep nil) (ignore nil) (ensure nil)) +respectively. PREFIXLESS-IGNORE is the subset of IGNORE entries +that arrived without a leading `+'/`-'/`!'/comment marker; they +are accepted for backward compatibility but recorded separately so +callers can flag the deprecated syntax. All slots default to nil." + (keep nil) (ignore nil) (ensure nil) (prefixless-ignore nil)) (defun projectile--warn-glob-in-keep-entry (entry dirconfig) "Warn that ENTRY in DIRCONFIG looks like a glob pattern after a `+'. @@ -2212,9 +2228,12 @@ or move the pattern to a `-'/`!' rule." (defun projectile--dirconfig-classify-line (line) "Classify LINE from a dirconfig file. Return a cons (BUCKET . VALUE) where BUCKET is one of `:keep', -`:ignore', `:ensure', or `:comment'. Return nil for a blank line. -Leading whitespace is skipped before dispatch so an accidental space -or tab before the prefix does not change classification." +`:ignore', `:ensure', `:legacy-ignore', or `:comment'. Return nil +for a blank line. Leading whitespace is skipped before dispatch +so an accidental space or tab before the prefix does not change +classification. `:legacy-ignore' is reserved for prefix-less +lines, which are still treated as ignore patterns for backward +compatibility but are tracked separately so callers can warn." (let* ((trimmed (string-trim-left line)) (first-char (and (> (length trimmed) 0) (aref trimmed 0)))) (cond @@ -2222,23 +2241,27 @@ or tab before the prefix does not change classification." ((and projectile-dirconfig-comment-prefix (eql first-char projectile-dirconfig-comment-prefix)) (cons :comment nil)) - ((eql first-char ?+) (cons :keep (string-trim (substring trimmed 1)))) - ((eql first-char ?-) (cons :ignore (string-trim (substring trimmed 1)))) - ((eql first-char ?!) (cons :ensure (string-trim (substring trimmed 1)))) - (t (cons :ignore (string-trim trimmed)))))) + ((eql first-char ?+) (cons :keep (string-trim (substring trimmed 1)))) + ((eql first-char ?-) (cons :ignore (string-trim (substring trimmed 1)))) + ((eql first-char ?!) (cons :ensure (string-trim (substring trimmed 1)))) + (t (cons :legacy-ignore (string-trim trimmed)))))) (defun projectile--parse-dirconfig-string (text) "Parse TEXT (a dirconfig file's contents) into a `projectile-dirconfig'." - (let (keep ignore ensure) + (let (keep ignore ensure prefixless) (dolist (line (split-string text "\n")) (pcase (projectile--dirconfig-classify-line line) - (`(:keep . ,v) (unless (string-empty-p v) (push v keep))) - (`(:ignore . ,v) (unless (string-empty-p v) (push v ignore))) - (`(:ensure . ,v) (unless (string-empty-p v) (push v ensure))))) + (`(:keep . ,v) (unless (string-empty-p v) (push v keep))) + (`(:ignore . ,v) (unless (string-empty-p v) (push v ignore))) + (`(:ensure . ,v) (unless (string-empty-p v) (push v ensure))) + (`(:legacy-ignore . ,v) (unless (string-empty-p v) + (push v ignore) + (push v prefixless))))) (make-projectile-dirconfig - :keep (mapcar #'file-name-as-directory (nreverse keep)) - :ignore (nreverse ignore) - :ensure (nreverse ensure)))) + :keep (mapcar #'file-name-as-directory (nreverse keep)) + :ignore (nreverse ignore) + :ensure (nreverse ensure) + :prefixless-ignore (nreverse prefixless)))) (defun projectile--parse-dirconfig-file-uncached () "Parse the dirconfig file without caching. @@ -2254,6 +2277,28 @@ Return a `projectile-dirconfig' or nil if the file doesn't exist." (projectile--warn-glob-in-keep-entry entry dirconfig))) cfg)))) +(defun projectile--maybe-warn-prefixless-entries (project-root cfg) + "Warn once per session about prefix-less ignore entries in CFG for PROJECT-ROOT. +CFG is a `projectile-dirconfig' struct." + (when (and projectile-warn-on-prefixless-dirconfig-lines + cfg + (projectile-dirconfig-prefixless-ignore cfg) + (not (gethash project-root + projectile--prefixless-dirconfig-warned-projects))) + (puthash project-root t projectile--prefixless-dirconfig-warned-projects) + (display-warning + 'projectile + (format "%s contains entries without a `+'/`-'/`!' prefix: %s. \ +The implicit form is treated as an ignore rule for backward \ +compatibility but is being phased out — please prefix the lines \ +explicitly. Set `projectile-warn-on-prefixless-dirconfig-lines' \ +to nil to silence this warning." + (expand-file-name projectile-dirconfig-file project-root) + (mapconcat (lambda (s) (format "`%s'" s)) + (projectile-dirconfig-prefixless-ignore cfg) + ", ")) + :warning))) + (defun projectile-parse-dirconfig-file () "Parse project ignore file and return its rules. @@ -2280,13 +2325,17 @@ dirconfig file's modification time changes." (project-root (projectile-project-root)) (cached (gethash project-root projectile--dirconfig-cache)) (attrs (file-attributes dirconfig)) - (mtime (when attrs (file-attribute-modification-time attrs)))) - (if (and cached mtime (equal (car cached) mtime)) - (cdr cached) - (let ((result (projectile--parse-dirconfig-file-uncached))) - (when mtime - (puthash project-root (cons mtime result) projectile--dirconfig-cache)) - result)))) + (mtime (when attrs (file-attribute-modification-time attrs))) + (result (if (and cached mtime (equal (car cached) mtime)) + (cdr cached) + (let ((parsed (projectile--parse-dirconfig-file-uncached))) + (when mtime + (puthash project-root + (cons mtime parsed) + projectile--dirconfig-cache)) + parsed)))) + (projectile--maybe-warn-prefixless-entries project-root result) + result)) (defun projectile-expand-root (name &optional dir) "Expand NAME to project root. diff --git a/test/projectile-test.el b/test/projectile-test.el index fc6116d..9516ff1 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -539,9 +539,9 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. :to-equal '(:ignore . "/build")) (expect (projectile--dirconfig-classify-line "!/build/keepme") :to-equal '(:ensure . "/build/keepme"))) - (it "treats prefix-less lines as ignore for backward compatibility" + (it "tags prefix-less lines as legacy-ignore for backward compatibility" (expect (projectile--dirconfig-classify-line "stale-pattern") - :to-equal '(:ignore . "stale-pattern"))) + :to-equal '(:legacy-ignore . "stale-pattern"))) (it "skips leading whitespace before dispatch" (expect (projectile--dirconfig-classify-line " -indented") :to-equal '(:ignore . "indented")) @@ -556,7 +556,7 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. ;; Without a comment prefix, # is just a regular character. (let ((projectile-dirconfig-comment-prefix nil)) (expect (projectile--dirconfig-classify-line "#may-be-a-comment") - :to-equal '(:ignore . "#may-be-a-comment"))))) + :to-equal '(:legacy-ignore . "#may-be-a-comment"))))) (describe "projectile-parse-dirconfig-file" (it "parses dirconfig and returns directories to ignore and keep" @@ -572,7 +572,11 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. "#may-be-a-comment" "no-prefix" "left-wspace" - "right-wspace"))) + "right-wspace") + :prefixless-ignore '("#may-be-a-comment" + "no-prefix" + "left-wspace" + "right-wspace"))) ;; same test - but with comment lines enabled using prefix '#' (let ((projectile-dirconfig-comment-prefix ?#)) (expect (projectile-parse-dirconfig-file) @@ -581,7 +585,10 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. :ignore '("exclude" "no-prefix" "left-wspace" - "right-wspace"))))) + "right-wspace") + :prefixless-ignore '("no-prefix" + "left-wspace" + "right-wspace"))))) (it "skips leading whitespace before dispatching on the prefix" (spy-on 'file-exists-p :and-return-value t) (spy-on 'insert-file-contents :and-call-fake @@ -595,7 +602,8 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. :to-equal (make-projectile-dirconfig :keep '("indented-include/") :ignore '("indented-exclude" "no-prefix-indented") - :ensure '("indented-ensure")))) + :ensure '("indented-ensure") + :prefixless-ignore '("no-prefix-indented")))) (it "treats indented comment-prefix lines as comments" (spy-on 'file-exists-p :and-return-value t) (spy-on 'insert-file-contents :and-call-fake @@ -649,7 +657,8 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. :to-equal (make-projectile-dirconfig :keep '("/src/") :ignore '("/build" "stale-pattern") - :ensure '("/build/keepme"))))))) + :ensure '("/build/keepme") + :prefixless-ignore '("stale-pattern"))))))) (it "round-trips non-ASCII paths through the parser" (projectile-test-with-sandbox (projectile-test-with-files @@ -734,6 +743,48 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (projectile-invalidate-cache nil) (expect (gethash root projectile--dirconfig-cache) :to-be nil)))))) +(describe "prefix-less dirconfig warning" + (before-each + (clrhash projectile--dirconfig-cache) + (clrhash projectile--prefixless-dirconfig-warned-projects)) + (it "warns once when a dirconfig contains prefix-less ignore lines" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (with-temp-file (expand-file-name ".projectile" root) + (insert "-foo\nstale-pattern\n")) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'display-warning) + (let ((projectile-warn-on-prefixless-dirconfig-lines t)) + (projectile-parse-dirconfig-file) + (projectile-parse-dirconfig-file)) + (expect 'display-warning :to-have-been-called-times 1))))) + (it "does not warn for a fully-prefixed dirconfig" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (with-temp-file (expand-file-name ".projectile" root) + (insert "-foo\n+/src\n!/build/keep\n")) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'display-warning) + (let ((projectile-warn-on-prefixless-dirconfig-lines t)) + (projectile-parse-dirconfig-file)) + (expect 'display-warning :not :to-have-been-called))))) + (it "does not warn when the option is disabled" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (with-temp-file (expand-file-name ".projectile" root) + (insert "stale-pattern\n")) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'display-warning) + (let ((projectile-warn-on-prefixless-dirconfig-lines nil)) + (projectile-parse-dirconfig-file)) + (expect 'display-warning :not :to-have-been-called)))))) + (describe "alien-mode dirconfig warning" (before-each (clrhash projectile--alien-dirconfig-warned-projects)) |
