Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unnamed nodes capture #31

Merged
merged 2 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions kernel/src/analysis/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,76 @@ function visit(node, filename, code) {
);
}

// execute two rules and check that both rules are executed and their respective
// results reported.
#[test]
fn test_capture_unnamed_nodes() {
let rule_code1 = r#"
function visit(node, filename, code) {

const el = node.captures["less_than"];
if(el) {
const error = buildError(el.start.line, el.start.col, el.end.line, el.end.col,
"do not use less than", "CRITICAL", "security");
addError(error);
}
}
"#;

let tree_sitter_query = r#"
(
(for_statement
condition: (_
(binary_expression
left: (identifier)
operator: [
"<" @less_than
"<=" @less_than
">" @more_than
">=" @more_than
]
)
)
)
)
"#;

let js_code = r#"
for(var i = 0; i <= 10; i--){}
"#;

let rule1 = RuleInternal {
name: "myrule".to_string(),
short_description: Some("short desc".to_string()),
description: Some("description".to_string()),
category: RuleCategory::CodeStyle,
severity: RuleSeverity::Notice,
language: Language::JavaScript,
code: rule_code1.to_string(),
tree_sitter_query: Some(tree_sitter_query.to_string()),
variables: HashMap::new(),
};

let analysis_options = AnalysisOptions {
log_output: true,
use_debug: false,
};
let results = analyze(
&Language::JavaScript,
vec![rule1],
"myfile.js",
js_code,
&analysis_options,
);
assert_eq!(1, results.len());
let result1 = results.get(0).unwrap();
assert_eq!(result1.violations.len(), 1);
assert_eq!(
result1.violations.get(0).unwrap().message,
"do not use less than".to_string()
);
}

// test showing violation ignore
#[test]
fn test_violation_ignore() {
Expand Down
20 changes: 13 additions & 7 deletions kernel/src/analysis/tree_sitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,23 @@ pub fn get_query_nodes(
// map a node from the tree-sitter representation into our own internal representation
// this is the representation that is passed to the JavaScript layer and how we represent
// or expose the node to the end-user.
//
// If this is NOT a named node, we do not return anything.
pub fn map_node(node: tree_sitter::Node) -> Option<TreeSitterNode> {
fn map_node_internal(cursor: &mut tree_sitter::TreeCursor) -> Option<TreeSitterNode> {
// we do not map space, parenthesis and other non-named nodes.
if !cursor.node().is_named() {
fn map_node_internal(
cursor: &mut tree_sitter::TreeCursor,
only_named_node: bool,
) -> Option<TreeSitterNode> {
// we do not map space, parenthesis and other non-named nodes if there
// when `only_named_node` is true (which is `true` for children only).
if only_named_node && !cursor.node().is_named() {
return None;
}

// map all the children as we should
let mut children: Vec<TreeSitterNode> = vec![];
if cursor.goto_first_child() {
loop {
let maybe_child = map_node_internal(cursor);
// For the child, we only want to capture named nodes to avoid polluting the AST.
let maybe_child = map_node_internal(cursor, true);
if let Some(child) = maybe_child {
children.push(child);
}
Expand Down Expand Up @@ -145,7 +148,10 @@ pub fn map_node(node: tree_sitter::Node) -> Option<TreeSitterNode> {
}

let mut ts_cursor = node.walk();
map_node_internal(&mut ts_cursor)

// Initially, we capture both un/named nodes to allow capturing unnamed node from
// the tree-sitter query.
map_node_internal(&mut ts_cursor, false)
}

#[cfg(test)]
Expand Down