Skip to content

Commit bc9bb64

Browse files
committed
Enable self-doc and PEP 701 f-string detection by default
They are now always-on but can be disabled by using `--no-feature`.
1 parent 877b982 commit bc9bb64

9 files changed

Lines changed: 67 additions & 26 deletions

File tree

sample.vermin.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@
115115
# argparse
116116

117117
### Features ###
118-
# Some features are disabled by default due to being unstable but can be enabled explicitly.
118+
# fstring-self-doc and fstring-pep701 are enabled by default. Other features must be enabled
119+
# explicitly. Use `--no-feature` to disable all features.
119120
#
120121
# Get full list via `--help`.
121122
#

tests/arguments.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,18 +304,31 @@ def test_no_tips(self):
304304
self.assertFalse(self.config.show_tips())
305305

306306
def test_feature(self):
307+
# Default features are enabled.
308+
self.config.reset()
309+
self.assertContainsDict({"code": 0}, self.parse_args(["--verbose"]))
310+
self.assertEqualItems(Features.defaults(), self.config.features())
311+
312+
# `--feature` is additive on top of the default features.
313+
self.config.reset()
314+
self.assertContainsDict({"code": 0}, self.parse_args(["--feature", "union-types"]))
315+
feats = Features.defaults() | {"union-types"}
316+
self.assertEqualItems(feats, self.config.features())
317+
307318
# Needs <name> part.
319+
self.config.reset()
308320
self.assertContainsDict({"code": 1}, self.parse_args(["--feature"]))
309-
self.assertEmpty(self.config.features())
321+
self.assertEqualItems(Features.defaults(), self.config.features())
310322

311323
# Unknown feature.
324+
self.config.reset()
312325
self.assertContainsDict({"code": 1}, self.parse_args(["--feature", "foobarbaz"]))
313-
self.assertEmpty(self.config.features())
326+
self.assertEqualItems(Features.defaults(), self.config.features())
314327

315-
# Known features.
328+
# Known features can be enabled individually after clearing all defaults.
316329
for feature in Features.features():
317330
self.config.reset()
318-
self.assertContainsDict({"code": 0}, self.parse_args(["--feature", feature]))
331+
self.assertContainsDict({"code": 0}, self.parse_args(["--no-feature", "--feature", feature]))
319332
self.assertEqualItems([feature], self.config.features())
320333
self.assertContainsDict({"code": 0}, self.parse_args(["--no-feature"]))
321334
self.assertEmpty(self.config.features())

tests/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def test_defaults(self):
2020
self.assertFalse(self.config.analyze_hidden())
2121
self.assertEmpty(self.config.exclusions())
2222
self.assertEmpty(self.config.backports())
23-
self.assertEmpty(self.config.features())
23+
self.assertEqualItems(Features.defaults(), self.config.features())
2424
self.assertEmpty(self.config.targets())
2525
self.assertEqual("default", self.config.format().name())
2626
self.assertFalse(self.config.eval_annotations())

tests/lang.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,26 @@ def test_self_doc_detected(self, source):
185185
self.config.enable_feature("fstring-self-doc")
186186
self.assert_self_doc(source)
187187

188+
@VerminTest.skipUnlessVersion(3, 8)
189+
def test_self_doc_feature_flag(self):
190+
# Enabled by default.
191+
visitor = self.visit("a = 1\nf'{a=}'")
192+
self.assertTrue(visitor.fstrings())
193+
self.assertTrue(visitor.fstrings_self_doc())
194+
self.assertOnlyIn((3, 8), visitor.minimum_versions())
195+
196+
self.config.clear_features()
197+
visitor = self.visit("a = 1\nf'{a=}'")
198+
self.assertTrue(visitor.fstrings())
199+
self.assertFalse(visitor.fstrings_self_doc())
200+
self.assertOnlyIn((3, 6), visitor.minimum_versions())
201+
202+
self.config.enable_feature("fstring-self-doc")
203+
visitor = self.visit("a = 1\nf'{a=}'")
204+
self.assertTrue(visitor.fstrings())
205+
self.assertTrue(visitor.fstrings_self_doc())
206+
self.assertOnlyIn((3, 8), visitor.minimum_versions())
207+
188208
def assert_pep701(self, source):
189209
visitor = self.visit(source)
190210
self.assertTrue(visitor.fstrings())
@@ -282,9 +302,16 @@ def test_pep701_comment_variants(self, source):
282302

283303
@VerminTest.skipUnlessVersion(3, 12)
284304
def test_pep701_feature_flag(self):
305+
# Enabled by default.
285306
self.config.reset()
286307
visitor = self.visit('f"outer {f"inner"}"')
287308
self.assertTrue(visitor.fstrings())
309+
self.assertTrue(visitor.fstrings_pep701())
310+
self.assertOnlyIn((3, 12), visitor.minimum_versions())
311+
312+
self.config.clear_features()
313+
visitor = self.visit('f"outer {f"inner"}"')
314+
self.assertTrue(visitor.fstrings())
288315
self.assertFalse(visitor.fstrings_pep701())
289316
self.assertOnlyIn((3, 6), visitor.minimum_versions())
290317

vermin/arguments.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,11 @@ def print_usage(full=False):
187187
print("\n --no-backport (default)\n"
188188
" Use no backports. Clears any backports specified before this.")
189189
print("\n [--feature <name>] ...\n"
190-
" Some features are disabled by default due to being unstable:\n{}".
191-
format(Features.str(10)))
192-
print("\n --no-feature (default)\n"
193-
" Use no features. Clears any features specified before this.")
190+
" Some features are enabled by default. Others must be\n"
191+
" explicitly enabled:\n{}".format(Features.str(10)))
192+
print("\n --no-feature\n"
193+
" Use no features. Disables all features, including those\n"
194+
" that are enabled by default.")
194195

195196
def parse(self, config, detect_folder=None):
196197
assert config is not None

vermin/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def reset(self):
2929
self.__exclusion_regex = set()
3030
self.__make_paths_absolute = True
3131
self.__backports = set()
32-
self.__features = set()
32+
self.__features = Features.defaults()
3333
self.__targets = []
3434
self.__eval_annotations = False
3535
self.__only_show_violations = False

vermin/features.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from .utility import format_title_descs
22

3+
DEFAULT_FEATURES = ("fstring-self-doc", "fstring-pep701")
4+
35
FEATURES = (
46
("fstring-self-doc", [
5-
"[Unstable] Detect self-documenting fstrings. Can in",
6-
"some cases wrongly report fstrings as self-documenting."
7+
"Detect self-documenting fstrings. Enabled by default.",
78
]),
89
("fstring-pep701", [
9-
"[Unstable] Detect PEP 701 f-string features (3.12+).",
10-
"Same-quote nesting and multi-line expressions.",
11-
"Requires running on Python 3.12+."
10+
"Detect PEP 701 f-string features (3.12+): same-quote",
11+
"nesting and multi-line expressions. Requires running on",
12+
"Python 3.12+. Enabled by default."
1213
]),
1314
("union-types", [
1415
"[Unstable] Detect union types `X | Y`. Can in some cases",
@@ -17,6 +18,10 @@
1718
)
1819

1920
class Features:
21+
@staticmethod
22+
def defaults():
23+
return set(DEFAULT_FEATURES)
24+
2025
@staticmethod
2126
def str(indent=0):
2227
return format_title_descs(FEATURES, Features.features(), indent)

vermin/source_state.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,7 @@ def __init__(self, config, path=None, source=None):
149149
# Lines that should be ignored if they have the comment "novermin" or "novm".
150150
self.no_lines = set()
151151

152-
# Default to disabling fstring self-doc detection since the built-in AST cannot distinguish
153-
# `f'{a=}'` from `f'a={a}'`, for instance, because it optimizes some information away. And this
154-
# incorrectly marks some source code as using fstring self-doc when only using general fstring.
155152
self.fstring_self_doc_enabled = self.config.has_feature("fstring-self-doc")
156-
157-
# Default to disabling PEP 701 fstring detection since it requires source-text heuristics to
158-
# detect same-quote nesting and multi-line expressions in fstrings.
159153
self.fstrings_pep701_enabled = self.config.has_feature("fstring-pep701")
160154

161155
# Default to disabling union types detection because it sometimes fails to report it correctly

vermin/source_visitor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,10 @@ def minimum_versions(self):
372372
if self.fstrings():
373373
mins = self.__add_versions_entity(mins, (None, (3, 6)), "f-strings")
374374

375-
if self.fstrings_self_doc(): # pragma: no cover
375+
if self.fstrings_self_doc():
376376
mins = self.__add_versions_entity(mins, (None, (3, 8)), "self-documenting f-strings")
377377

378-
if self.fstrings_pep701(): # pragma: no cover
378+
if self.fstrings_pep701():
379379
mins = self.__add_versions_entity(mins, (None, (3, 12)), "f-strings (PEP 701)")
380380

381381
if self.bool_const(): # pragma: no cover
@@ -1466,15 +1466,15 @@ def visit_JoinedStr(self, node):
14661466
self.__vvprint("f-strings", versions=[None, (3, 6)])
14671467

14681468
if self.__s.fstring_self_doc_enabled and hasattr(node, "values") and \
1469-
hasattr(node, "end_lineno"): # pragma: no cover
1469+
hasattr(node, "end_lineno"):
14701470
if self.__fstr.is_self_doc(node):
14711471
self.__s.fstrings_self_doc = True
14721472
self.__vvprint("self-documenting f-strings", versions=[None, (3, 8)])
14731473

14741474
# PEP 701 fstrings detection requires Python 3.12+ to parse the syntax in the first place, and
14751475
# avoids both false positives and false negatives.
14761476
if self.__s.fstrings_pep701_enabled and sys.version_info >= (3, 12) and \
1477-
hasattr(node, "values") and hasattr(node, "end_lineno"): # pragma: no cover
1477+
hasattr(node, "values") and hasattr(node, "end_lineno"):
14781478
if self.__fstr.pep701_violation(node) is not None:
14791479
self.__s.fstrings_pep701 = True
14801480
self.__vvprint("f-strings (PEP 701)", versions=[None, (3, 12)])

0 commit comments

Comments
 (0)