mirror of https://github.com/astral-sh/ruff
Compile for WASM
- wasm-pack build --target web - rm -rf demo/src/pkg && cp -r pkg demo/src/pkg - npm run dev
This commit is contained in:
parent
32e62d9209
commit
a716cd1989
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
17
Cargo.toml
|
|
@ -5,41 +5,28 @@ edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
wasm-bindgen = "0.2.83"
|
||||||
anyhow = { version = "1.0.60" }
|
anyhow = { version = "1.0.60" }
|
||||||
bincode = { version = "1.3.3" }
|
|
||||||
cacache = { version = "10.0.1" }
|
|
||||||
chrono = { version = "0.4.21" }
|
chrono = { version = "0.4.21" }
|
||||||
clap = { version = "3.2.16", features = ["derive"] }
|
|
||||||
clearscreen = { version = "1.0.10" }
|
|
||||||
colored = { version = "2.0.0" }
|
|
||||||
common-path = { version = "1.0.0" }
|
|
||||||
dirs = { version = "4.0.0" }
|
|
||||||
fern = { version = "0.6.1" }
|
fern = { version = "0.6.1" }
|
||||||
filetime = { version = "0.2.17" }
|
filetime = { version = "0.2.17" }
|
||||||
glob = { version = "0.3.0" }
|
glob = { version = "0.3.0" }
|
||||||
itertools = { version = "0.10.3" }
|
itertools = { version = "0.10.3" }
|
||||||
log = { version = "0.4.17" }
|
log = { version = "0.4.17" }
|
||||||
notify = { version = "4.0.17" }
|
|
||||||
once_cell = { version = "1.13.1" }
|
once_cell = { version = "1.13.1" }
|
||||||
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
|
|
||||||
rayon = { version = "1.5.3" }
|
rayon = { version = "1.5.3" }
|
||||||
regex = { version = "1.6.0" }
|
regex = { version = "1.6.0" }
|
||||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }
|
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }
|
||||||
serde = { version = "1.0.143", features = ["derive"] }
|
serde = { version = "1.0.143", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.83" }
|
serde_json = { version = "1.0.83" }
|
||||||
toml = { version = "0.5.9" }
|
toml = { version = "0.5.9" }
|
||||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
|
||||||
walkdir = { version = "2.3.2" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = { version = "1.19.1", features = ["yaml"] }
|
insta = { version = "1.19.1", features = ["yaml"] }
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["update-informer"]
|
|
||||||
update-informer = ["dep:update-informer"]
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
body,
|
||||||
|
html,
|
||||||
|
#root {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="An in-browser playground for Ruff, an extremely fast Python linter written in Rust."
|
||||||
|
/>
|
||||||
|
<meta name="keywords" content="ruff, python, rust, webassembly, wasm" />
|
||||||
|
<title>Ruff Playground</title>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛠️</text></svg>"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<div style="display: flex; position: fixed; right: 16px; top: 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>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "demo",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"push": "wrangler pages publish dist --project-name=ruff"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.4.5",
|
||||||
|
"monaco-editor": "^0.34.0",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.17",
|
||||||
|
"@types/react-dom": "^18.0.6",
|
||||||
|
"@vitejs/plugin-react": "^2.1.0",
|
||||||
|
"typescript": "^4.6.4",
|
||||||
|
"vite": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,69 @@
|
||||||
|
import Editor, { Monaco } from "@monaco-editor/react";
|
||||||
|
import { editor, MarkerSeverity } from "monaco-editor/esm/vs/editor/editor.api";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { DEFAULT_SOURCE } from "./constants";
|
||||||
|
import init, { check } from "./pkg/ruff.js";
|
||||||
|
import { Check } from "./types";
|
||||||
|
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [initialized, setInitialized] = useState<boolean>(false);
|
||||||
|
const [_, setCounter] = useState(0);
|
||||||
|
const [monaco, setMonaco] = useState<Monaco | null>(null);
|
||||||
|
|
||||||
|
// Load the WASM module.
|
||||||
|
useEffect(() => {
|
||||||
|
init().then(() => setInitialized(true));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleEditorChange = useCallback(
|
||||||
|
() => setCounter((counter) => counter + 1),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleEditorDidMount = useCallback(
|
||||||
|
(editor: IStandaloneCodeEditor, monaco: Monaco) => {
|
||||||
|
setMonaco(monaco);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (initialized) {
|
||||||
|
if (monaco) {
|
||||||
|
const editor = monaco.editor;
|
||||||
|
if (editor) {
|
||||||
|
const model = editor.getModels()[0];
|
||||||
|
if (model) {
|
||||||
|
const checks: Check[] = JSON.parse(check(model.getValue()));
|
||||||
|
editor.setModelMarkers(
|
||||||
|
model,
|
||||||
|
"owner",
|
||||||
|
checks.map((check) => ({
|
||||||
|
startLineNumber: check.location.row,
|
||||||
|
startColumn: check.location.column,
|
||||||
|
endLineNumber: check.location.row,
|
||||||
|
endColumn: check.location.column,
|
||||||
|
message: `${check.code}: ${check.message}`,
|
||||||
|
severity: MarkerSeverity.Error,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
height={"100%"}
|
||||||
|
width={"100%"}
|
||||||
|
path={"ruff"}
|
||||||
|
options={{ readOnly: false, minimap: { enabled: false } }}
|
||||||
|
defaultLanguage="python"
|
||||||
|
defaultValue={DEFAULT_SOURCE}
|
||||||
|
theme={"light"}
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
|
onChange={handleEditorChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" class="iconify iconify--logos"
|
||||||
|
width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
|
|
@ -0,0 +1,55 @@
|
||||||
|
export const DEFAULT_SOURCE = `#: E721
|
||||||
|
if type(res) == type(42):
|
||||||
|
pass
|
||||||
|
#: E721
|
||||||
|
if type(res) != type(""):
|
||||||
|
pass
|
||||||
|
#: E721
|
||||||
|
import types
|
||||||
|
|
||||||
|
if res == types.IntType:
|
||||||
|
pass
|
||||||
|
#: E721
|
||||||
|
import types
|
||||||
|
|
||||||
|
if type(res) is not types.ListType:
|
||||||
|
pass
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type(False) or type(res) == type(None)
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type([])
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type(())
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type((0,))
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type((0))
|
||||||
|
#: E721
|
||||||
|
assert type(res) != type((1,))
|
||||||
|
#: E721
|
||||||
|
assert type(res) is type((1,))
|
||||||
|
#: E721
|
||||||
|
assert type(res) is not type((1,))
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type(
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type(())
|
||||||
|
#: E721
|
||||||
|
assert type(res) == type((0,))
|
||||||
|
|
||||||
|
#: Okay
|
||||||
|
import types
|
||||||
|
|
||||||
|
if isinstance(res, int):
|
||||||
|
pass
|
||||||
|
if isinstance(res, str):
|
||||||
|
pass
|
||||||
|
if isinstance(res, types.MethodType):
|
||||||
|
pass
|
||||||
|
if type(a) != type(b) or type(a) == type(ccc):
|
||||||
|
pass
|
||||||
|
`;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
import "../index.css";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type Check = {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
location: { row: number; column: number };
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>hello-wasm example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
import init, {check} from "./pkg/ruff.js";
|
||||||
|
|
||||||
|
init().then(() => {
|
||||||
|
console.log(check("if (1, 2): pass"));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use rustpython_parser::ast::Location;
|
|
||||||
|
|
||||||
use crate::checks::{Check, Fix};
|
|
||||||
|
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Generate,
|
Generate,
|
||||||
|
|
@ -21,196 +13,3 @@ impl From<bool> for Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
|
||||||
pub fn fix_file(checks: &mut [Check], contents: &str, path: &Path) -> Result<()> {
|
|
||||||
if checks.iter().all(|check| check.fix.is_none()) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = apply_fixes(
|
|
||||||
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
|
|
||||||
contents,
|
|
||||||
);
|
|
||||||
|
|
||||||
fs::write(path, output).map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply a series of fixes.
|
|
||||||
fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) -> String {
|
|
||||||
let lines: Vec<&str> = contents.lines().collect();
|
|
||||||
|
|
||||||
let mut output = "".to_string();
|
|
||||||
let mut last_pos: Location = Location::new(0, 0);
|
|
||||||
|
|
||||||
for fix in fixes {
|
|
||||||
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
|
|
||||||
if last_pos > fix.start {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if fix.start.row() > last_pos.row() {
|
|
||||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
|
||||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
|
||||||
output.push('\n');
|
|
||||||
}
|
|
||||||
for line in &lines[last_pos.row()..fix.start.row() - 1] {
|
|
||||||
output.push_str(line);
|
|
||||||
output.push('\n');
|
|
||||||
}
|
|
||||||
output.push_str(&lines[fix.start.row() - 1][..fix.start.column() - 1]);
|
|
||||||
output.push_str(&fix.content);
|
|
||||||
} else {
|
|
||||||
output.push_str(
|
|
||||||
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.start.column() - 1],
|
|
||||||
);
|
|
||||||
output.push_str(&fix.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_pos = fix.end;
|
|
||||||
fix.applied = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
|
||||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
|
|
||||||
output.push('\n');
|
|
||||||
}
|
|
||||||
for line in &lines[last_pos.row()..] {
|
|
||||||
output.push_str(line);
|
|
||||||
output.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use anyhow::Result;
|
|
||||||
use rustpython_parser::ast::Location;
|
|
||||||
|
|
||||||
use crate::autofix::fixer::apply_fixes;
|
|
||||||
use crate::checks::Fix;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_file() -> Result<()> {
|
|
||||||
let mut fixes = vec![];
|
|
||||||
let actual = apply_fixes(fixes.iter_mut(), "");
|
|
||||||
let expected = "";
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_single_replacement() -> Result<()> {
|
|
||||||
let mut fixes = vec![Fix {
|
|
||||||
content: "Bar".to_string(),
|
|
||||||
start: Location::new(1, 9),
|
|
||||||
end: Location::new(1, 15),
|
|
||||||
applied: false,
|
|
||||||
}];
|
|
||||||
let actual = apply_fixes(
|
|
||||||
fixes.iter_mut(),
|
|
||||||
"class A(object):
|
|
||||||
...
|
|
||||||
",
|
|
||||||
);
|
|
||||||
|
|
||||||
let expected = "class A(Bar):
|
|
||||||
...
|
|
||||||
";
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_single_removal() -> Result<()> {
|
|
||||||
let mut fixes = vec![Fix {
|
|
||||||
content: "".to_string(),
|
|
||||||
start: Location::new(1, 8),
|
|
||||||
end: Location::new(1, 16),
|
|
||||||
applied: false,
|
|
||||||
}];
|
|
||||||
let actual = apply_fixes(
|
|
||||||
fixes.iter_mut(),
|
|
||||||
"class A(object):
|
|
||||||
...
|
|
||||||
",
|
|
||||||
);
|
|
||||||
|
|
||||||
let expected = "class A:
|
|
||||||
...
|
|
||||||
";
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_double_removal() -> Result<()> {
|
|
||||||
let mut fixes = vec![
|
|
||||||
Fix {
|
|
||||||
content: "".to_string(),
|
|
||||||
start: Location::new(1, 8),
|
|
||||||
end: Location::new(1, 17),
|
|
||||||
applied: false,
|
|
||||||
},
|
|
||||||
Fix {
|
|
||||||
content: "".to_string(),
|
|
||||||
start: Location::new(1, 17),
|
|
||||||
end: Location::new(1, 24),
|
|
||||||
applied: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let actual = apply_fixes(
|
|
||||||
fixes.iter_mut(),
|
|
||||||
"class A(object, object):
|
|
||||||
...
|
|
||||||
",
|
|
||||||
);
|
|
||||||
|
|
||||||
let expected = "class A:
|
|
||||||
...
|
|
||||||
";
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ignore_overlapping_fixes() -> Result<()> {
|
|
||||||
let mut fixes = vec![
|
|
||||||
Fix {
|
|
||||||
content: "".to_string(),
|
|
||||||
start: Location::new(1, 8),
|
|
||||||
end: Location::new(1, 16),
|
|
||||||
applied: false,
|
|
||||||
},
|
|
||||||
Fix {
|
|
||||||
content: "ignored".to_string(),
|
|
||||||
start: Location::new(1, 10),
|
|
||||||
end: Location::new(1, 12),
|
|
||||||
applied: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let actual = apply_fixes(
|
|
||||||
fixes.iter_mut(),
|
|
||||||
"class A(object):
|
|
||||||
...
|
|
||||||
",
|
|
||||||
);
|
|
||||||
|
|
||||||
let expected = "class A:
|
|
||||||
...
|
|
||||||
";
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
153
src/cache.rs
153
src/cache.rs
|
|
@ -1,153 +0,0 @@
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::fs::{create_dir_all, File, Metadata};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use cacache::Error::EntryNotFound;
|
|
||||||
use filetime::FileTime;
|
|
||||||
use log::error;
|
|
||||||
use path_absolutize::Absolutize;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::autofix::fixer;
|
|
||||||
use crate::message::Message;
|
|
||||||
use crate::settings::Settings;
|
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct CacheMetadata {
|
|
||||||
mtime: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct CheckResultRef<'a> {
|
|
||||||
metadata: &'a CacheMetadata,
|
|
||||||
messages: &'a [Message],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CheckResult {
|
|
||||||
metadata: CacheMetadata,
|
|
||||||
messages: Vec<Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Mode {
|
|
||||||
ReadWrite,
|
|
||||||
ReadOnly,
|
|
||||||
WriteOnly,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mode {
|
|
||||||
fn allow_read(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Mode::ReadWrite => true,
|
|
||||||
Mode::ReadOnly => true,
|
|
||||||
Mode::WriteOnly => false,
|
|
||||||
Mode::None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allow_write(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Mode::ReadWrite => true,
|
|
||||||
Mode::ReadOnly => false,
|
|
||||||
Mode::WriteOnly => true,
|
|
||||||
Mode::None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for Mode {
|
|
||||||
fn from(value: bool) -> Self {
|
|
||||||
match value {
|
|
||||||
true => Mode::ReadWrite,
|
|
||||||
false => Mode::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache_dir() -> &'static str {
|
|
||||||
"./.ruff_cache"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> String {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
settings.hash(&mut hasher);
|
|
||||||
autofix.hash(&mut hasher);
|
|
||||||
format!(
|
|
||||||
"{}@{}@{}",
|
|
||||||
path.absolutize().unwrap().to_string_lossy(),
|
|
||||||
VERSION,
|
|
||||||
hasher.finish()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
|
||||||
let gitignore_path = Path::new(cache_dir()).join(".gitignore");
|
|
||||||
if gitignore_path.exists() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
create_dir_all(cache_dir())?;
|
|
||||||
let mut file = File::create(gitignore_path)?;
|
|
||||||
file.write_all(b"*").map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(
|
|
||||||
path: &Path,
|
|
||||||
metadata: &Metadata,
|
|
||||||
settings: &Settings,
|
|
||||||
autofix: &fixer::Mode,
|
|
||||||
mode: &Mode,
|
|
||||||
) -> Option<Vec<Message>> {
|
|
||||||
if !mode.allow_read() {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
match cacache::read_sync(cache_dir(), cache_key(path, settings, autofix)) {
|
|
||||||
Ok(encoded) => match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
|
||||||
Ok(CheckResult {
|
|
||||||
metadata: CacheMetadata { mtime },
|
|
||||||
messages,
|
|
||||||
}) => {
|
|
||||||
if FileTime::from_last_modification_time(metadata).unix_seconds() == mtime {
|
|
||||||
return Some(messages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => error!("Failed to deserialize encoded cache entry: {e:?}"),
|
|
||||||
},
|
|
||||||
Err(EntryNotFound(_, _)) => {}
|
|
||||||
Err(e) => error!("Failed to read from cache: {e:?}"),
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(
|
|
||||||
path: &Path,
|
|
||||||
metadata: &Metadata,
|
|
||||||
settings: &Settings,
|
|
||||||
autofix: &fixer::Mode,
|
|
||||||
messages: &[Message],
|
|
||||||
mode: &Mode,
|
|
||||||
) {
|
|
||||||
if !mode.allow_write() {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let check_result = CheckResultRef {
|
|
||||||
metadata: &CacheMetadata {
|
|
||||||
mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),
|
|
||||||
},
|
|
||||||
messages,
|
|
||||||
};
|
|
||||||
if let Err(e) = cacache::write_sync(
|
|
||||||
cache_dir(),
|
|
||||||
cache_key(path, settings, autofix),
|
|
||||||
bincode::serialize(&check_result).unwrap(),
|
|
||||||
) {
|
|
||||||
error!("Failed to write to cache: {e:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
243
src/fs.rs
243
src/fs.rs
|
|
@ -1,243 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{BufReader, Read};
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use log::debug;
|
|
||||||
use path_absolutize::path_dedot;
|
|
||||||
use path_absolutize::Absolutize;
|
|
||||||
use walkdir::{DirEntry, WalkDir};
|
|
||||||
|
|
||||||
use crate::settings::FilePattern;
|
|
||||||
|
|
||||||
/// Extract the absolute path and basename (as strings) from a Path.
|
|
||||||
fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
|
||||||
let file_path = path
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?;
|
|
||||||
let file_basename = path
|
|
||||||
.file_name()
|
|
||||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?;
|
|
||||||
Ok((file_path, file_basename))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_excluded(file_path: &str, file_basename: &str, exclude: &[FilePattern]) -> bool {
|
|
||||||
for pattern in exclude {
|
|
||||||
match &pattern {
|
|
||||||
FilePattern::Simple(basename) => {
|
|
||||||
if *basename == file_basename {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FilePattern::Complex(absolute, basename) => {
|
|
||||||
if absolute.matches(file_path) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if basename
|
|
||||||
.as_ref()
|
|
||||||
.map(|pattern| pattern.matches(file_basename))
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_included(path: &Path) -> bool {
|
|
||||||
let file_name = path.to_string_lossy();
|
|
||||||
file_name.ends_with(".py") || file_name.ends_with(".pyi")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_python_files<'a>(
|
|
||||||
path: &'a Path,
|
|
||||||
exclude: &'a [FilePattern],
|
|
||||||
extend_exclude: &'a [FilePattern],
|
|
||||||
) -> impl Iterator<Item = Result<DirEntry, walkdir::Error>> + 'a {
|
|
||||||
// Run some checks over the provided patterns, to enable optimizations below.
|
|
||||||
let has_exclude = !exclude.is_empty();
|
|
||||||
let has_extend_exclude = !extend_exclude.is_empty();
|
|
||||||
let exclude_simple = exclude
|
|
||||||
.iter()
|
|
||||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
|
||||||
let extend_exclude_simple = extend_exclude
|
|
||||||
.iter()
|
|
||||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
|
||||||
|
|
||||||
WalkDir::new(normalize_path(path))
|
|
||||||
.follow_links(true)
|
|
||||||
.into_iter()
|
|
||||||
.filter_entry(move |entry| {
|
|
||||||
if !has_exclude && !has_extend_exclude {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = entry.path();
|
|
||||||
match extract_path_names(path) {
|
|
||||||
Ok((file_path, file_basename)) => {
|
|
||||||
let file_type = entry.file_type();
|
|
||||||
|
|
||||||
if has_exclude
|
|
||||||
&& (!exclude_simple || file_type.is_dir())
|
|
||||||
&& is_excluded(file_path, file_basename, exclude)
|
|
||||||
{
|
|
||||||
debug!("Ignored path via `exclude`: {:?}", path);
|
|
||||||
false
|
|
||||||
} else if has_extend_exclude
|
|
||||||
&& (!extend_exclude_simple || file_type.is_dir())
|
|
||||||
&& is_excluded(file_path, file_basename, extend_exclude)
|
|
||||||
{
|
|
||||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
debug!("Ignored path due to error in parsing: {:?}", path);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(|entry| {
|
|
||||||
entry.as_ref().map_or(true, |entry| {
|
|
||||||
(entry.depth() == 0 && !entry.file_type().is_dir()) || is_included(entry.path())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert any path to an absolute path (based on the current working directory).
|
|
||||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
|
||||||
if let Ok(path) = path.absolutize() {
|
|
||||||
return path.to_path_buf();
|
|
||||||
}
|
|
||||||
path.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert any path to an absolute path (based on the specified project root).
|
|
||||||
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
|
||||||
if let Ok(path) = path.absolutize_from(project_root) {
|
|
||||||
return path.to_path_buf();
|
|
||||||
}
|
|
||||||
path.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert an absolute path to be relative to the current working directory.
|
|
||||||
pub fn relativize_path(path: &Path) -> Cow<str> {
|
|
||||||
if let Ok(path) = path.strip_prefix(path_dedot::CWD.deref()) {
|
|
||||||
return path.to_string_lossy();
|
|
||||||
}
|
|
||||||
path.to_string_lossy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a file's contents from disk.
|
|
||||||
pub fn read_file(path: &Path) -> Result<String> {
|
|
||||||
let file = File::open(path)?;
|
|
||||||
let mut buf_reader = BufReader::new(file);
|
|
||||||
let mut contents = String::new();
|
|
||||||
buf_reader.read_to_string(&mut contents)?;
|
|
||||||
Ok(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use path_absolutize::Absolutize;
|
|
||||||
|
|
||||||
use crate::fs::{extract_path_names, is_excluded, is_included};
|
|
||||||
use crate::settings::FilePattern;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inclusions() {
|
|
||||||
let path = Path::new("foo/bar/baz.py").absolutize().unwrap();
|
|
||||||
assert!(is_included(&path));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.pyi").absolutize().unwrap();
|
|
||||||
assert!(is_included(&path));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.js").absolutize().unwrap();
|
|
||||||
assert!(!is_included(&path));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz").absolutize().unwrap();
|
|
||||||
assert!(!is_included(&path));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exclusions() -> Result<()> {
|
|
||||||
let project_root = Path::new("/tmp/");
|
|
||||||
|
|
||||||
let path = Path::new("foo").absolutize_from(project_root).unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"foo",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"bar",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.py")
|
|
||||||
.absolutize_from(project_root)
|
|
||||||
.unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"baz.py",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"foo/bar",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.py")
|
|
||||||
.absolutize_from(project_root)
|
|
||||||
.unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"foo/bar/baz.py",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.py")
|
|
||||||
.absolutize_from(project_root)
|
|
||||||
.unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"foo/bar/*.py",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.py")
|
|
||||||
.absolutize_from(project_root)
|
|
||||||
.unwrap();
|
|
||||||
let exclude = vec![FilePattern::from_user(
|
|
||||||
"baz",
|
|
||||||
&Some(project_root.to_path_buf()),
|
|
||||||
)];
|
|
||||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
|
||||||
assert!(!is_excluded(file_path, file_basename, &exclude));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
73
src/lib.rs
73
src/lib.rs
|
|
@ -1,16 +1,73 @@
|
||||||
extern crate core;
|
use rustpython_parser::ast::Location;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use rustpython_parser::parser;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use crate::check_ast::check_ast;
|
||||||
|
use crate::check_lines::check_lines;
|
||||||
|
use crate::checks::{Check, CheckCode, CheckKind, ALL_CHECK_CODES};
|
||||||
|
use crate::settings::Settings;
|
||||||
|
|
||||||
mod ast;
|
mod ast;
|
||||||
mod autofix;
|
mod autofix;
|
||||||
pub mod cache;
|
|
||||||
pub mod check_ast;
|
pub mod check_ast;
|
||||||
mod check_lines;
|
mod check_lines;
|
||||||
pub mod checks;
|
pub mod checks;
|
||||||
pub mod fs;
|
|
||||||
pub mod linter;
|
|
||||||
pub mod logging;
|
|
||||||
pub mod message;
|
|
||||||
pub mod printer;
|
|
||||||
pub mod pyproject;
|
|
||||||
mod python;
|
mod python;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
struct Message {
|
||||||
|
code: CheckCode,
|
||||||
|
message: String,
|
||||||
|
location: Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
pub fn alert(s: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn check(contents: &str) -> String {
|
||||||
|
let settings = Settings::for_rules(ALL_CHECK_CODES.to_vec());
|
||||||
|
let autofix = autofix::fixer::Mode::None;
|
||||||
|
|
||||||
|
// Aggregate all checks.
|
||||||
|
let mut checks: Vec<Check> = vec![];
|
||||||
|
|
||||||
|
// Run the AST-based checks.
|
||||||
|
match parser::parse_program(contents, "<filename>") {
|
||||||
|
Ok(python_ast) => checks.extend(check_ast(
|
||||||
|
&python_ast,
|
||||||
|
contents,
|
||||||
|
&settings,
|
||||||
|
&autofix,
|
||||||
|
Path::new("<filename>"),
|
||||||
|
)),
|
||||||
|
Err(parse_error) => {
|
||||||
|
if settings.select.contains(&CheckCode::E999) {
|
||||||
|
checks.push(Check::new(
|
||||||
|
CheckKind::SyntaxError(parse_error.error.to_string()),
|
||||||
|
parse_error.location,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the lines-based checks.
|
||||||
|
check_lines(&mut checks, contents, &settings);
|
||||||
|
|
||||||
|
let messages: Vec<Message> = checks
|
||||||
|
.into_iter()
|
||||||
|
.map(|check| Message {
|
||||||
|
code: check.kind.code().clone(),
|
||||||
|
message: check.kind.body(),
|
||||||
|
location: check.location,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
serde_json::to_string(&messages).unwrap()
|
||||||
|
}
|
||||||
|
|
|
||||||
628
src/linter.rs
628
src/linter.rs
|
|
@ -1,628 +0,0 @@
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use log::debug;
|
|
||||||
use rustpython_parser::parser;
|
|
||||||
|
|
||||||
use crate::autofix::fixer;
|
|
||||||
use crate::autofix::fixer::fix_file;
|
|
||||||
use crate::check_ast::check_ast;
|
|
||||||
use crate::check_lines::check_lines;
|
|
||||||
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
|
|
||||||
use crate::message::Message;
|
|
||||||
use crate::settings::Settings;
|
|
||||||
use crate::{cache, fs};
|
|
||||||
|
|
||||||
fn check_path(
|
|
||||||
path: &Path,
|
|
||||||
contents: &str,
|
|
||||||
settings: &Settings,
|
|
||||||
autofix: &fixer::Mode,
|
|
||||||
) -> Vec<Check> {
|
|
||||||
// Aggregate all checks.
|
|
||||||
let mut checks: Vec<Check> = vec![];
|
|
||||||
|
|
||||||
// Run the AST-based checks.
|
|
||||||
if settings
|
|
||||||
.select
|
|
||||||
.iter()
|
|
||||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
|
||||||
{
|
|
||||||
match parser::parse_program(contents, "<filename>") {
|
|
||||||
Ok(python_ast) => {
|
|
||||||
checks.extend(check_ast(&python_ast, contents, settings, autofix, path))
|
|
||||||
}
|
|
||||||
Err(parse_error) => {
|
|
||||||
if settings.select.contains(&CheckCode::E999) {
|
|
||||||
checks.push(Check::new(
|
|
||||||
CheckKind::SyntaxError(parse_error.error.to_string()),
|
|
||||||
parse_error.location,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the lines-based checks.
|
|
||||||
check_lines(&mut checks, contents, settings);
|
|
||||||
|
|
||||||
checks
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lint_path(
|
|
||||||
path: &Path,
|
|
||||||
settings: &Settings,
|
|
||||||
mode: &cache::Mode,
|
|
||||||
autofix: &fixer::Mode,
|
|
||||||
) -> Result<Vec<Message>> {
|
|
||||||
let metadata = path.metadata()?;
|
|
||||||
|
|
||||||
// Check the cache.
|
|
||||||
if let Some(messages) = cache::get(path, &metadata, settings, autofix, mode) {
|
|
||||||
debug!("Cache hit for: {}", path.to_string_lossy());
|
|
||||||
return Ok(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the file from disk.
|
|
||||||
let contents = fs::read_file(path)?;
|
|
||||||
|
|
||||||
// Generate checks.
|
|
||||||
let mut checks = check_path(path, &contents, settings, autofix);
|
|
||||||
|
|
||||||
// Apply autofix.
|
|
||||||
if matches!(autofix, fixer::Mode::Apply) {
|
|
||||||
fix_file(&mut checks, &contents, path)?;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert to messages.
|
|
||||||
let messages: Vec<Message> = checks
|
|
||||||
.into_iter()
|
|
||||||
.map(|check| Message {
|
|
||||||
kind: check.kind,
|
|
||||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
|
||||||
location: check.location,
|
|
||||||
filename: path.to_string_lossy().to_string(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
cache::set(path, &metadata, settings, autofix, &messages, mode);
|
|
||||||
|
|
||||||
Ok(messages)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::autofix::fixer;
|
|
||||||
use crate::checks::{Check, CheckCode};
|
|
||||||
use crate::fs;
|
|
||||||
use crate::linter;
|
|
||||||
use crate::settings;
|
|
||||||
|
|
||||||
fn check_path(
|
|
||||||
path: &Path,
|
|
||||||
settings: &settings::Settings,
|
|
||||||
autofix: &fixer::Mode,
|
|
||||||
) -> Result<Vec<Check>> {
|
|
||||||
let contents = fs::read_file(path)?;
|
|
||||||
Ok(linter::check_path(path, &contents, settings, autofix))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e402() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E402.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E402),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e501() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E501.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E501),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e711() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E711.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E711),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e712() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E712.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E712),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e713() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E713.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E713),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e721() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E721.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E721),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e722() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E722.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E722),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e714() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E714.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E714),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e731() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E731.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E731),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e741() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E741.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E741),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e742() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E742.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E742),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn e743() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/E743.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::E743),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f401() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F401.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F401),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f402() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F402.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F402),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f403() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F403.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F403),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f404() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F404.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F404),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f405() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F405.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F405),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f406() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F406.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F406),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f407() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F407.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F407),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f541() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F541.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F541),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f601() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F601.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F601),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f602() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F602.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F602),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f622() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F622.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F622),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f631() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F631.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F631),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f632() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F632.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F632),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f633() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F633.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F633),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f634() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F634.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F634),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f701() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F701.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F701),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f702() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F702.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F702),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f704() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F704.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F704),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f706() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F706.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F706),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f707() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F707.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F707),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f722() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F722.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F722),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f821() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F821.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F821),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f822() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F822.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F822),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f823() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F823.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F823),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f831() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F831.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F831),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f841() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F841.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F841),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn f901() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/F901.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::F901),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn r001() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/R001.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::R001),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn r002() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/R002.py"),
|
|
||||||
&settings::Settings::for_rule(CheckCode::R002),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn init() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/__init__.py"),
|
|
||||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn future_annotations() -> Result<()> {
|
|
||||||
let mut checks = check_path(
|
|
||||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
|
||||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
|
||||||
&fixer::Mode::Generate,
|
|
||||||
)?;
|
|
||||||
checks.sort_by_key(|check| check.location);
|
|
||||||
insta::assert_yaml_snapshot!(checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use fern;
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! tell_user {
|
|
||||||
($($arg:tt)*) => {
|
|
||||||
println!(
|
|
||||||
"[{}] {}",
|
|
||||||
chrono::Local::now()
|
|
||||||
.format("%H:%M:%S %p")
|
|
||||||
.to_string()
|
|
||||||
.dimmed(),
|
|
||||||
format_args!($($arg)*)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_up_logging(verbose: bool) -> Result<()> {
|
|
||||||
fern::Dispatch::new()
|
|
||||||
.format(|out, message, record| {
|
|
||||||
out.finish(format_args!(
|
|
||||||
"{}[{}][{}] {}",
|
|
||||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
|
||||||
record.target(),
|
|
||||||
record.level(),
|
|
||||||
message
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.level(if verbose {
|
|
||||||
log::LevelFilter::Debug
|
|
||||||
} else {
|
|
||||||
log::LevelFilter::Info
|
|
||||||
})
|
|
||||||
.chain(std::io::stdout())
|
|
||||||
.apply()
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
304
src/main.rs
304
src/main.rs
|
|
@ -1,304 +0,0 @@
|
||||||
extern crate core;
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::ExitCode;
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::{Parser, ValueHint};
|
|
||||||
use colored::Colorize;
|
|
||||||
use log::{debug, error};
|
|
||||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use walkdir::DirEntry;
|
|
||||||
|
|
||||||
use ::ruff::cache;
|
|
||||||
use ::ruff::checks::CheckCode;
|
|
||||||
use ::ruff::checks::CheckKind;
|
|
||||||
use ::ruff::fs::iter_python_files;
|
|
||||||
use ::ruff::linter::lint_path;
|
|
||||||
use ::ruff::logging::set_up_logging;
|
|
||||||
use ::ruff::message::Message;
|
|
||||||
use ::ruff::printer::{Printer, SerializationFormat};
|
|
||||||
use ::ruff::pyproject;
|
|
||||||
use ::ruff::settings::{FilePattern, Settings};
|
|
||||||
use ::ruff::tell_user;
|
|
||||||
|
|
||||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
|
||||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
#[clap(name = format!("{CARGO_PKG_NAME} (v{CARGO_PKG_VERSION})"))]
|
|
||||||
#[clap(about = "An extremely fast Python linter.", long_about = None)]
|
|
||||||
#[clap(version)]
|
|
||||||
struct Cli {
|
|
||||||
#[clap(parse(from_os_str), value_hint = ValueHint::AnyPath, required = true)]
|
|
||||||
files: Vec<PathBuf>,
|
|
||||||
/// Enable verbose logging.
|
|
||||||
#[clap(short, long, action)]
|
|
||||||
verbose: bool,
|
|
||||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
|
||||||
#[clap(short, long, action)]
|
|
||||||
quiet: bool,
|
|
||||||
/// Exit with status code "0", even upon detecting errors.
|
|
||||||
#[clap(short, long, action)]
|
|
||||||
exit_zero: bool,
|
|
||||||
/// Run in watch mode by re-running whenever files change.
|
|
||||||
#[clap(short, long, action)]
|
|
||||||
watch: bool,
|
|
||||||
/// Attempt to automatically fix lint errors.
|
|
||||||
#[clap(short, long, action)]
|
|
||||||
fix: bool,
|
|
||||||
/// Disable cache reads.
|
|
||||||
#[clap(short, long, action)]
|
|
||||||
no_cache: bool,
|
|
||||||
/// List of error codes to enable.
|
|
||||||
#[clap(long, multiple = true)]
|
|
||||||
select: Vec<CheckCode>,
|
|
||||||
/// List of error codes to ignore.
|
|
||||||
#[clap(long, multiple = true)]
|
|
||||||
ignore: Vec<CheckCode>,
|
|
||||||
/// List of paths, used to exclude files and/or directories from checks.
|
|
||||||
#[clap(long, multiple = true)]
|
|
||||||
exclude: Vec<String>,
|
|
||||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
|
||||||
#[clap(long, multiple = true)]
|
|
||||||
extend_exclude: Vec<String>,
|
|
||||||
/// Output serialization format for error messages.
|
|
||||||
#[clap(long, arg_enum, default_value_t=SerializationFormat::Text)]
|
|
||||||
format: SerializationFormat,
|
|
||||||
/// See the files ruff will be run against with the current settings.
|
|
||||||
#[clap(long, action)]
|
|
||||||
show_files: bool,
|
|
||||||
/// See ruff's settings.
|
|
||||||
#[clap(long, action)]
|
|
||||||
show_settings: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "update-informer")]
|
|
||||||
fn check_for_updates() {
|
|
||||||
use update_informer::{registry, Check};
|
|
||||||
|
|
||||||
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
|
|
||||||
|
|
||||||
if let Some(new_version) = informer.check_version().ok().flatten() {
|
|
||||||
let msg = format!(
|
|
||||||
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
|
|
||||||
pkg_name = CARGO_PKG_NAME.italic().cyan(),
|
|
||||||
pkg_version = CARGO_PKG_VERSION,
|
|
||||||
new_version = new_version.to_string().green()
|
|
||||||
);
|
|
||||||
|
|
||||||
let cmd = format!(
|
|
||||||
"Run to update: {cmd} {pkg_name}",
|
|
||||||
cmd = "pip3 install --upgrade".green(),
|
|
||||||
pkg_name = CARGO_PKG_NAME.green()
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("\n{msg}\n{cmd}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_settings(settings: &Settings) {
|
|
||||||
println!("{:#?}", settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_files(files: &[PathBuf], settings: &Settings) {
|
|
||||||
let mut entries: Vec<DirEntry> = files
|
|
||||||
.iter()
|
|
||||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
entries.sort_by(|a, b| a.path().cmp(b.path()));
|
|
||||||
for entry in entries {
|
|
||||||
println!("{}", entry.path().to_string_lossy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_once(
|
|
||||||
files: &[PathBuf],
|
|
||||||
settings: &Settings,
|
|
||||||
cache: bool,
|
|
||||||
autofix: bool,
|
|
||||||
) -> Result<Vec<Message>> {
|
|
||||||
// Collect all the files to check.
|
|
||||||
let start = Instant::now();
|
|
||||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
|
||||||
.iter()
|
|
||||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
|
||||||
.collect();
|
|
||||||
let duration = start.elapsed();
|
|
||||||
debug!("Identified files to lint in: {:?}", duration);
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let mut messages: Vec<Message> = paths
|
|
||||||
.par_iter()
|
|
||||||
.map(|entry| {
|
|
||||||
match entry {
|
|
||||||
Ok(entry) => {
|
|
||||||
let path = entry.path();
|
|
||||||
lint_path(path, settings, &cache.into(), &autofix.into())
|
|
||||||
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
|
||||||
}
|
|
||||||
Err(e) => Err((
|
|
||||||
e.path().map(Path::to_owned),
|
|
||||||
e.io_error()
|
|
||||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
.unwrap_or_else(|(path, message)| {
|
|
||||||
if let Some(path) = path {
|
|
||||||
if settings.select.contains(&CheckCode::E902) {
|
|
||||||
vec![Message {
|
|
||||||
kind: CheckKind::IOError(message),
|
|
||||||
fixed: false,
|
|
||||||
location: Default::default(),
|
|
||||||
filename: path.to_string_lossy().to_string(),
|
|
||||||
}]
|
|
||||||
} else {
|
|
||||||
error!("Failed to check {}: {message}", path.to_string_lossy());
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("{message}");
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
messages.sort_unstable();
|
|
||||||
let duration = start.elapsed();
|
|
||||||
debug!("Checked files in: {:?}", duration);
|
|
||||||
|
|
||||||
Ok(messages)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inner_main() -> Result<ExitCode> {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
set_up_logging(cli.verbose)?;
|
|
||||||
|
|
||||||
// Find the project root and pyproject.toml.
|
|
||||||
let project_root = pyproject::find_project_root(&cli.files);
|
|
||||||
match &project_root {
|
|
||||||
Some(path) => debug!("Found project root at: {:?}", path),
|
|
||||||
None => debug!("Unable to identify project root; assuming current directory..."),
|
|
||||||
};
|
|
||||||
let pyproject = pyproject::find_pyproject_toml(&project_root);
|
|
||||||
match &pyproject {
|
|
||||||
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
|
|
||||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse the settings from the pyproject.toml and command-line arguments.
|
|
||||||
let exclude: Vec<FilePattern> = cli
|
|
||||||
.exclude
|
|
||||||
.iter()
|
|
||||||
.map(|path| FilePattern::from_user(path, &project_root))
|
|
||||||
.collect();
|
|
||||||
let extend_exclude: Vec<FilePattern> = cli
|
|
||||||
.extend_exclude
|
|
||||||
.iter()
|
|
||||||
.map(|path| FilePattern::from_user(path, &project_root))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut settings = Settings::from_pyproject(pyproject, project_root);
|
|
||||||
if !exclude.is_empty() {
|
|
||||||
settings.exclude = exclude;
|
|
||||||
}
|
|
||||||
if !extend_exclude.is_empty() {
|
|
||||||
settings.extend_exclude = extend_exclude;
|
|
||||||
}
|
|
||||||
if !cli.select.is_empty() {
|
|
||||||
settings.select(cli.select);
|
|
||||||
}
|
|
||||||
if !cli.ignore.is_empty() {
|
|
||||||
settings.ignore(&cli.ignore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.show_settings && cli.show_files {
|
|
||||||
println!("Error: specify --show-settings or show-files (not both).");
|
|
||||||
return Ok(ExitCode::FAILURE);
|
|
||||||
}
|
|
||||||
if cli.show_settings {
|
|
||||||
show_settings(&settings);
|
|
||||||
return Ok(ExitCode::SUCCESS);
|
|
||||||
}
|
|
||||||
if cli.show_files {
|
|
||||||
show_files(&cli.files, &settings);
|
|
||||||
return Ok(ExitCode::SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
cache::init()?;
|
|
||||||
|
|
||||||
let mut printer = Printer::new(cli.format, cli.verbose);
|
|
||||||
if cli.watch {
|
|
||||||
if cli.fix {
|
|
||||||
println!("Warning: --fix is not enabled in watch mode.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.format != SerializationFormat::Text {
|
|
||||||
println!("Warning: --format 'text' is used in watch mode.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform an initial run instantly.
|
|
||||||
printer.clear_screen()?;
|
|
||||||
tell_user!("Starting linter in watch mode...\n");
|
|
||||||
|
|
||||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, false)?;
|
|
||||||
if !cli.quiet {
|
|
||||||
printer.write_continuously(&messages)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the file watcher.
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let mut watcher = raw_watcher(tx)?;
|
|
||||||
for file in &cli.files {
|
|
||||||
watcher.watch(file, RecursiveMode::Recursive)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match rx.recv() {
|
|
||||||
Ok(e) => {
|
|
||||||
if let Some(path) = e.path {
|
|
||||||
if path.to_string_lossy().ends_with(".py") {
|
|
||||||
printer.clear_screen()?;
|
|
||||||
tell_user!("File change detected...\n");
|
|
||||||
|
|
||||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, false)?;
|
|
||||||
if !cli.quiet {
|
|
||||||
printer.write_continuously(&messages)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
|
|
||||||
if !cli.quiet {
|
|
||||||
printer.write_once(&messages)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "update-informer")]
|
|
||||||
check_for_updates();
|
|
||||||
|
|
||||||
if !messages.is_empty() && !cli.exit_zero {
|
|
||||||
return Ok(ExitCode::FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
|
||||||
match inner_main() {
|
|
||||||
Ok(code) => code,
|
|
||||||
Err(_) => ExitCode::FAILURE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::fmt;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use colored::Colorize;
|
|
||||||
use rustpython_parser::ast::Location;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::checks::CheckKind;
|
|
||||||
use crate::fs::relativize_path;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct Message {
|
|
||||||
pub kind: CheckKind,
|
|
||||||
pub fixed: bool,
|
|
||||||
pub location: Location,
|
|
||||||
pub filename: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Message {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
(&self.filename, self.location.row(), self.location.column()).cmp(&(
|
|
||||||
&other.filename,
|
|
||||||
other.location.row(),
|
|
||||||
other.location.column(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Message {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Message {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}{}{}{}{}{} {} {}",
|
|
||||||
relativize_path(Path::new(&self.filename)).white().bold(),
|
|
||||||
// self.filename.white(),
|
|
||||||
":".cyan(),
|
|
||||||
self.location.row(),
|
|
||||||
":".cyan(),
|
|
||||||
self.location.column(),
|
|
||||||
":".cyan(),
|
|
||||||
self.kind.code().as_str().red().bold(),
|
|
||||||
self.kind.body()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use clap::ValueEnum;
|
|
||||||
use colored::Colorize;
|
|
||||||
|
|
||||||
use crate::message::Message;
|
|
||||||
use crate::tell_user;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Debug)]
|
|
||||||
pub enum SerializationFormat {
|
|
||||||
Text,
|
|
||||||
Json,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Printer {
|
|
||||||
format: SerializationFormat,
|
|
||||||
verbose: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Printer {
|
|
||||||
pub fn new(format: SerializationFormat, verbose: bool) -> Self {
|
|
||||||
Self { format, verbose }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_once(&mut self, messages: &[Message]) -> Result<()> {
|
|
||||||
let (fixed, outstanding): (Vec<&Message>, Vec<&Message>) =
|
|
||||||
messages.iter().partition(|message| message.fixed);
|
|
||||||
let num_fixable = outstanding
|
|
||||||
.iter()
|
|
||||||
.filter(|message| message.kind.fixable())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
match self.format {
|
|
||||||
SerializationFormat::Json => {
|
|
||||||
println!("{}", serde_json::to_string_pretty(&messages)?)
|
|
||||||
}
|
|
||||||
SerializationFormat::Text => {
|
|
||||||
if !fixed.is_empty() {
|
|
||||||
println!(
|
|
||||||
"Found {} error(s) ({} fixed).",
|
|
||||||
outstanding.len(),
|
|
||||||
fixed.len()
|
|
||||||
)
|
|
||||||
} else if !outstanding.is_empty() || self.verbose {
|
|
||||||
println!("Found {} error(s).", outstanding.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
for message in outstanding {
|
|
||||||
println!("{}", message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if num_fixable > 0 {
|
|
||||||
println!("{num_fixable} potentially fixable with the --fix option.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_continuously(&mut self, messages: &[Message]) -> Result<()> {
|
|
||||||
tell_user!(
|
|
||||||
"Found {} error(s). Watching for file changes.",
|
|
||||||
messages.len(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if !messages.is_empty() {
|
|
||||||
println!();
|
|
||||||
for message in messages {
|
|
||||||
println!("{}", message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_screen(&mut self) -> Result<()> {
|
|
||||||
clearscreen::clear()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
289
src/pyproject.rs
289
src/pyproject.rs
|
|
@ -1,289 +0,0 @@
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use common_path::common_path_all;
|
|
||||||
use path_absolutize::Absolutize;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::checks::CheckCode;
|
|
||||||
use crate::fs;
|
|
||||||
|
|
||||||
pub fn load_config(pyproject: &Option<PathBuf>) -> Config {
|
|
||||||
match pyproject {
|
|
||||||
Some(pyproject) => match parse_pyproject_toml(pyproject) {
|
|
||||||
Ok(pyproject) => pyproject
|
|
||||||
.tool
|
|
||||||
.and_then(|tool| tool.ruff)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failed to load pyproject.toml: {:?}", e);
|
|
||||||
println!("Falling back to default configuration...");
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
println!("No pyproject.toml found.");
|
|
||||||
println!("Falling back to default configuration...");
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
|
||||||
pub struct Config {
|
|
||||||
pub line_length: Option<usize>,
|
|
||||||
pub exclude: Option<Vec<String>>,
|
|
||||||
pub extend_exclude: Option<Vec<String>>,
|
|
||||||
pub select: Option<Vec<CheckCode>>,
|
|
||||||
pub ignore: Option<Vec<CheckCode>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
struct Tools {
|
|
||||||
ruff: Option<Config>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
struct PyProject {
|
|
||||||
tool: Option<Tools>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_pyproject_toml(path: &Path) -> Result<PyProject> {
|
|
||||||
let contents = fs::read_file(path)?;
|
|
||||||
toml::from_str(&contents).map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_pyproject_toml(path: &Option<PathBuf>) -> Option<PathBuf> {
|
|
||||||
if let Some(path) = path {
|
|
||||||
let path_pyproject_toml = path.join("pyproject.toml");
|
|
||||||
if path_pyproject_toml.is_file() {
|
|
||||||
return Some(path_pyproject_toml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
find_user_pyproject_toml()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_user_pyproject_toml() -> Option<PathBuf> {
|
|
||||||
let mut path = dirs::config_dir()?;
|
|
||||||
path.push("ruff");
|
|
||||||
path.push("pyproject.toml");
|
|
||||||
if path.is_file() {
|
|
||||||
Some(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
|
|
||||||
let absolute_sources: Vec<PathBuf> = sources
|
|
||||||
.iter()
|
|
||||||
.flat_map(|source| source.absolutize().map(|path| path.to_path_buf()))
|
|
||||||
.collect();
|
|
||||||
if let Some(prefix) = common_path_all(absolute_sources.iter().map(PathBuf::as_path)) {
|
|
||||||
for directory in prefix.ancestors() {
|
|
||||||
if directory.join(".git").is_dir() {
|
|
||||||
return Some(directory.to_path_buf());
|
|
||||||
}
|
|
||||||
if directory.join(".hg").is_dir() {
|
|
||||||
return Some(directory.to_path_buf());
|
|
||||||
}
|
|
||||||
if directory.join("pyproject.toml").is_file() {
|
|
||||||
return Some(directory.to_path_buf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::env::current_dir;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::checks::CheckCode;
|
|
||||||
use crate::pyproject::{
|
|
||||||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Config, PyProject, Tools,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize() -> Result<()> {
|
|
||||||
let pyproject: PyProject = toml::from_str(r#""#)?;
|
|
||||||
assert_eq!(pyproject.tool, None);
|
|
||||||
|
|
||||||
let pyproject: PyProject = toml::from_str(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
assert_eq!(pyproject.tool, Some(Tools { ruff: None }));
|
|
||||||
|
|
||||||
let pyproject: PyProject = toml::from_str(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
assert_eq!(
|
|
||||||
pyproject.tool,
|
|
||||||
Some(Tools {
|
|
||||||
ruff: Some(Config {
|
|
||||||
line_length: None,
|
|
||||||
exclude: None,
|
|
||||||
extend_exclude: None,
|
|
||||||
select: None,
|
|
||||||
ignore: None,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let pyproject: PyProject = toml::from_str(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
line-length = 79
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
assert_eq!(
|
|
||||||
pyproject.tool,
|
|
||||||
Some(Tools {
|
|
||||||
ruff: Some(Config {
|
|
||||||
line_length: Some(79),
|
|
||||||
exclude: None,
|
|
||||||
extend_exclude: None,
|
|
||||||
select: None,
|
|
||||||
ignore: None,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let pyproject: PyProject = toml::from_str(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
exclude = ["foo.py"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
assert_eq!(
|
|
||||||
pyproject.tool,
|
|
||||||
Some(Tools {
|
|
||||||
ruff: Some(Config {
|
|
||||||
line_length: None,
|
|
||||||
exclude: Some(vec!["foo.py".to_string()]),
|
|
||||||
extend_exclude: None,
|
|
||||||
select: None,
|
|
||||||
ignore: None,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let pyproject: PyProject = toml::from_str(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
select = ["E501"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
assert_eq!(
|
|
||||||
pyproject.tool,
|
|
||||||
Some(Tools {
|
|
||||||
ruff: Some(Config {
|
|
||||||
line_length: None,
|
|
||||||
exclude: None,
|
|
||||||
extend_exclude: None,
|
|
||||||
select: Some(vec![CheckCode::E501]),
|
|
||||||
ignore: None,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let pyproject: PyProject = toml::from_str(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
ignore = ["E501"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
assert_eq!(
|
|
||||||
pyproject.tool,
|
|
||||||
Some(Tools {
|
|
||||||
ruff: Some(Config {
|
|
||||||
line_length: None,
|
|
||||||
exclude: None,
|
|
||||||
extend_exclude: None,
|
|
||||||
select: None,
|
|
||||||
ignore: Some(vec![CheckCode::E501]),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(toml::from_str::<PyProject>(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
line_length = 79
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
assert!(toml::from_str::<PyProject>(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
select = ["E123"]
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
assert!(toml::from_str::<PyProject>(
|
|
||||||
r#"
|
|
||||||
[tool.black]
|
|
||||||
[tool.ruff]
|
|
||||||
line-length = 79
|
|
||||||
other-attribute = 1
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn find_and_parse_pyproject_toml() -> Result<()> {
|
|
||||||
let cwd = current_dir()?;
|
|
||||||
let project_root =
|
|
||||||
find_project_root(&[PathBuf::from("resources/test/fixtures/__init__.py")])
|
|
||||||
.expect("Unable to find project root.");
|
|
||||||
assert_eq!(project_root, cwd.join("resources/test/fixtures"));
|
|
||||||
|
|
||||||
let path =
|
|
||||||
find_pyproject_toml(&Some(project_root)).expect("Unable to find pyproject.toml.");
|
|
||||||
assert_eq!(path, cwd.join("resources/test/fixtures/pyproject.toml"));
|
|
||||||
|
|
||||||
let pyproject = parse_pyproject_toml(&path)?;
|
|
||||||
let config = pyproject
|
|
||||||
.tool
|
|
||||||
.and_then(|tool| tool.ruff)
|
|
||||||
.expect("Unable to find tool.ruff.");
|
|
||||||
assert_eq!(
|
|
||||||
config,
|
|
||||||
Config {
|
|
||||||
line_length: Some(88),
|
|
||||||
exclude: None,
|
|
||||||
extend_exclude: Some(vec![
|
|
||||||
"excluded.py".to_string(),
|
|
||||||
"migrations".to_string(),
|
|
||||||
"directory/also_excluded.py".to_string(),
|
|
||||||
]),
|
|
||||||
select: None,
|
|
||||||
ignore: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
120
src/settings.rs
120
src/settings.rs
|
|
@ -1,68 +1,25 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use glob::Pattern;
|
use crate::checks::CheckCode;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::checks::{CheckCode, ALL_CHECK_CODES};
|
|
||||||
use crate::fs;
|
|
||||||
use crate::pyproject::load_config;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum FilePattern {
|
|
||||||
Simple(&'static str),
|
|
||||||
Complex(Pattern, Option<Pattern>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FilePattern {
|
|
||||||
pub fn from_user(pattern: &str, project_root: &Option<PathBuf>) -> Self {
|
|
||||||
let path = Path::new(pattern);
|
|
||||||
let absolute_path = match project_root {
|
|
||||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
|
||||||
None => fs::normalize_path(path),
|
|
||||||
};
|
|
||||||
|
|
||||||
let absolute = Pattern::new(&absolute_path.to_string_lossy()).expect("Invalid pattern.");
|
|
||||||
let basename = if !pattern.contains(std::path::MAIN_SEPARATOR) {
|
|
||||||
Some(Pattern::new(pattern).expect("Invalid pattern."))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
FilePattern::Complex(absolute, basename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub pyproject: Option<PathBuf>,
|
|
||||||
pub project_root: Option<PathBuf>,
|
|
||||||
pub line_length: usize,
|
pub line_length: usize,
|
||||||
pub exclude: Vec<FilePattern>,
|
|
||||||
pub extend_exclude: Vec<FilePattern>,
|
|
||||||
pub select: BTreeSet<CheckCode>,
|
pub select: BTreeSet<CheckCode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
pub fn for_rule(check_code: CheckCode) -> Self {
|
pub fn for_rule(check_code: CheckCode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pyproject: None,
|
|
||||||
project_root: None,
|
|
||||||
line_length: 88,
|
line_length: 88,
|
||||||
exclude: vec![],
|
|
||||||
extend_exclude: vec![],
|
|
||||||
select: BTreeSet::from([check_code]),
|
select: BTreeSet::from([check_code]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
|
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pyproject: None,
|
|
||||||
project_root: None,
|
|
||||||
line_length: 88,
|
line_length: 88,
|
||||||
exclude: vec![],
|
|
||||||
extend_exclude: vec![],
|
|
||||||
select: BTreeSet::from_iter(check_codes),
|
select: BTreeSet::from_iter(check_codes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,78 +33,3 @@ impl Hash for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
|
|
||||||
vec![
|
|
||||||
FilePattern::Simple(".bzr"),
|
|
||||||
FilePattern::Simple(".direnv"),
|
|
||||||
FilePattern::Simple(".eggs"),
|
|
||||||
FilePattern::Simple(".git"),
|
|
||||||
FilePattern::Simple(".hg"),
|
|
||||||
FilePattern::Simple(".mypy_cache"),
|
|
||||||
FilePattern::Simple(".nox"),
|
|
||||||
FilePattern::Simple(".pants.d"),
|
|
||||||
FilePattern::Simple(".ruff_cache"),
|
|
||||||
FilePattern::Simple(".svn"),
|
|
||||||
FilePattern::Simple(".tox"),
|
|
||||||
FilePattern::Simple(".venv"),
|
|
||||||
FilePattern::Simple("__pypackages__"),
|
|
||||||
FilePattern::Simple("_build"),
|
|
||||||
FilePattern::Simple("buck-out"),
|
|
||||||
FilePattern::Simple("build"),
|
|
||||||
FilePattern::Simple("dist"),
|
|
||||||
FilePattern::Simple("node_modules"),
|
|
||||||
FilePattern::Simple("venv"),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Settings {
|
|
||||||
pub fn from_pyproject(pyproject: Option<PathBuf>, project_root: Option<PathBuf>) -> Self {
|
|
||||||
let config = load_config(&pyproject);
|
|
||||||
let mut settings = Settings {
|
|
||||||
line_length: config.line_length.unwrap_or(88),
|
|
||||||
exclude: config
|
|
||||||
.exclude
|
|
||||||
.map(|paths| {
|
|
||||||
paths
|
|
||||||
.iter()
|
|
||||||
.map(|path| FilePattern::from_user(path, &project_root))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
|
||||||
extend_exclude: config
|
|
||||||
.extend_exclude
|
|
||||||
.map(|paths| {
|
|
||||||
paths
|
|
||||||
.iter()
|
|
||||||
.map(|path| FilePattern::from_user(path, &project_root))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
select: if let Some(select) = config.select {
|
|
||||||
BTreeSet::from_iter(select)
|
|
||||||
} else {
|
|
||||||
BTreeSet::from_iter(ALL_CHECK_CODES)
|
|
||||||
},
|
|
||||||
pyproject,
|
|
||||||
project_root,
|
|
||||||
};
|
|
||||||
if let Some(ignore) = &config.ignore {
|
|
||||||
settings.ignore(ignore);
|
|
||||||
}
|
|
||||||
settings
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&mut self, codes: Vec<CheckCode>) {
|
|
||||||
self.select.clear();
|
|
||||||
for code in codes {
|
|
||||||
self.select.insert(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ignore(&mut self, codes: &[CheckCode]) {
|
|
||||||
for code in codes {
|
|
||||||
self.select.remove(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue