From 86265c1d7cb035002d156d1aa5b2e8889e5c1083 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 28 Oct 2022 17:52:11 -0400 Subject: [PATCH] Implement the `flake8-quotes` plugin (#495) --- README.md | 23 +- .../flake8_quotes/docstring_doubles.py | 38 +++ .../flake8_quotes/docstring_doubles_class.py | 9 + .../docstring_doubles_function.py | 22 ++ .../docstring_doubles_module_multiline.py | 11 + .../docstring_doubles_module_singleline.py | 6 + .../flake8_quotes/docstring_singles.py | 40 +++ .../flake8_quotes/docstring_singles_class.py | 9 + .../docstring_singles_function.py | 23 ++ .../docstring_singles_module_multiline.py | 11 + .../docstring_singles_module_singleline.py | 6 + .../test/fixtures/flake8_quotes/doubles.py | 2 + .../fixtures/flake8_quotes/doubles_escaped.py | 5 + .../flake8_quotes/doubles_multiline_string.py | 9 + .../fixtures/flake8_quotes/doubles_noqa.py | 1 + .../fixtures/flake8_quotes/doubles_wrapped.py | 2 + .../test/fixtures/flake8_quotes/singles.py | 2 + .../fixtures/flake8_quotes/singles_escaped.py | 5 + .../flake8_quotes/singles_multiline_string.py | 9 + .../fixtures/flake8_quotes/singles_noqa.py | 1 + .../fixtures/flake8_quotes/singles_wrapped.py | 2 + resources/test/fixtures/pyproject.toml | 6 + src/check_tokens.rs | 31 +- src/checks.rs | 53 ++- src/flake8_quotes/checks.rs | 303 ++++++++++++++++++ src/flake8_quotes/docstring_detection.rs | 118 +++++++ src/flake8_quotes/mod.rs | 3 + src/flake8_quotes/settings.rs | 49 +++ ...double_docstring_docstring_doubles.py.snap | 50 +++ ..._docstring_docstring_doubles_class.py.snap | 23 ++ ...cstring_docstring_doubles_function.py.snap | 50 +++ ...docstring_doubles_module_multiline.py.snap | 23 ++ ...ocstring_doubles_module_singleline.py.snap | 23 ++ ...double_docstring_docstring_singles.py.snap | 32 ++ ..._docstring_docstring_singles_class.py.snap | 32 ++ ...cstring_docstring_singles_function.py.snap | 23 ++ ...docstring_singles_module_multiline.py.snap | 14 + ...ocstring_singles_module_singleline.py.snap | 14 + ...es__checks__tests__doubles_doubles.py.snap | 23 ++ ...ks__tests__doubles_doubles_escaped.py.snap | 13 + ...__doubles_doubles_multiline_string.py.snap | 14 + ...hecks__tests__doubles_doubles_noqa.py.snap | 6 + ...ks__tests__doubles_doubles_wrapped.py.snap | 6 + ...single_docstring_docstring_doubles.py.snap | 32 ++ ..._docstring_docstring_doubles_class.py.snap | 32 ++ ...cstring_docstring_doubles_function.py.snap | 23 ++ ...docstring_doubles_module_multiline.py.snap | 14 + ...ocstring_doubles_module_singleline.py.snap | 14 + ...single_docstring_docstring_singles.py.snap | 59 ++++ ..._docstring_docstring_singles_class.py.snap | 23 ++ ...cstring_docstring_singles_function.py.snap | 50 +++ ...docstring_singles_module_multiline.py.snap | 23 ++ ...ocstring_singles_module_singleline.py.snap | 23 ++ ...es__checks__tests__singles_singles.py.snap | 23 ++ ...ks__tests__singles_singles_escaped.py.snap | 13 + ...__singles_singles_multiline_string.py.snap | 14 + ...hecks__tests__singles_singles_noqa.py.snap | 6 + ...ks__tests__singles_singles_wrapped.py.snap | 6 + src/lib.rs | 1 + src/pyproject.rs | 9 +- src/settings.rs | 21 +- 61 files changed, 1524 insertions(+), 7 deletions(-) create mode 100644 resources/test/fixtures/flake8_quotes/docstring_doubles.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_doubles_class.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_doubles_function.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_singles.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_singles_class.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_singles_function.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py create mode 100644 resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py create mode 100644 resources/test/fixtures/flake8_quotes/doubles.py create mode 100644 resources/test/fixtures/flake8_quotes/doubles_escaped.py create mode 100644 resources/test/fixtures/flake8_quotes/doubles_multiline_string.py create mode 100644 resources/test/fixtures/flake8_quotes/doubles_noqa.py create mode 100644 resources/test/fixtures/flake8_quotes/doubles_wrapped.py create mode 100644 resources/test/fixtures/flake8_quotes/singles.py create mode 100644 resources/test/fixtures/flake8_quotes/singles_escaped.py create mode 100644 resources/test/fixtures/flake8_quotes/singles_multiline_string.py create mode 100644 resources/test/fixtures/flake8_quotes/singles_noqa.py create mode 100644 resources/test/fixtures/flake8_quotes/singles_wrapped.py create mode 100644 src/flake8_quotes/checks.rs create mode 100644 src/flake8_quotes/docstring_detection.rs create mode 100644 src/flake8_quotes/mod.rs create mode 100644 src/flake8_quotes/settings.rs create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_class.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_function.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_multiline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_singleline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_class.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_function.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_multiline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_singleline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_escaped.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_multiline_string.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_noqa.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_wrapped.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_class.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_function.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_multiline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_singleline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_class.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_function.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_multiline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_singleline.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_escaped.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_multiline_string.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_noqa.py.snap create mode 100644 src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_wrapped.py.snap diff --git a/README.md b/README.md index d0d4aeb5e9..68dbc653da 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,17 @@ per-file-ignores = [ ] ``` -Alternatively, on the command-line: +Plugin configurations should be expressed as subsections, e.g.: + +```toml +[tool.ruff] +line-length = 88 + +[tool.ruff.flake8-quotes] +docstring-quotes = "double" +``` + +Alternatively, common configuration settings can be provided via the command-line: ```shell ruff path/to/code/ --select F401 --select F403 @@ -415,6 +425,15 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com | T201 | PrintFound | `print` found | 🛠 | | T203 | PPrintFound | `pprint` found | 🛠 | +### flake8-quotes + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| Q000 | BadQuotesInlineString | Single quotes found but double quotes preferred | | +| Q001 | BadQuotesMultilineString | Single quote multiline found but double quotes preferred | | +| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | | +| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | | + ### Meta rules | Code | Name | Message | Fix | @@ -486,6 +505,7 @@ including: - [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) - [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-print`](https://pypi.org/project/flake8-print/) +- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34) @@ -506,6 +526,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) - [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-print`](https://pypi.org/project/flake8-print/) +- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32) diff --git a/resources/test/fixtures/flake8_quotes/docstring_doubles.py b/resources/test/fixtures/flake8_quotes/docstring_doubles.py new file mode 100644 index 0000000000..3684034d7a --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_doubles.py @@ -0,0 +1,38 @@ +""" +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 diff --git a/resources/test/fixtures/flake8_quotes/docstring_doubles_class.py b/resources/test/fixtures/flake8_quotes/docstring_doubles_class.py new file mode 100644 index 0000000000..571aebf844 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_doubles_class.py @@ -0,0 +1,9 @@ +class 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 diff --git a/resources/test/fixtures/flake8_quotes/docstring_doubles_function.py b/resources/test/fixtures/flake8_quotes/docstring_doubles_function.py new file mode 100644 index 0000000000..bcd5587978 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_doubles_function.py @@ -0,0 +1,22 @@ +def 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 diff --git a/resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py b/resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py new file mode 100644 index 0000000000..ab17dab3d5 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_doubles_module_multiline.py @@ -0,0 +1,11 @@ +""" +Double quotes multiline module docstring +""" +""" +this is not a docstring +""" +def foo(): + pass +""" +this is not a docstring +""" diff --git a/resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py b/resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py new file mode 100644 index 0000000000..cfb857c252 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_doubles_module_singleline.py @@ -0,0 +1,6 @@ +""" Double quotes singleline module docstring """ +""" this is not a docstring """ + +def foo(): + pass +""" this is not a docstring """ diff --git a/resources/test/fixtures/flake8_quotes/docstring_singles.py b/resources/test/fixtures/flake8_quotes/docstring_singles.py new file mode 100644 index 0000000000..b11fa21be1 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_singles.py @@ -0,0 +1,40 @@ +''' +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 diff --git a/resources/test/fixtures/flake8_quotes/docstring_singles_class.py b/resources/test/fixtures/flake8_quotes/docstring_singles_class.py new file mode 100644 index 0000000000..816eeb97db --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_singles_class.py @@ -0,0 +1,9 @@ +class 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 diff --git a/resources/test/fixtures/flake8_quotes/docstring_singles_function.py b/resources/test/fixtures/flake8_quotes/docstring_singles_function.py new file mode 100644 index 0000000000..50c241fa3e --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_singles_function.py @@ -0,0 +1,23 @@ +def 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 + diff --git a/resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py b/resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py new file mode 100644 index 0000000000..85acebb03f --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_singles_module_multiline.py @@ -0,0 +1,11 @@ +''' +Double quotes multiline module docstring +''' +''' +this is not a docstring +''' +def foo(): + pass +''' +this is not a docstring +''' diff --git a/resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py b/resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py new file mode 100644 index 0000000000..2545767414 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/docstring_singles_module_singleline.py @@ -0,0 +1,6 @@ +''' Double quotes singleline module docstring ''' +''' this is not a docstring ''' + +def foo(): + pass +''' this is not a docstring ''' diff --git a/resources/test/fixtures/flake8_quotes/doubles.py b/resources/test/fixtures/flake8_quotes/doubles.py new file mode 100644 index 0000000000..cf4e54f7cd --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/doubles.py @@ -0,0 +1,2 @@ +this_should_be_linted = "double quote string" +this_should_be_linted = u"double quote string" diff --git a/resources/test/fixtures/flake8_quotes/doubles_escaped.py b/resources/test/fixtures/flake8_quotes/doubles_escaped.py new file mode 100644 index 0000000000..5a8a12527e --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/doubles_escaped.py @@ -0,0 +1,5 @@ +this_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\'' diff --git a/resources/test/fixtures/flake8_quotes/doubles_multiline_string.py b/resources/test/fixtures/flake8_quotes/doubles_multiline_string.py new file mode 100644 index 0000000000..6f79509c21 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/doubles_multiline_string.py @@ -0,0 +1,9 @@ +s = """ This "should" +be +"linted" """ + +s = ''' This "should" +"not" be +"linted" ''' + +s = """'This should not be linted due to having would-be quadruple end quote'""" diff --git a/resources/test/fixtures/flake8_quotes/doubles_noqa.py b/resources/test/fixtures/flake8_quotes/doubles_noqa.py new file mode 100644 index 0000000000..825ae7e279 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/doubles_noqa.py @@ -0,0 +1 @@ +this_should_not_be_linted = "double quote string" # noqa diff --git a/resources/test/fixtures/flake8_quotes/doubles_wrapped.py b/resources/test/fixtures/flake8_quotes/doubles_wrapped.py new file mode 100644 index 0000000000..9eabf07e51 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/doubles_wrapped.py @@ -0,0 +1,2 @@ +s = 'double "quotes" wrapped in singles are ignored' +s = "single 'quotes' wrapped in doubles are ignored" diff --git a/resources/test/fixtures/flake8_quotes/singles.py b/resources/test/fixtures/flake8_quotes/singles.py new file mode 100644 index 0000000000..9888a0c707 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/singles.py @@ -0,0 +1,2 @@ +this_should_be_linted = 'single quote string' +this_should_be_linted = u'double quote string' diff --git a/resources/test/fixtures/flake8_quotes/singles_escaped.py b/resources/test/fixtures/flake8_quotes/singles_escaped.py new file mode 100644 index 0000000000..fa615d78fb --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/singles_escaped.py @@ -0,0 +1,5 @@ +this_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\"" diff --git a/resources/test/fixtures/flake8_quotes/singles_multiline_string.py b/resources/test/fixtures/flake8_quotes/singles_multiline_string.py new file mode 100644 index 0000000000..f8fe9c5eca --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/singles_multiline_string.py @@ -0,0 +1,9 @@ +s = ''' This 'should' +be +'linted' ''' + +s = """ This 'should' +'not' be +'linted' """ + +s = '''"This should not be linted due to having would-be quadruple end quote"''' diff --git a/resources/test/fixtures/flake8_quotes/singles_noqa.py b/resources/test/fixtures/flake8_quotes/singles_noqa.py new file mode 100644 index 0000000000..08c56ff8e1 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/singles_noqa.py @@ -0,0 +1 @@ +this_should_not_be_linted = 'single quote string' # noqa diff --git a/resources/test/fixtures/flake8_quotes/singles_wrapped.py b/resources/test/fixtures/flake8_quotes/singles_wrapped.py new file mode 100644 index 0000000000..5a7e641b34 --- /dev/null +++ b/resources/test/fixtures/flake8_quotes/singles_wrapped.py @@ -0,0 +1,2 @@ +s = "single 'quotes' wrapped in doubles are ignored" +s = 'double "quotes" wrapped in singles are ignored' diff --git a/resources/test/fixtures/pyproject.toml b/resources/test/fixtures/pyproject.toml index 5d61c76a19..69d119f8bb 100644 --- a/resources/test/fixtures/pyproject.toml +++ b/resources/test/fixtures/pyproject.toml @@ -8,3 +8,9 @@ extend-exclude = [ per-file-ignores = [ "__init__.py:F401", ] + +[tool.ruff.flake8-quotes] +inline-quotes = "single" +multiline-quotes = "double" +docstring-quotes = "double" +avoid-escape = true diff --git a/src/check_tokens.rs b/src/check_tokens.rs index d616bade4a..fd29e04456 100644 --- a/src/check_tokens.rs +++ b/src/check_tokens.rs @@ -4,7 +4,8 @@ use rustpython_parser::lexer::{LexResult, Tok}; use crate::ast::operations::SourceCodeLocator; use crate::checks::{Check, CheckCode}; -use crate::{pycodestyle, Settings}; +use crate::flake8_quotes::docstring_detection::StateMachine; +use crate::{flake8_quotes, pycodestyle, Settings}; pub fn check_tokens( checks: &mut Vec, @@ -12,10 +13,18 @@ pub fn check_tokens( tokens: &[LexResult], settings: &Settings, ) { + let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605); + let enforce_quotes = settings.enabled.contains(&CheckCode::Q000) + | settings.enabled.contains(&CheckCode::Q001) + | settings.enabled.contains(&CheckCode::Q002) + | settings.enabled.contains(&CheckCode::Q003); + // TODO(charlie): Use a shared SourceCodeLocator between this site and the AST traversal. let locator = SourceCodeLocator::new(contents); - let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605); + + let mut state_machine = StateMachine::new(); for (start, tok, end) in tokens.iter().flatten() { + // W605 if enforce_invalid_escape_sequence { if matches!(tok, Tok::String { .. }) { checks.extend(pycodestyle::checks::invalid_escape_sequence( @@ -23,5 +32,23 @@ pub fn check_tokens( )); } } + + // flake8-quotes + if enforce_quotes { + let is_docstring = state_machine.consume(tok); + if matches!(tok, Tok::String { .. }) { + if let Some(check) = flake8_quotes::checks::quotes( + &locator, + start, + end, + is_docstring, + &settings.flake8_quotes, + ) { + if settings.enabled.contains(check.kind.code()) { + checks.push(check); + } + } + } + } } } diff --git a/src/checks.rs b/src/checks.rs index 838c682074..741d9ebba1 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -7,6 +7,7 @@ use strum_macros::{AsRefStr, EnumIter, EnumString}; use crate::ast::types::Range; use crate::autofix::Fix; +use crate::flake8_quotes::settings::Quote; use crate::pyupgrade::types::Primitive; #[derive( @@ -102,6 +103,11 @@ pub enum CheckCode { // flake8-print T201, T203, + // flake8-quotes + Q000, + Q001, + Q002, + Q003, // pyupgrade U001, U002, @@ -184,6 +190,7 @@ pub enum CheckCategory { Flake8Bugbear, Flake8Builtins, Flake8Print, + Flake8Quotes, Meta, } @@ -197,6 +204,7 @@ impl CheckCategory { CheckCategory::Flake8Bugbear => "flake8-bugbear", CheckCategory::Flake8Comprehensions => "flake8-comprehensions", CheckCategory::Flake8Print => "flake8-print", + CheckCategory::Flake8Quotes => "flake8-quotes", CheckCategory::Pyupgrade => "pyupgrade", CheckCategory::Pydocstyle => "pydocstyle", CheckCategory::PEP8Naming => "pep8-naming", @@ -299,6 +307,11 @@ pub enum CheckKind { // flake8-print PrintFound, PPrintFound, + // flake8-quotes + BadQuotesInlineString(Quote), + BadQuotesMultilineString(Quote), + BadQuotesDocstring(Quote), + AvoidQuoteEscape, // pyupgrade TypeOfPrimitive(Primitive), UnnecessaryAbspath, @@ -374,7 +387,11 @@ impl CheckCode { pub fn lint_source(&self) -> &'static LintSource { match self { CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines, - CheckCode::W605 => &LintSource::Tokens, + CheckCode::W605 + | CheckCode::Q000 + | CheckCode::Q001 + | CheckCode::Q002 + | CheckCode::Q003 => &LintSource::Tokens, CheckCode::E902 => &LintSource::FileSystem, _ => &LintSource::AST, } @@ -476,6 +493,11 @@ impl CheckCode { // flake8-print CheckCode::T201 => CheckKind::PrintFound, CheckCode::T203 => CheckKind::PPrintFound, + // flake8-quotes + CheckCode::Q000 => CheckKind::BadQuotesInlineString(Quote::Double), + CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double), + CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double), + CheckCode::Q003 => CheckKind::AvoidQuoteEscape, // pyupgrade CheckCode::U001 => CheckKind::UselessMetaclassType, CheckCode::U002 => CheckKind::UnnecessaryAbspath, @@ -639,6 +661,10 @@ impl CheckCode { CheckCode::C417 => CheckCategory::Flake8Comprehensions, CheckCode::T201 => CheckCategory::Flake8Print, CheckCode::T203 => CheckCategory::Flake8Print, + CheckCode::Q000 => CheckCategory::Flake8Quotes, + CheckCode::Q001 => CheckCategory::Flake8Quotes, + CheckCode::Q002 => CheckCategory::Flake8Quotes, + CheckCode::Q003 => CheckCategory::Flake8Quotes, CheckCode::U001 => CheckCategory::Pyupgrade, CheckCode::U002 => CheckCategory::Pyupgrade, CheckCode::U003 => CheckCategory::Pyupgrade, @@ -788,6 +814,11 @@ impl CheckKind { // flake8-print CheckKind::PrintFound => &CheckCode::T201, CheckKind::PPrintFound => &CheckCode::T203, + // flake8-quotes + CheckKind::BadQuotesInlineString(_) => &CheckCode::Q000, + CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001, + CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002, + CheckKind::AvoidQuoteEscape => &CheckCode::Q003, // pyupgrade CheckKind::TypeOfPrimitive(_) => &CheckCode::U003, CheckKind::UnnecessaryAbspath => &CheckCode::U002, @@ -1101,6 +1132,26 @@ impl CheckKind { // flake8-print CheckKind::PrintFound => "`print` found".to_string(), CheckKind::PPrintFound => "`pprint` found".to_string(), + // flake8-quotes + CheckKind::BadQuotesInlineString(quote) => { + match quote { + Quote::Single => "Double quotes found but single quotes preferred".to_string(), + Quote::Double => "Single quotes found but double quotes preferred".to_string(), + } + }, + CheckKind::BadQuotesMultilineString(quote) => { + match quote { + Quote::Single => "Double quote multiline found but single quotes preferred".to_string(), + Quote::Double => "Single quote multiline found but double quotes preferred".to_string(), + } + }, + CheckKind::BadQuotesDocstring(quote) => { + match quote { + Quote::Single => "Double quote docstring found but single quotes preferred".to_string(), + Quote::Double => "Single quote docstring found but double quotes preferred".to_string(), + } + }, + CheckKind::AvoidQuoteEscape => "Change outer quotes to avoid escaping inner quotes".to_string(), // pyupgrade CheckKind::TypeOfPrimitive(primitive) => { format!("Use `{}` instead of `type(...)`", primitive.builtin()) diff --git a/src/flake8_quotes/checks.rs b/src/flake8_quotes/checks.rs new file mode 100644 index 0000000000..0003be2c7b --- /dev/null +++ b/src/flake8_quotes/checks.rs @@ -0,0 +1,303 @@ +use rustpython_ast::Location; + +use crate::ast::operations::SourceCodeLocator; +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; +use crate::flake8_quotes::settings::{Quote, Settings}; + +fn good_single(quote: &Quote) -> char { + match quote { + Quote::Single => '\'', + Quote::Double => '"', + } +} + +fn bad_single(quote: &Quote) -> char { + match quote { + Quote::Double => '\'', + Quote::Single => '"', + } +} + +fn good_multiline(quote: &Quote) -> &str { + match quote { + Quote::Single => "'''", + Quote::Double => "\"\"\"", + } +} + +fn good_multiline_ending(quote: &Quote) -> &str { + match quote { + Quote::Single => "'\"\"\"", + Quote::Double => "\"'''", + } +} + +fn good_docstring(quote: &Quote) -> &str { + match quote { + Quote::Single => "'''", + Quote::Double => "\"\"\"", + } +} + +pub fn quotes( + locator: &SourceCodeLocator, + start: &Location, + end: &Location, + is_docstring: bool, + settings: &Settings, +) -> Option { + let text = locator.slice_source_code_range(&Range { + location: *start, + end_location: *end, + }); + + // Remove any prefixes (e.g., remove `u` from `u"foo"`). + let last_quote_char = text.chars().last().unwrap(); + let first_quote_char = text.find(last_quote_char).unwrap(); + let prefix = &text[..first_quote_char].to_lowercase(); + let raw_text = &text[first_quote_char..]; + + // Determine if the string is multiline-based. + let is_multiline = if raw_text.len() >= 3 { + let mut chars = raw_text.chars(); + let first = chars.next().unwrap(); + let second = chars.next().unwrap(); + let third = chars.next().unwrap(); + first == second && second == third + } else { + false + }; + + if is_docstring { + if raw_text.contains(good_docstring(&settings.docstring_quotes)) { + return None; + } + + return Some(Check::new( + CheckKind::BadQuotesDocstring(settings.docstring_quotes.clone()), + Range { + location: *start, + end_location: *end, + }, + )); + } else if is_multiline { + // If our string is or contains a known good string, ignore it. + if raw_text.contains(good_multiline(&settings.multiline_quotes)) { + return None; + } + + // If our string ends with a known good ending, then ignore it. + if raw_text.ends_with(good_multiline_ending(&settings.multiline_quotes)) { + return None; + } + + return Some(Check::new( + CheckKind::BadQuotesMultilineString(settings.multiline_quotes.clone()), + Range { + location: *start, + end_location: *end, + }, + )); + } else { + let string_contents = &raw_text[1..raw_text.len() - 1]; + + // If we're using the preferred quotation type, check for escapes. + if last_quote_char == good_single(&settings.inline_quotes) { + if !settings.avoid_escape || prefix.contains('r') { + return None; + } + if string_contents.contains(good_single(&settings.inline_quotes)) + && !string_contents.contains(bad_single(&settings.inline_quotes)) + { + return Some(Check::new( + CheckKind::AvoidQuoteEscape, + Range { + location: *start, + end_location: *end, + }, + )); + } + return None; + } + + // If we're not using the preferred type, only allow use to avoid escapes. + if !string_contents.contains(good_single(&settings.inline_quotes)) { + return Some(Check::new( + CheckKind::BadQuotesInlineString(settings.inline_quotes.clone()), + Range { + location: *start, + end_location: *end, + }, + )); + } + } + + None +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use anyhow::Result; + use rustpython_parser::lexer::LexResult; + use test_case::test_case; + + use crate::autofix::fixer; + use crate::checks::{Check, CheckCode}; + use crate::flake8_quotes::settings::Quote; + use crate::linter::tokenize; + use crate::settings; + use crate::{flake8_quotes, linter}; + use crate::{fs, noqa}; + + fn check_path( + path: &Path, + settings: &settings::Settings, + autofix: &fixer::Mode, + ) -> Result> { + let contents = fs::read_file(path)?; + let tokens: Vec = tokenize(&contents); + let noqa_line_for = noqa::extract_noqa_line_for(&tokens); + linter::check_path(path, &contents, tokens, &noqa_line_for, settings, autofix) + } + + #[test_case(Path::new("doubles.py"))] + #[test_case(Path::new("doubles_escaped.py"))] + #[test_case(Path::new("doubles_multiline_string.py"))] + #[test_case(Path::new("doubles_noqa.py"))] + #[test_case(Path::new("doubles_wrapped.py"))] + fn doubles(path: &Path) -> Result<()> { + let snapshot = format!("doubles_{}", path.to_string_lossy()); + let mut checks = check_path( + Path::new("./resources/test/fixtures/flake8_quotes") + .join(path) + .as_path(), + &settings::Settings { + flake8_quotes: flake8_quotes::settings::Settings { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Single, + docstring_quotes: Quote::Single, + avoid_escape: true, + }, + ..settings::Settings::for_rules(vec![ + CheckCode::Q000, + CheckCode::Q001, + CheckCode::Q002, + CheckCode::Q003, + ]) + }, + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } + + #[test_case(Path::new("singles.py"))] + #[test_case(Path::new("singles_escaped.py"))] + #[test_case(Path::new("singles_multiline_string.py"))] + #[test_case(Path::new("singles_noqa.py"))] + #[test_case(Path::new("singles_wrapped.py"))] + fn singles(path: &Path) -> Result<()> { + let snapshot = format!("singles_{}", path.to_string_lossy()); + let mut checks = check_path( + Path::new("./resources/test/fixtures/flake8_quotes") + .join(path) + .as_path(), + &settings::Settings { + flake8_quotes: flake8_quotes::settings::Settings { + inline_quotes: Quote::Double, + multiline_quotes: Quote::Double, + docstring_quotes: Quote::Double, + avoid_escape: true, + }, + ..settings::Settings::for_rules(vec![ + CheckCode::Q000, + CheckCode::Q001, + CheckCode::Q002, + CheckCode::Q003, + ]) + }, + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } + + #[test_case(Path::new("docstring_doubles.py"))] + #[test_case(Path::new("docstring_doubles_module_multiline.py"))] + #[test_case(Path::new("docstring_doubles_module_singleline.py"))] + #[test_case(Path::new("docstring_doubles_class.py"))] + #[test_case(Path::new("docstring_doubles_function.py"))] + #[test_case(Path::new("docstring_singles.py"))] + #[test_case(Path::new("docstring_singles_module_multiline.py"))] + #[test_case(Path::new("docstring_singles_module_singleline.py"))] + #[test_case(Path::new("docstring_singles_class.py"))] + #[test_case(Path::new("docstring_singles_function.py"))] + fn double_docstring(path: &Path) -> Result<()> { + let snapshot = format!("double_docstring_{}", path.to_string_lossy()); + let mut checks = check_path( + Path::new("./resources/test/fixtures/flake8_quotes") + .join(path) + .as_path(), + &settings::Settings { + flake8_quotes: flake8_quotes::settings::Settings { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Single, + docstring_quotes: Quote::Double, + avoid_escape: true, + }, + ..settings::Settings::for_rules(vec![ + CheckCode::Q000, + CheckCode::Q001, + CheckCode::Q002, + CheckCode::Q003, + ]) + }, + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } + + #[test_case(Path::new("docstring_doubles.py"))] + #[test_case(Path::new("docstring_doubles_module_multiline.py"))] + #[test_case(Path::new("docstring_doubles_module_singleline.py"))] + #[test_case(Path::new("docstring_doubles_class.py"))] + #[test_case(Path::new("docstring_doubles_function.py"))] + #[test_case(Path::new("docstring_singles.py"))] + #[test_case(Path::new("docstring_singles_module_multiline.py"))] + #[test_case(Path::new("docstring_singles_module_singleline.py"))] + #[test_case(Path::new("docstring_singles_class.py"))] + #[test_case(Path::new("docstring_singles_function.py"))] + fn single_docstring(path: &Path) -> Result<()> { + let snapshot = format!("single_docstring_{}", path.to_string_lossy()); + let mut checks = check_path( + Path::new("./resources/test/fixtures/flake8_quotes") + .join(path) + .as_path(), + &settings::Settings { + flake8_quotes: flake8_quotes::settings::Settings { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Double, + docstring_quotes: Quote::Single, + avoid_escape: true, + }, + ..settings::Settings::for_rules(vec![ + CheckCode::Q000, + CheckCode::Q001, + CheckCode::Q002, + CheckCode::Q003, + ]) + }, + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } +} diff --git a/src/flake8_quotes/docstring_detection.rs b/src/flake8_quotes/docstring_detection.rs new file mode 100644 index 0000000000..5c454ab7d4 --- /dev/null +++ b/src/flake8_quotes/docstring_detection.rs @@ -0,0 +1,118 @@ +//! Extract docstrings via tokenization. +//! +//! See: https://github.com/zheller/flake8-quotes/blob/ef0d9a90249a080e460b70ab62bf4b65e5aa5816/flake8_quotes/docstring_detection.py#L29 +//! +//! TODO(charlie): Consolidate with the existing AST-based docstring extraction. + +use rustpython_parser::lexer::Tok; + +#[derive(Debug)] +enum State { + // Start of the module: first string gets marked as a docstring. + ExpectModuleDocstring, + // After seeing a class definition, we're waiting for the block colon (and do bracket counting). + ExpectClassColon, + // After seeing the block colon in a class definition, we expect a docstring. + ExpectClassDocstring, + // Same as ExpectClassColon, but for function definitions. + ExpectFunctionColon, + // Same as ExpectClassDocstring, but for function definitions. + ExpectFunctionDocstring, + // Skip tokens until we observe a `class` or `def`. + Other, +} + +pub struct StateMachine { + state: State, + bracket_count: usize, +} + +impl StateMachine { + pub fn new() -> Self { + Self { + state: State::ExpectModuleDocstring, + bracket_count: 0, + } + } + + pub fn consume(&mut self, tok: &Tok) -> bool { + if matches!(tok, Tok::Newline | Tok::Indent | Tok::Dedent) { + return false; + } + + if matches!(tok, Tok::String { .. }) { + return if matches!( + self.state, + State::ExpectModuleDocstring + | State::ExpectClassDocstring + | State::ExpectFunctionDocstring + ) { + self.state = State::Other; + true + } else { + false + }; + } + + if matches!(tok, Tok::Class) { + self.state = State::ExpectClassColon; + self.bracket_count = 0; + return false; + } + + if matches!(tok, Tok::Def) { + self.state = State::ExpectFunctionColon; + self.bracket_count = 0; + return false; + } + + if matches!(tok, Tok::Colon) { + if self.bracket_count == 0 { + if matches!(self.state, State::ExpectClassColon) { + self.state = State::ExpectClassDocstring; + } else if matches!(self.state, State::ExpectFunctionColon) { + self.state = State::ExpectFunctionDocstring; + } + } + return false; + } + + if matches!(tok, Tok::Lpar | Tok::Lbrace | Tok::Lsqb) { + self.bracket_count += 1; + if matches!( + self.state, + State::ExpectModuleDocstring + | State::ExpectClassDocstring + | State::ExpectFunctionDocstring + ) { + self.state = State::Other; + } + return false; + } + + if matches!(tok, Tok::Rpar | Tok::Rbrace | Tok::Rsqb) { + self.bracket_count -= 1; + if matches!( + self.state, + State::ExpectModuleDocstring + | State::ExpectClassDocstring + | State::ExpectFunctionDocstring + ) { + self.state = State::Other; + } + return false; + } + + if matches!( + self.state, + State::ExpectModuleDocstring + | State::ExpectClassDocstring + | State::ExpectFunctionDocstring + ) { + self.state = State::Other; + return false; + } + + false + } +} diff --git a/src/flake8_quotes/mod.rs b/src/flake8_quotes/mod.rs new file mode 100644 index 0000000000..52bd09ae23 --- /dev/null +++ b/src/flake8_quotes/mod.rs @@ -0,0 +1,3 @@ +pub mod checks; +pub mod docstring_detection; +pub mod settings; diff --git a/src/flake8_quotes/settings.rs b/src/flake8_quotes/settings.rs new file mode 100644 index 0000000000..98fb7f0117 --- /dev/null +++ b/src/flake8_quotes/settings.rs @@ -0,0 +1,49 @@ +//! Settings for the `flake_quotes` plugin. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub enum Quote { + Single, + Double, +} + +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub struct Config { + pub inline_quotes: Option, + pub multiline_quotes: Option, + pub docstring_quotes: Option, + pub avoid_escape: Option, +} + +#[derive(Debug)] +pub struct Settings { + pub inline_quotes: Quote, + pub multiline_quotes: Quote, + pub docstring_quotes: Quote, + pub avoid_escape: bool, +} + +impl Settings { + pub fn from_config(config: Config) -> Self { + Self { + inline_quotes: config.inline_quotes.unwrap_or(Quote::Single), + multiline_quotes: config.multiline_quotes.unwrap_or(Quote::Double), + docstring_quotes: config.docstring_quotes.unwrap_or(Quote::Double), + avoid_escape: config.avoid_escape.unwrap_or(true), + } + } +} + +impl Default for Settings { + fn default() -> Self { + Self { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Double, + docstring_quotes: Quote::Double, + avoid_escape: true, + } + } +} diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles.py.snap new file mode 100644 index 0000000000..48bcb659b3 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles.py.snap @@ -0,0 +1,50 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: single + location: + row: 5 + column: 1 + end_location: + row: 7 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 16 + column: 5 + end_location: + row: 18 + column: 8 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 21 + column: 21 + end_location: + row: 22 + column: 38 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 30 + column: 9 + end_location: + row: 32 + column: 12 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 35 + column: 13 + end_location: + row: 37 + column: 16 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_class.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_class.py.snap new file mode 100644 index 0000000000..766cbcba5e --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_class.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: single + location: + row: 3 + column: 5 + end_location: + row: 3 + column: 28 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 5 + column: 23 + end_location: + row: 5 + column: 44 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_function.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_function.py.snap new file mode 100644 index 0000000000..ae9af10d68 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_function.py.snap @@ -0,0 +1,50 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: single + location: + row: 3 + column: 5 + end_location: + row: 3 + column: 27 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 11 + column: 5 + end_location: + row: 11 + column: 27 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 15 + column: 39 + end_location: + row: 17 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 17 + column: 5 + end_location: + row: 17 + column: 20 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 21 + column: 5 + end_location: + row: 21 + column: 28 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_multiline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_multiline.py.snap new file mode 100644 index 0000000000..809d55c18c --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_multiline.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: single + location: + row: 4 + column: 1 + end_location: + row: 6 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 9 + column: 1 + end_location: + row: 11 + column: 4 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_singleline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_singleline.py.snap new file mode 100644 index 0000000000..1d9d60c991 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_doubles_module_singleline.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: single + location: + row: 2 + column: 1 + end_location: + row: 2 + column: 32 + fix: ~ +- kind: + BadQuotesMultilineString: single + location: + row: 6 + column: 1 + end_location: + row: 6 + column: 32 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles.py.snap new file mode 100644 index 0000000000..2f4395b41f --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles.py.snap @@ -0,0 +1,32 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: double + location: + row: 1 + column: 1 + end_location: + row: 3 + column: 4 + fix: ~ +- kind: + BadQuotesDocstring: double + location: + row: 14 + column: 5 + end_location: + row: 16 + column: 8 + fix: ~ +- kind: + BadQuotesDocstring: double + location: + row: 26 + column: 9 + end_location: + row: 28 + column: 12 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_class.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_class.py.snap new file mode 100644 index 0000000000..7427ff3de4 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_class.py.snap @@ -0,0 +1,32 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: double + location: + row: 2 + column: 5 + end_location: + row: 2 + column: 54 + fix: ~ +- kind: + BadQuotesDocstring: double + location: + row: 6 + column: 9 + end_location: + row: 6 + column: 58 + fix: ~ +- kind: + BadQuotesDocstring: double + location: + row: 9 + column: 29 + end_location: + row: 9 + column: 53 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_function.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_function.py.snap new file mode 100644 index 0000000000..175eef4951 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_function.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: double + location: + row: 2 + column: 5 + end_location: + row: 2 + column: 57 + fix: ~ +- kind: + BadQuotesDocstring: double + location: + row: 8 + column: 5 + end_location: + row: 10 + column: 8 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_multiline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_multiline.py.snap new file mode 100644 index 0000000000..69f63bf394 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_multiline.py.snap @@ -0,0 +1,14 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: double + location: + row: 1 + column: 1 + end_location: + row: 3 + column: 4 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_singleline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_singleline.py.snap new file mode 100644 index 0000000000..826ee513c1 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__double_docstring_docstring_singles_module_singleline.py.snap @@ -0,0 +1,14 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: double + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 50 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles.py.snap new file mode 100644 index 0000000000..548a6fab43 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesInlineString: single + location: + row: 1 + column: 25 + end_location: + row: 1 + column: 46 + fix: ~ +- kind: + BadQuotesInlineString: single + location: + row: 2 + column: 25 + end_location: + row: 2 + column: 47 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_escaped.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_escaped.py.snap new file mode 100644 index 0000000000..7a30dcf2a4 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_escaped.py.snap @@ -0,0 +1,13 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: AvoidQuoteEscape + location: + row: 1 + column: 26 + end_location: + row: 1 + column: 48 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_multiline_string.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_multiline_string.py.snap new file mode 100644 index 0000000000..2d390af27a --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_multiline_string.py.snap @@ -0,0 +1,14 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: single + location: + row: 1 + column: 5 + end_location: + row: 3 + column: 13 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_noqa.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_noqa.py.snap new file mode 100644 index 0000000000..5037171f0b --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_noqa.py.snap @@ -0,0 +1,6 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +[] + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_wrapped.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_wrapped.py.snap new file mode 100644 index 0000000000..5037171f0b --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__doubles_doubles_wrapped.py.snap @@ -0,0 +1,6 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +[] + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles.py.snap new file mode 100644 index 0000000000..605b43a9ec --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles.py.snap @@ -0,0 +1,32 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: single + location: + row: 1 + column: 1 + end_location: + row: 3 + column: 4 + fix: ~ +- kind: + BadQuotesDocstring: single + location: + row: 12 + column: 5 + end_location: + row: 14 + column: 8 + fix: ~ +- kind: + BadQuotesDocstring: single + location: + row: 24 + column: 9 + end_location: + row: 26 + column: 12 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_class.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_class.py.snap new file mode 100644 index 0000000000..397a4e3506 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_class.py.snap @@ -0,0 +1,32 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: single + location: + row: 2 + column: 5 + end_location: + row: 2 + column: 54 + fix: ~ +- kind: + BadQuotesDocstring: single + location: + row: 6 + column: 9 + end_location: + row: 6 + column: 58 + fix: ~ +- kind: + BadQuotesDocstring: single + location: + row: 9 + column: 29 + end_location: + row: 9 + column: 53 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_function.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_function.py.snap new file mode 100644 index 0000000000..51fb83c219 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_function.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: single + location: + row: 2 + column: 5 + end_location: + row: 2 + column: 57 + fix: ~ +- kind: + BadQuotesDocstring: single + location: + row: 8 + column: 5 + end_location: + row: 10 + column: 8 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_multiline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_multiline.py.snap new file mode 100644 index 0000000000..8148a1d635 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_multiline.py.snap @@ -0,0 +1,14 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: single + location: + row: 1 + column: 1 + end_location: + row: 3 + column: 4 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_singleline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_singleline.py.snap new file mode 100644 index 0000000000..1241d06d07 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_doubles_module_singleline.py.snap @@ -0,0 +1,14 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesDocstring: single + location: + row: 1 + column: 1 + end_location: + row: 1 + column: 50 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles.py.snap new file mode 100644 index 0000000000..9d57b3b529 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles.py.snap @@ -0,0 +1,59 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: double + location: + row: 5 + column: 1 + end_location: + row: 7 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 11 + column: 21 + end_location: + row: 13 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 18 + column: 5 + end_location: + row: 20 + column: 8 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 23 + column: 21 + end_location: + row: 24 + column: 38 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 32 + column: 9 + end_location: + row: 34 + column: 12 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 37 + column: 13 + end_location: + row: 39 + column: 16 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_class.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_class.py.snap new file mode 100644 index 0000000000..7b7d4ce5bc --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_class.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: double + location: + row: 3 + column: 5 + end_location: + row: 3 + column: 28 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 5 + column: 23 + end_location: + row: 5 + column: 44 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_function.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_function.py.snap new file mode 100644 index 0000000000..19cb86b411 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_function.py.snap @@ -0,0 +1,50 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: double + location: + row: 3 + column: 5 + end_location: + row: 3 + column: 27 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 11 + column: 5 + end_location: + row: 11 + column: 27 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 15 + column: 39 + end_location: + row: 17 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 17 + column: 5 + end_location: + row: 17 + column: 20 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 21 + column: 5 + end_location: + row: 21 + column: 28 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_multiline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_multiline.py.snap new file mode 100644 index 0000000000..a57b616596 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_multiline.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: double + location: + row: 4 + column: 1 + end_location: + row: 6 + column: 4 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 9 + column: 1 + end_location: + row: 11 + column: 4 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_singleline.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_singleline.py.snap new file mode 100644 index 0000000000..5fdff83502 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__single_docstring_docstring_singles_module_singleline.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: double + location: + row: 2 + column: 1 + end_location: + row: 2 + column: 32 + fix: ~ +- kind: + BadQuotesMultilineString: double + location: + row: 6 + column: 1 + end_location: + row: 6 + column: 32 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles.py.snap new file mode 100644 index 0000000000..f84a172892 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles.py.snap @@ -0,0 +1,23 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesInlineString: double + location: + row: 1 + column: 25 + end_location: + row: 1 + column: 46 + fix: ~ +- kind: + BadQuotesInlineString: double + location: + row: 2 + column: 25 + end_location: + row: 2 + column: 47 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_escaped.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_escaped.py.snap new file mode 100644 index 0000000000..7a30dcf2a4 --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_escaped.py.snap @@ -0,0 +1,13 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: AvoidQuoteEscape + location: + row: 1 + column: 26 + end_location: + row: 1 + column: 48 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_multiline_string.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_multiline_string.py.snap new file mode 100644 index 0000000000..f4cf72cdbc --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_multiline_string.py.snap @@ -0,0 +1,14 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +- kind: + BadQuotesMultilineString: double + location: + row: 1 + column: 5 + end_location: + row: 3 + column: 13 + fix: ~ + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_noqa.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_noqa.py.snap new file mode 100644 index 0000000000..5037171f0b --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_noqa.py.snap @@ -0,0 +1,6 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +[] + diff --git a/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_wrapped.py.snap b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_wrapped.py.snap new file mode 100644 index 0000000000..5037171f0b --- /dev/null +++ b/src/flake8_quotes/snapshots/ruff__flake8_quotes__checks__tests__singles_singles_wrapped.py.snap @@ -0,0 +1,6 @@ +--- +source: src/flake8_quotes/checks.rs +expression: checks +--- +[] + diff --git a/src/lib.rs b/src/lib.rs index 389dc857a3..2e3cf1d274 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ mod flake8_bugbear; mod flake8_builtins; mod flake8_comprehensions; mod flake8_print; +mod flake8_quotes; pub mod fs; pub mod linter; pub mod logging; diff --git a/src/pyproject.rs b/src/pyproject.rs index f952e7b430..2155b328dc 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -8,8 +8,8 @@ use serde::de; use serde::{Deserialize, Deserializer}; use crate::checks::CheckCode; -use crate::fs; use crate::settings::PythonVersion; +use crate::{flake8_quotes, fs}; pub fn load_config(pyproject: &Option, quiet: bool) -> Result { match pyproject { @@ -45,6 +45,7 @@ pub struct Config { pub per_file_ignores: Vec, pub dummy_variable_rgx: Option, pub target_version: Option, + pub flake8_quotes: Option, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -195,6 +196,7 @@ mod tests { per_file_ignores: vec![], dummy_variable_rgx: None, target_version: None, + flake8_quotes: None }) }) ); @@ -220,6 +222,7 @@ line-length = 79 per_file_ignores: vec![], dummy_variable_rgx: None, target_version: None, + flake8_quotes: None }) }) ); @@ -245,6 +248,7 @@ exclude = ["foo.py"] per_file_ignores: vec![], dummy_variable_rgx: None, target_version: None, + flake8_quotes: None }) }) ); @@ -270,6 +274,7 @@ select = ["E501"] per_file_ignores: vec![], dummy_variable_rgx: None, target_version: None, + flake8_quotes: None }) }) ); @@ -296,6 +301,7 @@ ignore = ["E501"] per_file_ignores: vec![], dummy_variable_rgx: None, target_version: None, + flake8_quotes: None }) }) ); @@ -368,6 +374,7 @@ other-attribute = 1 }], dummy_variable_rgx: None, target_version: None, + flake8_quotes: None } ); diff --git a/src/settings.rs b/src/settings.rs index 01402a3765..650d847c0e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use crate::checks::{CheckCategory, CheckCode}; -use crate::fs; use crate::pyproject::{load_config, StrCheckCodePair}; +use crate::{flake8_quotes, fs}; #[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)] pub enum PythonVersion { @@ -97,6 +97,8 @@ pub struct RawSettings { pub per_file_ignores: Vec, pub select: Vec, pub target_version: PythonVersion, + // Plugins + pub flake8_quotes: flake8_quotes::settings::Settings, } static DEFAULT_EXCLUDE: Lazy> = Lazy::new(|| { @@ -133,6 +135,7 @@ impl RawSettings { quiet: bool, ) -> Result { let config = load_config(pyproject, quiet)?; + println!("{:?}", config.flake8_quotes); Ok(RawSettings { dummy_variable_rgx: match config.dummy_variable_rgx { Some(pattern) => Regex::new(&pattern) @@ -173,6 +176,11 @@ impl RawSettings { .into_iter() .map(|pair| PerFileIgnore::new(pair, project_root)) .collect(), + // Plugins + flake8_quotes: config + .flake8_quotes + .map(flake8_quotes::settings::Settings::from_config) + .unwrap_or_default(), }) } } @@ -186,6 +194,8 @@ pub struct Settings { pub line_length: usize, pub per_file_ignores: Vec, pub target_version: PythonVersion, + // Plugins + pub flake8_quotes: flake8_quotes::settings::Settings, } impl Settings { @@ -205,9 +215,10 @@ impl Settings { enabled, exclude: settings.exclude, extend_exclude: settings.extend_exclude, + flake8_quotes: settings.flake8_quotes, line_length: settings.line_length, per_file_ignores: settings.per_file_ignores, - target_version: PythonVersion::Py310, + target_version: settings.target_version, } } @@ -220,6 +231,7 @@ impl Settings { line_length: 88, per_file_ignores: vec![], target_version: PythonVersion::Py310, + flake8_quotes: Default::default(), } } @@ -232,6 +244,7 @@ impl Settings { line_length: 88, per_file_ignores: vec![], target_version: PythonVersion::Py310, + flake8_quotes: Default::default(), } } } @@ -285,6 +298,9 @@ pub struct CurrentSettings { pub per_file_ignores: Vec, pub select: Vec, pub target_version: PythonVersion, + // Plugins + pub flake8_quotes: flake8_quotes::settings::Settings, + // Non-settings exposed to the user pub project_root: Option, pub pyproject: Option, } @@ -314,6 +330,7 @@ impl CurrentSettings { per_file_ignores: settings.per_file_ignores, select: settings.select, target_version: settings.target_version, + flake8_quotes: settings.flake8_quotes, project_root, pyproject, }