Skip to content

Commit d200c68

Browse files
authored
refactor: construct reusable source code information only once (#21)
Closes #11.
2 parents e3f12c5 + d96f578 commit d200c68

File tree

18 files changed

+123
-115
lines changed

18 files changed

+123
-115
lines changed

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use codespan_reporting::{
3434
},
3535
};
3636
use crashlog::cargo_metadata;
37+
use rules::api::SourceInfo;
3738
use tree_sitter::{Parser, Tree};
3839

3940
pub mod helpers;
@@ -145,8 +146,9 @@ fn main() -> ExitCode {
145146

146147
// Do checks
147148
let rules: Vec<Box<dyn Rule>> = crate::rules::get_rules();
149+
let source = SourceInfo::new(&code);
148150
for rule in rules {
149-
let diagnostics = rule.check(&tree, &code);
151+
let diagnostics = rule.check(&source);
150152
for diagnostic in diagnostics {
151153
match cli.format {
152154
OutputFormat::Pretty => {

src/rules/api.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@
1717
use codespan_reporting::diagnostic::Diagnostic;
1818
use tree_sitter::Tree;
1919

20+
use crate::helpers::LinesWithPosition;
21+
22+
pub struct SourceInfo<'src> {
23+
pub tree: Tree,
24+
pub code: &'src str,
25+
pub lines: Box<[(&'src str, usize)]>,
26+
}
27+
28+
impl<'src> SourceInfo<'src> {
29+
pub fn new(code: &'src str) -> Self {
30+
let mut parser = tree_sitter::Parser::new();
31+
parser
32+
.set_language(&tree_sitter_c::LANGUAGE.into())
33+
.expect("Failed to set language");
34+
let tree = parser.parse(code, None).expect("Failed to parse code");
35+
let lines = LinesWithPosition::from(code).collect();
36+
Self { tree, code, lines }
37+
}
38+
}
39+
2040
/// Represents a linter rule.
2141
pub trait Rule {
2242
/// Checks a source file for compliance with this rule.
@@ -27,5 +47,5 @@ pub trait Rule {
2747
/// - `tree`: [`Tree`] representing the file.
2848
/// - `code`: Text/code of the given file.
2949
#[must_use]
30-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>>;
50+
fn check(&self, source: &SourceInfo) -> Vec<Diagnostic<()>>;
3151
}

src/rules/rule01a.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@
3232
3333
use codespan_reporting::diagnostic::{Diagnostic, Label};
3434
use indoc::indoc;
35-
use tree_sitter::Tree;
3635

3736
use crate::{helpers::QueryHelper, rules::api::Rule};
3837

38+
use crate::rules::api::SourceInfo;
39+
3940
const QUERY_STR: &str = indoc! { /* query */ r#"
4041
(
4142
[
@@ -65,7 +66,7 @@ const QUERY_STR: &str = indoc! { /* query */ r#"
6566
pub struct Rule01a {}
6667

6768
impl Rule for Rule01a {
68-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
69+
fn check(&self, SourceInfo { tree, code, .. }: &SourceInfo) -> Vec<Diagnostic<()>> {
6970
let helper = QueryHelper::new(QUERY_STR, tree, code);
7071
let mut diagnostics = Vec::new();
7172
helper.for_each_capture(|_label, capture| {

src/rules/rule01b.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,16 @@
3636
//! - Make this rule produce a table of all declared identifiers at the end of parsing.
3737
3838
use codespan_reporting::diagnostic::Diagnostic;
39-
use tree_sitter::Tree;
4039

41-
use crate::rules::api::Rule;
40+
use crate::rules::api::{Rule, SourceInfo};
4241

4342
/// # Rule I:B.
4443
///
4544
/// See module-level documentation for details.
4645
pub struct Rule01b {}
4746

4847
impl Rule for Rule01b {
49-
fn check(&self, _tree: &Tree, _code: &str) -> Vec<Diagnostic<()>> {
48+
fn check(&self, _: &SourceInfo) -> Vec<Diagnostic<()>> {
5049
Vec::with_capacity(0)
5150
}
5251
}

src/rules/rule01c.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@
4040
4141
use codespan_reporting::diagnostic::{Diagnostic, Label};
4242
use indoc::indoc;
43-
use tree_sitter::{QueryCapture, Tree};
43+
use tree_sitter::QueryCapture;
4444

4545
use crate::{helpers::QueryHelper, rules::api::Rule};
4646

47+
use crate::rules::api::SourceInfo;
48+
4749
/// Tree-sitter query for Rule I:C.
4850
const QUERY_STR: &str = indoc! { /* query */ r#"
4951
(
@@ -66,7 +68,7 @@ const QUERY_STR: &str = indoc! { /* query */ r#"
6668
pub struct Rule01c {}
6769

6870
impl Rule for Rule01c {
69-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
71+
fn check(&self, SourceInfo { tree, code, .. }: &SourceInfo) -> Vec<Diagnostic<()>> {
7072
let helper = QueryHelper::new(QUERY_STR, tree, code);
7173
let mut diagnostics = Vec::new();
7274
helper.for_each_capture(|name: &str, capture: QueryCapture| {

src/rules/rule01d.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
3333
use codespan_reporting::diagnostic::{Diagnostic, Label};
3434
use indoc::indoc;
35-
use tree_sitter::{QueryCapture, Tree};
35+
use tree_sitter::QueryCapture;
3636

37-
use crate::{helpers::QueryHelper, rules::api::Rule};
37+
use crate::{
38+
helpers::QueryHelper,
39+
rules::api::{Rule, SourceInfo},
40+
};
3841

3942
/// Tree-sitter query for Rule I:D.
4043
const QUERY_STR: &str = indoc! {
@@ -64,7 +67,7 @@ const QUERY_STR: &str = indoc! {
6467
pub struct Rule01d {}
6568

6669
impl Rule for Rule01d {
67-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
70+
fn check(&self, SourceInfo { tree, code, .. }: &SourceInfo) -> Vec<Diagnostic<()>> {
6871
let helper = QueryHelper::new(QUERY_STR, tree, code);
6972
let mut first_function_position = None;
7073
let mut diagnostics = Vec::new();

src/rules/rule02a.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,12 @@
3535
3636
use codespan_reporting::diagnostic::{Diagnostic, Label};
3737
use indoc::indoc;
38-
use tree_sitter::{Range, Tree};
38+
use tree_sitter::Range;
3939
use unicode_width::UnicodeWidthStr;
4040

41-
use crate::{
42-
helpers::{LinesWithPosition, QueryHelper},
43-
rules::api::Rule,
44-
};
41+
use crate::{helpers::QueryHelper, rules::api::Rule};
42+
43+
use crate::rules::api::SourceInfo;
4544

4645
/// Amount that wrapped lines must be indented, in columns.
4746
const WRAPPED_LINE_INDENT_WIDTH: usize = 2;
@@ -106,11 +105,11 @@ const QUERY_STR: &str = indoc! { /* query */ r##"
106105
"## };
107106

108107
impl Rule for Rule02a {
109-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
108+
fn check(&self, SourceInfo { tree, code, lines }: &SourceInfo) -> Vec<Diagnostic<()>> {
110109
let mut diagnostics = Vec::new();
111110

112111
// Check for lines >80 columns long
113-
for (line, index) in LinesWithPosition::from(code) {
112+
for (line, index) in lines {
114113
let width = line_width(line);
115114
if width > 80 {
116115
let diagnostic = Diagnostic::warning()
@@ -156,15 +155,15 @@ impl Rule for Rule02a {
156155
}
157156

158157
// Check indentation of wrapped lines and construct list of labels
159-
let mut code_lines = LinesWithPosition::from(code)
158+
let mut code_lines = lines.iter()
160159
.skip(range.start_point.row)
161160
.take(range.end_point.row + 1 - range.start_point.row);
162-
let (first_line, first_line_byte_pos) = code_lines.next().unwrap();
161+
let &(first_line, first_line_byte_pos) = code_lines.next().unwrap();
163162
let first_line_indent = get_indentation(first_line);
164163
let first_line_indent_width = line_width(first_line_indent);
165164
let expected_indent_width = first_line_indent_width + WRAPPED_LINE_INDENT_WIDTH;
166165
let mut labels = Vec::new();
167-
for (this_line, this_line_pos) in &mut code_lines {
166+
for &(this_line, this_line_pos) in &mut code_lines {
168167
let this_line_indent = get_indentation(this_line);
169168
let this_line_indent_width = line_width(this_line_indent);
170169
if this_line_indent_width < expected_indent_width {
@@ -224,9 +223,11 @@ mod tests {
224223

225224
use indoc::indoc;
226225
use pretty_assertions::assert_eq;
227-
use tree_sitter::Parser;
228226

229-
use crate::{helpers::testing::test_captures, rules::api::Rule};
227+
use crate::{
228+
helpers::testing::test_captures,
229+
rules::api::{Rule, SourceInfo},
230+
};
230231

231232
use super::{Rule02a, QUERY_STR};
232233

@@ -318,8 +319,6 @@ mod tests {
318319
#[test]
319320
fn test_rule02a_diagnostics() {
320321
let rule = Rule02a {};
321-
let mut parser = Parser::new();
322-
parser.set_language(&tree_sitter_c::LANGUAGE.into()).unwrap();
323322

324323
macro_rules! test {
325324
($code:literal, $ndiag:expr, $nlabels_list:expr) => {
@@ -333,8 +332,8 @@ mod tests {
333332
}
334333
code.push_str("}\n");
335334
dbg!(&code);
336-
let tree = parser.parse(code.as_bytes(), None).unwrap();
337-
let diagnostics = rule.check(&tree, &code);
335+
let source = SourceInfo::new(&code);
336+
let diagnostics = rule.check(&source);
338337
assert_eq!($ndiag, diagnostics.len());
339338
let nlabels_list: &[usize] = &$nlabels_list;
340339
assert_eq!(

src/rules/rule02b.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727
2828
use codespan_reporting::diagnostic::{Diagnostic, Label};
2929
use indoc::indoc;
30-
use tree_sitter::{QueryCapture, Tree};
30+
use tree_sitter::QueryCapture;
3131

3232
use crate::{
3333
helpers::{function_definition_name, QueryHelper},
3434
rules::api::Rule,
3535
};
3636

37+
use crate::rules::api::SourceInfo;
38+
3739
/// Number of lines per page
3840
const PAGE_SIZE: usize = 61;
3941
/// Maximum number of pages a function definition may span
@@ -53,7 +55,7 @@ const QUERY_STR: &str = indoc! {
5355
pub struct Rule02b {}
5456

5557
impl Rule for Rule02b {
56-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
58+
fn check(&self, SourceInfo { tree, code, .. }: &SourceInfo) -> Vec<Diagnostic<()>> {
5759
let helper = QueryHelper::new(QUERY_STR, tree, code);
5860
let mut diagnostics = Vec::new();
5961
helper.for_each_capture(|label: &str, capture: QueryCapture| match label {
@@ -86,11 +88,10 @@ impl Rule for Rule02b {
8688

8789
#[cfg(test)]
8890
mod tests {
89-
use crate::rules::api::Rule;
91+
use crate::rules::api::{Rule, SourceInfo};
9092

9193
use codespan_reporting::diagnostic::{Diagnostic, Label};
9294
use pretty_assertions::assert_eq;
93-
use tree_sitter::Parser;
9495

9596
use super::{Rule02b, MAX_PAGES_PER_FUNCTION, PAGE_SIZE};
9697

@@ -105,12 +106,10 @@ mod tests {
105106
code.push_str("}\n");
106107

107108
// Test for diagnostic
108-
let mut parser = Parser::new();
109-
parser.set_language(&tree_sitter_c::LANGUAGE.into()).unwrap();
110-
let tree = parser.parse(code.as_bytes(), None).unwrap();
111109
let rule02b = Rule02b {};
110+
let source = SourceInfo::new(&code);
112111
assert_eq!(
113-
rule02b.check(&tree, &code),
112+
rule02b.check(&source),
114113
vec![Diagnostic::warning()
115114
.with_code("II:B")
116115
.with_message(format!(

src/rules/rule03a.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
2727
use codespan_reporting::diagnostic::{Diagnostic, Label};
2828
use indoc::indoc;
29-
use tree_sitter::{Node, Tree};
29+
use tree_sitter::Node;
3030

3131
use crate::{helpers::QueryHelper, rules::api::Rule};
3232

33+
use crate::rules::api::SourceInfo;
34+
3335
/// Tree-sitter query for Rule III:A.
3436
const QUERY_STR: &str = indoc! {
3537
/* query */
@@ -88,7 +90,7 @@ const QUERY_STR: &str = indoc! {
8890
pub struct Rule03a {}
8991

9092
impl Rule for Rule03a {
91-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
93+
fn check(&self, SourceInfo { tree, code, .. }: &SourceInfo) -> Vec<Diagnostic<()>> {
9294
let mut diagnostics = Vec::new();
9395

9496
// Part 1: Space between parentheses and braces
@@ -119,7 +121,8 @@ impl Rule for Rule03a {
119121
// Check spacing between keyword and (
120122
let keyword = helper.expect_node_for_capture_index(qmatch, keyword_capture_i);
121123
let lparen = helper.expect_node_for_capture_index(qmatch, lparen_capture_i);
122-
let message = format!("Expected a single space after `{code}'");
124+
let message =
125+
format!("Expected a single space after `{}'", &code[keyword.byte_range()]);
123126
if let Some(diagnostic) = check_single_space_between(keyword, lparen, code, &message) {
124127
diagnostics.push(diagnostic);
125128
}

src/rules/rule03b.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
3333
use codespan_reporting::diagnostic::{Diagnostic, Label};
3434
use indoc::indoc;
35-
use tree_sitter::{Node, Tree};
35+
use tree_sitter::Node;
3636

3737
use crate::{helpers::QueryHelper, rules::api::Rule};
3838

39+
use crate::rules::api::SourceInfo;
40+
3941
/// Tree-sitter query to capture binary expressions/operators.
4042
const QUERY_STR_BINARY: &str = indoc! {
4143
/* query */
@@ -95,7 +97,7 @@ const QUERY_STR_FIELD: &str = indoc! {
9597
pub struct Rule03b {}
9698

9799
impl Rule for Rule03b {
98-
fn check(&self, tree: &Tree, code: &str) -> Vec<Diagnostic<()>> {
100+
fn check(&self, SourceInfo { tree, code, .. }: &SourceInfo) -> Vec<Diagnostic<()>> {
99101
let mut diagnostics = Vec::new();
100102

101103
// Binary expressions

0 commit comments

Comments
 (0)