Compare commits

...

23 Commits

Author SHA1 Message Date
Charlie Marsh
818582fe8a Bump version to 0.0.202 2022-12-30 08:16:32 -05:00
Charlie Marsh
90574c1088 Set editor background on top-level component (#1478) 2022-12-30 07:55:54 -05:00
Charlie Marsh
3061a35e7c Use more precise ranges for class and function checks (#1476) 2022-12-30 07:39:20 -05:00
Martin Fischer
87681697ae Improve CLI help for --select (#1471) 2022-12-30 07:16:44 -05:00
Martin Fischer
e9ec2a7b36 Add use test_case::test_case; to CONTRIBUTING.md
I previously tried adding the #[test_case()] attribute macro and got
confused because the Rust compilation suddenly failed with:

    error[E0658]: use of unstable library feature 'custom_test_frameworks': custom test frameworks are an unstable feature

which is a quite confusing error message.  The solution is to just add
`use test_case::test_case;`, so this commit adds that line to the
example in CONTRIBUTING.md to spare others this source of confusion.
2022-12-30 07:13:48 -05:00
Martin Fischer
b0bb75dc1c Add --select to command suggested in CONTRIBUTING.md
By default only E* and F* lints are enabled. I previously followed the
CONTRIBUTING.md instructions to implement a TID* lint and was confused
why my lint wasn't being run.
2022-12-30 07:13:48 -05:00
Martin Fischer
ebca5c2df8 Make banned-api config setting optional (#1465) 2022-12-30 07:09:56 -05:00
Charlie Marsh
16b10c42f0 Fix lint issues 2022-12-29 23:12:28 -05:00
Charlie Marsh
4a6e5d1549 Bump version to 0.0.201 2022-12-29 23:01:35 -05:00
Charlie Marsh
b078050732 Implicit flake8-implicit-str-concat (#1463) 2022-12-29 23:00:55 -05:00
Charlie Marsh
5f796b39b4 Run cargo fmt 2022-12-29 22:25:14 -05:00
Martin Fischer
9d34da23bd Implement TID251 (banning modules & module members) (#1436) 2022-12-29 22:11:12 -05:00
Charlie Marsh
bde12c3bb3 Restore quick fixes for playground 2022-12-29 20:16:19 -05:00
Colin Delahunty
f735660801 Removed unicode literals (#1448) 2022-12-29 20:11:33 -05:00
Charlie Marsh
34cd22dfc1 Copy URL but don't update the hash (#1458) 2022-12-29 19:46:50 -05:00
Charlie Marsh
9fafe16a55 Re-add GitHub badge to the bottom of the page 2022-12-29 19:38:33 -05:00
Charlie Marsh
e9a4cb1c1d Remove generated TypeScript options (#1456) 2022-12-29 19:37:49 -05:00
Charlie Marsh
9db825c731 Use trailingComma: 'all' (#1457) 2022-12-29 19:36:51 -05:00
Charlie Marsh
2c7464604a Implement dark mode (#1455) 2022-12-29 19:33:46 -05:00
Charlie Marsh
cd2099f772 Move default options into WASM interface (#1453) 2022-12-29 18:06:57 -05:00
Adam Turner
091d36cd30 Add Sphinx to user list (#1451) 2022-12-29 18:06:09 -05:00
Mathieu Kniewallner
02f156c6cb docs(README): add missing flake8-simplify (#1449) 2022-12-29 17:02:26 -05:00
Charlie Marsh
9f7350961e Rename config to settings in the playground (#1450) 2022-12-29 16:59:38 -05:00
95 changed files with 2511 additions and 824 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.200
rev: v0.0.202
hooks:
- id: ruff

View File

@@ -66,11 +66,13 @@ understand how other, similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run Ruff locally
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache`. Once you're satisfied with the
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache --select E402`. Once you're satisfied with the
output, codify the behavior as a snapshot test by adding a new `testcase` macro to the `mod tests`
section of `src/linter.rs`, like so:
```rust
use test_case::test_case;
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
...
```

8
Cargo.lock generated
View File

@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.200-dev.0"
version = "0.0.202-dev.0"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1878,7 +1878,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.200"
version = "0.0.202"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1946,7 +1946,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.200"
version = "0.0.202"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1967,7 +1967,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.200"
version = "0.0.202"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.200"
version = "0.0.202"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -51,7 +51,7 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
quick-junit = { version = "0.3.2" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.200", path = "ruff_macros" }
ruff_macros = { version = "0.0.202", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }

25
LICENSE
View File

@@ -388,6 +388,31 @@ are:
SOFTWARE.
"""
- flake8-implicit-str-concat, licensed as follows:
"""
The MIT License (MIT)
Copyright (c) 2019 Dylan Turner
Permission 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-import-conventions, licensed as follows:
"""
MIT License

View File

@@ -44,6 +44,7 @@ Ruff is extremely actively developed and used in major open-source projects like
- [Bokeh](https://github.com/bokeh/bokeh)
- [Zulip](https://github.com/zulip/zulip)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
- [Hatch](https://github.com/pypa/hatch)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Synapse](https://github.com/matrix-org/synapse)
@@ -94,6 +95,7 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [flake8-comprehensions (C4)](#flake8-comprehensions-c4)
1. [flake8-debugger (T10)](#flake8-debugger-t10)
1. [flake8-errmsg (EM)](#flake8-errmsg-em)
1. [flake8-implicit-str-concat (ISC)](#flake8-implicit-str-concat-isc)
1. [flake8-import-conventions (ICN)](#flake8-import-conventions-icn)
1. [flake8-print (T20)](#flake8-print-t20)
1. [flake8-quotes (Q)](#flake8-quotes-q)
@@ -167,7 +169,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.200'
rev: 'v0.0.202'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
@@ -328,11 +330,11 @@ Options:
-n, --no-cache
Disable cache reads
--select <SELECT>
List of error codes to enable
Comma-separated list of error codes to enable (or ALL, to enable all checks)
--extend-select <EXTEND_SELECT>
Like --select, but adds additional error codes on top of the selected ones
--ignore <IGNORE>
List of error codes to ignore
Comma-separated list of error codes to disable
--extend-ignore <EXTEND_IGNORE>
Like --ignore, but adds additional error codes on top of the ignored ones
--exclude <EXCLUDE>
@@ -360,7 +362,7 @@ Options:
--show-settings
See the settings Ruff will use to check a given Python file
--add-noqa
Enable automatic additions of noqa directives to failing lines
Enable automatic additions of `noqa` directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
@@ -368,7 +370,7 @@ Options:
--line-length <LINE_LENGTH>
Set the line-length for length-associated checks and automatic formatting
--max-complexity <MAX_COMPLEXITY>
Max McCabe complexity allowed for a function
Maximum McCabe complexity allowed for a given function
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
--explain <EXPLAIN>
@@ -677,6 +679,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 |
| UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 |
| UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 |
| UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 |
### pep8-naming (N)
@@ -852,6 +855,16 @@ For more, see [flake8-errmsg](https://pypi.org/project/flake8-errmsg/0.4.0/) on
| EM102 | FStringInException | Exception must not use an f-string literal, assign to variable first | |
| EM103 | DotFormatInException | Exception must not use a `.format()` string directly, assign to variable first | |
### flake8-implicit-str-concat (ISC)
For more, see [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/0.3.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ISC001 | SingleLineImplicitStringConcatenation | Implicitly concatenated string literals on one line | |
| ISC002 | MultiLineImplicitStringConcatenation | Implicitly concatenated string literals over continuation line | |
| ISC003 | ExplicitStringConcatenation | Explicitly concatenated string should be implicitly concatenated | |
### flake8-import-conventions (ICN)
| Code | Name | Message | Fix |
@@ -907,6 +920,7 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TID251 | BannedApi | `...` is banned: ... | |
| TID252 | BannedRelativeImport | Relative imports are banned | |
### flake8-unused-arguments (ARG)
@@ -1271,10 +1285,12 @@ natively, including:
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (1/37)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`isort`](https://pypi.org/project/isort/)
@@ -1326,10 +1342,12 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (1/37)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)
@@ -2477,6 +2495,27 @@ ban-relative-imports = "all"
---
#### [`banned-api`](#banned-api)
Specific modules or module members that may not be imported or accessed.
Note that this check is only meant to flag accidental uses,
and can be circumvented via `eval` or `importlib`.
**Default value**: `{}`
**Type**: `HashMap<String, BannedApi>`
**Example usage**:
```toml
[tool.ruff.flake8-tidy-imports]
[tool.ruff.flake8-tidy-imports.banned-api]
"cgi".msg = "The cgi module is deprecated, see https://peps.python.org/pep-0594/#cgi."
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
```
---
### `flake8-unused-arguments`
#### [`ignore-variadic-names`](#ignore-variadic-names)

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.200"
version = "0.0.202"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.200"
version = "0.0.202"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.200-dev.0"
version = "0.0.202-dev.0"
edition = "2021"
[lib]

View File

@@ -164,17 +164,17 @@ pub fn convert(
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.inline_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.inline_quotes = Some(Quote::Double),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"multiline-quotes" | "multiline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.multiline_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.multiline_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.multiline_quotes = Some(Quote::Double),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"docstring-quotes" | "docstring_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.docstring_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Double),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"avoid-escape" | "avoid_escape" => match parser::parse_bool(value.as_ref()) {

View File

@@ -16,16 +16,17 @@ pub enum Plugin {
Flake8Datetimez,
Flake8Debugger,
Flake8Docstrings,
Flake8ErrMsg,
Flake8Eradicate,
Flake8ErrMsg,
Flake8ImplicitStrConcat,
Flake8Print,
Flake8Quotes,
Flake8Return,
Flake8Simplify,
Flake8TidyImports,
McCabe,
PandasVet,
PEP8Naming,
PandasVet,
Pyupgrade,
}
@@ -45,6 +46,7 @@ impl FromStr for Plugin {
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-eradicate" => Ok(Plugin::Flake8Eradicate),
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
"flake8-implicit-str-concat" => Ok(Plugin::Flake8ImplicitStrConcat),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-return" => Ok(Plugin::Flake8Return),
@@ -76,14 +78,15 @@ impl fmt::Debug for Plugin {
Plugin::Flake8Docstrings => "flake8-docstrings",
Plugin::Flake8Eradicate => "flake8-eradicate",
Plugin::Flake8ErrMsg => "flake8-errmsg",
Plugin::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
Plugin::Flake8Print => "flake8-print",
Plugin::Flake8Quotes => "flake8-quotes",
Plugin::Flake8Return => "flake8-return",
Plugin::Flake8Simplify => "flake8-simplify",
Plugin::Flake8TidyImports => "flake8-tidy-imports",
Plugin::McCabe => "mccabe",
Plugin::PandasVet => "pandas-vet",
Plugin::PEP8Naming => "pep8-naming",
Plugin::PandasVet => "pandas-vet",
Plugin::Pyupgrade => "pyupgrade",
}
)
@@ -106,6 +109,7 @@ impl Plugin {
// TODO(charlie): Handle rename of `E` to `ERA`.
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
Plugin::Flake8ErrMsg => CheckCodePrefix::EM,
Plugin::Flake8ImplicitStrConcat => CheckCodePrefix::ISC,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Return => CheckCodePrefix::RET,
@@ -146,6 +150,7 @@ impl Plugin {
}
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8ErrMsg => vec![CheckCodePrefix::EM],
Plugin::Flake8ImplicitStrConcat => vec![CheckCodePrefix::ISC],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Return => vec![CheckCodePrefix::RET],
@@ -449,6 +454,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
Plugin::Flake8Docstrings,
Plugin::Flake8Eradicate,
Plugin::Flake8ErrMsg,
Plugin::Flake8ImplicitStrConcat,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Return,

View File

@@ -1 +0,0 @@
src/ruff_options.ts

View File

@@ -0,0 +1,3 @@
{
"trailingComma": "all"
}

View File

@@ -17,6 +17,14 @@
</head>
<body>
<div id="root"></div>
<div style="display: flex; position: fixed; right: 16px; bottom: 16px">
<a href="https://GitHub.com/charliermarsh/ruff"
><img
src="https://img.shields.io/github/stars/charliermarsh/ruff.svg?style=social&label=GitHub&maxAge=2592000&?logoWidth=100"
alt="GitHub stars"
style="width: 120px"
/></a>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,12 +1,13 @@
import { useCallback, useEffect, useState } from "react";
import { persist, restore } from "./config";
import { DEFAULT_CONFIG_SOURCE, DEFAULT_PYTHON_SOURCE } from "../constants";
import { DEFAULT_PYTHON_SOURCE } from "../constants";
import init, { check, Check, currentVersion, defaultSettings } from "../pkg";
import { ErrorMessage } from "./ErrorMessage";
import Header from "./Header";
import init, { check, current_version, Check } from "../pkg";
import { useTheme } from "./theme";
import { persist, restore, stringify } from "./settings";
import SettingsEditor from "./SettingsEditor";
import SourceEditor from "./SourceEditor";
import Themes from "./Themes";
import MonacoThemes from "./MonacoThemes";
type Tab = "Source" | "Settings";
@@ -15,17 +16,18 @@ export default function Editor() {
const [version, setVersion] = useState<string | null>(null);
const [tab, setTab] = useState<Tab>("Source");
const [edit, setEdit] = useState<number>(0);
const [configSource, setConfigSource] = useState<string | null>(null);
const [settingsSource, setSettingsSource] = useState<string | null>(null);
const [pythonSource, setPythonSource] = useState<string | null>(null);
const [checks, setChecks] = useState<Check[]>([]);
const [error, setError] = useState<string | null>(null);
const [theme, setTheme] = useTheme();
useEffect(() => {
init().then(() => setInitialized(true));
}, []);
useEffect(() => {
if (!initialized || configSource == null || pythonSource == null) {
if (!initialized || settingsSource == null || pythonSource == null) {
return;
}
@@ -33,7 +35,7 @@ export default function Editor() {
let checks: Check[];
try {
config = JSON.parse(configSource);
config = JSON.parse(settingsSource);
} catch (e) {
setChecks([]);
setError((e as Error).message);
@@ -49,73 +51,85 @@ export default function Editor() {
setError(null);
setChecks(checks);
}, [initialized, configSource, pythonSource]);
useEffect(() => {
if (configSource == null || pythonSource == null) {
const payload = restore();
if (payload) {
const [configSource, pythonSource] = payload;
setConfigSource(configSource);
setPythonSource(pythonSource);
} else {
setConfigSource(DEFAULT_CONFIG_SOURCE);
setPythonSource(DEFAULT_PYTHON_SOURCE);
}
}
}, [configSource, pythonSource]);
}, [initialized, settingsSource, pythonSource]);
useEffect(() => {
if (!initialized) {
return;
}
setVersion(current_version());
}, [initialized]);
if (settingsSource == null || pythonSource == null) {
const payload = restore();
if (payload) {
const [settingsSource, pythonSource] = payload;
setSettingsSource(settingsSource);
setPythonSource(pythonSource);
} else {
setSettingsSource(stringify(defaultSettings()));
setPythonSource(DEFAULT_PYTHON_SOURCE);
}
}
}, [initialized, settingsSource, pythonSource]);
const handleShare = useCallback(() => {
if (!initialized || configSource == null || pythonSource == null) {
useEffect(() => {
if (!initialized) {
return;
}
persist(configSource, pythonSource);
}, [initialized, configSource, pythonSource]);
setVersion(currentVersion());
}, [initialized]);
const handleShare = useCallback(() => {
if (!initialized || settingsSource == null || pythonSource == null) {
return;
}
persist(settingsSource, pythonSource);
}, [initialized, settingsSource, pythonSource]);
const handlePythonSourceChange = useCallback((pythonSource: string) => {
setEdit((edit) => edit + 1);
setPythonSource(pythonSource);
}, []);
const handleConfigSourceChange = useCallback((configSource: string) => {
const handleSettingsSourceChange = useCallback((settingsSource: string) => {
setEdit((edit) => edit + 1);
setConfigSource(configSource);
setSettingsSource(settingsSource);
}, []);
return (
<main className={"h-full w-full flex flex-auto"}>
<main
className={
"h-full w-full flex flex-auto bg-ayu-background dark:bg-ayu-background-dark"
}
>
<Header
edit={edit}
version={version}
tab={tab}
onChange={setTab}
theme={theme}
version={version}
onChangeTab={setTab}
onChangeTheme={setTheme}
onShare={initialized ? handleShare : undefined}
/>
<Themes />
<MonacoThemes />
<div className={"mt-12 relative flex-auto"}>
{initialized && configSource != null && pythonSource != null ? (
{initialized && settingsSource != null && pythonSource != null ? (
<>
<SourceEditor
visible={tab === "Source"}
source={pythonSource}
theme={theme}
checks={checks}
onChange={handlePythonSourceChange}
/>
<SettingsEditor
visible={tab === "Settings"}
source={configSource}
onChange={handleConfigSourceChange}
source={settingsSource}
theme={theme}
onChange={handleSettingsSourceChange}
/>
</>
) : null}

View File

@@ -18,7 +18,7 @@ export function ErrorMessage({ children }: { children: string }) {
children.startsWith("Error: ")
? children.slice("Error: ".length)
: children,
120
120,
)}
</p>
</div>

View File

@@ -1,26 +1,50 @@
import classNames from "classnames";
import ThemeButton from "./ThemeButton";
import ShareButton from "./ShareButton";
import { Theme } from "./theme";
import VersionTag from "./VersionTag";
export type Tab = "Source" | "Settings";
export default function Header({
edit,
version,
tab,
onChange,
theme,
version,
onChangeTab,
onChangeTheme,
onShare,
}: {
edit: number;
version: string | null;
tab: Tab;
onChange: (tab: Tab) => void;
theme: Theme;
version: string | null;
onChangeTab: (tab: Tab) => void;
onChangeTheme: (theme: Theme) => void;
onShare?: () => void;
}) {
return (
<div
className="w-full flex items-center justify-between flex-none pl-5 sm:pl-6 pr-4 lg:pr-6 absolute z-10 top-0 left-0 -mb-px antialiased border-b border-gray-200 dark:border-gray-800"
style={{ background: "#f8f9fa" }}
className={classNames(
"w-full",
"flex",
"items-center",
"justify-between",
"flex-none",
"pl-5",
"sm:pl-6",
"pr-4",
"lg:pr-6",
"absolute",
"z-10",
"top-0",
"left-0",
"-mb-px",
"antialiased",
"border-b",
"border-gray-200",
"dark:border-gray-800",
)}
>
<div className="flex space-x-5">
<button
@@ -28,15 +52,15 @@ export default function Header({
className={classNames(
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
tab === "Source"
? "text-ayu"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white"
? "text-ayu-accent"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white",
)}
onClick={() => onChange("Source")}
onClick={() => onChangeTab("Source")}
>
<span
className={classNames(
"absolute bottom-0 inset-x-0 bg-ayu h-0.5 rounded-full transition-opacity duration-150",
tab === "Source" ? "opacity-100" : "opacity-0"
"absolute bottom-0 inset-x-0 bg-ayu-accent h-0.5 rounded-full transition-opacity duration-150",
tab === "Source" ? "opacity-100" : "opacity-0",
)}
/>
Source
@@ -46,15 +70,15 @@ export default function Header({
className={classNames(
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
tab === "Settings"
? "text-ayu"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white"
? "text-ayu-accent"
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white",
)}
onClick={() => onChange("Settings")}
onClick={() => onChangeTab("Settings")}
>
<span
className={classNames(
"absolute bottom-0 inset-x-0 bg-ayu h-0.5 rounded-full transition-opacity duration-150",
tab === "Settings" ? "opacity-100" : "opacity-0"
"absolute bottom-0 inset-x-0 bg-ayu-accent h-0.5 rounded-full transition-opacity duration-150",
tab === "Settings" ? "opacity-100" : "opacity-0",
)}
/>
Settings
@@ -67,6 +91,8 @@ export default function Header({
</div>
<div className={"hidden sm:flex items-center min-w-0"}>
<ShareButton key={edit} onShare={onShare} />
<div className="hidden sm:block mx-6 lg:mx-4 w-px h-6 bg-gray-200 dark:bg-gray-700" />
<ThemeButton theme={theme} onChange={onChangeTheme} />
</div>
</div>
);

View File

@@ -1,7 +1,11 @@
/**
* Non-rendering component that loads the Monaco editor themes.
*/
import { useMonaco } from "@monaco-editor/react";
import { useEffect } from "react";
export default function Themes() {
export default function MonacoThemes() {
const monaco = useMonaco();
useEffect(() => {
@@ -631,7 +635,642 @@ export default function Themes() {
foreground: "#787b8099",
token: "punctuation.definition.markdown",
},
// Edits.
// Manual changes.
{
foreground: "#fa8d3e",
token: "number",
},
],
encodedTokensColors: [],
});
// Generated via `monaco-vscode-textmate-theme-converter`.
// See: https://github.com/ayu-theme/vscode-ayu/blob/91839e8a9dfa78d61e58dbcf9b52272a01fee66a/ayu-dark.json.
monaco?.editor.defineTheme("Ayu-Dark", {
inherit: false,
base: "vs-dark",
colors: {
focusBorder: "#e6b450b3",
foreground: "#565b66",
"widget.shadow": "#00000080",
"selection.background": "#409fff4d",
"icon.foreground": "#565b66",
errorForeground: "#d95757",
descriptionForeground: "#565b66",
"textBlockQuote.background": "#0f131a",
"textLink.foreground": "#e6b450",
"textLink.activeForeground": "#e6b450",
"textPreformat.foreground": "#bfbdb6",
"button.background": "#e6b450",
"button.foreground": "#0b0e14",
"button.hoverBackground": "#e1af4b",
"button.secondaryBackground": "#565b6633",
"button.secondaryForeground": "#bfbdb6",
"button.secondaryHoverBackground": "#565b6680",
"dropdown.background": "#0d1017",
"dropdown.foreground": "#565b66",
"dropdown.border": "#565b6645",
"input.background": "#0d1017",
"input.border": "#565b6645",
"input.foreground": "#bfbdb6",
"input.placeholderForeground": "#565b6680",
"inputOption.activeBorder": "#e6b4504d",
"inputOption.activeBackground": "#e6b45033",
"inputOption.activeForeground": "#e6b450",
"inputValidation.errorBackground": "#0d1017",
"inputValidation.errorBorder": "#d95757",
"inputValidation.infoBackground": "#0b0e14",
"inputValidation.infoBorder": "#39bae6",
"inputValidation.warningBackground": "#0b0e14",
"inputValidation.warningBorder": "#ffb454",
"scrollbar.shadow": "#11151c00",
"scrollbarSlider.background": "#565b6666",
"scrollbarSlider.hoverBackground": "#565b6699",
"scrollbarSlider.activeBackground": "#565b66b3",
"badge.background": "#e6b45033",
"badge.foreground": "#e6b450",
"progressBar.background": "#e6b450",
"list.activeSelectionBackground": "#47526640",
"list.activeSelectionForeground": "#bfbdb6",
"list.focusBackground": "#47526640",
"list.focusForeground": "#bfbdb6",
"list.focusOutline": "#47526640",
"list.highlightForeground": "#e6b450",
"list.deemphasizedForeground": "#d95757",
"list.hoverBackground": "#47526640",
"list.inactiveSelectionBackground": "#47526633",
"list.inactiveSelectionForeground": "#565b66",
"list.invalidItemForeground": "#565b664d",
"list.errorForeground": "#d95757",
"tree.indentGuidesStroke": "#6c738080",
"listFilterWidget.background": "#0f131a",
"listFilterWidget.outline": "#e6b450",
"listFilterWidget.noMatchesOutline": "#d95757",
"list.filterMatchBackground": "#5f4c7266",
"list.filterMatchBorder": "#6c598066",
"activityBar.background": "#0b0e14",
"activityBar.foreground": "#565b66cc",
"activityBar.inactiveForeground": "#565b6699",
"activityBar.border": "#0b0e14",
"activityBar.activeBorder": "#e6b450b3",
"activityBarBadge.background": "#e6b450",
"activityBarBadge.foreground": "#0b0e14",
"sideBar.background": "#0b0e14",
"sideBar.border": "#0b0e14",
"sideBarTitle.foreground": "#565b66",
"sideBarSectionHeader.background": "#0b0e14",
"sideBarSectionHeader.foreground": "#565b66",
"sideBarSectionHeader.border": "#0b0e14",
"minimap.background": "#0b0e14",
"minimap.selectionHighlight": "#409fff4d",
"minimap.errorHighlight": "#d95757",
"minimap.findMatchHighlight": "#6c5980",
"minimapGutter.addedBackground": "#7fd962",
"minimapGutter.modifiedBackground": "#73b8ff",
"minimapGutter.deletedBackground": "#f26d78",
"editorGroup.border": "#11151c",
"editorGroup.background": "#0f131a",
"editorGroupHeader.noTabsBackground": "#0b0e14",
"editorGroupHeader.tabsBackground": "#0b0e14",
"editorGroupHeader.tabsBorder": "#0b0e14",
"tab.activeBackground": "#0b0e14",
"tab.activeForeground": "#bfbdb6",
"tab.border": "#0b0e14",
"tab.activeBorder": "#e6b450",
"tab.unfocusedActiveBorder": "#565b66",
"tab.inactiveBackground": "#0b0e14",
"tab.inactiveForeground": "#565b66",
"tab.unfocusedActiveForeground": "#565b66",
"tab.unfocusedInactiveForeground": "#565b66",
"editor.background": "#0b0e14",
"editor.foreground": "#bfbdb6",
"editorLineNumber.foreground": "#6c738099",
"editorLineNumber.activeForeground": "#6c7380e6",
"editorCursor.foreground": "#e6b450",
"editor.inactiveSelectionBackground": "#409fff21",
"editor.selectionBackground": "#409fff4d",
"editor.selectionHighlightBackground": "#7fd96226",
"editor.selectionHighlightBorder": "#7fd96200",
"editor.wordHighlightBackground": "#73b8ff14",
"editor.wordHighlightStrongBackground": "#7fd96214",
"editor.wordHighlightBorder": "#73b8ff80",
"editor.wordHighlightStrongBorder": "#7fd96280",
"editor.findMatchBackground": "#6c5980",
"editor.findMatchBorder": "#6c5980",
"editor.findMatchHighlightBackground": "#6c598066",
"editor.findMatchHighlightBorder": "#5f4c7266",
"editor.findRangeHighlightBackground": "#6c598040",
"editor.rangeHighlightBackground": "#6c598033",
"editor.lineHighlightBackground": "#131721",
"editorLink.activeForeground": "#e6b450",
"editorWhitespace.foreground": "#6c738099",
"editorIndentGuide.background": "#6c738033",
"editorIndentGuide.activeBackground": "#6c738080",
"editorRuler.foreground": "#6c738033",
"editorCodeLens.foreground": "#acb6bf8c",
"editorBracketMatch.background": "#6c73804d",
"editorBracketMatch.border": "#6c73804d",
"editor.snippetTabstopHighlightBackground": "#7fd96233",
"editorOverviewRuler.border": "#11151c",
"editorOverviewRuler.modifiedForeground": "#73b8ff",
"editorOverviewRuler.addedForeground": "#7fd962",
"editorOverviewRuler.deletedForeground": "#f26d78",
"editorOverviewRuler.errorForeground": "#d95757",
"editorOverviewRuler.warningForeground": "#e6b450",
"editorOverviewRuler.bracketMatchForeground": "#6c7380b3",
"editorOverviewRuler.wordHighlightForeground": "#73b8ff66",
"editorOverviewRuler.wordHighlightStrongForeground": "#7fd96266",
"editorOverviewRuler.findMatchForeground": "#6c5980",
"editorError.foreground": "#d95757",
"editorWarning.foreground": "#e6b450",
"editorGutter.modifiedBackground": "#73b8ffcc",
"editorGutter.addedBackground": "#7fd962cc",
"editorGutter.deletedBackground": "#f26d78cc",
"diffEditor.insertedTextBackground": "#7fd9621f",
"diffEditor.removedTextBackground": "#f26d781f",
"diffEditor.diagonalFill": "#11151c",
"editorWidget.background": "#0f131a",
"editorWidget.border": "#11151c",
"editorHoverWidget.background": "#0f131a",
"editorHoverWidget.border": "#11151c",
"editorSuggestWidget.background": "#0f131a",
"editorSuggestWidget.border": "#11151c",
"editorSuggestWidget.highlightForeground": "#e6b450",
"editorSuggestWidget.selectedBackground": "#47526640",
"debugExceptionWidget.border": "#11151c",
"debugExceptionWidget.background": "#0f131a",
"editorMarkerNavigation.background": "#0f131a",
"peekView.border": "#47526640",
"peekViewTitle.background": "#47526640",
"peekViewTitleDescription.foreground": "#565b66",
"peekViewTitleLabel.foreground": "#bfbdb6",
"peekViewEditor.background": "#0f131a",
"peekViewEditor.matchHighlightBackground": "#6c598066",
"peekViewEditor.matchHighlightBorder": "#5f4c7266",
"peekViewResult.background": "#0f131a",
"peekViewResult.fileForeground": "#bfbdb6",
"peekViewResult.lineForeground": "#565b66",
"peekViewResult.matchHighlightBackground": "#6c598066",
"peekViewResult.selectionBackground": "#47526640",
"panel.background": "#0b0e14",
"panel.border": "#11151c",
"panelTitle.activeBorder": "#e6b450",
"panelTitle.activeForeground": "#bfbdb6",
"panelTitle.inactiveForeground": "#565b66",
"statusBar.background": "#0b0e14",
"statusBar.foreground": "#565b66",
"statusBar.border": "#0b0e14",
"statusBar.debuggingBackground": "#f29668",
"statusBar.debuggingForeground": "#0d1017",
"statusBar.noFolderBackground": "#0f131a",
"statusBarItem.activeBackground": "#565b6633",
"statusBarItem.hoverBackground": "#565b6633",
"statusBarItem.prominentBackground": "#11151c",
"statusBarItem.prominentHoverBackground": "#00000030",
"statusBarItem.remoteBackground": "#e6b450",
"statusBarItem.remoteForeground": "#0d1017",
"titleBar.activeBackground": "#0b0e14",
"titleBar.activeForeground": "#bfbdb6",
"titleBar.inactiveBackground": "#0b0e14",
"titleBar.inactiveForeground": "#565b66",
"titleBar.border": "#0b0e14",
"extensionButton.prominentForeground": "#0d1017",
"extensionButton.prominentBackground": "#e6b450",
"extensionButton.prominentHoverBackground": "#e1af4b",
"pickerGroup.border": "#11151c",
"pickerGroup.foreground": "#565b6680",
"debugToolBar.background": "#0f131a",
"debugIcon.breakpointForeground": "#f29668",
"debugIcon.breakpointDisabledForeground": "#f2966880",
"debugConsoleInputIcon.foreground": "#e6b450",
"welcomePage.tileBackground": "#0b0e14",
"welcomePage.tileShadow": "#00000080",
"welcomePage.progress.background": "#131721",
"welcomePage.buttonBackground": "#e6b45066",
"walkThrough.embeddedEditorBackground": "#0f131a",
"gitDecoration.modifiedResourceForeground": "#73b8ffb3",
"gitDecoration.deletedResourceForeground": "#f26d78b3",
"gitDecoration.untrackedResourceForeground": "#7fd962b3",
"gitDecoration.ignoredResourceForeground": "#565b6680",
"gitDecoration.conflictingResourceForeground": "",
"gitDecoration.submoduleResourceForeground": "#d2a6ffb3",
"settings.headerForeground": "#bfbdb6",
"settings.modifiedItemIndicator": "#73b8ff",
"keybindingLabel.background": "#565b661a",
"keybindingLabel.foreground": "#bfbdb6",
"keybindingLabel.border": "#bfbdb61a",
"keybindingLabel.bottomBorder": "#bfbdb61a",
"terminal.background": "#0b0e14",
"terminal.foreground": "#bfbdb6",
"terminal.ansiBlack": "#11151c",
"terminal.ansiRed": "#ea6c73",
"terminal.ansiGreen": "#7fd962",
"terminal.ansiYellow": "#f9af4f",
"terminal.ansiBlue": "#53bdfa",
"terminal.ansiMagenta": "#cda1fa",
"terminal.ansiCyan": "#90e1c6",
"terminal.ansiWhite": "#c7c7c7",
"terminal.ansiBrightBlack": "#686868",
"terminal.ansiBrightRed": "#f07178",
"terminal.ansiBrightGreen": "#aad94c",
"terminal.ansiBrightYellow": "#ffb454",
"terminal.ansiBrightBlue": "#59c2ff",
"terminal.ansiBrightMagenta": "#d2a6ff",
"terminal.ansiBrightCyan": "#95e6cb",
"terminal.ansiBrightWhite": "#ffffff",
},
rules: [
{
fontStyle: "italic",
foreground: "#acb6bf8c",
token: "comment",
},
{
foreground: "#aad94c",
token: "string",
},
{
foreground: "#aad94c",
token: "constant.other.symbol",
},
{
foreground: "#95e6cb",
token: "string.regexp",
},
{
foreground: "#95e6cb",
token: "constant.character",
},
{
foreground: "#95e6cb",
token: "constant.other",
},
{
foreground: "#d2a6ff",
token: "constant.numeric",
},
{
foreground: "#d2a6ff",
token: "constant.language",
},
{
foreground: "#bfbdb6",
token: "variable",
},
{
foreground: "#bfbdb6",
token: "variable.parameter.function-call",
},
{
foreground: "#f07178",
token: "variable.member",
},
{
fontStyle: "italic",
foreground: "#39bae6",
token: "variable.language",
},
{
foreground: "#ff8f40",
token: "storage",
},
{
foreground: "#ff8f40",
token: "keyword",
},
{
foreground: "#f29668",
token: "keyword.operator",
},
{
foreground: "#bfbdb6b3",
token: "punctuation.separator",
},
{
foreground: "#bfbdb6b3",
token: "punctuation.terminator",
},
{
foreground: "#bfbdb6",
token: "punctuation.section",
},
{
foreground: "#f29668",
token: "punctuation.accessor",
},
{
foreground: "#ff8f40",
token: "punctuation.definition.template-expression",
},
{
foreground: "#ff8f40",
token: "punctuation.section.embedded",
},
{
foreground: "#bfbdb6",
token: "meta.embedded",
},
{
foreground: "#59c2ff",
token: "source.java storage.type",
},
{
foreground: "#59c2ff",
token: "source.haskell storage.type",
},
{
foreground: "#59c2ff",
token: "source.c storage.type",
},
{
foreground: "#39bae6",
token: "entity.other.inherited-class",
},
{
foreground: "#ff8f40",
token: "storage.type.function",
},
{
foreground: "#39bae6",
token: "source.java storage.type.primitive",
},
{
foreground: "#ffb454",
token: "entity.name.function",
},
{
foreground: "#d2a6ff",
token: "variable.parameter",
},
{
foreground: "#d2a6ff",
token: "meta.parameter",
},
{
foreground: "#ffb454",
token: "variable.function",
},
{
foreground: "#ffb454",
token: "variable.annotation",
},
{
foreground: "#ffb454",
token: "meta.function-call.generic",
},
{
foreground: "#ffb454",
token: "support.function.go",
},
{
foreground: "#f07178",
token: "support.function",
},
{
foreground: "#f07178",
token: "support.macro",
},
{
foreground: "#aad94c",
token: "entity.name.import",
},
{
foreground: "#aad94c",
token: "entity.name.package",
},
{
foreground: "#59c2ff",
token: "entity.name",
},
{
foreground: "#39bae6",
token: "entity.name.tag",
},
{
foreground: "#39bae6",
token: "meta.tag.sgml",
},
{
foreground: "#59c2ff",
token: "support.class.component",
},
{
foreground: "#39bae680",
token: "punctuation.definition.tag.end",
},
{
foreground: "#39bae680",
token: "punctuation.definition.tag.begin",
},
{
foreground: "#39bae680",
token: "punctuation.definition.tag",
},
{
foreground: "#ffb454",
token: "entity.other.attribute-name",
},
{
fontStyle: "italic",
foreground: "#f29668",
token: "support.constant",
},
{
foreground: "#39bae6",
token: "support.type",
},
{
foreground: "#39bae6",
token: "support.class",
},
{
foreground: "#39bae6",
token: "source.go storage.type",
},
{
foreground: "#e6b673",
token: "meta.decorator variable.other",
},
{
foreground: "#e6b673",
token: "meta.decorator punctuation.decorator",
},
{
foreground: "#e6b673",
token: "storage.type.annotation",
},
{
foreground: "#d95757",
token: "invalid",
},
{
foreground: "#c594c5",
token: "meta.diff",
},
{
foreground: "#c594c5",
token: "meta.diff.header",
},
{
foreground: "#ffb454",
token: "source.ruby variable.other.readwrite",
},
{
foreground: "#59c2ff",
token: "source.css entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.sass entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.scss entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.less entity.name.tag",
},
{
foreground: "#59c2ff",
token: "source.stylus entity.name.tag",
},
{
foreground: "#acb6bf8c",
token: "source.css support.type",
},
{
foreground: "#acb6bf8c",
token: "source.sass support.type",
},
{
foreground: "#acb6bf8c",
token: "source.scss support.type",
},
{
foreground: "#acb6bf8c",
token: "source.less support.type",
},
{
foreground: "#acb6bf8c",
token: "source.stylus support.type",
},
{
fontStyle: "normal",
foreground: "#39bae6",
token: "support.type.property-name",
},
{
foreground: "#acb6bf8c",
token: "constant.numeric.line-number.find-in-files - match",
},
{
foreground: "#ff8f40",
token: "constant.numeric.line-number.match",
},
{
foreground: "#aad94c",
token: "entity.name.filename.find-in-files",
},
{
foreground: "#d95757",
token: "message.error",
},
{
fontStyle: "bold",
foreground: "#aad94c",
token: "markup.heading",
},
{
fontStyle: "bold",
foreground: "#aad94c",
token: "markup.heading entity.name",
},
{
foreground: "#39bae6",
token: "markup.underline.link",
},
{
foreground: "#39bae6",
token: "string.other.link",
},
{
fontStyle: "italic",
foreground: "#f07178",
token: "markup.italic",
},
{
fontStyle: "bold",
foreground: "#f07178",
token: "markup.bold",
},
{
fontStyle: "bold italic",
token: "markup.italic markup.bold",
},
{
fontStyle: "bold italic",
token: "markup.bold markup.italic",
},
{
background: "#bfbdb605",
token: "markup.raw",
},
{
background: "#bfbdb60f",
token: "markup.raw.inline",
},
{
fontStyle: "bold",
background: "#bfbdb60f",
foreground: "#acb6bf8c",
token: "meta.separator",
},
{
foreground: "#95e6cb",
fontStyle: "italic",
token: "markup.quote",
},
{
foreground: "#ffb454",
token: "markup.list punctuation.definition.list.begin",
},
{
foreground: "#7fd962",
token: "markup.inserted",
},
{
foreground: "#73b8ff",
token: "markup.changed",
},
{
foreground: "#f26d78",
token: "markup.deleted",
},
{
foreground: "#e6b673",
token: "markup.strike",
},
{
background: "#bfbdb60f",
foreground: "#39bae6",
token: "markup.table",
},
{
foreground: "#f29668",
token: "text.html.markdown markup.inline.raw",
},
{
background: "#acb6bf8c",
foreground: "#acb6bf8c",
token: "text.html.markdown meta.dummy.line-break",
},
{
background: "#bfbdb6",
foreground: "#acb6bf8c",
token: "punctuation.definition.markdown",
},
// Manual changes.
{
foreground: "#fa8d3e",
token: "number",

View File

@@ -5,14 +5,17 @@
import Editor, { useMonaco } from "@monaco-editor/react";
import { useCallback, useEffect } from "react";
import schema from "../../../ruff.schema.json";
import { Theme } from "./theme";
export default function SettingsEditor({
visible,
source,
theme,
onChange,
}: {
visible: boolean;
source: string;
theme: Theme;
onChange: (source: string) => void;
}) {
const monaco = useMonaco();
@@ -33,7 +36,7 @@ export default function SettingsEditor({
(value: string | undefined) => {
onChange(value ?? "");
},
[onChange]
[onChange],
);
return (
<Editor
@@ -47,7 +50,7 @@ export default function SettingsEditor({
wrapperProps={visible ? {} : { style: { display: "none" } }}
language={"json"}
value={source}
theme={"Ayu-Light"}
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
onChange={handleChange}
/>
);

View File

@@ -13,7 +13,7 @@ export default function ShareButton({ onShare }: { onShare?: () => void }) {
return copied ? (
<button
type="button"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 cursor-auto text-ayu shadow-copied dark:bg-ayu/10"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 cursor-auto text-ayu-accent shadow-copied dark:bg-ayu-accent/10"
>
<span
className="absolute inset-0 flex items-center justify-center invisible"
@@ -28,7 +28,7 @@ export default function ShareButton({ onShare }: { onShare?: () => void }) {
) : (
<button
type="button"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu/80 bg-ayu text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu-accent/80 bg-ayu-accent text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
disabled={!onShare || copied}
onClick={
onShare

View File

@@ -6,18 +6,19 @@ import Editor, { useMonaco } from "@monaco-editor/react";
import { MarkerSeverity, MarkerTag } from "monaco-editor";
import { useCallback, useEffect } from "react";
import { Check } from "../pkg";
export type Mode = "JSON" | "Python";
import { Theme } from "./theme";
export default function SourceEditor({
visible,
source,
theme,
checks,
onChange,
}: {
visible: boolean;
source: string;
checks: Check[];
theme: Theme;
onChange: (pythonSource: string) => void;
}) {
const monaco = useMonaco();
@@ -43,7 +44,7 @@ export default function SourceEditor({
check.code === "F401" || check.code === "F841"
? [MarkerTag.Unnecessary]
: [],
}))
})),
);
const codeActionProvider = monaco?.languages.registerCodeActionProvider(
@@ -80,7 +81,7 @@ export default function SourceEditor({
}));
return { actions, dispose: () => {} };
},
}
},
);
return () => {
@@ -92,7 +93,7 @@ export default function SourceEditor({
(value: string | undefined) => {
onChange(value ?? "");
},
[onChange]
[onChange],
);
return (
@@ -104,9 +105,9 @@ export default function SourceEditor({
roundedSelection: false,
scrollBeyondLastLine: false,
}}
wrapperProps={visible ? {} : { style: { display: "none" } }}
theme={"Ayu-Light"}
language={"python"}
wrapperProps={visible ? {} : { style: { display: "none" } }}
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
value={source}
onChange={handleChange}
/>

View File

@@ -0,0 +1,49 @@
/**
* Button to toggle between light and dark mode themes.
*/
import { Theme } from "./theme";
export default function ThemeButton({
theme,
onChange,
}: {
theme: Theme;
onChange: (theme: Theme) => void;
}) {
return (
<button
type="button"
className="ml-4 sm:ml-0 ring-1 ring-gray-900/5 shadow-sm hover:bg-gray-50 dark:ring-0 dark:bg-gray-800 dark:hover:bg-gray-700 dark:shadow-highlight/4 group focus:outline-none focus-visible:ring-2 rounded-md focus-visible:ring-ayu-accent dark:focus-visible:ring-2 dark:focus-visible:ring-gray-400"
onClick={() => onChange(theme === "light" ? "dark" : "light")}
>
<span className="sr-only">
<span className="dark:hidden">Switch to dark theme</span>
<span className="hidden dark:inline">Switch to light theme</span>
</span>
<svg
width="36"
height="36"
viewBox="-6 -6 36 36"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="stroke-ayu-accent fill-ayu-accent/10 group-hover:stroke-ayu-accent/80 dark:stroke-gray-400 dark:fill-gray-400/20 dark:group-hover:stroke-gray-300"
>
<g className="dark:opacity-0">
<path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"></path>
<path
d="M12 4v.01M17.66 6.345l-.007.007M20.005 12.005h-.01M17.66 17.665l-.007-.007M12 20.01V20M6.34 17.665l.007-.007M3.995 12.005h.01M6.34 6.344l.007.007"
fill="none"
/>
</g>
<g className="opacity-0 dark:opacity-100">
<path d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z" />
<path
d="M12 3v1M18.66 5.345l-.828.828M21.005 12.005h-1M18.66 18.665l-.828-.828M12 21.01V20M5.34 18.666l.835-.836M2.995 12.005h1.01M5.34 5.344l.835.836"
fill="none"
/>
</g>
</svg>
</button>
);
}

View File

@@ -17,7 +17,7 @@ export default function VersionTag({ children }: { children: ReactNode }) {
"items-center",
"dark:bg-gray-800",
"dark:text-gray-400",
"dark:shadow-highlight/4"
"dark:shadow-highlight/4",
)}
>
{children}

View File

@@ -1,63 +0,0 @@
import lzstring from "lz-string";
import { OptionGroup } from "../ruff_options";
export type Config = { [K: string]: any };
/**
* Parse an encoded value from the options export.
*
* TODO(charlie): Use JSON for the default values.
*/
function parse(value: any): any {
if (value == "None") {
return null;
}
return JSON.parse(value);
}
/**
* The default configuration for the playground.
*/
export function defaultConfig(availableOptions: OptionGroup[]): Config {
const config: Config = {};
for (const group of availableOptions) {
if (group.name == "globals") {
for (const field of group.fields) {
config[field.name] = parse(field.default);
}
} else {
config[group.name] = {};
for (const field of group.fields) {
config[group.name][field.name] = parse(field.default);
}
}
}
return config;
}
/**
* Persist the configuration to a URL.
*/
export function persist(configSource: string, pythonSource: string) {
window.location.hash = lzstring.compressToEncodedURIComponent(
configSource + "$$$" + pythonSource
);
}
/**
* Restore the configuration from a URL.
*/
export function restore(): [string, string] | null {
const value = lzstring.decompressFromEncodedURIComponent(
window.location.hash.slice(1)
);
if (value) {
const parts = value.split("$$$");
const configSource = parts[0];
const pythonSource = parts[1];
return [configSource, pythonSource];
} else {
return null;
}
}

View File

@@ -0,0 +1,50 @@
import lzstring from "lz-string";
export type Settings = { [K: string]: any };
/**
* Stringify a settings object to JSON.
*/
export function stringify(settings: Settings): string {
return JSON.stringify(
settings,
(k, v) => {
if (v instanceof Map) {
return Object.fromEntries(v.entries());
} else {
return v;
}
},
2,
);
}
/**
* Persist the configuration to a URL.
*/
export async function persist(settingsSource: string, pythonSource: string) {
const hash = lzstring.compressToEncodedURIComponent(
settingsSource + "$$$" + pythonSource,
);
await navigator.clipboard.writeText(
window.location.href.split("#")[0] + "#" + hash,
);
}
/**
* Restore the configuration from a URL.
*/
export function restore(): [string, string] | null {
const value = lzstring.decompressFromEncodedURIComponent(
window.location.hash.slice(1),
);
if (value) {
const parts = value.split("$$$");
const settingsSource = parts[0];
const pythonSource = parts[1];
return [settingsSource, pythonSource];
} else {
return null;
}
}

View File

@@ -0,0 +1,35 @@
/**
* Light and dark mode theming.
*/
import { useEffect, useState } from "react";
export type Theme = "dark" | "light";
export function useTheme(): [Theme, (theme: Theme) => void] {
const [localTheme, setLocalTheme] = useState<Theme>("light");
const setTheme = (mode: Theme) => {
if (mode === "dark") {
document.body.classList.add("dark");
} else {
document.body.classList.remove("dark");
}
localStorage.setItem("theme", mode);
setLocalTheme(mode);
};
useEffect(() => {
const initialTheme = localStorage.getItem("theme");
if (initialTheme === "dark") {
setTheme("dark");
} else if (initialTheme === "light") {
setTheme("light");
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
setTheme("dark");
} else {
setTheme("light");
}
}, []);
return [localTheme, setTheme];
}

View File

@@ -0,0 +1,7 @@
export async function copyTextToClipboard(text: string) {
if ("clipboard" in navigator) {
return await navigator.clipboard.writeText(text);
} else {
return document.execCommand("copy", true, text);
}
}

View File

@@ -1,6 +1,3 @@
import { defaultConfig } from "./Editor/config";
import { AVAILABLE_OPTIONS } from "./ruff_options";
export const DEFAULT_PYTHON_SOURCE =
"import os\n" +
"\n" +
@@ -32,9 +29,3 @@ export const DEFAULT_PYTHON_SOURCE =
"# 13\n" +
"# 21\n" +
"# 34\n";
export const DEFAULT_CONFIG_SOURCE = JSON.stringify(
defaultConfig(AVAILABLE_OPTIONS),
null,
2
);

View File

@@ -6,5 +6,5 @@ import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<Editor />
</React.StrictMode>
</React.StrictMode>,
);

View File

@@ -1,244 +0,0 @@
// This file is auto-generated by `cargo dev generate-playground-options`.
export interface OptionGroup {
name: string;
fields: {
name: string;
default: string;
type: string;
}[];
};
export const AVAILABLE_OPTIONS: OptionGroup[] = [
{"name": "globals", "fields": [
{
"name": "allowed-confusables",
"default": '[]',
"type": 'Vec<char>',
},
{
"name": "dummy-variable-rgx",
"default": '"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"',
"type": 'Regex',
},
{
"name": "extend-ignore",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "extend-select",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "external",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "fix-only",
"default": 'false',
"type": 'bool',
},
{
"name": "ignore",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "line-length",
"default": '88',
"type": 'usize',
},
{
"name": "required-version",
"default": 'None',
"type": 'String',
},
{
"name": "select",
"default": '["E", "F"]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "target-version",
"default": '"py310"',
"type": 'PythonVersion',
},
{
"name": "unfixable",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "update-check",
"default": 'true',
"type": 'bool',
},
]},
{"name": "flake8-annotations", "fields": [
{
"name": "allow-star-arg-any",
"default": 'false',
"type": 'bool',
},
{
"name": "mypy-init-return",
"default": 'false',
"type": 'bool',
},
{
"name": "suppress-dummy-args",
"default": 'false',
"type": 'bool',
},
{
"name": "suppress-none-returning",
"default": 'false',
"type": 'bool',
},
]},
{"name": "flake8-bugbear", "fields": [
{
"name": "extend-immutable-calls",
"default": '[]',
"type": 'Vec<String>',
},
]},
{"name": "flake8-errmsg", "fields": [
{
"name": "max-string-length",
"default": '0',
"type": 'usize',
},
]},
{"name": "flake8-import-conventions", "fields": [
{
"name": "aliases",
"default": '{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}',
"type": 'FxHashMap<String, String>',
},
{
"name": "extend-aliases",
"default": '{}',
"type": 'FxHashMap<String, String>',
},
]},
{"name": "flake8-quotes", "fields": [
{
"name": "avoid-escape",
"default": 'true',
"type": 'bool',
},
{
"name": "docstring-quotes",
"default": '"double"',
"type": 'Quote',
},
{
"name": "inline-quotes",
"default": '"double"',
"type": 'Quote',
},
{
"name": "multiline-quotes",
"default": '"double"',
"type": 'Quote',
},
]},
{"name": "flake8-tidy-imports", "fields": [
{
"name": "ban-relative-imports",
"default": '"parents"',
"type": 'Strictness',
},
]},
{"name": "flake8-unused-arguments", "fields": [
{
"name": "ignore-variadic-names",
"default": 'false',
"type": 'bool',
},
]},
{"name": "isort", "fields": [
{
"name": "combine-as-imports",
"default": 'false',
"type": 'bool',
},
{
"name": "extra-standard-library",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "force-single-line",
"default": 'false',
"type": 'bool',
},
{
"name": "force-wrap-aliases",
"default": 'false',
"type": 'bool',
},
{
"name": "known-first-party",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "known-third-party",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "single-line-exclusions",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "split-on-trailing-comma",
"default": 'true',
"type": 'bool',
},
]},
{"name": "mccabe", "fields": [
{
"name": "max-complexity",
"default": '10',
"type": 'usize',
},
]},
{"name": "pep8-naming", "fields": [
{
"name": "classmethod-decorators",
"default": '["classmethod"]',
"type": 'Vec<String>',
},
{
"name": "ignore-names",
"default": '["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]',
"type": 'Vec<String>',
},
{
"name": "staticmethod-decorators",
"default": '["staticmethod"]',
"type": 'Vec<String>',
},
]},
{"name": "pydocstyle", "fields": [
{
"name": "convention",
"default": 'None',
"type": 'Convention',
},
]},
{"name": "pyupgrade", "fields": [
{
"name": "keep-runtime-typing",
"default": 'false',
"type": 'bool',
},
]},
];

View File

@@ -1,6 +1,6 @@
declare module "lz-string" {
function decompressFromEncodedURIComponent(
input: string | null
input: string | null,
): string | null;
function compressToEncodedURIComponent(input: string | null): string;
}

View File

@@ -2,11 +2,16 @@
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
darkMode: "class",
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
ayu: "#f07171",
"ayu-accent": "#f07171",
"ayu-background": {
DEFAULT: "#f8f9fa",
dark: "#0b0e14",
},
},
fontFamily: {
sans: ["Inter var", ...defaultTheme.fontFamily.sans],

View File

@@ -0,0 +1,36 @@
_ = "a" "b" "c"
_ = "abc" + "def"
_ = "abc" \
"def"
_ = (
"abc" +
"def"
)
_ = (
f"abc" +
"def"
)
_ = (
b"abc" +
b"def"
)
_ = (
"abc"
"def"
)
_ = (
f"abc"
"def"
)
_ = (
b"abc"
b"def"
)

View File

@@ -0,0 +1,33 @@
## Banned modules ##
import cgi
from cgi import *
from cgi import a, b, c
# banning a module also bans any submodules
import cgi.foo.bar
from cgi.foo import bar
from cgi.foo.bar import *
## Banned module members ##
from typing import TypedDict
import typing
# attribute access is checked
typing.TypedDict
typing.TypedDict.anything
# function calls are checked
typing.TypedDict()
typing.TypedDict.anything()
# import aliases are resolved
import typing as totally_not_typing
totally_not_typing.TypedDict

View File

@@ -0,0 +1,11 @@
# module members cannot be imported with that syntax
import typing.TypedDict
# we don't track reassignments
import typing, other
typing = other
typing.TypedDict()
# yet another false positive
def foo(typing):
typing.TypedDict()

View File

@@ -42,6 +42,10 @@ staticmethod-decorators = ["staticmethod"]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "parents"
[tool.ruff.flake8-tidy-imports.banned-api]
"cgi".msg = "The cgi module is deprecated."
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
[tool.ruff.flake8-errmsg]
max-string-length = 20

View File

@@ -0,0 +1,27 @@
# These should change
x = u"Hello"
u'world'
print(u"Hello")
print(u'world')
import foo
foo(u"Hello", U"world", a=u"Hello", b=u"world")
# These should stay quoted they way they are
x = u'hello'
x = u"""hello"""
x = u'''hello'''
x = u'Hello "World"'
# These should not change
u = "Hello"
u = u
def hello():
return"Hello"

View File

@@ -375,6 +375,19 @@
},
"additionalProperties": false,
"definitions": {
"BannedApi": {
"type": "object",
"required": [
"msg"
],
"properties": {
"msg": {
"description": "The message to display when the API is used.",
"type": "string"
}
},
"additionalProperties": false
},
"CheckCodePrefix": {
"type": "string",
"enum": [
@@ -670,6 +683,12 @@
"ICN0",
"ICN00",
"ICN001",
"ISC",
"ISC0",
"ISC00",
"ISC001",
"ISC002",
"ISC003",
"M",
"M0",
"M001",
@@ -842,6 +861,7 @@
"TID",
"TID2",
"TID25",
"TID251",
"TID252",
"U",
"U0",
@@ -891,6 +911,7 @@
"UP021",
"UP022",
"UP023",
"UP025",
"W",
"W2",
"W29",
@@ -1085,6 +1106,16 @@
"type": "null"
}
]
},
"banned-api": {
"description": "Specific modules or module members that may not be imported or accessed. Note that this check is only meant to flag accidental uses, and can be circumvented via `eval` or `importlib`.",
"type": [
"object",
"null"
],
"additionalProperties": {
"$ref": "#/definitions/BannedApi"
}
}
},
"additionalProperties": false

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.200"
version = "0.0.202"
edition = "2021"
[dependencies]

View File

@@ -4,8 +4,7 @@ use anyhow::Result;
use clap::Args;
use crate::{
generate_check_code_prefix, generate_json_schema, generate_options,
generate_playground_options, generate_rules_table,
generate_check_code_prefix, generate_json_schema, generate_options, generate_rules_table,
};
#[derive(Args)]
@@ -28,8 +27,5 @@ pub fn main(cli: &Cli) -> Result<()> {
generate_options::main(&generate_options::Cli {
dry_run: cli.dry_run,
})?;
generate_playground_options::main(&generate_playground_options::Cli {
dry_run: cli.dry_run,
})?;
Ok(())
}

View File

@@ -1,142 +0,0 @@
//! Generate typescript file defining options to be used by the web playground.
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::settings::options::Options;
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
#[derive(Args)]
pub struct Cli {
/// Write the generated table to stdout (rather than to `TODO`).
#[arg(long)]
pub(crate) dry_run: bool,
}
fn emit_field(output: &mut String, field: &OptionField) {
output.push_str(&textwrap::indent(
&textwrap::dedent(&format!(
"
{{
\"name\": \"{}\",
\"default\": '{}',
\"type\": '{}',
}},",
field.name, field.default, field.value_type
)),
" ",
));
}
pub fn main(cli: &Cli) -> Result<()> {
let mut output = String::new();
// Generate all the top-level fields.
output.push_str(&format!("{{\"name\": \"{}\", \"fields\": [", "globals"));
for field in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
// Filter out options that don't make sense in the playground.
.filter(|field| {
!matches!(
field.name,
"src"
| "fix"
| "format"
| "exclude"
| "extend"
| "extend-exclude"
| "fixable"
| "force-exclude"
| "ignore-init-module-imports"
| "respect-gitignore"
| "show-source"
| "cache-dir"
| "per-file-ignores"
)
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, &field);
}
output.push_str("\n]},\n");
// Generate all the sub-groups.
for group in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Group(group) = entry {
Some(group)
} else {
None
}
})
.sorted_by_key(|group| group.name)
{
output.push_str(&format!("{{\"name\": \"{}\", \"fields\": [", group.name));
for field in group
.fields
.iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, field);
}
output.push_str("\n]},\n");
}
let prefix = textwrap::dedent(
r"
// This file is auto-generated by `cargo dev generate-playground-options`.
export interface OptionGroup {
name: string;
fields: {
name: string;
default: string;
type: string;
}[];
};
export const AVAILABLE_OPTIONS: OptionGroup[] = [
",
);
let postfix = "];";
if cli.dry_run {
print!("{output}");
} else {
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("playground")
.join("src")
.join("ruff_options.ts");
let mut f = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(file)?;
write!(f, "{prefix}")?;
write!(f, "{}", textwrap::indent(&output, " "))?;
write!(f, "{postfix}")?;
}
Ok(())
}

View File

@@ -15,7 +15,6 @@ pub mod generate_all;
pub mod generate_check_code_prefix;
pub mod generate_json_schema;
pub mod generate_options;
pub mod generate_playground_options;
pub mod generate_rules_table;
pub mod print_ast;
pub mod print_cst;

View File

@@ -15,8 +15,7 @@ use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_all, generate_check_code_prefix, generate_json_schema, generate_options,
generate_playground_options, generate_rules_table, print_ast, print_cst, print_tokens,
round_trip,
generate_rules_table, print_ast, print_cst, print_tokens, round_trip,
};
#[derive(Parser)]
@@ -39,9 +38,6 @@ enum Commands {
GenerateRulesTable(generate_rules_table::Cli),
/// Generate a Markdown-compatible listing of configuration options.
GenerateOptions(generate_options::Cli),
/// Generate typescript file defining options to be used by the web
/// playground.
GeneratePlaygroundOptions(generate_playground_options::Cli),
/// Print the AST for a given Python file.
PrintAST(print_ast::Cli),
/// Print the LibCST CST for a given Python file.
@@ -60,7 +56,6 @@ fn main() -> Result<()> {
Commands::GenerateJSONSchema(args) => generate_json_schema::main(args)?,
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateOptions(args) => generate_options::main(args)?,
Commands::GeneratePlaygroundOptions(args) => generate_playground_options::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.200"
version = "0.0.202"
edition = "2021"
[lib]

View File

@@ -39,10 +39,10 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
flake8_debugger, flake8_errmsg, flake8_import_conventions, flake8_print, flake8_return,
flake8_simplify, flake8_tidy_imports, flake8_unused_arguments, mccabe, noqa, pandas_vet,
pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
visibility,
flake8_debugger, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions,
flake8_print, flake8_return, flake8_simplify, flake8_tidy_imports, flake8_unused_arguments,
mccabe, noqa, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint,
pyupgrade, ruff, visibility,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -397,10 +397,9 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::E743) {
if let Some(check) = pycodestyle::checks::ambiguous_function_name(
name,
Range::from_located(stmt),
) {
if let Some(check) = pycodestyle::checks::ambiguous_function_name(name, || {
helpers::identifier_range(stmt, self.locator)
}) {
self.add_check(check);
}
}
@@ -585,9 +584,9 @@ where
}
if self.settings.enabled.contains(&CheckCode::E742) {
if let Some(check) =
pycodestyle::checks::ambiguous_class_name(name, Range::from_located(stmt))
{
if let Some(check) = pycodestyle::checks::ambiguous_class_name(name, || {
helpers::identifier_range(stmt, self.locator)
}) {
self.add_check(check);
}
}
@@ -723,6 +722,17 @@ where
}
}
// flake8_tidy_imports
if self.settings.enabled.contains(&CheckCode::TID251) {
if let Some(check) = flake8_tidy_imports::checks::name_or_parent_is_banned(
alias,
&alias.node.name,
&self.settings.flake8_tidy_imports.banned_api,
) {
self.add_check(check);
}
}
// pylint
if self.settings.enabled.contains(&CheckCode::PLC0414) {
pylint::plugins::useless_import_alias(self, alias);
@@ -854,6 +864,27 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::TID251) {
if let Some(module) = module {
for name in names {
if let Some(check) = flake8_tidy_imports::checks::name_is_banned(
module,
name,
&self.settings.flake8_tidy_imports.banned_api,
) {
self.add_check(check);
}
}
if let Some(check) = flake8_tidy_imports::checks::name_or_parent_is_banned(
stmt,
module,
&self.settings.flake8_tidy_imports.banned_api,
) {
self.add_check(check);
}
}
}
for alias in names {
if let Some("__future__") = module.as_deref() {
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
@@ -1586,6 +1617,15 @@ where
};
}
}
if self.settings.enabled.contains(&CheckCode::TID251) {
flake8_tidy_imports::checks::banned_attribute_access(
self,
&dealias_call_path(collect_call_paths(expr), &self.import_aliases),
expr,
&self.settings.flake8_tidy_imports.banned_api,
);
}
}
ExprKind::Call {
func,
@@ -2230,6 +2270,15 @@ where
}
}
}
ExprKind::BinOp {
op: Operator::Add, ..
} => {
if self.settings.enabled.contains(&CheckCode::ISC003) {
if let Some(check) = flake8_implicit_str_concat::checks::explicit(expr) {
self.add_check(check);
}
}
}
ExprKind::UnaryOp { op, operand } => {
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
@@ -2329,7 +2378,7 @@ where
}
ExprKind::Constant {
value: Constant::Str(value),
..
kind,
} => {
if self.in_type_definition && !self.in_literal {
self.deferred_string_type_definitions.push((
@@ -2347,6 +2396,9 @@ where
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::UP025) {
pyupgrade::plugins::rewrite_unicode_literal(self, expr, value, kind);
}
}
ExprKind::Lambda { args, body, .. } => {
// Visit the arguments, but avoid the body, which will be deferred.

View File

@@ -7,7 +7,7 @@ use crate::lex::docstring_detection::StateMachine;
use crate::ruff::checks::Context;
use crate::settings::flags;
use crate::source_code_locator::SourceCodeLocator;
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
use crate::{eradicate, flake8_implicit_str_concat, flake8_quotes, pycodestyle, ruff, Settings};
pub fn check_tokens(
locator: &SourceCodeLocator,
@@ -26,6 +26,8 @@ pub fn check_tokens(
|| settings.enabled.contains(&CheckCode::Q003);
let enforce_commented_out_code = settings.enabled.contains(&CheckCode::ERA001);
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let enforce_implicit_string_concatenation = settings.enabled.contains(&CheckCode::ISC001)
|| settings.enabled.contains(&CheckCode::ISC002);
let mut state_machine = StateMachine::default();
for &(start, ref tok, end) in tokens.iter().flatten() {
@@ -99,5 +101,14 @@ pub fn check_tokens(
}
}
// ISC001, ISC002
if enforce_implicit_string_concatenation {
checks.extend(
flake8_implicit_str_concat::checks::implicit(tokens, locator)
.into_iter()
.filter(|check| settings.enabled.contains(check.kind.code())),
);
}
checks
}

View File

@@ -165,6 +165,7 @@ pub enum CheckCode {
// mccabe
C901,
// flake8-tidy-imports
TID251,
TID252,
// flake8-return
RET501,
@@ -175,6 +176,10 @@ pub enum CheckCode {
RET506,
RET507,
RET508,
// flake8-implicit-str-concat
ISC001,
ISC002,
ISC003,
// flake8-print
T201,
T203,
@@ -231,6 +236,7 @@ pub enum CheckCode {
UP021,
UP022,
UP023,
UP025,
// pydocstyle
D100,
D101,
@@ -375,6 +381,7 @@ pub enum CheckCategory {
Flake8Comprehensions,
Flake8Debugger,
Flake8ErrMsg,
Flake8ImplicitStrConcat,
Flake8ImportConventions,
Flake8Print,
Flake8Quotes,
@@ -418,6 +425,7 @@ impl CheckCategory {
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8ErrMsg => "flake8-errmsg",
CheckCategory::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
CheckCategory::Flake8ImportConventions => "flake8-import-conventions",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
@@ -451,19 +459,21 @@ impl CheckCategory {
CheckCategory::Flake8Bugbear => vec![CheckCodePrefix::B],
CheckCategory::Flake8Builtins => vec![CheckCodePrefix::A],
CheckCategory::Flake8Comprehensions => vec![CheckCodePrefix::C4],
CheckCategory::Flake8Datetimez => vec![CheckCodePrefix::DTZ],
CheckCategory::Flake8Debugger => vec![CheckCodePrefix::T10],
CheckCategory::Flake8ErrMsg => vec![CheckCodePrefix::EM],
CheckCategory::Flake8ImplicitStrConcat => vec![CheckCodePrefix::ISC],
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
CheckCategory::Flake8Simplify => vec![CheckCodePrefix::SIM],
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::TID],
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
CheckCategory::Flake8Datetimez => vec![CheckCodePrefix::DTZ],
CheckCategory::Isort => vec![CheckCodePrefix::I],
CheckCategory::McCabe => vec![CheckCodePrefix::C90],
CheckCategory::PandasVet => vec![CheckCodePrefix::PD],
CheckCategory::PEP8Naming => vec![CheckCodePrefix::N],
CheckCategory::PandasVet => vec![CheckCodePrefix::PD],
CheckCategory::Pycodestyle => vec![CheckCodePrefix::E, CheckCodePrefix::W],
CheckCategory::Pydocstyle => vec![CheckCodePrefix::D],
CheckCategory::Pyflakes => vec![CheckCodePrefix::F],
@@ -475,7 +485,6 @@ impl CheckCategory {
CheckCodePrefix::PLW,
],
CheckCategory::Pyupgrade => vec![CheckCodePrefix::UP],
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
CheckCategory::Ruff => vec![CheckCodePrefix::RUF],
}
}
@@ -525,6 +534,10 @@ impl CheckCategory {
"https://pypi.org/project/flake8-errmsg/0.4.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8ImplicitStrConcat => Some((
"https://pypi.org/project/flake8-implicit-str-concat/0.3.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8ImportConventions => None,
CheckCategory::Flake8Print => Some((
"https://pypi.org/project/flake8-print/5.0.0/",
@@ -783,6 +796,7 @@ pub enum CheckKind {
// flake8-debugger
Debugger(DebuggerUsingType),
// flake8-tidy-imports
BannedApi { name: String, message: String },
BannedRelativeImport(Strictness),
// flake8-return
UnnecessaryReturnNone,
@@ -793,6 +807,10 @@ pub enum CheckKind {
SuperfluousElseRaise(Branch),
SuperfluousElseContinue(Branch),
SuperfluousElseBreak(Branch),
// flake8-implicit-str-concat
SingleLineImplicitStringConcatenation,
MultiLineImplicitStringConcatenation,
ExplicitStringConcatenation,
// flake8-print
PrintFound,
PPrintFound,
@@ -849,6 +867,7 @@ pub enum CheckKind {
ReplaceUniversalNewlines,
ReplaceStdoutStderr,
RewriteCElementTree,
RewriteUnicodeLiteral,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -988,6 +1007,8 @@ impl CheckCode {
| CheckCode::PGH003
| CheckCode::PGH004 => &LintSource::Lines,
CheckCode::ERA001
| CheckCode::ISC001
| CheckCode::ISC002
| CheckCode::Q000
| CheckCode::Q001
| CheckCode::Q002
@@ -1162,6 +1183,10 @@ impl CheckCode {
// flake8-debugger
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::TID251 => CheckKind::BannedApi {
name: "...".to_string(),
message: "...".to_string(),
},
CheckCode::TID252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-return
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
@@ -1172,6 +1197,10 @@ impl CheckCode {
CheckCode::RET506 => CheckKind::SuperfluousElseRaise(Branch::Else),
CheckCode::RET507 => CheckKind::SuperfluousElseContinue(Branch::Else),
CheckCode::RET508 => CheckKind::SuperfluousElseBreak(Branch::Else),
// flake8-implicit-str-concat
CheckCode::ISC001 => CheckKind::SingleLineImplicitStringConcatenation,
CheckCode::ISC002 => CheckKind::MultiLineImplicitStringConcatenation,
CheckCode::ISC003 => CheckKind::ExplicitStringConcatenation,
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
@@ -1233,6 +1262,7 @@ impl CheckCode {
CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines,
CheckCode::UP022 => CheckKind::ReplaceStdoutStderr,
CheckCode::UP023 => CheckKind::RewriteCElementTree,
CheckCode::UP025 => CheckKind::RewriteUnicodeLiteral,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -1567,8 +1597,10 @@ impl CheckCode {
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
CheckCode::ICN001 => CheckCategory::Flake8ImportConventions,
CheckCode::ISC001 => CheckCategory::Flake8ImplicitStrConcat,
CheckCode::ISC002 => CheckCategory::Flake8ImplicitStrConcat,
CheckCode::ISC003 => CheckCategory::Flake8ImplicitStrConcat,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
CheckCode::N803 => CheckCategory::PEP8Naming,
@@ -1639,6 +1671,8 @@ impl CheckCode {
CheckCode::T100 => CheckCategory::Flake8Debugger,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::TID251 => CheckCategory::Flake8TidyImports,
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
CheckCode::UP001 => CheckCategory::Pyupgrade,
CheckCode::UP003 => CheckCategory::Pyupgrade,
CheckCode::UP004 => CheckCategory::Pyupgrade,
@@ -1661,6 +1695,7 @@ impl CheckCode {
CheckCode::UP021 => CheckCategory::Pyupgrade,
CheckCode::UP022 => CheckCategory::Pyupgrade,
CheckCode::UP023 => CheckCategory::Pyupgrade,
CheckCode::UP025 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
@@ -1812,6 +1847,7 @@ impl CheckKind {
// flake8-debugger
CheckKind::Debugger(..) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedApi { .. } => &CheckCode::TID251,
CheckKind::BannedRelativeImport(..) => &CheckCode::TID252,
// flake8-return
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
@@ -1822,6 +1858,10 @@ impl CheckKind {
CheckKind::SuperfluousElseRaise(..) => &CheckCode::RET506,
CheckKind::SuperfluousElseContinue(..) => &CheckCode::RET507,
CheckKind::SuperfluousElseBreak(..) => &CheckCode::RET508,
// flake8-implicit-str-concat
CheckKind::SingleLineImplicitStringConcatenation => &CheckCode::ISC001,
CheckKind::MultiLineImplicitStringConcatenation => &CheckCode::ISC002,
CheckKind::ExplicitStringConcatenation => &CheckCode::ISC003,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
@@ -1878,6 +1918,7 @@ impl CheckKind {
CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021,
CheckKind::ReplaceStdoutStderr => &CheckCode::UP022,
CheckKind::RewriteCElementTree => &CheckCode::UP023,
CheckKind::RewriteUnicodeLiteral => &CheckCode::UP025,
// pydocstyle
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
@@ -2439,6 +2480,7 @@ impl CheckKind {
DebuggerUsingType::Import(name) => format!("Import for `{name}` found"),
},
// flake8-tidy-imports
CheckKind::BannedApi { name, message } => format!("`{name}` is banned: {message}"),
CheckKind::BannedRelativeImport(strictness) => match strictness {
Strictness::Parents => {
"Relative imports from parent modules are banned".to_string()
@@ -2470,6 +2512,16 @@ impl CheckKind {
CheckKind::SuperfluousElseBreak(branch) => {
format!("Unnecessary `{branch}` after `break` statement")
}
// flake8-implicit-str-concat
CheckKind::SingleLineImplicitStringConcatenation => {
"Implicitly concatenated string literals on one line".to_string()
}
CheckKind::MultiLineImplicitStringConcatenation => {
"Implicitly concatenated string literals over continuation line".to_string()
}
CheckKind::ExplicitStringConcatenation => {
"Explicitly concatenated string should be implicitly concatenated".to_string()
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
@@ -2618,6 +2670,7 @@ impl CheckKind {
CheckKind::RewriteCElementTree => {
"`cElementTree` is deprecated, use `ElementTree`".to_string()
}
CheckKind::RewriteUnicodeLiteral => "Remove unicode literals from strings".to_string(),
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
}
@@ -3066,6 +3119,7 @@ impl CheckKind {
| CheckKind::ReplaceUniversalNewlines
| CheckKind::ReplaceStdoutStderr
| CheckKind::RewriteCElementTree
| CheckKind::RewriteUnicodeLiteral
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
| CheckKind::NoBlankLineBeforeClass(..)

View File

@@ -314,6 +314,12 @@ pub enum CheckCodePrefix {
ICN0,
ICN00,
ICN001,
ISC,
ISC0,
ISC00,
ISC001,
ISC002,
ISC003,
M,
M0,
M001,
@@ -486,6 +492,7 @@ pub enum CheckCodePrefix {
TID,
TID2,
TID25,
TID251,
TID252,
U,
U0,
@@ -535,6 +542,7 @@ pub enum CheckCodePrefix {
UP021,
UP022,
UP023,
UP025,
W,
W2,
W29,
@@ -705,6 +713,7 @@ impl CheckCodePrefix {
CheckCode::C417,
CheckCode::T100,
CheckCode::C901,
CheckCode::TID251,
CheckCode::TID252,
CheckCode::RET501,
CheckCode::RET502,
@@ -714,6 +723,9 @@ impl CheckCodePrefix {
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
CheckCode::ISC001,
CheckCode::ISC002,
CheckCode::ISC003,
CheckCode::T201,
CheckCode::T203,
CheckCode::Q000,
@@ -764,6 +776,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
CheckCode::D100,
CheckCode::D101,
CheckCode::D102,
@@ -1662,7 +1675,7 @@ impl CheckCodePrefix {
":".bold(),
"`I2` has been remapped to `TID2`".bold()
);
vec![CheckCode::TID252]
vec![CheckCode::TID251, CheckCode::TID252]
}
CheckCodePrefix::I25 => {
one_time_warning!(
@@ -1671,7 +1684,7 @@ impl CheckCodePrefix {
":".bold(),
"`I25` has been remapped to `TID25`".bold()
);
vec![CheckCode::TID252]
vec![CheckCode::TID251, CheckCode::TID252]
}
CheckCodePrefix::I252 => {
one_time_warning!(
@@ -1740,6 +1753,12 @@ impl CheckCodePrefix {
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
CheckCodePrefix::ISC => vec![CheckCode::ISC001, CheckCode::ISC002, CheckCode::ISC003],
CheckCodePrefix::ISC0 => vec![CheckCode::ISC001, CheckCode::ISC002, CheckCode::ISC003],
CheckCodePrefix::ISC00 => vec![CheckCode::ISC001, CheckCode::ISC002, CheckCode::ISC003],
CheckCodePrefix::ISC001 => vec![CheckCode::ISC001],
CheckCodePrefix::ISC002 => vec![CheckCode::ISC002],
CheckCodePrefix::ISC003 => vec![CheckCode::ISC003],
CheckCodePrefix::M => {
one_time_warning!(
"{}{} {}",
@@ -2405,9 +2424,10 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T201 => vec![CheckCode::T201],
CheckCodePrefix::T203 => vec![CheckCode::T203],
CheckCodePrefix::TID => vec![CheckCode::TID252],
CheckCodePrefix::TID2 => vec![CheckCode::TID252],
CheckCodePrefix::TID25 => vec![CheckCode::TID252],
CheckCodePrefix::TID => vec![CheckCode::TID251, CheckCode::TID252],
CheckCodePrefix::TID2 => vec![CheckCode::TID251, CheckCode::TID252],
CheckCodePrefix::TID25 => vec![CheckCode::TID251, CheckCode::TID252],
CheckCodePrefix::TID251 => vec![CheckCode::TID251],
CheckCodePrefix::TID252 => vec![CheckCode::TID252],
CheckCodePrefix::U => {
one_time_warning!(
@@ -2439,6 +2459,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
]
}
CheckCodePrefix::U0 => {
@@ -2471,6 +2492,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
]
}
CheckCodePrefix::U00 => {
@@ -2687,6 +2709,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
@@ -2711,6 +2734,7 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
@@ -2757,11 +2781,13 @@ impl CheckCodePrefix {
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::UP025,
],
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
CheckCodePrefix::UP022 => vec![CheckCode::UP022],
CheckCodePrefix::UP023 => vec![CheckCode::UP023],
CheckCodePrefix::UP025 => vec![CheckCode::UP025],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -3107,6 +3133,12 @@ impl CheckCodePrefix {
CheckCodePrefix::ICN0 => SuffixLength::One,
CheckCodePrefix::ICN00 => SuffixLength::Two,
CheckCodePrefix::ICN001 => SuffixLength::Three,
CheckCodePrefix::ISC => SuffixLength::Zero,
CheckCodePrefix::ISC0 => SuffixLength::One,
CheckCodePrefix::ISC00 => SuffixLength::Two,
CheckCodePrefix::ISC001 => SuffixLength::Three,
CheckCodePrefix::ISC002 => SuffixLength::Three,
CheckCodePrefix::ISC003 => SuffixLength::Three,
CheckCodePrefix::M => SuffixLength::Zero,
CheckCodePrefix::M0 => SuffixLength::One,
CheckCodePrefix::M001 => SuffixLength::Three,
@@ -3279,6 +3311,7 @@ impl CheckCodePrefix {
CheckCodePrefix::TID => SuffixLength::Zero,
CheckCodePrefix::TID2 => SuffixLength::One,
CheckCodePrefix::TID25 => SuffixLength::Two,
CheckCodePrefix::TID251 => SuffixLength::Three,
CheckCodePrefix::TID252 => SuffixLength::Three,
CheckCodePrefix::U => SuffixLength::Zero,
CheckCodePrefix::U0 => SuffixLength::One,
@@ -3328,6 +3361,7 @@ impl CheckCodePrefix {
CheckCodePrefix::UP021 => SuffixLength::Three,
CheckCodePrefix::UP022 => SuffixLength::Three,
CheckCodePrefix::UP023 => SuffixLength::Three,
CheckCodePrefix::UP025 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,
@@ -3373,6 +3407,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::ICN,
CheckCodePrefix::ISC,
CheckCodePrefix::N,
CheckCodePrefix::PD,
CheckCodePrefix::PGH,

View File

@@ -57,14 +57,15 @@ pub struct Cli {
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
/// List of error codes to enable.
/// Comma-separated list of error codes to enable (or ALL, to enable all
/// checks).
#[arg(long, value_delimiter = ',')]
pub select: Option<Vec<CheckCodePrefix>>,
/// Like --select, but adds additional error codes on top of the selected
/// ones.
#[arg(long, value_delimiter = ',')]
pub extend_select: Option<Vec<CheckCodePrefix>>,
/// List of error codes to ignore.
/// Comma-separated list of error codes to disable.
#[arg(long, value_delimiter = ',')]
pub ignore: Option<Vec<CheckCodePrefix>>,
/// Like --ignore, but adds additional error codes on top of the ignored
@@ -120,7 +121,7 @@ pub struct Cli {
/// See the settings Ruff will use to check a given Python file.
#[arg(long)]
pub show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
/// Enable automatic additions of `noqa` directives to failing lines.
#[arg(long)]
pub add_noqa: bool,
/// Regular expression matching the name of dummy variables.
@@ -133,7 +134,7 @@ pub struct Cli {
/// formatting.
#[arg(long)]
pub line_length: Option<usize>,
/// Max McCabe complexity allowed for a function.
/// Maximum McCabe complexity allowed for a given function.
#[arg(long)]
pub max_complexity: Option<usize>,
/// Round-trip auto-formatting.

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::ast::{cast, visitor};
use crate::ast::{cast, helpers, visitor};
use crate::checkers::ast::Checker;
use crate::checks::{CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind};
@@ -167,7 +167,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}
@@ -175,7 +175,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}
@@ -309,14 +309,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN206) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
} else if visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
} else if visibility::is_init(stmt) {
@@ -328,7 +328,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
{
let mut check = Check::new(
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
);
if checker.patch(check.kind.code()) {
match fixes::add_return_none_annotation(checker.locator, stmt) {
@@ -343,7 +343,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeSpecialMethod(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
} else {
@@ -352,7 +352,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}
@@ -360,7 +360,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}
}

View File

@@ -5,7 +5,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
@@ -51,7 +51,7 @@ pub struct Options {
pub allow_star_arg_any: Option<bool>,
}
#[derive(Debug, Hash, Default)]
#[derive(Debug, Default, Hash)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub mypy_init_return: bool,
@@ -60,14 +60,24 @@ pub struct Settings {
pub allow_star_arg_any: bool,
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or_default(),
suppress_none_returning: options.suppress_none_returning.unwrap_or_default(),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or_default(),
mypy_init_return: options.mypy_init_return.unwrap_or(false),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or(false),
suppress_none_returning: options.suppress_none_returning.unwrap_or(false),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or(false),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
mypy_init_return: Some(settings.mypy_init_return),
suppress_dummy_args: Some(settings.suppress_dummy_args),
suppress_none_returning: Some(settings.suppress_none_returning),
allow_star_arg_any: Some(settings.allow_star_arg_any),
}
}
}

View File

@@ -6,9 +6,9 @@ expression: checks
MissingReturnTypePublicFunction: bar
location:
row: 29
column: 4
column: 8
end_location:
row: 30
column: 16
row: 29
column: 11
fix: ~

View File

@@ -6,10 +6,10 @@ expression: checks
MissingReturnTypePublicFunction: foo
location:
row: 4
column: 0
column: 4
end_location:
row: 5
column: 8
row: 4
column: 7
fix: ~
- kind:
MissingTypeFunctionArgument: a
@@ -33,10 +33,10 @@ expression: checks
MissingReturnTypePublicFunction: foo
location:
row: 9
column: 0
column: 4
end_location:
row: 10
column: 8
row: 9
column: 7
fix: ~
- kind:
MissingTypeFunctionArgument: b
@@ -60,19 +60,19 @@ expression: checks
MissingReturnTypePublicFunction: foo
location:
row: 19
column: 0
column: 4
end_location:
row: 20
column: 8
row: 19
column: 7
fix: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 24
column: 0
column: 4
end_location:
row: 25
column: 8
row: 24
column: 7
fix: ~
- kind:
DynamicallyTypedExpression: a

View File

@@ -6,10 +6,10 @@ expression: checks
MissingReturnTypeSpecialMethod: __init__
location:
row: 5
column: 4
column: 8
end_location:
row: 6
column: 11
row: 5
column: 16
fix:
content: " -> None"
location:
@@ -22,10 +22,10 @@ expression: checks
MissingReturnTypeSpecialMethod: __init__
location:
row: 11
column: 4
column: 8
end_location:
row: 12
column: 11
row: 11
column: 16
fix:
content: " -> None"
location:
@@ -38,9 +38,9 @@ expression: checks
MissingReturnTypePrivateFunction: __init__
location:
row: 40
column: 0
column: 4
end_location:
row: 41
column: 7
row: 40
column: 12
fix: ~

View File

@@ -6,18 +6,18 @@ expression: checks
MissingReturnTypePublicFunction: foo
location:
row: 45
column: 0
column: 4
end_location:
row: 46
column: 15
row: 45
column: 7
fix: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 50
column: 0
column: 4
end_location:
row: 55
column: 14
row: 50
column: 7
fix: ~

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::helpers;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -17,6 +17,6 @@ pub fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
};
checker.add_check(Check::new(
CheckKind::FStringDocstring,
Range::from_located(stmt),
helpers::identifier_range(stmt, checker.locator),
));
}

View File

@@ -5,7 +5,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
@@ -26,15 +26,23 @@ pub struct Options {
pub extend_immutable_calls: Option<Vec<String>>,
}
#[derive(Debug, Hash, Default)]
#[derive(Debug, Default, Hash)]
pub struct Settings {
pub extend_immutable_calls: Vec<String>,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
extend_immutable_calls: options.extend_immutable_calls.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
extend_immutable_calls: Some(settings.extend_immutable_calls),
}
}
}

View File

@@ -27,11 +27,18 @@ pub struct Settings {
pub max_string_length: usize,
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
max_string_length: options.max_string_length.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
max_string_length: Some(settings.max_string_length),
}
}
}

View File

@@ -0,0 +1,73 @@
use itertools::Itertools;
use rustpython_ast::{Constant, Expr, ExprKind, Location, Operator};
use rustpython_parser::lexer::{LexResult, Tok};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::source_code_locator::SourceCodeLocator;
/// ISC001, ISC002
pub fn implicit(tokens: &[LexResult], locator: &SourceCodeLocator) -> Vec<Check> {
let mut checks = vec![];
for ((a_start, a_tok, a_end), (b_start, b_tok, b_end)) in
tokens.iter().flatten().tuple_windows()
{
if matches!(a_tok, Tok::String { .. }) && matches!(b_tok, Tok::String { .. }) {
if a_end.row() == b_start.row() {
checks.push(Check::new(
CheckKind::SingleLineImplicitStringConcatenation,
Range {
location: *a_start,
end_location: *b_end,
},
));
} else {
// TODO(charlie): The RustPython tokenization doesn't differentiate between
// continuations and newlines, so we have to detect them manually.
let contents = locator.slice_source_code_range(&Range {
location: *a_end,
end_location: Location::new(a_end.row() + 1, 0),
});
if contents.trim().ends_with('\\') {
checks.push(Check::new(
CheckKind::MultiLineImplicitStringConcatenation,
Range {
location: *a_start,
end_location: *b_end,
},
));
}
}
}
}
checks
}
/// ISC003
pub fn explicit(expr: &Expr) -> Option<Check> {
if let ExprKind::BinOp { left, op, right } = &expr.node {
if matches!(op, Operator::Add) {
if matches!(
left.node,
ExprKind::JoinedStr { .. }
| ExprKind::Constant {
value: Constant::Str(..) | Constant::Bytes(..),
..
}
) && matches!(
right.node,
ExprKind::JoinedStr { .. }
| ExprKind::Constant {
value: Constant::Str(..) | Constant::Bytes(..),
..
}
) {
return Some(Check::new(
CheckKind::ExplicitStringConcatenation,
Range::from_located(expr),
));
}
}
}
None
}

View File

@@ -0,0 +1,30 @@
pub mod checks;
#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::settings;
#[test_case(CheckCode::ISC001, Path::new("ISC.py"); "ISC001")]
#[test_case(CheckCode::ISC002, Path::new("ISC.py"); "ISC002")]
#[test_case(CheckCode::ISC003, Path::new("ISC.py"); "ISC003")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_implicit_str_concat")
.join(path)
.as_path(),
&settings::Settings::for_rule(check_code),
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@@ -0,0 +1,21 @@
---
source: src/flake8_implicit_str_concat/mod.rs
expression: checks
---
- kind: SingleLineImplicitStringConcatenation
location:
row: 1
column: 4
end_location:
row: 1
column: 11
fix: ~
- kind: SingleLineImplicitStringConcatenation
location:
row: 1
column: 8
end_location:
row: 1
column: 15
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/flake8_implicit_str_concat/mod.rs
expression: checks
---
- kind: MultiLineImplicitStringConcatenation
location:
row: 5
column: 4
end_location:
row: 6
column: 9
fix: ~

View File

@@ -0,0 +1,37 @@
---
source: src/flake8_implicit_str_concat/mod.rs
expression: checks
---
- kind: ExplicitStringConcatenation
location:
row: 3
column: 4
end_location:
row: 3
column: 17
fix: ~
- kind: ExplicitStringConcatenation
location:
row: 9
column: 2
end_location:
row: 10
column: 7
fix: ~
- kind: ExplicitStringConcatenation
location:
row: 14
column: 2
end_location:
row: 15
column: 7
fix: ~
- kind: ExplicitStringConcatenation
location:
row: 19
column: 2
end_location:
row: 20
column: 8
fix: ~

View File

@@ -29,16 +29,14 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/custom.py"),
&Settings {
flake8_import_conventions:
flake8_import_conventions::settings::Settings::from_options(
flake8_import_conventions::settings::Options {
aliases: None,
extend_aliases: Some(FxHashMap::from_iter([
("dask.array".to_string(), "da".to_string()),
("dask.dataframe".to_string(), "dd".to_string()),
])),
},
),
flake8_import_conventions: flake8_import_conventions::settings::Options {
aliases: None,
extend_aliases: Some(FxHashMap::from_iter([
("dask.array".to_string(), "da".to_string()),
("dask.dataframe".to_string(), "dd".to_string()),
])),
}
.into(),
..Settings::for_rule(CheckCode::ICN001)
},
)?;
@@ -52,18 +50,16 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/remove_default.py"),
&Settings {
flake8_import_conventions:
flake8_import_conventions::settings::Settings::from_options(
flake8_import_conventions::settings::Options {
aliases: Some(FxHashMap::from_iter([
("altair".to_string(), "alt".to_string()),
("matplotlib.pyplot".to_string(), "plt".to_string()),
("pandas".to_string(), "pd".to_string()),
("seaborn".to_string(), "sns".to_string()),
])),
extend_aliases: None,
},
),
flake8_import_conventions: flake8_import_conventions::settings::Options {
aliases: Some(FxHashMap::from_iter([
("altair".to_string(), "alt".to_string()),
("matplotlib.pyplot".to_string(), "plt".to_string()),
("pandas".to_string(), "pd".to_string()),
("seaborn".to_string(), "sns".to_string()),
])),
extend_aliases: None,
}
.into(),
..Settings::for_rule(CheckCode::ICN001)
},
)?;
@@ -77,16 +73,14 @@ mod tests {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/override_default.py"),
&Settings {
flake8_import_conventions:
flake8_import_conventions::settings::Settings::from_options(
flake8_import_conventions::settings::Options {
aliases: None,
extend_aliases: Some(FxHashMap::from_iter([(
"numpy".to_string(),
"nmp".to_string(),
)])),
},
),
flake8_import_conventions: flake8_import_conventions::settings::Options {
aliases: None,
extend_aliases: Some(FxHashMap::from_iter([(
"numpy".to_string(),
"nmp".to_string(),
)])),
}
.into(),
..Settings::for_rule(CheckCode::ICN001)
},
)?;

View File

@@ -84,14 +84,6 @@ fn resolve_aliases(options: Options) -> FxHashMap<String, String> {
aliases
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
aliases: resolve_aliases(options),
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
@@ -99,3 +91,20 @@ impl Default for Settings {
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
aliases: resolve_aliases(options),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
aliases: Some(settings.aliases),
extend_aliases: None,
}
}
}

View File

@@ -13,6 +13,12 @@ pub enum Quote {
Double,
}
impl Default for Quote {
fn default() -> Self {
Self::Double
}
}
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
@@ -74,24 +80,35 @@ pub struct Settings {
pub avoid_escape: bool,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
impl Default for Settings {
fn default() -> Self {
Self {
inline_quotes: options.inline_quotes.unwrap_or(Quote::Double),
multiline_quotes: options.multiline_quotes.unwrap_or(Quote::Double),
docstring_quotes: options.docstring_quotes.unwrap_or(Quote::Double),
inline_quotes: Quote::default(),
multiline_quotes: Quote::default(),
docstring_quotes: Quote::default(),
avoid_escape: true,
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
inline_quotes: options.inline_quotes.unwrap_or_default(),
multiline_quotes: options.multiline_quotes.unwrap_or_default(),
docstring_quotes: options.docstring_quotes.unwrap_or_default(),
avoid_escape: options.avoid_escape.unwrap_or(true),
}
}
}
impl Default for Settings {
fn default() -> Self {
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,
inline_quotes: Some(settings.inline_quotes),
multiline_quotes: Some(settings.multiline_quotes),
docstring_quotes: Some(settings.docstring_quotes),
avoid_escape: Some(settings.avoid_escape),
}
}
}

View File

@@ -1,9 +1,14 @@
use rustpython_ast::Stmt;
use rustc_hash::FxHashMap;
use rustpython_ast::{Alias, Expr, Located, Stmt};
use super::settings::BannedApi;
use crate::ast::helpers::match_call_path;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::flake8_tidy_imports::settings::Strictness;
/// TID252
pub fn banned_relative_import(
stmt: &Stmt,
level: Option<&usize>,
@@ -22,3 +27,71 @@ pub fn banned_relative_import(
None
}
}
/// TID251
pub fn name_is_banned(
module: &str,
name: &Alias,
banned_apis: &FxHashMap<String, BannedApi>,
) -> Option<Check> {
let full_name = format!("{module}.{}", &name.node.name);
if let Some(ban) = banned_apis.get(&full_name) {
return Some(Check::new(
CheckKind::BannedApi {
name: full_name,
message: ban.msg.to_string(),
},
Range::from_located(name),
));
}
None
}
/// TID251
pub fn name_or_parent_is_banned<T>(
located: &Located<T>,
name: &str,
banned_apis: &FxHashMap<String, BannedApi>,
) -> Option<Check> {
let mut name = name;
loop {
if let Some(ban) = banned_apis.get(name) {
return Some(Check::new(
CheckKind::BannedApi {
name: name.to_string(),
message: ban.msg.to_string(),
},
Range::from_located(located),
));
}
match name.rfind('.') {
Some(idx) => {
name = &name[..idx];
}
None => return None,
}
}
}
/// TID251
pub fn banned_attribute_access(
checker: &mut Checker,
call_path: &[&str],
expr: &Expr,
banned_apis: &FxHashMap<String, BannedApi>,
) {
for (banned_path, ban) in banned_apis {
if let Some((module, member)) = banned_path.rsplit_once('.') {
if match_call_path(call_path, module, member, &checker.from_imports) {
checker.add_check(Check::new(
CheckKind::BannedApi {
name: banned_path.to_string(),
message: ban.msg.to_string(),
},
Range::from_located(expr),
));
return;
}
}
}
}

View File

@@ -6,9 +6,10 @@ mod tests {
use std::path::Path;
use anyhow::Result;
use rustc_hash::FxHashMap;
use crate::checks::CheckCode;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::flake8_tidy_imports::settings::{BannedApi, Strictness};
use crate::linter::test_path;
use crate::{flake8_tidy_imports, Settings};
@@ -19,6 +20,7 @@ mod tests {
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
ban_relative_imports: Strictness::Parents,
..Default::default()
},
..Settings::for_rules(vec![CheckCode::TID252])
},
@@ -35,6 +37,7 @@ mod tests {
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
ban_relative_imports: Strictness::All,
..Default::default()
},
..Settings::for_rules(vec![CheckCode::TID252])
},
@@ -43,4 +46,56 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn banned_api_true_positives() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_tidy_imports/TID251.py"),
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
banned_api: FxHashMap::from_iter([
(
"cgi".to_string(),
BannedApi {
msg: "The cgi module is deprecated.".to_string(),
},
),
(
"typing.TypedDict".to_string(),
BannedApi {
msg: "Use typing_extensions.TypedDict instead.".to_string(),
},
),
]),
..Default::default()
},
..Settings::for_rules(vec![CheckCode::TID251])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn banned_api_false_positives() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_tidy_imports/TID251_false_positives.py"),
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
banned_api: FxHashMap::from_iter([(
"typing.TypedDict".to_string(),
BannedApi {
msg: "Use typing_extensions.TypedDict instead.".to_string(),
},
)]),
..Default::default()
},
..Settings::for_rules(vec![CheckCode::TID251])
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -1,6 +1,10 @@
//! Settings for the `flake8-tidy-imports` plugin.
use std::hash::{Hash, Hasher};
use itertools::Itertools;
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -13,6 +17,13 @@ pub enum Strictness {
All,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct BannedApi {
/// The message to display when the API is used.
pub msg: String,
}
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
@@ -33,25 +44,60 @@ pub struct Options {
/// Whether to ban all relative imports (`"all"`), or only those imports
/// that extend into the parent module or beyond (`"parents"`).
pub ban_relative_imports: Option<Strictness>,
#[option(
default = r#"{}"#,
value_type = "HashMap<String, BannedApi>",
example = r#"
[tool.ruff.flake8-tidy-imports.banned-api]
"cgi".msg = "The cgi module is deprecated, see https://peps.python.org/pep-0594/#cgi."
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
"#
)]
/// Specific modules or module members that may not be imported or accessed.
/// Note that this check is only meant to flag accidental uses,
/// and can be circumvented via `eval` or `importlib`.
pub banned_api: Option<FxHashMap<String, BannedApi>>,
}
#[derive(Debug, Hash)]
#[derive(Debug)]
pub struct Settings {
pub ban_relative_imports: Strictness,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents),
}
}
pub banned_api: FxHashMap<String, BannedApi>,
}
impl Default for Settings {
fn default() -> Self {
Self {
ban_relative_imports: Strictness::Parents,
banned_api: FxHashMap::default(),
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents),
banned_api: options.banned_api.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
ban_relative_imports: Some(settings.ban_relative_imports),
banned_api: Some(settings.banned_api),
}
}
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ban_relative_imports.hash(state);
for key in self.banned_api.keys().sorted() {
key.hash(state);
self.banned_api[key].hash(state);
}
}
}

View File

@@ -0,0 +1,38 @@
---
source: src/flake8_tidy_imports/mod.rs
expression: checks
---
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 2
column: 7
end_location:
row: 2
column: 23
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 7
column: 0
end_location:
row: 7
column: 16
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 11
column: 4
end_location:
row: 11
column: 20
fix: ~

View File

@@ -0,0 +1,137 @@
---
source: src/flake8_tidy_imports/mod.rs
expression: checks
---
- kind:
BannedApi:
name: cgi
message: The cgi module is deprecated.
location:
row: 2
column: 7
end_location:
row: 2
column: 10
fix: ~
- kind:
BannedApi:
name: cgi
message: The cgi module is deprecated.
location:
row: 4
column: 0
end_location:
row: 4
column: 17
fix: ~
- kind:
BannedApi:
name: cgi
message: The cgi module is deprecated.
location:
row: 6
column: 0
end_location:
row: 6
column: 23
fix: ~
- kind:
BannedApi:
name: cgi
message: The cgi module is deprecated.
location:
row: 9
column: 7
end_location:
row: 9
column: 18
fix: ~
- kind:
BannedApi:
name: cgi
message: The cgi module is deprecated.
location:
row: 11
column: 0
end_location:
row: 11
column: 23
fix: ~
- kind:
BannedApi:
name: cgi
message: The cgi module is deprecated.
location:
row: 13
column: 0
end_location:
row: 13
column: 25
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 17
column: 19
end_location:
row: 17
column: 28
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 22
column: 0
end_location:
row: 22
column: 16
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 24
column: 0
end_location:
row: 24
column: 16
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 27
column: 0
end_location:
row: 27
column: 16
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 29
column: 0
end_location:
row: 29
column: 16
fix: ~
- kind:
BannedApi:
name: typing.TypedDict
message: Use typing_extensions.TypedDict instead.
location:
row: 33
column: 0
end_location:
row: 33
column: 28
fix: ~

View File

@@ -22,16 +22,23 @@ pub struct Options {
pub ignore_variadic_names: Option<bool>,
}
#[derive(Debug, Hash, Default)]
#[derive(Debug, Default, Hash)]
pub struct Settings {
pub ignore_variadic_names: bool,
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
ignore_variadic_names: options.ignore_variadic_names.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
ignore_variadic_names: Some(settings.ignore_variadic_names),
}
}
}

View File

@@ -123,8 +123,23 @@ pub struct Settings {
pub extra_standard_library: BTreeSet<String>,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
impl Default for Settings {
fn default() -> Self {
Self {
combine_as_imports: false,
force_wrap_aliases: false,
split_on_trailing_comma: true,
force_single_line: false,
single_line_exclusions: BTreeSet::new(),
known_first_party: BTreeSet::new(),
known_third_party: BTreeSet::new(),
extra_standard_library: BTreeSet::new(),
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
combine_as_imports: options.combine_as_imports.unwrap_or(false),
force_wrap_aliases: options.force_wrap_aliases.unwrap_or(false),
@@ -142,17 +157,17 @@ impl Settings {
}
}
impl Default for Settings {
fn default() -> Self {
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
combine_as_imports: false,
force_wrap_aliases: false,
split_on_trailing_comma: true,
force_single_line: false,
single_line_exclusions: BTreeSet::new(),
known_first_party: BTreeSet::new(),
known_third_party: BTreeSet::new(),
extra_standard_library: BTreeSet::new(),
combine_as_imports: Some(settings.combine_as_imports),
force_wrap_aliases: Some(settings.force_wrap_aliases),
split_on_trailing_comma: Some(settings.split_on_trailing_comma),
force_single_line: Some(settings.force_single_line),
single_line_exclusions: Some(settings.single_line_exclusions.into_iter().collect()),
known_first_party: Some(settings.known_first_party.into_iter().collect()),
known_third_party: Some(settings.known_third_party.into_iter().collect()),
extra_standard_library: Some(settings.extra_standard_library.into_iter().collect()),
}
}
}

View File

@@ -39,6 +39,7 @@ mod flake8_comprehensions;
mod flake8_datetimez;
mod flake8_debugger;
pub mod flake8_errmsg;
mod flake8_implicit_str_concat;
mod flake8_import_conventions;
mod flake8_print;
pub mod flake8_quotes;

View File

@@ -7,14 +7,20 @@ use wasm_bindgen::prelude::*;
use crate::autofix::Fix;
use crate::checks::CheckCode;
use crate::directives;
use crate::checks_gen::CheckCodePrefix;
use crate::linter::check_path;
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
use crate::settings::options::Options;
use crate::settings::types::PythonVersion;
use crate::settings::{flags, Settings};
use crate::source_code_locator::SourceCodeLocator;
use crate::source_code_style::SourceCodeStyleDetector;
use crate::{
directives, flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming,
pydocstyle, pyupgrade,
};
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -62,11 +68,65 @@ pub fn run() {
}
#[wasm_bindgen]
pub fn current_version() -> JsValue {
#[allow(non_snake_case)]
pub fn currentVersion() -> JsValue {
JsValue::from(VERSION)
}
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn defaultSettings() -> Result<JsValue, JsValue> {
Ok(serde_wasm_bindgen::to_value(&Options {
// Propagate defaults.
allowed_confusables: Some(Vec::default()),
dummy_variable_rgx: Some("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$".to_string()),
extend_ignore: Some(Vec::default()),
extend_select: Some(Vec::default()),
external: Some(Vec::default()),
ignore: Some(Vec::default()),
line_length: Some(88),
select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]),
target_version: Some(PythonVersion::default()),
// Ignore a bunch of options that don't make sense in a single-file editor.
cache_dir: None,
exclude: None,
extend: None,
extend_exclude: None,
fix: None,
fix_only: None,
fixable: None,
force_exclude: None,
format: None,
ignore_init_module_imports: None,
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
show_source: None,
src: None,
unfixable: None,
update_check: None,
// Use default options for all plugins.
flake8_annotations: Some(flake8_annotations::settings::Settings::default().into()),
flake8_bugbear: Some(flake8_bugbear::settings::Settings::default().into()),
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Settings::default().into()),
flake8_import_conventions: Some(
flake8_import_conventions::settings::Settings::default().into(),
),
flake8_unused_arguments: Some(
flake8_unused_arguments::settings::Settings::default().into(),
),
isort: Some(isort::settings::Settings::default().into()),
mccabe: Some(mccabe::settings::Settings::default().into()),
pep8_naming: Some(pep8_naming::settings::Settings::default().into()),
pydocstyle: Some(pydocstyle::settings::Settings::default().into()),
pyupgrade: Some(pyupgrade::settings::Settings::default().into()),
})?)
}
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
let options: Options = serde_wasm_bindgen::from_value(options).map_err(|e| e.to_string())?;
let configuration =

View File

@@ -30,16 +30,24 @@ pub struct Settings {
pub max_complexity: usize,
}
impl Settings {
pub fn from_options(options: &Options) -> Self {
impl Default for Settings {
fn default() -> Self {
Self { max_complexity: 10 }
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
max_complexity: options.max_complexity.unwrap_or_default(),
}
}
}
impl Default for Settings {
fn default() -> Self {
Self { max_complexity: 10 }
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
max_complexity: Some(settings.max_complexity),
}
}
}

View File

@@ -76,8 +76,18 @@ pub struct Settings {
pub staticmethod_decorators: Vec<String>,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
impl Default for Settings {
fn default() -> Self {
Self {
ignore_names: IGNORE_NAMES.map(String::from).to_vec(),
classmethod_decorators: CLASSMETHOD_DECORATORS.map(String::from).to_vec(),
staticmethod_decorators: STATICMETHOD_DECORATORS.map(String::from).to_vec(),
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
ignore_names: options
.ignore_names
@@ -92,12 +102,12 @@ impl Settings {
}
}
impl Default for Settings {
fn default() -> Self {
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
ignore_names: IGNORE_NAMES.map(String::from).to_vec(),
classmethod_decorators: CLASSMETHOD_DECORATORS.map(String::from).to_vec(),
staticmethod_decorators: STATICMETHOD_DECORATORS.map(String::from).to_vec(),
ignore_names: Some(settings.ignore_names),
classmethod_decorators: Some(settings.classmethod_decorators),
staticmethod_decorators: Some(settings.staticmethod_decorators),
}
}
}

View File

@@ -118,11 +118,14 @@ pub fn ambiguous_variable_name<T>(name: &str, located: &Located<T>) -> Option<Ch
}
/// E742
pub fn ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
pub fn ambiguous_class_name<F>(name: &str, locate: F) -> Option<Check>
where
F: FnOnce() -> Range,
{
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousClassName(name.to_string()),
location,
locate(),
))
} else {
None
@@ -130,11 +133,14 @@ pub fn ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
}
/// E743
pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
pub fn ambiguous_function_name<F>(name: &str, locate: F) -> Option<Check>
where
F: FnOnce() -> Range,
{
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousFunctionName(name.to_string()),
location,
locate(),
))
} else {
None

View File

@@ -6,27 +6,27 @@ expression: checks
AmbiguousClassName: l
location:
row: 1
column: 0
column: 6
end_location:
row: 2
column: 8
row: 1
column: 7
fix: ~
- kind:
AmbiguousClassName: I
location:
row: 5
column: 0
column: 6
end_location:
row: 6
column: 8
row: 5
column: 7
fix: ~
- kind:
AmbiguousClassName: O
location:
row: 9
column: 0
column: 6
end_location:
row: 10
column: 8
row: 9
column: 7
fix: ~

View File

@@ -6,27 +6,27 @@ expression: checks
AmbiguousFunctionName: l
location:
row: 1
column: 0
column: 4
end_location:
row: 2
column: 8
row: 1
column: 5
fix: ~
- kind:
AmbiguousFunctionName: I
location:
row: 5
column: 0
column: 4
end_location:
row: 6
column: 8
row: 5
column: 5
fix: ~
- kind:
AmbiguousFunctionName: O
location:
row: 10
column: 4
column: 8
end_location:
row: 11
column: 12
row: 10
column: 9
fix: ~

View File

@@ -4,6 +4,7 @@ use regex::Regex;
use rustc_hash::FxHashSet;
use rustpython_ast::{Location, StmtKind};
use crate::ast::helpers::identifier_range;
use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::ast::{cast, whitespace};
@@ -57,7 +58,7 @@ pub fn not_missing(
if checker.settings.enabled.contains(&CheckCode::D101) {
checker.add_check(Check::new(
CheckKind::PublicClass,
Range::from_located(stmt),
identifier_range(stmt, checker.locator),
));
}
false
@@ -66,7 +67,7 @@ pub fn not_missing(
if checker.settings.enabled.contains(&CheckCode::D106) {
checker.add_check(Check::new(
CheckKind::PublicNestedClass,
Range::from_located(stmt),
identifier_range(stmt, checker.locator),
));
}
false
@@ -78,7 +79,7 @@ pub fn not_missing(
if checker.settings.enabled.contains(&CheckCode::D103) {
checker.add_check(Check::new(
CheckKind::PublicFunction,
Range::from_located(stmt),
identifier_range(stmt, checker.locator),
));
}
false
@@ -93,20 +94,23 @@ pub fn not_missing(
if checker.settings.enabled.contains(&CheckCode::D105) {
checker.add_check(Check::new(
CheckKind::MagicMethod,
Range::from_located(stmt),
identifier_range(stmt, checker.locator),
));
}
true
} else if is_init(stmt) {
if checker.settings.enabled.contains(&CheckCode::D107) {
checker.add_check(Check::new(CheckKind::PublicInit, Range::from_located(stmt)));
checker.add_check(Check::new(
CheckKind::PublicInit,
identifier_range(stmt, checker.locator),
));
}
true
} else {
if checker.settings.enabled.contains(&CheckCode::D102) {
checker.add_check(Check::new(
CheckKind::PublicMethod,
Range::from_located(stmt),
identifier_range(stmt, checker.locator),
));
}
true
@@ -835,7 +839,7 @@ pub fn if_needed(checker: &mut Checker, docstring: &Docstring) {
}
checker.add_check(Check::new(
CheckKind::SkipDocstring,
Range::from_located(stmt),
identifier_range(stmt, checker.locator),
));
}

View File

@@ -4,7 +4,7 @@ use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Convention {
/// Use Google-style docstrings.
@@ -37,10 +37,18 @@ pub struct Settings {
pub convention: Option<Convention>,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
convention: options.convention,
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
convention: settings.convention,
}
}
}

View File

@@ -5,9 +5,9 @@ expression: checks
- kind: PublicClass
location:
row: 15
column: 0
column: 6
end_location:
row: 69
row: 15
column: 12
fix: ~

View File

@@ -5,25 +5,25 @@ expression: checks
- kind: PublicMethod
location:
row: 23
column: 4
column: 8
end_location:
row: 24
column: 12
row: 23
column: 14
fix: ~
- kind: PublicMethod
location:
row: 56
column: 4
column: 8
end_location:
row: 57
column: 12
row: 56
column: 15
fix: ~
- kind: PublicMethod
location:
row: 68
column: 4
column: 8
end_location:
row: 69
column: 12
row: 68
column: 16
fix: ~

View File

@@ -5,9 +5,9 @@ expression: checks
- kind: PublicFunction
location:
row: 400
column: 0
column: 4
end_location:
row: 400
column: 27
column: 17
fix: ~

View File

@@ -5,9 +5,9 @@ expression: checks
- kind: MagicMethod
location:
row: 64
column: 4
column: 8
end_location:
row: 65
column: 12
row: 64
column: 15
fix: ~

View File

@@ -5,17 +5,17 @@ expression: checks
- kind: PublicInit
location:
row: 60
column: 4
column: 8
end_location:
row: 61
column: 12
row: 60
column: 16
fix: ~
- kind: PublicInit
location:
row: 534
column: 4
column: 8
end_location:
row: 535
column: 12
row: 534
column: 16
fix: ~

View File

@@ -5,25 +5,25 @@ expression: checks
- kind: SkipDocstring
location:
row: 34
column: 4
column: 8
end_location:
row: 36
column: 11
row: 34
column: 25
fix: ~
- kind: SkipDocstring
location:
row: 90
column: 4
column: 8
end_location:
row: 92
column: 11
row: 90
column: 30
fix: ~
- kind: SkipDocstring
location:
row: 110
column: 0
column: 4
end_location:
row: 112
column: 7
row: 110
column: 19
fix: ~

View File

@@ -42,6 +42,7 @@ mod tests {
#[test_case(CheckCode::UP021, Path::new("UP021.py"); "UP021")]
#[test_case(CheckCode::UP022, Path::new("UP022.py"); "UP022")]
#[test_case(CheckCode::UP023, Path::new("UP023.py"); "UP023")]
#[test_case(CheckCode::UP025, Path::new("UP025.py"); "UP025")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(

View File

@@ -9,6 +9,7 @@ pub use remove_six_compat::remove_six_compat;
pub use replace_stdout_stderr::replace_stdout_stderr;
pub use replace_universal_newlines::replace_universal_newlines;
pub use rewrite_c_element_tree::replace_c_element_tree;
pub use rewrite_unicode_literal::rewrite_unicode_literal;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use typing_text_str_alias::typing_text_str_alias;
@@ -31,6 +32,7 @@ mod remove_six_compat;
mod replace_stdout_stderr;
mod replace_universal_newlines;
mod rewrite_c_element_tree;
mod rewrite_unicode_literal;
mod super_call_with_parameters;
mod type_of_primitive;
mod typing_text_str_alias;

View File

@@ -0,0 +1,48 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::pydocstyle::helpers::leading_quote;
/// Strip any leading kind prefixes (e..g. "u") from a quote string.
fn strip_kind(leading_quote: &str) -> &str {
if let Some(index) = leading_quote.find('\'') {
&leading_quote[index..]
} else if let Some(index) = leading_quote.find('\"') {
&leading_quote[index..]
} else {
unreachable!("Expected docstring to start with a valid triple- or single-quote prefix")
}
}
pub fn rewrite_unicode_literal(
checker: &mut Checker,
expr: &Expr,
value: &str,
kind: &Option<String>,
) {
if let Some(const_kind) = kind {
if const_kind.to_lowercase() == "u" {
let mut check = Check::new(CheckKind::RewriteUnicodeLiteral, Range::from_located(expr));
if checker.patch(check.kind.code()) {
let content = checker
.locator
.slice_source_code_range(&Range::from_located(expr));
if let Some(leading_quote) = leading_quote(&content).map(strip_kind) {
let mut contents = String::with_capacity(value.len() + leading_quote.len() * 2);
contents.push_str(leading_quote);
contents.push_str(value);
contents.push_str(leading_quote);
check.amend(Fix::replacement(
contents,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
}
}

View File

@@ -29,15 +29,23 @@ pub struct Options {
pub keep_runtime_typing: Option<bool>,
}
#[derive(Debug, Hash, Default)]
#[derive(Debug, Default, Hash)]
pub struct Settings {
pub keep_runtime_typing: bool,
}
impl Settings {
pub fn from_options(options: &Options) -> Self {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
keep_runtime_typing: options.keep_runtime_typing.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
keep_runtime_typing: Some(settings.keep_runtime_typing),
}
}
}

View File

@@ -0,0 +1,185 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind: RewriteUnicodeLiteral
location:
row: 2
column: 4
end_location:
row: 2
column: 12
fix:
content: "\"Hello\""
location:
row: 2
column: 4
end_location:
row: 2
column: 12
- kind: RewriteUnicodeLiteral
location:
row: 4
column: 0
end_location:
row: 4
column: 8
fix:
content: "'world'"
location:
row: 4
column: 0
end_location:
row: 4
column: 8
- kind: RewriteUnicodeLiteral
location:
row: 6
column: 6
end_location:
row: 6
column: 14
fix:
content: "\"Hello\""
location:
row: 6
column: 6
end_location:
row: 6
column: 14
- kind: RewriteUnicodeLiteral
location:
row: 8
column: 6
end_location:
row: 8
column: 14
fix:
content: "'world'"
location:
row: 8
column: 6
end_location:
row: 8
column: 14
- kind: RewriteUnicodeLiteral
location:
row: 12
column: 4
end_location:
row: 12
column: 12
fix:
content: "\"Hello\""
location:
row: 12
column: 4
end_location:
row: 12
column: 12
- kind: RewriteUnicodeLiteral
location:
row: 12
column: 14
end_location:
row: 12
column: 22
fix:
content: "\"world\""
location:
row: 12
column: 14
end_location:
row: 12
column: 22
- kind: RewriteUnicodeLiteral
location:
row: 12
column: 26
end_location:
row: 12
column: 34
fix:
content: "\"Hello\""
location:
row: 12
column: 26
end_location:
row: 12
column: 34
- kind: RewriteUnicodeLiteral
location:
row: 12
column: 38
end_location:
row: 12
column: 46
fix:
content: "\"world\""
location:
row: 12
column: 38
end_location:
row: 12
column: 46
- kind: RewriteUnicodeLiteral
location:
row: 16
column: 4
end_location:
row: 16
column: 12
fix:
content: "'hello'"
location:
row: 16
column: 4
end_location:
row: 16
column: 12
- kind: RewriteUnicodeLiteral
location:
row: 17
column: 4
end_location:
row: 17
column: 16
fix:
content: "\"\"\"hello\"\"\""
location:
row: 17
column: 4
end_location:
row: 17
column: 16
- kind: RewriteUnicodeLiteral
location:
row: 18
column: 4
end_location:
row: 18
column: 16
fix:
content: "'''hello'''"
location:
row: 18
column: 4
end_location:
row: 18
column: 16
- kind: RewriteUnicodeLiteral
location:
row: 19
column: 4
end_location:
row: 19
column: 20
fix:
content: "'Hello \"World\"'"
location:
row: 19
column: 4
end_location:
row: 19
column: 20

View File

@@ -153,58 +153,56 @@ impl Settings {
src: config
.src
.unwrap_or_else(|| vec![project_root.to_path_buf()]),
target_version: config.target_version.unwrap_or(PythonVersion::Py310),
target_version: config.target_version.unwrap_or_default(),
update_check: config.update_check.unwrap_or(true),
// Plugins
flake8_annotations: config
.flake8_annotations
.map(flake8_annotations::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
flake8_bugbear: config
.flake8_bugbear
.map(flake8_bugbear::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
flake8_errmsg: config
.flake8_errmsg
.map(flake8_errmsg::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
flake8_import_conventions: config
.flake8_import_conventions
.map(flake8_import_conventions::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
flake8_quotes: config
.flake8_quotes
.map(flake8_quotes::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
flake8_tidy_imports: config
.flake8_tidy_imports
.map(flake8_tidy_imports::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
flake8_unused_arguments: config
.flake8_unused_arguments
.map(flake8_unused_arguments::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
isort: config
.isort
.map(isort::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
mccabe: config
.mccabe
.as_ref()
.map(mccabe::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
pep8_naming: config
.pep8_naming
.map(pep8_naming::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
pydocstyle: config
.pydocstyle
.map(pydocstyle::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
pyupgrade: config
.pyupgrade
.as_ref()
.map(pyupgrade::settings::Settings::from_options)
.map(std::convert::Into::into)
.unwrap_or_default(),
})
}

View File

@@ -131,7 +131,7 @@ mod tests {
use crate::checks_gen::CheckCodePrefix;
use crate::flake8_quotes::settings::Quote;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::flake8_tidy_imports::settings::{BannedApi, Strictness};
use crate::settings::pyproject::{
find_settings_toml, parse_pyproject_toml, Options, Pyproject, Tools,
};
@@ -514,7 +514,21 @@ other-attribute = 1
avoid_escape: Some(true),
}),
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Options {
ban_relative_imports: Some(Strictness::Parents)
ban_relative_imports: Some(Strictness::Parents),
banned_api: Some(FxHashMap::from_iter([
(
"cgi".to_string(),
BannedApi {
msg: "The cgi module is deprecated.".to_string()
}
),
(
"typing.TypedDict".to_string(),
BannedApi {
msg: "Use typing_extensions.TypedDict instead.".to_string()
}
)
]))
}),
flake8_import_conventions: Some(flake8_import_conventions::settings::Options {
aliases: Some(FxHashMap::from_iter([(

View File

@@ -31,6 +31,12 @@ pub enum PythonVersion {
Py311,
}
impl Default for PythonVersion {
fn default() -> Self {
Self::Py310
}
}
impl FromStr for PythonVersion {
type Err = anyhow::Error;