flake8-quotes-3.3.1/0000775000175000017500000000000014133467223014137 5ustar jriverojriveroflake8-quotes-3.3.1/setup.py0000664000175000017500000000376614133467223015665 0ustar jriverojriveroimport io import os from setuptools import setup __dir__ = os.path.dirname(__file__) def read(*filenames, **kwargs): encoding = kwargs.get('encoding', 'utf-8') sep = kwargs.get('sep', '\n') buf = [] for filename in filenames: with io.open(filename, encoding=encoding) as f: buf.append(f.read()) return sep.join(buf) LONG_DESCRIPTION = read(os.path.join(__dir__, 'README.rst')) about = {} with open(os.path.join(__dir__, 'flake8_quotes', '__about__.py')) as file: exec(file.read(), about) setup( name='flake8-quotes', author='Zachary Wright Heller', author_email='zheller@gmail.com', version=about['__version__'], install_requires=[ 'flake8', ], url='http://github.com/zheller/flake8-quotes/', long_description=LONG_DESCRIPTION, description='Flake8 lint for quotes.', packages=['flake8_quotes'], test_suite='test', include_package_data=True, entry_points={ 'flake8.extension': [ 'Q0 = flake8_quotes:QuoteChecker', ], }, license='MIT', zip_safe=True, keywords='flake8 lint quotes', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Framework :: Flake8', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Quality Assurance', ] ) flake8-quotes-3.3.1/README.rst0000664000175000017500000000640314133467223015631 0ustar jriverojriveroFlake8 Extension to lint for quotes. =========================================== .. image:: https://travis-ci.org/zheller/flake8-quotes.svg?branch=master :target: https://travis-ci.org/zheller/flake8-quotes :alt: Build Status Major update in 2.0.0 --------------------- We automatically encourage avoiding escaping quotes as per `PEP 8 `_. To disable this, use ``--no-avoid-escape`` (can be used in configuration file via ``avoid-escape``). Deprecation notice in 0.3.0 --------------------------- To anticipate multiline support, we are renaming ``--quotes`` to ``--inline-quotes``. Please adjust your configurations appropriately. Usage ----- If you are using flake8 it's as easy as: .. code:: shell pip install flake8-quotes Now you don't need to worry about people like @sectioneight constantly complaining that you are using double-quotes and not single-quotes. Warnings -------- This package adds flake8 warnings with the prefix ``Q0``. You might want to enable this warning inside your flake8 configuration file. Typically that will be ``.flake8`` inside the root folder of your project. .. code:: ini select = Q0 The current set of warnings is: ==== ========================================================================= Code Description ---- ------------------------------------------------------------------------- Q000 Remove bad quotes Q001 Remove bad quotes from multiline string Q002 Remove bad quotes from docstring Q003 Change outer quotes to avoid escaping inner quotes ==== ========================================================================= Configuration ------------- By default, we expect single quotes (') and look for unwanted double quotes ("). To expect double quotes (") and find unwanted single quotes ('), use the CLI option: .. code:: shell flake8 --inline-quotes '"' # We also support "double" and "single" # flake8 --inline-quotes 'double' # # We also support configuration for multiline quotes # flake8 --inline-quotes '"' --multiline-quotes "'" # We also support "'''" # flake8 --inline-quotes '"' --multiline-quotes "'''" # # We also support docstring quotes similarly # flake8 --inline-quotes '"' --docstring-quotes "'" # flake8 --inline-quotes '"' --docstring-quotes "'''" # We also support disabling escaping quotes # flake8 --no-avoid-escape or configuration option in `tox.ini`/`setup.cfg`. .. code:: ini [flake8] inline-quotes = " # We also support "double" and "single" # inline-quotes = double # # We also support configuration for multiline quotes # multiline-quotes = ' # We also support "'''" # multiline-quotes = ''' # # We also support docstring quotes similarly # docstring-quotes = ' # docstring-quotes = ''' # # We also support disabling escaping quotes # avoid-escape = False Caveats ------- We follow the `PEP8 conventions `_ to avoid backslashes in the string. So, no matter what configuration you are using (single or double quotes) these are always valid strings .. code:: python s = 'double "quotes" wrapped in singles are ignored' s = "single 'quotes' wrapped in doubles are ignored" flake8-quotes-3.3.1/release.sh0000775000175000017500000000162414133467223016121 0ustar jriverojrivero#!/usr/bin/env bash # Exit on first error set -e # Parse our CLI arguments version="$1" if test "$version" = ""; then echo "Expected a version to be provided to \`release.sh\` but none was provided." 1>&2 echo "Usage: $0 [version] # (e.g. $0 1.0.0)" 1>&2 exit 1 fi # Bump the version via regexp sed -E "s/^(__version__ = ')[0-9]+\.[0-9]+\.[0-9]+(')$/\1$version\2/" flake8_quotes/__about__.py --in-place # Verify our version made it into the file if ! grep "$version" flake8_quotes/__about__.py &> /dev/null; then echo "Expected \`__version__\` to update via \`sed\` but it didn't" 1>&2 exit 1 fi # Commit the change git add flake8_quotes/__about__.py git commit -a -m "Release $version" # Tag the release git tag "$version" # Publish the release to GitHub git push git push --tags # Publish the release to PyPI python setup.py sdist --formats=gztar twine upload "dist/flake8-quotes-$version.tar.gz" flake8-quotes-3.3.1/.gitignore0000664000175000017500000000005314133467223016125 0ustar jriverojrivero*.egg *.egg-info *.py[cod] .tox dist build flake8-quotes-3.3.1/test/0000775000175000017500000000000014133467223015116 5ustar jriverojriveroflake8-quotes-3.3.1/test/__init__.py0000664000175000017500000000000014133467223017215 0ustar jriverojriveroflake8-quotes-3.3.1/test/test_docstring_detection.py0000664000175000017500000000475414133467223022573 0ustar jriverojriveroimport tokenize from unittest import TestCase from flake8_quotes import Token, get_docstring_tokens from test.test_checks import get_absolute_path class GetDocstringTokensTests(TestCase): def _get_docstring_tokens(self, filename): with open(get_absolute_path(filename), 'r') as f: tokens = [Token(t) for t in tokenize.generate_tokens(f.readline)] return get_docstring_tokens(tokens) def test_get_docstring_tokens_absent(self): self.assertEqual(self._get_docstring_tokens('data/doubles.py'), set()) self.assertEqual(self._get_docstring_tokens('data/doubles_multiline_string.py'), set()) self.assertEqual(self._get_docstring_tokens('data/doubles_noqa.py'), set()) self.assertEqual(self._get_docstring_tokens('data/doubles_wrapped.py'), set()) self.assertEqual(self._get_docstring_tokens('data/multiline_string.py'), set()) self.assertEqual(self._get_docstring_tokens('data/no_qa.py'), set()) self.assertEqual(self._get_docstring_tokens('data/singles.py'), set()) self.assertEqual(self._get_docstring_tokens('data/singles_multiline_string.py'), set()) self.assertEqual(self._get_docstring_tokens('data/singles_noqa.py'), set()) self.assertEqual(self._get_docstring_tokens('data/singles_wrapped.py'), set()) self.assertEqual(self._get_docstring_tokens('data/docstring_not_docstrings.py'), set()) def test_get_docstring_tokens_doubles(self): with open(get_absolute_path('data/docstring_doubles.py'), 'r') as f: tokens = [Token(t) for t in tokenize.generate_tokens(f.readline)] docstring_tokens = {t.string for t in get_docstring_tokens(tokens)} self.assertEqual(docstring_tokens, { '"""\nDouble quotes multiline module docstring\n"""', '"""\n Double quotes multiline class docstring\n """', '"""\n Double quotes multiline function docstring\n """', }) def test_get_docstring_tokens_singles(self): with open(get_absolute_path('data/docstring_singles.py'), 'r') as f: tokens = [Token(t) for t in tokenize.generate_tokens(f.readline)] docstring_tokens = {t.string for t in get_docstring_tokens(tokens)} self.assertEqual(docstring_tokens, { "'''\nSingle quotes multiline module docstring\n'''", "'''\n Single quotes multiline class docstring\n '''", "'''\n Single quotes multiline function docstring\n '''", }) flake8-quotes-3.3.1/test/test_checks.py0000664000175000017500000002420514133467223017772 0ustar jriverojriverofrom flake8_quotes import QuoteChecker import os import subprocess from unittest import TestCase class TestChecks(TestCase): def test_get_noqa_lines(self): checker = QuoteChecker(None, filename=get_absolute_path('data/no_qa.py')) self.assertEqual(checker.get_noqa_lines(checker.get_file_contents()), [2]) class TestFlake8Stdin(TestCase): def test_stdin(self): """Test using stdin.""" filepath = get_absolute_path('data/doubles.py') with open(filepath, 'rb') as f: p = subprocess.Popen(['flake8', '--select=Q', '-'], stdin=f, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() stdout_lines = stdout.splitlines() self.assertEqual(stderr, b'') self.assertEqual(len(stdout_lines), 3) self.assertRegex( stdout_lines[0], b'stdin:1:(24|25): Q000 Double quotes found but single quotes preferred') self.assertRegex( stdout_lines[1], b'stdin:2:(24|25): Q000 Double quotes found but single quotes preferred') self.assertRegex( stdout_lines[2], b'stdin:3:(24|25): Q000 Double quotes found but single quotes preferred') class DoublesTestChecks(TestCase): def setUp(self): class DoublesOptions(): inline_quotes = "'" multiline_quotes = "'" QuoteChecker.parse_options(DoublesOptions) def test_multiline_string(self): doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles_multiline_string.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), [ {'col': 4, 'line': 1, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) def test_multiline_string_using_lines(self): with open(get_absolute_path('data/doubles_multiline_string.py')) as f: lines = f.readlines() doubles_checker = QuoteChecker(None, lines=lines) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), [ {'col': 4, 'line': 1, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) def test_wrapped(self): doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles_wrapped.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), []) def test_doubles(self): doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), [ {'col': 24, 'line': 1, 'message': 'Q000 Double quotes found but single quotes preferred'}, {'col': 24, 'line': 2, 'message': 'Q000 Double quotes found but single quotes preferred'}, {'col': 24, 'line': 3, 'message': 'Q000 Double quotes found but single quotes preferred'}, ]) def test_noqa_doubles(self): checker = QuoteChecker(None, get_absolute_path('data/doubles_noqa.py')) self.assertEqual(list(checker.run()), []) def test_escapes(self): doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles_escaped.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), [ {'col': 25, 'line': 1, 'message': 'Q003 Change outer quotes to avoid escaping inner quotes'}, ]) def test_escapes_allowed(self): class Options(): inline_quotes = "'" avoid_escape = False QuoteChecker.parse_options(Options) doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles_escaped.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), []) class DoublesAliasTestChecks(TestCase): def setUp(self): class DoublesAliasOptions(): inline_quotes = 'single' multiline_quotes = 'single' QuoteChecker.parse_options(DoublesAliasOptions) def test_doubles(self): doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles_wrapped.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), []) doubles_checker = QuoteChecker(None, filename=get_absolute_path('data/doubles.py')) self.assertEqual(list(doubles_checker.get_quotes_errors(doubles_checker.get_file_contents())), [ {'col': 24, 'line': 1, 'message': 'Q000 Double quotes found but single quotes preferred'}, {'col': 24, 'line': 2, 'message': 'Q000 Double quotes found but single quotes preferred'}, {'col': 24, 'line': 3, 'message': 'Q000 Double quotes found but single quotes preferred'}, ]) class SinglesTestChecks(TestCase): def setUp(self): class SinglesOptions(): inline_quotes = '"' multiline_quotes = '"' QuoteChecker.parse_options(SinglesOptions) def test_multiline_string(self): singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles_multiline_string.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), [ {'col': 4, 'line': 1, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) def test_wrapped(self): singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles_wrapped.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), []) def test_singles(self): singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), [ {'col': 24, 'line': 1, 'message': 'Q000 Single quotes found but double quotes preferred'}, {'col': 24, 'line': 2, 'message': 'Q000 Single quotes found but double quotes preferred'}, {'col': 24, 'line': 3, 'message': 'Q000 Single quotes found but double quotes preferred'}, ]) def test_noqa_singles(self): checker = QuoteChecker(None, get_absolute_path('data/singles_noqa.py')) self.assertEqual(list(checker.run()), []) def test_escapes(self): singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles_escaped.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), [ {'col': 25, 'line': 1, 'message': 'Q003 Change outer quotes to avoid escaping inner quotes'}, ]) def test_escapes_allowed(self): class Options(): inline_quotes = '"' avoid_escape = False QuoteChecker.parse_options(Options) singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles_escaped.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), []) class SinglesAliasTestChecks(TestCase): def setUp(self): class SinglesAliasOptions(): inline_quotes = 'double' multiline_quotes = 'double' QuoteChecker.parse_options(SinglesAliasOptions) def test_singles(self): singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles_wrapped.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), []) singles_checker = QuoteChecker(None, filename=get_absolute_path('data/singles.py')) self.assertEqual(list(singles_checker.get_quotes_errors(singles_checker.get_file_contents())), [ {'col': 24, 'line': 1, 'message': 'Q000 Single quotes found but double quotes preferred'}, {'col': 24, 'line': 2, 'message': 'Q000 Single quotes found but double quotes preferred'}, {'col': 24, 'line': 3, 'message': 'Q000 Single quotes found but double quotes preferred'}, ]) class MultilineTestChecks(TestCase): def test_singles(self): class Options(): inline_quotes = "'" multiline_quotes = '"' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker(None, filename=get_absolute_path('data/multiline_string.py')) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 10, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) def test_singles_alias(self): class Options(): inline_quotes = 'single' multiline_quotes = 'double' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker(None, filename=get_absolute_path('data/multiline_string.py')) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 10, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) def test_doubles(self): class Options(): inline_quotes = '"' multiline_quotes = "'" QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker(None, filename=get_absolute_path('data/multiline_string.py')) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 1, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) def test_doubles_alias(self): class Options(): inline_quotes = 'double' multiline_quotes = 'single' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker(None, filename=get_absolute_path('data/multiline_string.py')) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 1, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) def get_absolute_path(filepath): return os.path.join(os.path.dirname(__file__), filepath) flake8-quotes-3.3.1/test/test_docstring_checks.py0000664000175000017500000003016414133467223022047 0ustar jriverojriverofrom unittest import TestCase from flake8_quotes import QuoteChecker from test.test_checks import get_absolute_path class DocstringTestChecks(TestCase): def test_require_double_docstring_double_present(self): class Options(): inline_quotes = 'single' multiline_quotes = 'single' docstring_quotes = 'double' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 5, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 4, 'line': 16, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 20, 'line': 21, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 8, 'line': 30, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 12, 'line': 35, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_module_multiline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 4, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 0, 'line': 9, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_module_singleline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 2, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 0, 'line': 6, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_class.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 3, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 22, 'line': 5, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_function.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 3, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 4, 'line': 11, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 38, 'line': 15, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 4, 'line': 17, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, {'col': 4, 'line': 21, 'message': 'Q001 Double quote multiline found but single quotes preferred'}, ]) def test_require_single_docstring_double_present(self): class Options(): inline_quotes = 'single' multiline_quotes = 'double' docstring_quotes = 'single' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 1, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, {'col': 4, 'line': 12, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, {'col': 8, 'line': 24, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_module_multiline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 1, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_module_singleline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 1, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_class.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 2, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, {'col': 8, 'line': 6, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, {'col': 28, 'line': 9, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_doubles_function.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 2, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, {'col': 4, 'line': 8, 'message': 'Q002 Double quote docstring found but single quotes preferred'}, ]) def test_require_double_docstring_single_present(self): class Options(): inline_quotes = 'single' multiline_quotes = 'single' docstring_quotes = 'double' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 1, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, {'col': 4, 'line': 14, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, {'col': 8, 'line': 26, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_module_multiline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 1, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_module_singleline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 1, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_class.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 2, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, {'col': 8, 'line': 6, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, {'col': 28, 'line': 9, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_function.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 2, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, {'col': 4, 'line': 8, 'message': 'Q002 Single quote docstring found but double quotes preferred'}, ]) def test_require_single_docstring_single_present(self): class Options(): inline_quotes = 'single' multiline_quotes = 'double' docstring_quotes = 'single' QuoteChecker.parse_options(Options) multiline_checker = QuoteChecker(None, filename=get_absolute_path('data/docstring_singles.py')) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 5, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 20, 'line': 11, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 4, 'line': 18, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 20, 'line': 23, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 8, 'line': 32, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 12, 'line': 37, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_module_multiline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 4, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 0, 'line': 9, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_module_singleline.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 0, 'line': 2, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 0, 'line': 6, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_class.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 3, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 22, 'line': 5, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) multiline_checker = QuoteChecker( None, filename=get_absolute_path('data/docstring_singles_function.py') ) self.assertEqual(list(multiline_checker.get_quotes_errors(multiline_checker.get_file_contents())), [ {'col': 4, 'line': 3, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 4, 'line': 11, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 38, 'line': 15, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 4, 'line': 17, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, {'col': 4, 'line': 21, 'message': 'Q001 Single quote multiline found but double quotes preferred'}, ]) flake8-quotes-3.3.1/test/data/0000775000175000017500000000000014133467223016027 5ustar jriverojriveroflake8-quotes-3.3.1/test/data/docstring_singles_class.py0000664000175000017500000000044214133467223023306 0ustar jriverojriveroclass SingleLineDocstrings(): ''' Double quotes single line class docstring ''' ''' Not a docstring ''' def foo(self, bar='''not a docstring'''): ''' Double quotes single line method docstring''' pass class Nested(foo()[:]): ''' inline docstring '''; pass flake8-quotes-3.3.1/test/data/singles_noqa.py0000664000175000017500000000007214133467223021062 0ustar jriverojriverothis_should_not_be_linted = 'single quote string' # noqa flake8-quotes-3.3.1/test/data/singles_multiline_string.py0000664000175000017500000000024514133467223023516 0ustar jriverojriveros = ''' This 'should' be 'linted' ''' s = """ This 'should' 'not' be 'linted' """ s = '''"This should not be linted due to having would-be quadruple end quote"''' flake8-quotes-3.3.1/test/data/no_qa.py0000664000175000017500000000010314133467223017470 0ustar jriverojriverosome_code = 2 this_should_not_be_checked = 1 # noqa some_code = 3 flake8-quotes-3.3.1/test/data/docstring_doubles_class.py0000664000175000017500000000044214133467223023277 0ustar jriverojriveroclass SingleLineDocstrings(): """ Double quotes single line class docstring """ """ Not a docstring """ def foo(self, bar="""not a docstring"""): """ Double quotes single line method docstring""" pass class Nested(foo()[:]): """ inline docstring """; pass flake8-quotes-3.3.1/test/data/docstring_singles_module_singleline.py0000664000175000017500000000020714133467223025676 0ustar jriverojrivero''' Double quotes singleline module docstring ''' ''' this is not a docstring ''' def foo(): pass ''' this is not a docstring ''' flake8-quotes-3.3.1/test/data/docstring_not_docstrings.py0000664000175000017500000000122114133467223023510 0ustar jriverojrivero var0 = True l = [] if var0: """ not a docstring""" pass while(var0 < 0 or "def" in l[:] ): """ also not a docstring """ with open(l["def":]) as f: """ not a docstring """ pass if var0 < 10: """ not a multiline docstring """ pass if var0: ''' not a docstring''' pass while(var0 < 0 or "def" in l[:] ): ''' also not a docstring ''' with open(l["def":]) as f: ''' not a docstring ''' pass if var0 < 10: ''' not a multiline docstring ''' pass # https://github.com/zheller/flake8-quotes/issues/97 def test(): {}["a"] class test: {}["a"] flake8-quotes-3.3.1/test/data/docstring_doubles_module_multiline.py0000664000175000017500000000020514133467223025536 0ustar jriverojrivero""" Double quotes multiline module docstring """ """ this is not a docstring """ def foo(): pass """ this is not a docstring """ flake8-quotes-3.3.1/test/data/docstring_singles_function.py0000664000175000017500000000063014133467223024025 0ustar jriverojriverodef foo(): '''function without params, single line docstring''' ''' not a docstring''' return def foo2(): ''' function without params, multiline docstring ''' ''' not a docstring''' return def fun_with_params_no_docstring(a, b=''' not a ''' '''docstring'''): pass def fun_with_params_no_docstring2(a, b=c[foo():], c=\ ''' not a docstring '''): pass flake8-quotes-3.3.1/test/data/doubles_noqa.py0000664000175000017500000000007214133467223021053 0ustar jriverojriverothis_should_not_be_linted = "double quote string" # noqa flake8-quotes-3.3.1/test/data/doubles_multiline_string.py0000664000175000017500000000024514133467223023507 0ustar jriverojriveros = """ This "should" be "linted" """ s = ''' This "should" "not" be "linted" ''' s = """'This should not be linted due to having would-be quadruple end quote'""" flake8-quotes-3.3.1/test/data/doubles_escaped.py0000664000175000017500000000036314133467223021524 0ustar jriverojriverothis_should_raise_Q003 = 'This is a \'string\'' this_is_fine = '"This" is a \'string\'' this_is_fine = "This is a 'string'" this_is_fine = "\"This\" is a 'string'" this_is_fine = r'This is a \'string\'' this_is_fine = br'This is a \'string\'' flake8-quotes-3.3.1/test/data/docstring_singles.py0000664000175000017500000000140414133467223022120 0ustar jriverojrivero''' Single quotes multiline module docstring ''' ''' this is not a docstring ''' l = [] class Cls(MakeKlass(''' class params \t not a docstring ''')): ''' Single quotes multiline class docstring ''' ''' this is not a docstring ''' # The colon in the list indexing below is an edge case for the docstring scanner def f(self, bar=''' definitely not a docstring''', val=l[Cls():3]): ''' Single quotes multiline function docstring ''' some_expression = 'hello world' ''' this is not a docstring ''' if l: ''' Looks like a docstring, but in reality it isn't - only modules, classes and functions ''' pass flake8-quotes-3.3.1/test/data/doubles_wrapped.py0000664000175000017500000000015214133467223021556 0ustar jriverojriveros = 'double "quotes" wrapped in singles are ignored' s = "single 'quotes' wrapped in doubles are ignored" flake8-quotes-3.3.1/test/data/docstring_doubles_module_singleline.py0000664000175000017500000000020714133467223025667 0ustar jriverojrivero""" Double quotes singleline module docstring """ """ this is not a docstring """ def foo(): pass """ this is not a docstring """ flake8-quotes-3.3.1/test/data/singles_wrapped.py0000664000175000017500000000015214133467223021565 0ustar jriverojriveros = "single 'quotes' wrapped in doubles are ignored" s = 'double "quotes" wrapped in singles are ignored' flake8-quotes-3.3.1/test/data/docstring_doubles_function.py0000664000175000017500000000063014133467223024016 0ustar jriverojriverodef foo(): """function without params, single line docstring""" """ not a docstring""" return def foo2(): """ function without params, multiline docstring """ """ not a docstring""" return def fun_with_params_no_docstring(a, b=""" not a """ """docstring"""): pass def fun_with_params_no_docstring2(a, b=c[foo():], c=\ """ not a docstring """): pass flake8-quotes-3.3.1/test/data/doubles.py0000664000175000017500000000027414133467223020041 0ustar jriverojriverothis_should_be_linted = "double quote string" this_should_be_linted = u"double quote string" this_should_be_linted = br"double quote string" # use b instead of u, as ur is invalid in Py3 flake8-quotes-3.3.1/test/data/docstring_singles_module_multiline.py0000664000175000017500000000020514133467223025545 0ustar jriverojrivero''' Double quotes multiline module docstring ''' ''' this is not a docstring ''' def foo(): pass ''' this is not a docstring ''' flake8-quotes-3.3.1/test/data/singles.py0000664000175000017500000000027414133467223020050 0ustar jriverojriverothis_should_be_linted = 'single quote string' this_should_be_linted = u'double quote string' this_should_be_linted = br'double quote string' # use b instead of u, as ur is invalid in Py3 flake8-quotes-3.3.1/test/data/singles_escaped.py0000664000175000017500000000036314133467223021533 0ustar jriverojriverothis_should_raise_Q003 = "This is a \"string\"" this_is_fine = "'This' is a \"string\"" this_is_fine = 'This is a "string"' this_is_fine = '\'This\' is a "string"' this_is_fine = r"This is a \"string\"" this_is_fine = br"This is a \"string\"" flake8-quotes-3.3.1/test/data/multiline_string.py0000664000175000017500000000015314133467223021770 0ustar jriverojriveros = """ abc def ghi """ s = """ abc def ''' ghi """ s = ''' abc def ghi ''' s = ''' abc def """ ghi ''' flake8-quotes-3.3.1/test/data/docstring_doubles.py0000664000175000017500000000131414133467223022111 0ustar jriverojrivero""" Double quotes multiline module docstring """ """ this is not a docstring """ l = [] class Cls: """ Double quotes multiline class docstring """ """ this is not a docstring """ # The colon in the list indexing below is an edge case for the docstring scanner def f(self, bar=""" definitely not a docstring""", val=l[Cls():3]): """ Double quotes multiline function docstring """ some_expression = 'hello world' """ this is not a docstring """ if l: """ Looks like a docstring, but in reality it isn't - only modules, classes and functions """ pass flake8-quotes-3.3.1/setup.cfg0000664000175000017500000000024214133467223015756 0ustar jriverojrivero[flake8] max-line-length = 120 # Force jobs to 1 as a workaround to avoid the PicklingError in Flake8 3.x # see https://gitlab.com/pycqa/flake8/issues/164 jobs=1 flake8-quotes-3.3.1/LICENSE0000664000175000017500000000177714133467223015160 0ustar jriverojriveroPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flake8-quotes-3.3.1/test.sh0000775000175000017500000000024314133467223015454 0ustar jriverojrivero#!/usr/bin/env bash # Exit on first error and echo commands set -e set -x # Run our linter and tests flake8 *.py flake8_quotes/ test/*.py python setup.py test $* flake8-quotes-3.3.1/MANIFEST.in0000664000175000017500000000012214133467223015670 0ustar jriverojriveroinclude LICENSE recursive-include flake8_quotes *.py recursive-include test *.py flake8-quotes-3.3.1/requirements-dev.txt0000664000175000017500000000002114133467223020170 0ustar jriverojriveroflake8<4.0,>=3.5 flake8-quotes-3.3.1/flake8_quotes/0000775000175000017500000000000014133467223016711 5ustar jriverojriveroflake8-quotes-3.3.1/flake8_quotes/__init__.py0000664000175000017500000003230414133467223021024 0ustar jriverojriveroimport optparse import tokenize import warnings # Polyfill stdin loading/reading lines # https://gitlab.com/pycqa/flake8-polyfill/blob/1.0.1/src/flake8_polyfill/stdin.py#L52-57 try: from flake8.engine import pep8 stdin_get_value = pep8.stdin_get_value readlines = pep8.readlines except ImportError: from flake8 import utils import pycodestyle stdin_get_value = utils.stdin_get_value readlines = pycodestyle.readlines from flake8_quotes.__about__ import __version__ from flake8_quotes.docstring_detection import get_docstring_tokens class QuoteChecker(object): name = __name__ version = __version__ INLINE_QUOTES = { # When user wants only single quotes "'": { 'good_single': "'", 'bad_single': '"', 'single_error_message': 'Double quotes found but single quotes preferred', }, # When user wants only double quotes '"': { 'good_single': '"', 'bad_single': "'", 'single_error_message': 'Single quotes found but double quotes preferred', }, } # Provide aliases for Windows CLI support # https://github.com/zheller/flake8-quotes/issues/49 INLINE_QUOTES['single'] = INLINE_QUOTES["'"] INLINE_QUOTES['double'] = INLINE_QUOTES['"'] MULTILINE_QUOTES = { "'": { 'good_multiline': "'''", 'good_multiline_ending': '\'"""', 'bad_multiline': '"""', 'multiline_error_message': 'Double quote multiline found but single quotes preferred', }, '"': { 'good_multiline': '"""', 'good_multiline_ending': '"\'\'\'', 'bad_multiline': "'''", 'multiline_error_message': 'Single quote multiline found but double quotes preferred', }, } # Provide Windows CLI and multi-quote aliases MULTILINE_QUOTES['single'] = MULTILINE_QUOTES["'"] MULTILINE_QUOTES['double'] = MULTILINE_QUOTES['"'] MULTILINE_QUOTES["'''"] = MULTILINE_QUOTES["'"] MULTILINE_QUOTES['"""'] = MULTILINE_QUOTES['"'] DOCSTRING_QUOTES = { "'": { 'good_docstring': "'''", 'bad_docstring': '"""', 'docstring_error_message': 'Double quote docstring found but single quotes preferred', }, '"': { 'good_docstring': '"""', 'bad_docstring': "'''", 'docstring_error_message': 'Single quote docstring found but double quotes preferred', }, } # Provide Windows CLI and docstring-quote aliases DOCSTRING_QUOTES['single'] = DOCSTRING_QUOTES["'"] DOCSTRING_QUOTES['double'] = DOCSTRING_QUOTES['"'] DOCSTRING_QUOTES["'''"] = DOCSTRING_QUOTES["'"] DOCSTRING_QUOTES['"""'] = DOCSTRING_QUOTES['"'] def __init__(self, tree, lines=None, filename='(none)'): self.filename = filename self.lines = lines @staticmethod def _register_opt(parser, *args, **kwargs): """ Handler to register an option for both Flake8 3.x and 2.x. This is based on: https://github.com/PyCQA/flake8/blob/3.0.0b2/docs/source/plugin-development/cross-compatibility.rst#option-handling-on-flake8-2-and-3 It only supports `parse_from_config` from the original function and it uses the `Option` object returned to get the string. """ try: # Flake8 3.x registration parser.add_option(*args, **kwargs) except (optparse.OptionError, TypeError): # Flake8 2.x registration parse_from_config = kwargs.pop('parse_from_config', False) option = parser.add_option(*args, **kwargs) if parse_from_config: parser.config_options.append(option.get_opt_string().lstrip('-')) @classmethod def add_options(cls, parser): cls._register_opt(parser, '--quotes', action='store', parse_from_config=True, type='choice', choices=sorted(cls.INLINE_QUOTES.keys()), help='Deprecated alias for `--inline-quotes`') cls._register_opt(parser, '--inline-quotes', default="'", action='store', parse_from_config=True, type='choice', choices=sorted(cls.INLINE_QUOTES.keys()), help="Quote to expect in all files (default: ')") cls._register_opt(parser, '--multiline-quotes', default=None, action='store', parse_from_config=True, type='choice', choices=sorted(cls.MULTILINE_QUOTES.keys()), help='Quote to expect in all files (default: """)') cls._register_opt(parser, '--docstring-quotes', default=None, action='store', parse_from_config=True, type='choice', choices=sorted(cls.DOCSTRING_QUOTES.keys()), help='Quote to expect in all files (default: """)') cls._register_opt(parser, '--avoid-escape', default=None, action='store_true', parse_from_config=True, help='Avoiding escaping same quotes in inline strings (enabled by default)') cls._register_opt(parser, '--no-avoid-escape', dest='avoid_escape', default=None, action='store_false', parse_from_config=False, help='Disable avoiding escaping same quotes in inline strings') @classmethod def parse_options(cls, options): # Define our default config # cls.config = {good_single: ', good_multiline: ''', bad_single: ", bad_multiline: """} cls.config = {} cls.config.update(cls.INLINE_QUOTES["'"]) cls.config.update(cls.MULTILINE_QUOTES['"""']) cls.config.update(cls.DOCSTRING_QUOTES['"""']) # If `options.quotes` was specified, then use it if hasattr(options, 'quotes') and options.quotes is not None: # https://docs.python.org/2/library/warnings.html#warnings.warn warnings.warn('flake8-quotes has deprecated `quotes` in favor of `inline-quotes`. ' 'Please update your configuration') cls.config.update(cls.INLINE_QUOTES[options.quotes]) # Otherwise, use the supported `inline_quotes` else: # cls.config = {good_single: ', good_multiline: """, bad_single: ", bad_multiline: '''} # -> {good_single: ", good_multiline: """, bad_single: ', bad_multiline: '''} cls.config.update(cls.INLINE_QUOTES[options.inline_quotes]) # If multiline quotes was specified, overload our config with those options if hasattr(options, 'multiline_quotes') and options.multiline_quotes is not None: # cls.config = {good_single: ', good_multiline: """, bad_single: ", bad_multiline: '''} # -> {good_single: ', good_multiline: ''', bad_single: ", bad_multiline: """} cls.config.update(cls.MULTILINE_QUOTES[options.multiline_quotes]) # If docstring quotes was specified, overload our config with those options if hasattr(options, 'docstring_quotes') and options.docstring_quotes is not None: cls.config.update(cls.DOCSTRING_QUOTES[options.docstring_quotes]) # If avoid escaped specified, add to config if hasattr(options, 'avoid_escape') and options.avoid_escape is not None: cls.config.update({'avoid_escape': options.avoid_escape}) else: cls.config.update({'avoid_escape': True}) def get_file_contents(self): if self.filename in ('stdin', '-', None): return stdin_get_value().splitlines(True) else: if self.lines: return self.lines else: return readlines(self.filename) def run(self): file_contents = self.get_file_contents() noqa_line_numbers = self.get_noqa_lines(file_contents) errors = self.get_quotes_errors(file_contents) for error in errors: if error.get('line') not in noqa_line_numbers: yield (error.get('line'), error.get('col'), error.get('message'), type(self)) def get_noqa_lines(self, file_contents): tokens = [Token(t) for t in tokenize.generate_tokens(lambda L=iter(file_contents): next(L))] return [token.start_row for token in tokens if token.type == tokenize.COMMENT and token.string.endswith('noqa')] def get_quotes_errors(self, file_contents): tokens = [Token(t) for t in tokenize.generate_tokens(lambda L=iter(file_contents): next(L))] docstring_tokens = get_docstring_tokens(tokens) for token in tokens: if token.type != tokenize.STRING: # ignore non strings continue # Remove any prefixes in strings like `u` from `u"foo"` # DEV: `last_quote_char` is 1 character, even for multiline strings # `"foo"` -> `"foo"` # `b"foo"` -> `"foo"` # `br"foo"` -> `"foo"` # `b"""foo"""` -> `"""foo"""` last_quote_char = token.string[-1] first_quote_index = token.string.index(last_quote_char) prefix = token.string[:first_quote_index].lower() unprefixed_string = token.string[first_quote_index:] # Determine if our string is multiline-based # "foo"[0] * 3 = " * 3 = """ # "foo"[0:3] = "fo # """foo"""[0:3] = """ is_docstring = token in docstring_tokens is_multiline_string = unprefixed_string[0] * 3 == unprefixed_string[0:3] start_row, start_col = token.start # If our string is a docstring # DEV: Docstring quotes must come before multiline quotes as it can as a multiline quote if is_docstring: if self.config['good_docstring'] in unprefixed_string: continue yield { 'message': 'Q002 ' + self.config['docstring_error_message'], 'line': start_row, 'col': start_col, } # Otherwise if our string is multiline elif is_multiline_string: # If our string is or containing a known good string, then ignore it # (""")foo""" -> good (continue) # '''foo(""")''' -> good (continue) # (''')foo''' -> possibly bad if self.config['good_multiline'] in unprefixed_string: continue # If our string ends with a known good ending, then ignore it # '''foo("''') -> good (continue) # Opposite, """foo"""", would break our parser (cannot handle """" ending) if unprefixed_string.endswith(self.config['good_multiline_ending']): continue # Output our error yield { 'message': 'Q001 ' + self.config['multiline_error_message'], 'line': start_row, 'col': start_col, } # Otherwise (string is inline quote) else: # 'This is a string' -> Good # 'This is a "string"' -> Good # 'This is a \"string\"' -> Good # 'This is a \'string\'' -> Bad (Q003) Escaped inner quotes # '"This" is a \'string\'' -> Good Changing outer quotes would not avoid escaping # "This is a string" -> Bad (Q000) # "This is a 'string'" -> Good Avoids escaped inner quotes # "This is a \"string\"" -> Bad (Q000) # "\"This\" is a 'string'" -> Good string_contents = unprefixed_string[1:-1] # If string preferred type, check for escapes if last_quote_char == self.config['good_single']: if not self.config['avoid_escape'] or 'r' in prefix: continue if (self.config['good_single'] in string_contents and not self.config['bad_single'] in string_contents): yield { 'message': 'Q003 Change outer quotes to avoid escaping inner quotes', 'line': start_row, 'col': start_col, } continue # If not preferred type, only allow use to avoid escapes. if not self.config['good_single'] in string_contents: yield { 'message': 'Q000 ' + self.config['single_error_message'], 'line': start_row, 'col': start_col, } class Token: """Python 2 and 3 compatible token""" def __init__(self, token): self.token = token @property def type(self): return self.token[0] @property def string(self): return self.token[1] @property def start(self): return self.token[2] @property def start_row(self): return self.token[2][0] @property def start_col(self): return self.token[2][1] flake8-quotes-3.3.1/flake8_quotes/docstring_detection.py0000664000175000017500000000730014133467223023315 0ustar jriverojriveroimport tokenize # I don't think this is a minimized state machine, but it's clearer this # way. Namely, the class vs. function states can be merged # In the start of the module when we're expecting possibly a string that gets marked as a docstring STATE_EXPECT_MODULE_DOCSTRING = 0 # After seeing the class keyword, we're waiting for the block colon (and do bracket counting) STATE_EXPECT_CLASS_COLON = 1 # After seeing the colon in a class definition we're expecting possibly a docstring STATE_EXPECT_CLASS_DOCSTRING = 2 # Same as EXPECT_CLASS_COLON, but for function definitions STATE_EXPECT_FUNCTION_COLON = 3 # Same as EXPECT_CLASS_DOCSTRING, but for function definitions STATE_EXPECT_FUNCTION_DOCSTRING = 4 # Just skipping tokens until we observe a class or a def. STATE_OTHER = 5 # These tokens don't matter here - they don't get in the way of docstrings TOKENS_TO_IGNORE = [ tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT, tokenize.NL, tokenize.COMMENT, ] def get_docstring_tokens(tokens): state = STATE_EXPECT_MODULE_DOCSTRING # The number of currently open parentheses, square brackets, etc. # This doesn't check if they're properly balanced, i.e. there isn't ([)], but we shouldn't # need to - if they aren't, it shouldn't parse at all, so we ignore the bracket type bracket_count = 0 docstring_tokens = set() for token in tokens: if token.type in TOKENS_TO_IGNORE: continue if token.type == tokenize.STRING: if state in [STATE_EXPECT_MODULE_DOCSTRING, STATE_EXPECT_CLASS_DOCSTRING, STATE_EXPECT_FUNCTION_DOCSTRING]: docstring_tokens.add(token) state = STATE_OTHER # A class means we'll expect the class token elif token.type == tokenize.NAME and token.string == 'class': state = STATE_EXPECT_CLASS_COLON # Just in case - they should be balanced normally bracket_count = 0 # A def means we'll expect a colon after that elif token.type == tokenize.NAME and token.string == 'def': state = STATE_EXPECT_FUNCTION_COLON # Just in case - they should be balanced normally bracket_count = 0 # If we get a colon and we're expecting it, move to the next state elif token.type == tokenize.OP and token.string == ':': # If there are still left brackets open, it must be something other than the block start if bracket_count == 0: if state == STATE_EXPECT_CLASS_COLON: state = STATE_EXPECT_CLASS_DOCSTRING elif state == STATE_EXPECT_FUNCTION_COLON: state = STATE_EXPECT_FUNCTION_DOCSTRING # Count opening and closing brackets in bracket_count elif token.type == tokenize.OP and token.string in ['(', '[', '{']: bracket_count += 1 if state in [STATE_EXPECT_MODULE_DOCSTRING, STATE_EXPECT_CLASS_DOCSTRING, STATE_EXPECT_FUNCTION_DOCSTRING]: state = STATE_OTHER elif token.type == tokenize.OP and token.string in [')', ']', '}']: bracket_count -= 1 if state in [STATE_EXPECT_MODULE_DOCSTRING, STATE_EXPECT_CLASS_DOCSTRING, STATE_EXPECT_FUNCTION_DOCSTRING]: state = STATE_OTHER # The token is not one of the recognized types. If we're expecting a colon, then all good, # but if we're expecting a docstring, it would no longer be a docstring elif state in [STATE_EXPECT_MODULE_DOCSTRING, STATE_EXPECT_CLASS_DOCSTRING, STATE_EXPECT_FUNCTION_DOCSTRING]: state = STATE_OTHER return docstring_tokens flake8-quotes-3.3.1/flake8_quotes/__about__.py0000664000175000017500000000002614133467223021167 0ustar jriverojrivero__version__ = '3.3.1'