Files
ruff/crates/red_knot_wasm/src/lib.rs
Micha Reiser c1837e4189 Rename custom-typeshed-dir, target-version and current-directory CLI options (#14930)
## Summary

This PR renames the `--custom-typeshed-dir`, `target-version`, and
`--current-directory` cli options to `--typeshed`,
`--python-version`, and `--project` as discussed in the CLI proposal
document.
I added aliases for `--target-version` (for Ruff compat) and
`--custom-typeshed-dir` (for Alex)

## Test Plan

Long help

```
An extremely fast Python type checker.

Usage: red_knot [OPTIONS] [COMMAND]

Commands:
  server  Start the language server
  help    Print this message or the help of the given subcommand(s)

Options:
      --project <PROJECT>
          Run the command within the given project directory.
          
          All `pyproject.toml` files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (`.venv`).
          
          Other command-line arguments (such as relative paths) will be resolved relative to the current working directory."#,

      --venv-path <PATH>
          Path to the virtual environment the project uses.
          
          If provided, red-knot will use the `site-packages` directory of this virtual environment to resolve type information for the project's third-party dependencies.

      --typeshed-path <PATH>
          Custom directory to use for stdlib typeshed stubs

      --extra-search-path <PATH>
          Additional path to use as a module-resolution source (can be passed multiple times)

      --python-version <VERSION>
          Python version to assume when resolving types
          
          [possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]

  -v, --verbose...
          Use verbose output (or `-vv` and `-vvv` for more verbose output)

  -W, --watch
          Run in watch mode by re-running whenever files change

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version
```

Short help 

```
An extremely fast Python type checker.

Usage: red_knot [OPTIONS] [COMMAND]

Commands:
  server  Start the language server
  help    Print this message or the help of the given subcommand(s)

Options:
      --project <PROJECT>         Run the command within the given project directory
      --venv-path <PATH>          Path to the virtual environment the project uses
      --typeshed-path <PATH>      Custom directory to use for stdlib typeshed stubs
      --extra-search-path <PATH>  Additional path to use as a module-resolution source (can be passed multiple times)
      --python-version <VERSION>  Python version to assume when resolving types [possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]
  -v, --verbose...                Use verbose output (or `-vv` and `-vvv` for more verbose output)
  -W, --watch                     Run in watch mode by re-running whenever files change
  -h, --help                      Print help (see more with '--help')
  -V, --version                   Print version

```

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-13 08:21:52 +00:00

307 lines
8.3 KiB
Rust

use std::any::Any;
use js_sys::Error;
use wasm_bindgen::prelude::*;
use red_knot_workspace::db::{Db, RootDatabase};
use red_knot_workspace::workspace::settings::Configuration;
use red_knot_workspace::workspace::WorkspaceMetadata;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{system_path_to_file, File};
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
use ruff_db::system::{
DirectoryEntry, GlobError, MemoryFileSystem, Metadata, PatternError, System, SystemPath,
SystemPathBuf, SystemVirtualPath,
};
use ruff_notebook::Notebook;
#[wasm_bindgen(start)]
pub fn run() {
use log::Level;
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
console_log::init_with_level(Level::Debug).expect("Initializing logger went wrong.");
}
#[wasm_bindgen]
pub struct Workspace {
db: RootDatabase,
system: WasmSystem,
}
#[wasm_bindgen]
impl Workspace {
#[wasm_bindgen(constructor)]
pub fn new(root: &str, settings: &Settings) -> Result<Workspace, Error> {
let system = WasmSystem::new(SystemPath::new(root));
let workspace = WorkspaceMetadata::discover(
SystemPath::new(root),
&system,
Some(&Configuration {
python_version: Some(settings.python_version.into()),
..Configuration::default()
}),
)
.map_err(into_error)?;
let db = RootDatabase::new(workspace, system.clone()).map_err(into_error)?;
Ok(Self { db, system })
}
#[wasm_bindgen(js_name = "openFile")]
pub fn open_file(&mut self, path: &str, contents: &str) -> Result<FileHandle, Error> {
self.system
.fs
.write_file(path, contents)
.map_err(into_error)?;
let file = system_path_to_file(&self.db, path).expect("File to exist");
file.sync(&mut self.db);
self.db.workspace().open_file(&mut self.db, file);
Ok(FileHandle {
file,
path: SystemPath::new(path).to_path_buf(),
})
}
#[wasm_bindgen(js_name = "updateFile")]
pub fn update_file(&mut self, file_id: &FileHandle, contents: &str) -> Result<(), Error> {
if !self.system.fs.exists(&file_id.path) {
return Err(Error::new("File does not exist"));
}
self.system
.fs
.write_file(&file_id.path, contents)
.map_err(into_error)?;
file_id.file.sync(&mut self.db);
Ok(())
}
#[wasm_bindgen(js_name = "closeFile")]
pub fn close_file(&mut self, file_id: &FileHandle) -> Result<(), Error> {
let file = file_id.file;
self.db.workspace().close_file(&mut self.db, file);
self.system
.fs
.remove_file(&file_id.path)
.map_err(into_error)?;
file.sync(&mut self.db);
Ok(())
}
/// Checks a single file.
#[wasm_bindgen(js_name = "checkFile")]
pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<String>, Error> {
let result = self.db.check_file(file_id.file).map_err(into_error)?;
Ok(result
.into_iter()
.map(|diagnostic| diagnostic.display(&self.db).to_string())
.collect())
}
/// Checks all open files
pub fn check(&self) -> Result<Vec<String>, Error> {
let result = self.db.check().map_err(into_error)?;
Ok(result
.into_iter()
.map(|diagnostic| diagnostic.display(&self.db).to_string())
.collect())
}
/// Returns the parsed AST for `path`
pub fn parsed(&self, file_id: &FileHandle) -> Result<String, Error> {
let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file);
Ok(format!("{:#?}", parsed.syntax()))
}
/// Returns the token stream for `path` serialized as a string.
pub fn tokens(&self, file_id: &FileHandle) -> Result<String, Error> {
let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file);
Ok(format!("{:#?}", parsed.tokens()))
}
#[wasm_bindgen(js_name = "sourceText")]
pub fn source_text(&self, file_id: &FileHandle) -> Result<String, Error> {
let source_text = ruff_db::source::source_text(&self.db, file_id.file);
Ok(source_text.to_string())
}
}
pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
Error::new(&err.to_string())
}
#[derive(Debug, Eq, PartialEq)]
#[wasm_bindgen(inspectable)]
pub struct FileHandle {
path: SystemPathBuf,
file: File,
}
#[wasm_bindgen]
impl FileHandle {
#[wasm_bindgen(js_name = toString)]
pub fn js_to_string(&self) -> String {
format!("file(id: {:?}, path: {})", self.file, self.path)
}
}
#[wasm_bindgen]
pub struct Settings {
pub python_version: PythonVersion,
}
#[wasm_bindgen]
impl Settings {
#[wasm_bindgen(constructor)]
pub fn new(python_version: PythonVersion) -> Self {
Self { python_version }
}
}
#[wasm_bindgen]
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum PythonVersion {
Py37,
Py38,
#[default]
Py39,
Py310,
Py311,
Py312,
Py313,
}
impl From<PythonVersion> for red_knot_python_semantic::PythonVersion {
fn from(value: PythonVersion) -> Self {
match value {
PythonVersion::Py37 => Self::PY37,
PythonVersion::Py38 => Self::PY38,
PythonVersion::Py39 => Self::PY39,
PythonVersion::Py310 => Self::PY310,
PythonVersion::Py311 => Self::PY311,
PythonVersion::Py312 => Self::PY312,
PythonVersion::Py313 => Self::PY313,
}
}
}
#[derive(Debug, Clone)]
struct WasmSystem {
fs: MemoryFileSystem,
}
impl WasmSystem {
fn new(root: &SystemPath) -> Self {
Self {
fs: MemoryFileSystem::with_current_directory(root),
}
}
}
impl System for WasmSystem {
fn path_metadata(&self, path: &SystemPath) -> ruff_db::system::Result<Metadata> {
self.fs.metadata(path)
}
fn canonicalize_path(&self, path: &SystemPath) -> ruff_db::system::Result<SystemPathBuf> {
self.fs.canonicalize(path)
}
fn read_to_string(&self, path: &SystemPath) -> ruff_db::system::Result<String> {
self.fs.read_to_string(path)
}
fn read_to_notebook(
&self,
path: &SystemPath,
) -> Result<ruff_notebook::Notebook, ruff_notebook::NotebookError> {
let content = self.read_to_string(path)?;
Notebook::from_source_code(&content)
}
fn read_virtual_path_to_string(
&self,
_path: &SystemVirtualPath,
) -> ruff_db::system::Result<String> {
Err(not_found())
}
fn read_virtual_path_to_notebook(
&self,
_path: &SystemVirtualPath,
) -> Result<Notebook, ruff_notebook::NotebookError> {
Err(ruff_notebook::NotebookError::Io(not_found()))
}
fn current_directory(&self) -> &SystemPath {
self.fs.current_directory()
}
fn read_directory<'a>(
&'a self,
path: &SystemPath,
) -> ruff_db::system::Result<
Box<dyn Iterator<Item = ruff_db::system::Result<DirectoryEntry>> + 'a>,
> {
Ok(Box::new(self.fs.read_directory(path)?))
}
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
self.fs.walk_directory(path)
}
fn glob(
&self,
pattern: &str,
) -> Result<Box<dyn Iterator<Item = Result<SystemPathBuf, GlobError>>>, PatternError> {
Ok(Box::new(self.fs.glob(pattern)?))
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
fn not_found() -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::NotFound, "No such file or directory")
}
#[cfg(test)]
mod tests {
use crate::PythonVersion;
#[test]
fn same_default_as_python_version() {
assert_eq!(
red_knot_python_semantic::PythonVersion::from(PythonVersion::default()),
red_knot_python_semantic::PythonVersion::default()
);
}
}