|
| 1 | +use std::fmt::Formatter; |
| 2 | +use std::ops::Deref; |
| 3 | +use std::sync::Arc; |
| 4 | + |
| 5 | +use ruff_python_ast::{ModModule, PySourceType}; |
| 6 | +use ruff_python_parser::{parse_unchecked_source, Parsed}; |
| 7 | + |
| 8 | +use crate::source::source_text; |
| 9 | +use crate::vfs::{VfsFile, VfsPath}; |
| 10 | +use crate::Db; |
| 11 | + |
| 12 | +/// Returns the parsed AST of `file`, including its token stream. |
| 13 | +/// |
| 14 | +/// The query uses Ruff's error-resilient parser. That means that the parser always succeeds to produce a |
| 15 | +/// AST even if the file contains syntax errors. The parse errors |
| 16 | +/// are then accessible through [`Parsed::errors`]. |
| 17 | +/// |
| 18 | +/// The query is only cached when the [`source_text()`] hasn't changed. This is because |
| 19 | +/// comparing two ASTs is a non-trivial operation and every offset change is directly |
| 20 | +/// reflected in the changed AST offsets. |
| 21 | +/// The other reason is that Ruff's AST doesn't implement `Eq` which Sala requires |
| 22 | +/// for determining if a query result is unchanged. |
| 23 | +#[salsa::tracked(return_ref, no_eq)] |
| 24 | +pub fn parsed_module(db: &dyn Db, file: VfsFile) -> ParsedModule { |
| 25 | + let source = source_text(db, file); |
| 26 | + let path = file.path(db); |
| 27 | + |
| 28 | + let ty = match path { |
| 29 | + VfsPath::FileSystem(path) => path |
| 30 | + .extension() |
| 31 | + .map_or(PySourceType::Python, PySourceType::from_extension), |
| 32 | + VfsPath::Vendored(_) => PySourceType::Stub, |
| 33 | + }; |
| 34 | + |
| 35 | + ParsedModule { |
| 36 | + inner: Arc::new(parse_unchecked_source(&source, ty)), |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +/// Cheap cloneable wrapper around the parsed module. |
| 41 | +#[derive(Clone, PartialEq)] |
| 42 | +pub struct ParsedModule { |
| 43 | + inner: Arc<Parsed<ModModule>>, |
| 44 | +} |
| 45 | + |
| 46 | +impl ParsedModule { |
| 47 | + /// Consumes `self` and returns the Arc storing the parsed module. |
| 48 | + pub fn into_arc(self) -> Arc<Parsed<ModModule>> { |
| 49 | + self.inner |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +impl Deref for ParsedModule { |
| 54 | + type Target = Parsed<ModModule>; |
| 55 | + |
| 56 | + fn deref(&self) -> &Self::Target { |
| 57 | + &self.inner |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +impl std::fmt::Debug for ParsedModule { |
| 62 | + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 63 | + f.debug_tuple("ParsedModule").field(&self.inner).finish() |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +#[cfg(test)] |
| 68 | +mod tests { |
| 69 | + use crate::file_system::FileSystemPath; |
| 70 | + use crate::parsed::parsed_module; |
| 71 | + use crate::tests::TestDb; |
| 72 | + use crate::vfs::VendoredPath; |
| 73 | + use crate::Db; |
| 74 | + |
| 75 | + #[test] |
| 76 | + fn python_file() { |
| 77 | + let mut db = TestDb::new(); |
| 78 | + let path = FileSystemPath::new("test.py"); |
| 79 | + |
| 80 | + db.file_system_mut().write_file(path, "x = 10".to_string()); |
| 81 | + |
| 82 | + let file = db.file(path); |
| 83 | + |
| 84 | + let parsed = parsed_module(&db, file); |
| 85 | + |
| 86 | + assert!(parsed.is_valid()); |
| 87 | + } |
| 88 | + |
| 89 | + #[test] |
| 90 | + fn python_ipynb_file() { |
| 91 | + let mut db = TestDb::new(); |
| 92 | + let path = FileSystemPath::new("test.ipynb"); |
| 93 | + |
| 94 | + db.file_system_mut() |
| 95 | + .write_file(path, "%timeit a = b".to_string()); |
| 96 | + |
| 97 | + let file = db.file(path); |
| 98 | + |
| 99 | + let parsed = parsed_module(&db, file); |
| 100 | + |
| 101 | + assert!(parsed.is_valid()); |
| 102 | + } |
| 103 | + |
| 104 | + #[test] |
| 105 | + fn vendored_file() { |
| 106 | + let mut db = TestDb::new(); |
| 107 | + db.vfs_mut().stub_vendored([( |
| 108 | + "path.pyi", |
| 109 | + r#" |
| 110 | +import sys |
| 111 | +
|
| 112 | +if sys.platform == "win32": |
| 113 | + from ntpath import * |
| 114 | + from ntpath import __all__ as __all__ |
| 115 | +else: |
| 116 | + from posixpath import * |
| 117 | + from posixpath import __all__ as __all__"#, |
| 118 | + )]); |
| 119 | + |
| 120 | + let file = db.vendored_file(VendoredPath::new("path.pyi")).unwrap(); |
| 121 | + |
| 122 | + let parsed = parsed_module(&db, file); |
| 123 | + |
| 124 | + assert!(parsed.is_valid()); |
| 125 | + } |
| 126 | +} |
0 commit comments