Skip to content

Commit

Permalink
feat: Rust language (comments, doc comments, strings)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpovel committed Dec 3, 2023
1 parent 6649c25 commit f8910c8
Show file tree
Hide file tree
Showing 14 changed files with 275 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ rayon = "1.7.0"
glob = "0.3.1"
const_format = "0.2.32"
tree-sitter-go = "0.20.0"
tree-sitter-rust = "0.20.4"

[features]
german = ["cached", "decompound", "fst", "once_cell"]
Expand Down
29 changes: 29 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use srgn::actions::Upper;
use srgn::actions::{Symbols, SymbolsInversion};
use srgn::scoping::langs::go::Go;
use srgn::scoping::langs::go::GoQuery;
use srgn::scoping::langs::rust::Rust;
use srgn::scoping::langs::rust::RustQuery;
use srgn::scoping::literal::LiteralError;
use srgn::scoping::regex::RegexError;
use srgn::{
Expand Down Expand Up @@ -296,6 +298,18 @@ fn assemble_scopers(args: &cli::Cli) -> Result<Vec<Box<dyn Scoper>>> {
}
}

if let Some(rust) = args.languages_scopes.rust.clone() {
if let Some(premade) = rust.rust {
let query = RustQuery::Premade(premade);

scopers.push(Box::new(Rust::new(query)));
} else if let Some(custom) = rust.rust_query {
let query = RustQuery::Custom(custom);

scopers.push(Box::new(Rust::new(query)));
}
}

if let Some(csharp) = args.languages_scopes.csharp.clone() {
if let Some(premade) = csharp.csharp {
let query = CSharpQuery::Premade(premade);
Expand Down Expand Up @@ -415,6 +429,7 @@ mod cli {
csharp::{CustomCSharpQuery, PremadeCSharpQuery},
go::{CustomGoQuery, PremadeGoQuery},
python::{CustomPythonQuery, PremadePythonQuery},
rust::{CustomRustQuery, PremadeRustQuery},
typescript::{CustomTypeScriptQuery, PremadeTypeScriptQuery},
},
GLOBAL_SCOPE,
Expand Down Expand Up @@ -617,6 +632,8 @@ mod cli {
#[command(flatten)]
pub typescript: Option<TypeScriptScope>,
#[command(flatten)]
pub rust: Option<RustScope>,
#[command(flatten)]
pub csharp: Option<CSharpScope>,
}

Expand Down Expand Up @@ -644,6 +661,18 @@ mod cli {
pub go_query: Option<CustomGoQuery>,
}

#[derive(Parser, Debug, Clone)]
#[group(required = false, multiple = false)]
pub(super) struct RustScope {
/// Scope Rust code using a premade query.
#[arg(long, env, verbatim_doc_comment)]
pub rust: Option<PremadeRustQuery>,

/// Scope Rust code using a custom tree-sitter query.
#[arg(long, env, verbatim_doc_comment)]
pub rust_query: Option<CustomRustQuery>,
}

#[derive(Parser, Debug, Clone)]
#[group(required = false, multiple = false)]
pub(super) struct TypeScriptScope {
Expand Down
2 changes: 2 additions & 0 deletions src/scoping/langs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub mod csharp;
pub mod go;
/// Python.
pub mod python;
/// Rust.
pub mod rust;
/// TypeScript.
pub mod typescript;

Expand Down
100 changes: 100 additions & 0 deletions src/scoping/langs/rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use super::{CodeQuery, Language, LanguageScoper, TSLanguage, TSQuery};
use crate::scoping::{ROScopes, Scoper};
use clap::ValueEnum;
use std::{fmt::Debug, str::FromStr};
use tree_sitter::QueryError;

/// The Rust language.
pub type Rust = Language<RustQuery>;
/// A query for Rust.
pub type RustQuery = CodeQuery<CustomRustQuery, PremadeRustQuery>;

/// Premade tree-sitter queries for Rust.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum PremadeRustQuery {
/// Comments (line and block styles; excluding doc comments; comment chars incl.).
Comments,
/// Doc comments (comment chars included).
DocComments,
/// Strings (regular, raw, byte; includes interpolation parts in format strings!).
///
/// There is currently no support for an 'interpolation' type node in
/// tree-sitter-rust (like there is in TypeScript and Python, for example).
Strings,
}

impl From<PremadeRustQuery> for TSQuery {
fn from(value: PremadeRustQuery) -> Self {
TSQuery::new(
Rust::lang(),
match value {
PremadeRustQuery::Comments => {
r#"
[
(line_comment)+ @line
(block_comment)
(#not-match? @line "^///")
]
@comment
"#
}
PremadeRustQuery::DocComments => {
r#"
(
(line_comment)+ @line
(#match? @line "^///")
)
"#
}
PremadeRustQuery::Strings => {
r#"
[
(string_literal)
(raw_string_literal)
]
@string
"#
}
},
)
.expect("Premade queries to be valid")
}
}

/// A custom tree-sitter query for Rust.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CustomRustQuery(String);

impl FromStr for CustomRustQuery {
type Err = QueryError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match TSQuery::new(Rust::lang(), s) {
Ok(_) => Ok(Self(s.to_string())),
Err(e) => Err(e),
}
}
}

impl From<CustomRustQuery> for TSQuery {
fn from(value: CustomRustQuery) -> Self {
TSQuery::new(Rust::lang(), &value.0)
.expect("Valid query, as object cannot be constructed otherwise")
}
}

impl Scoper for Rust {
fn scope<'viewee>(&self, input: &'viewee str) -> ROScopes<'viewee> {
ROScopes::from_raw_ranges(input, Self::scope_via_query(&mut self.query(), input))
}
}

impl LanguageScoper for Rust {
fn lang() -> TSLanguage {
tree_sitter_rust::language()
}

fn query(&self) -> TSQuery {
self.query.clone().into()
}
}
1 change: 1 addition & 0 deletions tests/langs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod csharp;
mod go;
mod python;
mod rust;
mod typescript;

use std::{fs::read_to_string, path::Path};
Expand Down
18 changes: 18 additions & 0 deletions tests/langs/rust/in/comments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// __T__A doc comment.
///
/// More context.__T__
///
/// ## Some section
///
/// Some context.__T__
pub fn doc__T__comment(left__T__: u8, right: u8) {
// This is a normal__T__ comment.
// Another line__T__ for the comment.
let _ = left__T__ + right;

/*
This is a__T__ block comment.
It also has multiple lines.__T__
*/
let _ = left__T__ - right;
}
18 changes: 18 additions & 0 deletions tests/langs/rust/in/doc-comments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// __T__A doc comment.
///
/// More context.__T__
///
/// ## Some section
///
/// Some context.__T__
pub fn doc__T__comment(left__T__: u8, right: u8) {
// This is a normal__T__ comment.
// Another line__T__ for the comment.
let _ = left__T__ + right;

/*
This is a__T__ block comment.
It also has multiple lines.__T__
*/
let _ = left__T__ - right;
}
20 changes: 20 additions & 0 deletions tests/langs/rust/in/strings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fn main() {
let regular = "Hello,__T__ world!";

let raw = r#"This is__T__ a raw string: \n won't escape__T__"#;

let byte_str = b"byte string__T__";

let byte_literal = br#"Byte string__T__ literal"#;

let value = 10;
let formatted = format!("Va__T__lue: __T__{}__T__", value);

let value__T__ = 10;
let formatted = format!("Va__T__lue: __T__{}__T__", value__T__);

let value__T__ = 10;
// CAUTION: This is nuked incorrectly; tree-sitter doesn't have an "interpolation"
// node for Rust like has for typescript, for example.
let formatted = format!("Va__T__lue: __T__{value__T__}__T__");
}
17 changes: 17 additions & 0 deletions tests/langs/rust/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use rstest::rstest;
use srgn::scoping::langs::rust::{PremadeRustQuery, Rust, RustQuery};

use super::{get_input_output, nuke_target};

#[rstest]
#[case("comments.rs", RustQuery::Premade(PremadeRustQuery::Comments))]
#[case("doc-comments.rs", RustQuery::Premade(PremadeRustQuery::DocComments))]
#[case("strings.rs", RustQuery::Premade(PremadeRustQuery::Strings))]
fn test_rust_nuke(#[case] file: &str, #[case] query: RustQuery) {
let lang = Rust::new(query);

let (input, output) = get_input_output("rust", file);
let result = nuke_target(&input, &lang);

assert_eq!(result, output);
}
18 changes: 18 additions & 0 deletions tests/langs/rust/out/comments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// __T__A doc comment.
///
/// More context.__T__
///
/// ## Some section
///
/// Some context.__T__
pub fn doc__T__comment(left__T__: u8, right: u8) {
// This is a normal comment.
// Another line for the comment.
let _ = left__T__ + right;

/*
This is a block comment.
It also has multiple lines.
*/
let _ = left__T__ - right;
}
18 changes: 18 additions & 0 deletions tests/langs/rust/out/doc-comments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// A doc comment.
///
/// More context.
///
/// ## Some section
///
/// Some context.
pub fn doc__T__comment(left__T__: u8, right: u8) {
// This is a normal__T__ comment.
// Another line__T__ for the comment.
let _ = left__T__ + right;

/*
This is a__T__ block comment.
It also has multiple lines.__T__
*/
let _ = left__T__ - right;
}
20 changes: 20 additions & 0 deletions tests/langs/rust/out/strings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fn main() {
let regular = "Hello, world!";

let raw = r#"This is a raw string: \n won't escape"#;

let byte_str = b"byte string";

let byte_literal = br#"Byte string literal"#;

let value = 10;
let formatted = format!("Value: {}", value);

let value__T__ = 10;
let formatted = format!("Value: {}", value__T__);

let value__T__ = 10;
// CAUTION: This is nuked incorrectly; tree-sitter doesn't have an "interpolation"
// node for Rust like has for typescript, for example.
let formatted = format!("Value: {value}");
}
2 changes: 2 additions & 0 deletions tests/readme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,8 @@ mod tests {
tag("go"),
tag("python-query"),
tag("python"),
tag("rust-query"),
tag("rust"),
tag("typescript-query"),
tag("typescript"),
)),
Expand Down

0 comments on commit f8910c8

Please sign in to comment.