1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
#!/usr/bin/env python3
"""Generate boilerplate for a new Flake8 plugin.
Example usage:
python scripts/add_plugin.py \
flake8-pie \
--url https://pypi.org/project/flake8-pie/ \
--prefix PIE
"""
from __future__ import annotations
import argparse
from _utils import ROOT_DIR, dir_name, get_indent, pascal_case
def main(*, plugin: str, url: str, prefix_code: str) -> None:
"""Generate boilerplate for a new plugin."""
# Create the test fixture folder.
(ROOT_DIR / "crates/ruff_linter/resources/test/fixtures" / dir_name(plugin)).mkdir(
exist_ok=True,
)
# Create the Plugin rules module.
plugin_dir = ROOT_DIR / "crates/ruff_linter/src/rules" / dir_name(plugin)
plugin_dir.mkdir(exist_ok=True)
with (plugin_dir / "mod.rs").open("w+") as fp:
fp.write(f"//! Rules from [{plugin}]({url}).\n")
fp.write("pub(crate) mod rules;\n")
fp.write("\n")
fp.write(
"""#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_messages, settings};
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("%s").join(path).as_path(),
&settings::Settings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}
"""
% dir_name(plugin),
)
# Create a subdirectory for rules and create a `mod.rs` placeholder
rules_dir = plugin_dir / "rules"
rules_dir.mkdir(exist_ok=True)
(rules_dir / "mod.rs").touch()
# Create the snapshots subdirectory
(plugin_dir / "snapshots").mkdir(exist_ok=True)
# Add the plugin to `rules/mod.rs`.
rules_mod_path = ROOT_DIR / "crates/ruff_linter/src/rules/mod.rs"
lines = rules_mod_path.read_text().strip().splitlines()
lines.append(f"pub mod {dir_name(plugin)};")
lines.sort()
rules_mod_path.write_text("\n".join(lines) + "\n")
# Add the relevant sections to `src/registry.rs`.
content = (ROOT_DIR / "crates/ruff_linter/src/registry.rs").read_text()
with (ROOT_DIR / "crates/ruff_linter/src/registry.rs").open("w") as fp:
for line in content.splitlines():
indent = get_indent(line)
if line.strip() == "// ruff":
fp.write(f"{indent}// {plugin}")
fp.write("\n")
elif line.strip() == "/// Ruff-specific rules":
fp.write(f"{indent}/// [{plugin}]({url})\n")
fp.write(f'{indent}#[prefix = "{prefix_code}"]\n')
fp.write(f"{indent}{pascal_case(plugin)},")
fp.write("\n")
fp.write(line)
fp.write("\n")
text = ""
with (ROOT_DIR / "crates/ruff_linter/src/codes.rs").open("r") as fp:
while (line := next(fp)).strip() != "// ruff":
text += line
text += " " * 8 + f"// {plugin}\n\n"
text += line
text += fp.read()
with (ROOT_DIR / "crates/ruff_linter/src/codes.rs").open("w") as fp:
fp.write(text)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate boilerplate for a new Flake8 plugin.",
epilog=(
"Example usage: python scripts/add_plugin.py flake8-pie "
"--url https://pypi.org/project/flake8-pie/"
),
)
parser.add_argument(
"plugin",
type=str,
help="The name of the plugin to generate.",
)
parser.add_argument(
"--url",
required=True,
type=str,
help="The URL of the latest release in PyPI.",
)
parser.add_argument(
"--prefix",
required=False,
default="TODO",
type=str,
help="Prefix code for the plugin. Leave empty to manually fill.",
)
args = parser.parse_args()
main(plugin=args.plugin, url=args.url, prefix_code=args.prefix)
|