Skip to content

Commit

Permalink
Merge pull request #4 from MichaelKim0407/develop
Browse files Browse the repository at this point in the history
release 1.1
  • Loading branch information
MichaelKim0407 committed Jun 8, 2020
2 parents bc11286 + ca0cf37 commit 02d13ff
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 14 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

name: Upload Python Package

on:
release:
types: [created]

jobs:
deploy:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist
twine upload dist/*
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ dist/
.pytest_cache/
.coverage

# mypy
.mypy_cache/

# System
.DS_Store
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# flake8-use-fstring

[![Build Status](https://travis-ci.com/MichaelKim0407/flake8-use-fstring.svg?branch=master)](https://travis-ci.com/MichaelKim0407/flake8-use-fstring)
[![Coverage Status](https://coveralls.io/repos/github/MichaelKim0407/flake8-use-fstring/badge.svg?branch=master)](https://coveralls.io/github/MichaelKim0407/flake8-use-fstring?branch=master)
* `master` (release)
[![Build Status](https://travis-ci.com/MichaelKim0407/flake8-use-fstring.svg?branch=master)](https://travis-ci.com/MichaelKim0407/flake8-use-fstring)
[![Coverage Status](https://coveralls.io/repos/github/MichaelKim0407/flake8-use-fstring/badge.svg?branch=master)](https://coveralls.io/github/MichaelKim0407/flake8-use-fstring?branch=master)
* `develop` (main)
[![Build Status](https://travis-ci.com/MichaelKim0407/flake8-use-fstring.svg?branch=develop)](https://travis-ci.com/MichaelKim0407/flake8-use-fstring)
[![Coverage Status](https://coveralls.io/repos/github/MichaelKim0407/flake8-use-fstring/badge.svg?branch=develop)](https://coveralls.io/github/MichaelKim0407/flake8-use-fstring?branch=develop)

Jump-start into modern Python by forcing yourself to use f-strings.

Expand All @@ -19,9 +23,11 @@ pip install flake8-use-fstring

* `FS002`: `.format` formatting is used.

* `FS003`: f-string missing prefix (ignored by default).

## Available Configurations

### `--percent-greedy` and `--format-greedy`
### `--percent-greedy`, `--format-greedy`, and `--fstring-ignore-format`

This plugin checks each python statement (logical line)
and see if `%` or `.format` is used.
Expand All @@ -45,5 +51,18 @@ Thus level 0 is the default level.
However, for most projects it should be reasonable to use greedy level 2 with confidence.

To set greedy levels,
set `--percent-greedy` and `--format-greedy` in the command line,
or set `percent-greedy` and `format-greedy` in the `.flake8` config file.
set `--percent-greedy=<level>` and `--format-greedy=<level>` in the command line,
or set `percent-greedy=<level>` and `format-greedy=<level>` in the `.flake8` config file.

Optionally, this plugin can also check for strings that appear to be intended to be f-strings
but are missing the `f` prefix.
This check is meant to assist when converting code to use f-strings.
Due to the potential for false positives, this check (`FS003`) is disabled by default.
To enable this check,
add the `--enable-extensions=FS003` command line option,
or set `enable-extensions=FS003` in the `.flake8` config file.

The missing prefix check normally ignores strings that are using `%` or `.format` formatting,
to check those strings as well,
add the `--fstring-ignore-format` command line option,
or set `fstring-ignore-format=True` in the `.flake8` config file.
2 changes: 1 addition & 1 deletion flake8_use_fstring/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.0'
__version__ = '1.1'
12 changes: 11 additions & 1 deletion flake8_use_fstring/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
OptionManager as _OptionManager,
)

Flake8Output = _typing.Tuple[_typing.Tuple[int, int], str]


class BaseLogicalLineChecker(object):
def __init__(
Expand All @@ -24,7 +26,15 @@ def __getitem__(self, i: int) -> bool:
def __call__(self, i: int) -> str:
raise NotImplementedError # pragma: no cover

def __iter__(self) -> _typing.Iterator[_typing.Tuple[int, str]]:
def __iter__(self) -> _typing.Iterator[Flake8Output]:
for i in range(len(self.tokens)):
if not self[i]:
continue
yield self.tokens[i].start, self(i)


class BaseGreedyLogicalLineChecker(BaseLogicalLineChecker):
def __iter__(self) -> _typing.Iterator[Flake8Output]:
met_string = False

for i in range(len(self.tokens)):
Expand Down
2 changes: 1 addition & 1 deletion flake8_use_fstring/format.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import token as _token

from .base import (
BaseLogicalLineChecker as _Base,
BaseGreedyLogicalLineChecker as _Base,
)


Expand Down
2 changes: 1 addition & 1 deletion flake8_use_fstring/percent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import token as _token

from .base import (
BaseLogicalLineChecker as _Base,
BaseGreedyLogicalLineChecker as _Base,
)


Expand Down
70 changes: 70 additions & 0 deletions flake8_use_fstring/prefix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import re as _re
import token as _token

from flake8.options.manager import (
OptionManager as _OptionManager,
)

from .base import (
BaseLogicalLineChecker as _Base,
)

FSTRING_REGEX = _re.compile(r'^([a-zA-Z]*?[fF][a-zA-Z]*?){1}["\']')
NON_FSTRING_REGEX = _re.compile(
r'^[a-zA-Z]*(?:\'\'\'|\'|"""|")(.*?{.+?}.*)(?:\'|\'\'\'|"|""")$',
)


class MissingPrefixDetector(_Base):
name = 'use-fstring-prefix'
version = '1.0'
ignore_format = False
off_by_default = True

def __getitem__(self, i: int) -> bool:
token = self.tokens[i]
if token.exact_type != _token.STRING:
return False

if FSTRING_REGEX.search(token.string): # already is an f-string
return False

if not self.ignore_format:
# look ahead for % or .format and skip if present
for next_i, next_token in enumerate(self.tokens[i + 1:], i + 1):
if next_token.exact_type == _token.STRING:
continue
if next_token.exact_type == _token.PERCENT:
return False
if next_token.exact_type == _token.DOT:
try:
next_token = self.tokens[next_i + 1]
if next_token.exact_type != _token.NAME:
break
if next_token.string == 'format':
return False
except IndexError:
pass
break

value = token.string.replace('{{', '').replace('}}', '')
return NON_FSTRING_REGEX.search(value) is not None

def __call__(self, i: int) -> str:
return 'FS003 f-string missing prefix'

@classmethod
def add_options(cls, option_manager: _OptionManager):
option_manager.add_option(
f'--{cls.OPTION_NAME}',
action='store_true',
default=False,
parse_from_config=True,
)

@classmethod
def parse_options(cls, options):
option_var = cls.OPTION_NAME.replace('-', '_')
cls.ignore_format = vars(options)[option_var]

OPTION_NAME = 'fstring-ignore-format'
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from flake8_use_fstring import __version__

extra_test = [
'coverage==4.*',
'pytest>=4',
'pytest-cov>=2',

Expand Down Expand Up @@ -50,6 +51,7 @@
'flake8.extension': [
'FS001 = flake8_use_fstring.percent:PercentFormatDetector',
'FS002 = flake8_use_fstring.format:StrFormatDetector',
'FS003 = flake8_use_fstring.prefix:MissingPrefixDetector',
],
},

Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class TestFlake8Cmd(object):
def __init__(self):
self.percent_greedy = 0
self.format_greedy = 0
self.enable_prefix = False
self.ignore_format = False
self.expected_output = None

def test(self):
Expand All @@ -19,6 +21,10 @@ def test(self):
f'--percent-greedy={self.percent_greedy}',
f'--format-greedy={self.format_greedy}',
]
if self.enable_prefix:
cmd.append('--enable-extensions=FS003')
if self.ignore_format:
cmd.append('--fstring-ignore-format')
p = subprocess.run(
cmd,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Expand Down
15 changes: 14 additions & 1 deletion tests/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
f = 3 % 2

# match greedy level 0
g = '{}'.format(123)
g = '{val}'.format(val=123)

# match greedy level 1
h = ('x' + '{}').format(123)
Expand All @@ -36,5 +36,18 @@ def format(self): # noqa: A003
# false positive greedy level 2
m = C().format()

# missing prefix false positive
n = (rf'{m}'
'{'
'}'
'm'
''
'{}'
'{{m}}')

# match missing prefix
o = ('{n}'
'{{m}} {n}')

# no errors below; coverage
''.strip()
32 changes: 28 additions & 4 deletions tests/test_00.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
def test_greedy_0(test_flake8_cmd):
test_flake8_cmd.expected_output = b"""\
tests/example.py:2:10: FS001 '%' operator used
tests/example.py:18:9: FS002 '.format' used
tests/example.py:18:12: FS002 '.format' used
"""
test_flake8_cmd.test()

Expand All @@ -13,7 +13,7 @@ def test_greedy_1(test_flake8_cmd):
tests/example.py:2:10: FS001 '%' operator used
tests/example.py:5:18: FS001 '%' operator used
tests/example.py:12:16: FS001 '%' operator used
tests/example.py:18:9: FS002 '.format' used
tests/example.py:18:12: FS002 '.format' used
tests/example.py:21:17: FS002 '.format' used
tests/example.py:34:13: FS002 '.format' used
"""
Expand All @@ -29,7 +29,7 @@ def test_greedy_2(test_flake8_cmd):
tests/example.py:9:7: FS001 '%' operator used
tests/example.py:12:16: FS001 '%' operator used
tests/example.py:15:7: FS001 '%' operator used
tests/example.py:18:9: FS002 '.format' used
tests/example.py:18:12: FS002 '.format' used
tests/example.py:21:17: FS002 '.format' used
tests/example.py:25:6: FS002 '.format' used
tests/example.py:34:13: FS002 '.format' used
Expand All @@ -46,6 +46,30 @@ def test_greedy_different(test_flake8_cmd):
tests/example.py:9:7: FS001 '%' operator used
tests/example.py:12:16: FS001 '%' operator used
tests/example.py:15:7: FS001 '%' operator used
tests/example.py:18:9: FS002 '.format' used
tests/example.py:18:12: FS002 '.format' used
"""
test_flake8_cmd.test()


def test_missing_prefix(test_flake8_cmd):
test_flake8_cmd.enable_prefix = True
test_flake8_cmd.expected_output = b"""\
tests/example.py:2:10: FS001 '%' operator used
tests/example.py:18:12: FS002 '.format' used
tests/example.py:49:6: FS003 f-string missing prefix
tests/example.py:50:6: FS003 f-string missing prefix
"""
test_flake8_cmd.test()


def test_missing_prefix_ignore_format(test_flake8_cmd):
test_flake8_cmd.enable_prefix = True
test_flake8_cmd.ignore_format = True
test_flake8_cmd.expected_output = b"""\
tests/example.py:2:10: FS001 '%' operator used
tests/example.py:18:5: FS003 f-string missing prefix
tests/example.py:18:12: FS002 '.format' used
tests/example.py:49:6: FS003 f-string missing prefix
tests/example.py:50:6: FS003 f-string missing prefix
"""
test_flake8_cmd.test()

0 comments on commit 02d13ff

Please sign in to comment.