mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:28 +00:00
playground: Add AST/Tokens/Formatter panels (#5859)
This commit is contained in:
parent
9ed7ceeb0a
commit
46a17d11f3
19 changed files with 1343 additions and 780 deletions
|
@ -1,6 +1,9 @@
|
|||
use std::path::Path;
|
||||
|
||||
use js_sys::Error;
|
||||
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::{parse_tokens, Mode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
|
@ -18,7 +21,10 @@ use ruff::rules::{
|
|||
use ruff::settings::configuration::Configuration;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::{defaults, flags, Settings};
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, SourceLocation, Stylist};
|
||||
use ruff_python_ast::source_code::{
|
||||
CommentRangesBuilder, Indexer, Locator, SourceLocation, Stylist,
|
||||
};
|
||||
use ruff_python_formatter::{format_module, format_node, PyFormatOptions};
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const TYPES: &'static str = r#"
|
||||
|
@ -89,157 +95,213 @@ pub fn run() {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn currentVersion() -> JsValue {
|
||||
JsValue::from(ruff::VERSION)
|
||||
pub struct Workspace {
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn defaultSettings() -> Result<JsValue, JsValue> {
|
||||
Ok(serde_wasm_bindgen::to_value(&Options {
|
||||
// Propagate defaults.
|
||||
allowed_confusables: Some(Vec::default()),
|
||||
builtins: Some(Vec::default()),
|
||||
dummy_variable_rgx: Some(defaults::DUMMY_VARIABLE_RGX.as_str().to_string()),
|
||||
extend_fixable: Some(Vec::default()),
|
||||
extend_ignore: Some(Vec::default()),
|
||||
extend_select: Some(Vec::default()),
|
||||
extend_unfixable: Some(Vec::default()),
|
||||
external: Some(Vec::default()),
|
||||
ignore: Some(Vec::default()),
|
||||
line_length: Some(LineLength::default()),
|
||||
tab_size: Some(TabSize::default()),
|
||||
select: Some(defaults::PREFIXES.to_vec()),
|
||||
target_version: Some(defaults::TARGET_VERSION),
|
||||
// Ignore a bunch of options that don't make sense in a single-file editor.
|
||||
cache_dir: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_include: None,
|
||||
extend_per_file_ignores: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
force_exclude: None,
|
||||
format: None,
|
||||
include: None,
|
||||
ignore_init_module_imports: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
show_fixes: None,
|
||||
show_source: None,
|
||||
src: None,
|
||||
task_tags: None,
|
||||
typing_modules: None,
|
||||
unfixable: None,
|
||||
// Use default options for all plugins.
|
||||
flake8_annotations: Some(flake8_annotations::settings::Settings::default().into()),
|
||||
flake8_bandit: Some(flake8_bandit::settings::Settings::default().into()),
|
||||
flake8_bugbear: Some(flake8_bugbear::settings::Settings::default().into()),
|
||||
flake8_builtins: Some(flake8_builtins::settings::Settings::default().into()),
|
||||
flake8_comprehensions: Some(flake8_comprehensions::settings::Settings::default().into()),
|
||||
flake8_copyright: Some(flake8_copyright::settings::Settings::default().into()),
|
||||
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
|
||||
flake8_gettext: Some(flake8_gettext::settings::Settings::default().into()),
|
||||
flake8_implicit_str_concat: Some(
|
||||
flake8_implicit_str_concat::settings::Settings::default().into(),
|
||||
),
|
||||
flake8_import_conventions: Some(
|
||||
flake8_import_conventions::settings::Settings::default().into(),
|
||||
),
|
||||
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
|
||||
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
|
||||
flake8_self: Some(flake8_self::settings::Settings::default().into()),
|
||||
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Settings::default().into()),
|
||||
flake8_type_checking: Some(flake8_type_checking::settings::Settings::default().into()),
|
||||
flake8_unused_arguments: Some(
|
||||
flake8_unused_arguments::settings::Settings::default().into(),
|
||||
),
|
||||
isort: Some(isort::settings::Settings::default().into()),
|
||||
mccabe: Some(mccabe::settings::Settings::default().into()),
|
||||
pep8_naming: Some(pep8_naming::settings::Settings::default().into()),
|
||||
pycodestyle: Some(pycodestyle::settings::Settings::default().into()),
|
||||
pydocstyle: Some(pydocstyle::settings::Settings::default().into()),
|
||||
pyflakes: Some(pyflakes::settings::Settings::default().into()),
|
||||
pylint: Some(pylint::settings::Settings::default().into()),
|
||||
pyupgrade: Some(pyupgrade::settings::Settings::default().into()),
|
||||
})?)
|
||||
}
|
||||
impl Workspace {
|
||||
pub fn version() -> String {
|
||||
ruff::VERSION.to_string()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
let options: Options = serde_wasm_bindgen::from_value(options).map_err(|e| e.to_string())?;
|
||||
let configuration =
|
||||
Configuration::from_options(options, Path::new(".")).map_err(|e| e.to_string())?;
|
||||
let settings =
|
||||
Settings::from_configuration(configuration, Path::new(".")).map_err(|e| e.to_string())?;
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(options: JsValue) -> Result<Workspace, Error> {
|
||||
let options: Options = serde_wasm_bindgen::from_value(options).map_err(into_error)?;
|
||||
let configuration =
|
||||
Configuration::from_options(options, Path::new(".")).map_err(into_error)?;
|
||||
let settings =
|
||||
Settings::from_configuration(configuration, Path::new(".")).map_err(into_error)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
Ok(Workspace { settings })
|
||||
}
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extra indices from the code.
|
||||
let indexer = Indexer::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives =
|
||||
directives::extract_directives(&tokens, directives::Flags::empty(), &locator, &indexer);
|
||||
|
||||
// Generate checks.
|
||||
let LinterResult {
|
||||
data: (diagnostics, _imports),
|
||||
..
|
||||
} = check_path(
|
||||
Path::new("<filename>"),
|
||||
None,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
&directives,
|
||||
&settings,
|
||||
flags::Noqa::Enabled,
|
||||
None,
|
||||
);
|
||||
|
||||
let source_code = locator.to_source_code();
|
||||
|
||||
let messages: Vec<ExpandedMessage> = diagnostics
|
||||
.into_iter()
|
||||
.map(|message| {
|
||||
let start_location = source_code.source_location(message.start());
|
||||
let end_location = source_code.source_location(message.end());
|
||||
|
||||
ExpandedMessage {
|
||||
code: message.kind.rule().noqa_code().to_string(),
|
||||
message: message.kind.body,
|
||||
location: start_location,
|
||||
end_location,
|
||||
fix: message.fix.map(|fix| ExpandedFix {
|
||||
message: message.kind.suggestion,
|
||||
edits: fix
|
||||
.into_edits()
|
||||
.into_iter()
|
||||
.map(|edit| ExpandedEdit {
|
||||
location: source_code.source_location(edit.start()),
|
||||
end_location: source_code.source_location(edit.end()),
|
||||
content: edit.content().map(ToString::to_string),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
}
|
||||
#[wasm_bindgen(js_name=defaultSettings)]
|
||||
pub fn default_settings() -> Result<JsValue, Error> {
|
||||
serde_wasm_bindgen::to_value(&Options {
|
||||
// Propagate defaults.
|
||||
allowed_confusables: Some(Vec::default()),
|
||||
builtins: Some(Vec::default()),
|
||||
dummy_variable_rgx: Some(defaults::DUMMY_VARIABLE_RGX.as_str().to_string()),
|
||||
extend_fixable: Some(Vec::default()),
|
||||
extend_ignore: Some(Vec::default()),
|
||||
extend_select: Some(Vec::default()),
|
||||
extend_unfixable: Some(Vec::default()),
|
||||
external: Some(Vec::default()),
|
||||
ignore: Some(Vec::default()),
|
||||
line_length: Some(LineLength::default()),
|
||||
tab_size: Some(TabSize::default()),
|
||||
select: Some(defaults::PREFIXES.to_vec()),
|
||||
target_version: Some(defaults::TARGET_VERSION),
|
||||
// Ignore a bunch of options that don't make sense in a single-file editor.
|
||||
cache_dir: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_include: None,
|
||||
extend_per_file_ignores: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
force_exclude: None,
|
||||
format: None,
|
||||
include: None,
|
||||
ignore_init_module_imports: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
show_fixes: None,
|
||||
show_source: None,
|
||||
src: None,
|
||||
task_tags: None,
|
||||
typing_modules: None,
|
||||
unfixable: None,
|
||||
// Use default options for all plugins.
|
||||
flake8_annotations: Some(flake8_annotations::settings::Settings::default().into()),
|
||||
flake8_bandit: Some(flake8_bandit::settings::Settings::default().into()),
|
||||
flake8_bugbear: Some(flake8_bugbear::settings::Settings::default().into()),
|
||||
flake8_builtins: Some(flake8_builtins::settings::Settings::default().into()),
|
||||
flake8_comprehensions: Some(
|
||||
flake8_comprehensions::settings::Settings::default().into(),
|
||||
),
|
||||
flake8_copyright: Some(flake8_copyright::settings::Settings::default().into()),
|
||||
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
|
||||
flake8_gettext: Some(flake8_gettext::settings::Settings::default().into()),
|
||||
flake8_implicit_str_concat: Some(
|
||||
flake8_implicit_str_concat::settings::Settings::default().into(),
|
||||
),
|
||||
flake8_import_conventions: Some(
|
||||
flake8_import_conventions::settings::Settings::default().into(),
|
||||
),
|
||||
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
|
||||
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
|
||||
flake8_self: Some(flake8_self::settings::Settings::default().into()),
|
||||
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Settings::default().into()),
|
||||
flake8_type_checking: Some(flake8_type_checking::settings::Settings::default().into()),
|
||||
flake8_unused_arguments: Some(
|
||||
flake8_unused_arguments::settings::Settings::default().into(),
|
||||
),
|
||||
isort: Some(isort::settings::Settings::default().into()),
|
||||
mccabe: Some(mccabe::settings::Settings::default().into()),
|
||||
pep8_naming: Some(pep8_naming::settings::Settings::default().into()),
|
||||
pycodestyle: Some(pycodestyle::settings::Settings::default().into()),
|
||||
pydocstyle: Some(pydocstyle::settings::Settings::default().into()),
|
||||
pyflakes: Some(pyflakes::settings::Settings::default().into()),
|
||||
pylint: Some(pylint::settings::Settings::default().into()),
|
||||
pyupgrade: Some(pyupgrade::settings::Settings::default().into()),
|
||||
})
|
||||
.collect();
|
||||
.map_err(into_error)
|
||||
}
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&messages)?)
|
||||
pub fn check(&self, contents: &str) -> Result<JsValue, Error> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extra indices from the code.
|
||||
let indexer = Indexer::from_tokens(&tokens, &locator);
|
||||
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives =
|
||||
directives::extract_directives(&tokens, directives::Flags::empty(), &locator, &indexer);
|
||||
|
||||
// Generate checks.
|
||||
let LinterResult {
|
||||
data: (diagnostics, _imports),
|
||||
..
|
||||
} = check_path(
|
||||
Path::new("<filename>"),
|
||||
None,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
&directives,
|
||||
&self.settings,
|
||||
flags::Noqa::Enabled,
|
||||
None,
|
||||
);
|
||||
|
||||
let source_code = locator.to_source_code();
|
||||
|
||||
let messages: Vec<ExpandedMessage> = diagnostics
|
||||
.into_iter()
|
||||
.map(|message| {
|
||||
let start_location = source_code.source_location(message.start());
|
||||
let end_location = source_code.source_location(message.end());
|
||||
|
||||
ExpandedMessage {
|
||||
code: message.kind.rule().noqa_code().to_string(),
|
||||
message: message.kind.body,
|
||||
location: start_location,
|
||||
end_location,
|
||||
fix: message.fix.map(|fix| ExpandedFix {
|
||||
message: message.kind.suggestion,
|
||||
edits: fix
|
||||
.into_edits()
|
||||
.into_iter()
|
||||
.map(|edit| ExpandedEdit {
|
||||
location: source_code.source_location(edit.start()),
|
||||
end_location: source_code.source_location(edit.end()),
|
||||
content: edit.content().map(ToString::to_string),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
serde_wasm_bindgen::to_value(&messages).map_err(into_error)
|
||||
}
|
||||
|
||||
pub fn format(&self, contents: &str) -> Result<String, Error> {
|
||||
let printed = format_module(contents, PyFormatOptions::default()).map_err(into_error)?;
|
||||
|
||||
Ok(printed.into_code())
|
||||
}
|
||||
|
||||
pub fn format_ir(&self, contents: &str) -> Result<String, Error> {
|
||||
let tokens: Vec<_> = rustpython_parser::lexer::lex(contents, Mode::Module).collect();
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for (token, range) in tokens.iter().flatten() {
|
||||
comment_ranges.visit_token(token, *range);
|
||||
}
|
||||
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
let module = parse_tokens(tokens, Mode::Module, ".").map_err(into_error)?;
|
||||
|
||||
let formatted = format_node(
|
||||
&module,
|
||||
&comment_ranges,
|
||||
contents,
|
||||
PyFormatOptions::default(),
|
||||
)
|
||||
.map_err(into_error)?;
|
||||
|
||||
Ok(format!("{formatted}"))
|
||||
}
|
||||
|
||||
/// Parses the content and returns its AST
|
||||
pub fn parse(&self, contents: &str) -> Result<String, Error> {
|
||||
let parsed = rustpython_parser::parse(contents, Mode::Module, ".").map_err(into_error)?;
|
||||
|
||||
Ok(format!("{parsed:#?}"))
|
||||
}
|
||||
|
||||
pub fn tokens(&self, contents: &str) -> Result<String, Error> {
|
||||
let tokens: Vec<_> = rustpython_parser::lexer::lex(contents, Mode::Module).collect();
|
||||
|
||||
Ok(format!("{tokens:#?}"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
|
||||
Error::new(&err.to_string())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue