#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use std::sync::OnceLock;
use aho_corasick::AhoCorasick;
use regex::bytes::Regex;
use crate::cfg_predicate::attribute_marks_test as rust_attribute_marks_test;
use crate::macros::csharp_invocation_expr_kinds;
use crate::*;
static AHO_CORASICK: OnceLock<AhoCorasick> = OnceLock::new();
static RE: OnceLock<Regex> = OnceLock::new();
macro_rules! js_ancestor_walk {
(
$node:ident,
[$($up:ident)|+],
[$($stop:ident)|+],
$extra:expr $(,)?
) => {
$node.count_specific_ancestors::<Self>(
|node| matches!(node.kind_id().into(), $($up)|+),
|node| matches!(node.kind_id().into(), $($stop)|+),
) > 0
|| $extra
};
}
macro_rules! check_if_func {
($node: ident) => {
js_ancestor_walk!(
$node,
[VariableDeclarator | AssignmentExpression | LabeledStatement | Pair],
[StatementBlock | ReturnStatement | NewExpression | Arguments],
$node.is_child(Identifier as u16),
)
};
}
macro_rules! check_if_arrow_func {
($node: ident) => {
js_ancestor_walk!(
$node,
[VariableDeclarator | AssignmentExpression | LabeledStatement],
[StatementBlock | ReturnStatement | NewExpression | CallExpression],
$node.has_sibling(PropertyIdentifier as u16),
)
};
}
macro_rules! is_js_func {
($node: ident) => {
match $node.kind_id().into() {
FunctionDeclaration | MethodDefinition => true,
FunctionExpression => check_if_func!($node),
ArrowFunction => check_if_arrow_func!($node),
_ => false,
}
};
}
macro_rules! is_js_closure {
($node: ident) => {
match $node.kind_id().into() {
GeneratorFunction | GeneratorFunctionDeclaration => true,
FunctionExpression => !check_if_func!($node),
ArrowFunction => !check_if_arrow_func!($node),
_ => false,
}
};
}
macro_rules! is_js_func_and_closure_checker {
($language: ident) => {
#[inline]
fn is_func(node: &Node) -> bool {
use $language::*;
is_js_func!(node)
}
#[inline]
fn is_closure(node: &Node) -> bool {
use $language::*;
is_js_closure!(node)
}
};
}
macro_rules! impl_js_family_is_string {
($lang:ident $(, $extra:ident)* $(,)?) => {
fn is_string(node: &Node) -> bool {
matches!(
node.kind_id().into(),
$lang::String | $lang::String2 | $lang::TemplateString
$(| $lang::$extra)*
)
}
};
}
macro_rules! impl_simple_is_string {
($lang:ident, $first:ident $(, $rest:ident)* $(,)?) => {
fn is_string(node: &Node) -> bool {
matches!(
node.kind_id().into(),
$lang::$first $(| $lang::$rest)*
)
}
};
}
macro_rules! impl_is_else_if_parent_clause {
($lang:ident, $if_kind:ident, $else_clause:ident) => {
#[inline]
fn is_else_if(node: &Node) -> bool {
node.kind_id() == $lang::$if_kind
&& node
.parent()
.is_some_and(|parent| parent.kind_id() == $lang::$else_clause)
}
};
}
macro_rules! impl_is_else_if_prev_sibling {
($lang:ident, $if_kind:ident, $else_kw:ident) => {
#[inline]
fn is_else_if(node: &Node) -> bool {
node.kind_id() == $lang::$if_kind
&& node
.previous_sibling()
.is_some_and(|prev| prev.kind_id() == $lang::$else_kw)
}
};
}
macro_rules! impl_is_else_if_clause {
($lang:ident, $first:ident $(, $rest:ident)* $(,)?) => {
#[inline]
fn is_else_if(node: &Node) -> bool {
matches!(
node.kind_id().into(),
$lang::$first $(| $lang::$rest)*
)
}
};
}
#[inline]
fn get_aho_corasick_match(code: &[u8]) -> bool {
AHO_CORASICK
.get_or_init(|| {
AhoCorasick::new(vec![b"<div rustbindgen"])
.expect("constant single-needle AhoCorasick automaton always compiles")
})
.is_match(code)
}
#[doc(hidden)]
pub(crate) trait Checker {
#[inline]
fn is_comment(_: &Node) -> bool {
false
}
#[inline]
fn is_useful_comment(_: &Node, _: &[u8]) -> bool {
false
}
#[inline]
fn is_func_space(_: &Node) -> bool {
false
}
#[inline]
fn is_func(_: &Node) -> bool {
false
}
#[inline]
fn is_closure(_: &Node) -> bool {
false
}
#[inline]
fn is_call(_: &Node) -> bool {
false
}
#[inline]
fn is_non_arg(_: &Node) -> bool {
false
}
#[inline]
fn is_string(_: &Node) -> bool {
false
}
#[inline]
fn is_else_if(_: &Node) -> bool {
false
}
#[inline]
fn is_primitive(_node: &Node) -> bool {
false
}
fn is_error(node: &Node) -> bool {
node.has_error()
}
#[inline]
fn should_skip_subtree(_node: &Node, _code: &[u8]) -> bool {
false
}
#[inline]
fn is_func_space_with_code(node: &Node, _code: &[u8]) -> bool {
Self::is_func_space(node)
}
#[inline]
fn is_func_with_code(node: &Node, _code: &[u8]) -> bool {
Self::is_func(node)
}
#[inline]
fn promotes_to_func_space_with_code(node: &Node, code: &[u8]) -> bool {
Self::is_func_with_code(node, code) || Self::is_func_space_with_code(node, code)
}
}
mod bash;
mod c;
mod ccomment;
mod cpp;
mod csharp;
mod elixir;
mod go;
mod groovy;
mod irules;
mod java;
mod javascript;
mod kotlin;
mod lua;
mod mozcpp;
mod mozjs;
mod objc;
mod perl;
mod php;
mod preproc;
mod python;
mod ruby;
mod rust;
mod tcl;
mod tsx;
mod typescript;
pub(crate) fn java_anonymous_class_body<'a>(node: &Node<'a>) -> Option<Node<'a>> {
node.first_child(|id| id == Java::ClassBody as u16)
}
pub(crate) fn csharp_accessor_count(node: &Node) -> usize {
node.children()
.filter(|c| c.kind_id() == Csharp::AccessorList as u16)
.flat_map(|list| list.children())
.filter(|c| c.kind_id() == Csharp::AccessorDeclaration as u16)
.count()
}
pub(crate) fn csharp_member_has_accessors(node: &Node) -> bool {
csharp_accessor_count(node) > 0
}
fn rust_attribute_body<'a>(text: &'a str, marker: &str) -> Option<&'a str> {
text.trim()
.strip_prefix(marker)
.and_then(|t| t.trim_start().strip_prefix('['))
.and_then(|t| t.trim().strip_suffix(']'))
}
fn rust_item_is_test_only(node: &Node, code: &[u8]) -> bool {
rust_outer_attr_marks_test(node, code) || rust_inner_attr_marks_test(node, code)
}
fn rust_outer_attr_marks_test(node: &Node, code: &[u8]) -> bool {
let mut sibling = node.previous_sibling();
while let Some(s) = sibling {
if s.kind_id() != Rust::AttributeItem {
break;
}
if let Some(text) = s.utf8_text(code)
&& let Some(inner) = rust_attribute_body(text, "#")
&& rust_attribute_marks_test(inner)
{
return true;
}
sibling = s.previous_sibling();
}
false
}
fn rust_inner_attr_marks_test(node: &Node, code: &[u8]) -> bool {
if node.kind_id() == Rust::ModItem
&& let Some(body) = node.child_by_field_name("body")
{
for child in body.children() {
if child.kind_id() != Rust::InnerAttributeItem {
continue;
}
if let Some(text) = child.utf8_text(code)
&& let Some(inner) = rust_attribute_body(text, "#!")
&& rust_attribute_marks_test(inner)
{
return true;
}
}
}
false
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
mod tests {
use super::*;
use crate::count::count;
use crate::langs::{
BashParser, JavascriptParser, MozjsParser, PhpParser, TsxParser, TypescriptParser,
};
use std::path::PathBuf;
fn parse(source: &str) -> BashParser {
BashParser::new(source.as_bytes().to_vec(), &PathBuf::from("test.sh"), None)
}
fn count_strings(source: &str) -> usize {
count(&parse(source), &["string".to_string()]).0
}
fn has_kind(source: &str, kind_id: u16) -> bool {
count(&parse(source), &[kind_id.to_string()]).0 > 0
}
#[test]
fn bash_is_string_excludes_word_tokens() {
assert_eq!(count_strings("echo hello world\n"), 0);
assert_eq!(
count_strings("if [ -f file.txt ]; then cat file.txt; fi\n"),
0
);
}
#[test]
fn bash_is_string_matches_quoted_literals() {
assert_eq!(count_strings("echo \"double\"\n"), 1);
assert_eq!(count_strings("echo 'single'\n"), 1);
assert_eq!(count_strings("echo $'ansi-c'\n"), 1);
}
#[test]
fn bash_is_string_matches_translated_string() {
let src = "x=$\"translated\"\n";
assert!(
has_kind(src, Bash::TranslatedString as u16),
"expected a translated_string node in {src:?}"
);
assert_eq!(count_strings(src), 2);
}
#[test]
fn bash_is_string_matches_heredoc_bodies() {
assert_eq!(
count_strings("cat <<EOF\nhello world\nEOF\n"),
1,
"heredoc body should be counted as a string literal"
);
assert_eq!(
count_strings("cat <<'EOF'\nliteral $not_expanded\nEOF\n"),
1
);
assert_eq!(count_strings("cat <<EOF\nhi $name\nEOF\n"), 1);
}
fn parse_php(source: &str) -> PhpParser {
PhpParser::new(source.as_bytes().to_vec(), &PathBuf::from("test.php"), None)
}
fn count_php_strings(source: &str) -> usize {
count(&parse_php(source), &["string".to_string()]).0
}
#[test]
fn php_is_string_matches_single_quoted_literal() {
assert_eq!(count_php_strings("<?php $x = 'single';"), 1);
}
#[test]
fn php_is_string_matches_encapsed_heredoc_nowdoc_shell() {
assert_eq!(count_php_strings("<?php $x = \"double\";"), 1);
assert_eq!(
count_php_strings("<?php $x = <<<EOT\nbody\nEOT;\n"),
1,
"heredoc should match is_string"
);
assert_eq!(
count_php_strings("<?php $x = <<<'EOT'\nbody\nEOT;\n"),
1,
"nowdoc should match is_string"
);
assert_eq!(
count_php_strings("<?php $x = `ls`;"),
1,
"shell command (backtick) should match is_string"
);
}
#[test]
fn php_is_string_matches_string_alias_kinds() {
let src = "<?php function f(): string { return 'x'; }";
assert_eq!(count_php_strings(src), 2);
}
fn ast_has_kind_id<P: ParserTrait>(parser: &P, target: u16) -> bool {
let mut stack = vec![parser.root()];
while let Some(node) = stack.pop() {
if node.kind_id() == target {
return true;
}
for i in (0..node.child_count()).rev() {
if let Some(c) = node.child(i) {
stack.push(c);
}
}
}
false
}
fn count_string_matches_for_kind<P: ParserTrait, F: Fn(&Node) -> bool>(
parser: &P,
target: u16,
is_string: F,
) -> usize {
let mut stack = vec![parser.root()];
let mut hits = 0;
while let Some(node) = stack.pop() {
if node.kind_id() == target && is_string(&node) {
hits += 1;
}
for i in (0..node.child_count()).rev() {
if let Some(c) = node.child(i) {
stack.push(c);
}
}
}
hits
}
#[test]
fn javascript_is_string_matches_string2_alias() {
let src = "const a = 'single';\nconst b = \"double\";\nimport \"m\";\n";
let parser = JavascriptParser::new(src.as_bytes().to_vec(), &PathBuf::from("t.js"), None);
assert!(
ast_has_kind_id(&parser, Javascript::String2 as u16),
"expected Javascript::String2 to appear in the parse",
);
assert!(
count_string_matches_for_kind(
&parser,
Javascript::String2 as u16,
JavascriptCode::is_string,
) > 0,
"Javascript::String2 nodes must match is_string",
);
}
#[test]
fn mozjs_is_string_matches_string2_alias() {
let src = "const a = 'single';\nconst b = \"double\";\nimport \"m\";\n";
let parser = MozjsParser::new(src.as_bytes().to_vec(), &PathBuf::from("t.js"), None);
assert!(
ast_has_kind_id(&parser, Mozjs::String2 as u16),
"expected Mozjs::String2 to appear in the parse",
);
assert!(
count_string_matches_for_kind(&parser, Mozjs::String2 as u16, MozjsCode::is_string) > 0,
"Mozjs::String2 nodes must match is_string",
);
}
#[test]
fn typescript_is_string_matches_string2_alias() {
let src = "const a: string = 'x';\nfunction f(): string { return 'y'; }\n";
let parser = TypescriptParser::new(src.as_bytes().to_vec(), &PathBuf::from("t.ts"), None);
assert!(
ast_has_kind_id(&parser, Typescript::String2 as u16),
"expected Typescript::String2 to appear in the parse",
);
assert!(
count_string_matches_for_kind(
&parser,
Typescript::String2 as u16,
TypescriptCode::is_string,
) > 0,
"Typescript::String2 nodes must match is_string",
);
}
#[test]
fn tsx_is_string_matches_string2_and_string3_aliases() {
let src = "const a: string = 'x';\n\
const b = \"y\";\n\
import \"m\";\n\
const el = <div className=\"c\">{\"t\"}</div>;\n";
let parser = TsxParser::new(src.as_bytes().to_vec(), &PathBuf::from("t.tsx"), None);
assert!(
ast_has_kind_id(&parser, Tsx::String3 as u16),
"expected Tsx::String3 (type-keyword `string`) in the parse",
);
assert!(
ast_has_kind_id(&parser, Tsx::String2 as u16),
"expected Tsx::String2 (string-literal alias) in the parse",
);
assert!(
count_string_matches_for_kind(&parser, Tsx::String3 as u16, TsxCode::is_string) > 0,
"Tsx::String3 nodes must match is_string",
);
assert!(
count_string_matches_for_kind(&parser, Tsx::String2 as u16, TsxCode::is_string) > 0,
"Tsx::String2 nodes must match is_string",
);
}
fn find_first_kind<P: ParserTrait>(parser: &P, target: u16) -> Option<Node<'_>> {
let mut stack = vec![parser.root()];
while let Some(node) = stack.pop() {
if node.kind_id() == target {
return Some(node);
}
for i in (0..node.child_count()).rev() {
if let Some(c) = node.child(i) {
stack.push(c);
}
}
}
None
}
#[test]
fn rust_outer_attr_on_mod_is_test_only() {
let src = "#[cfg(test)]\nmod tests {\n fn t() {}\n}\n";
let parser = RustParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.rs"), None);
let code = parser.code();
let node = find_first_kind(&parser, Rust::ModItem as u16).expect("mod_item");
assert!(rust_item_is_test_only(&node, code));
assert!(rust_outer_attr_marks_test(&node, code));
assert!(!rust_inner_attr_marks_test(&node, code));
}
#[test]
fn rust_inner_attr_in_mod_is_test_only() {
let src = "mod tests {\n #![cfg(test)]\n fn t() {}\n}\n";
let parser = RustParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.rs"), None);
let code = parser.code();
let node = find_first_kind(&parser, Rust::ModItem as u16).expect("mod_item");
assert!(rust_item_is_test_only(&node, code));
assert!(!rust_outer_attr_marks_test(&node, code));
assert!(rust_inner_attr_marks_test(&node, code));
}
#[test]
fn rust_plain_item_is_not_test_only() {
let src = "fn foo() {}\n";
let parser = RustParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.rs"), None);
let code = parser.code();
let node = find_first_kind(&parser, Rust::FunctionItem as u16).expect("function_item");
assert!(!rust_item_is_test_only(&node, code));
assert!(!rust_outer_attr_marks_test(&node, code));
assert!(!rust_inner_attr_marks_test(&node, code));
}
#[test]
fn groovy_is_else_if_recognises_else_followed_by_if() {
let src = "if (x) { } else if (y) { } else { }";
let parser =
GroovyParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.groovy"), None);
let outer =
find_first_kind(&parser, Groovy::IfStatement as u16).expect("outer if_statement");
let mut inner: Option<Node> = None;
for i in 0..outer.child_count() {
if let Some(c) = outer.child(i)
&& c.kind_id() == Groovy::IfStatement as u16
{
inner = Some(c);
break;
}
}
let inner = inner.expect("expected an inner if_statement");
assert!(
GroovyCode::is_else_if(&inner),
"inner if_statement after `else` must be recognised as else-if"
);
assert!(
!GroovyCode::is_else_if(&outer),
"outer if_statement must not be recognised as else-if"
);
}
#[test]
fn groovy_is_else_if_false_for_standalone_if() {
let src = "if (x) { println(x) }";
let parser =
GroovyParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.groovy"), None);
let node = find_first_kind(&parser, Groovy::IfStatement as u16).expect("if_statement");
assert!(!GroovyCode::is_else_if(&node));
}
#[test]
fn groovy_is_call_excludes_constructors() {
let src = "def m() {\n def a = new Foo()\n a.bar()\n println \"hi\"\n}\n";
let parser =
GroovyParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.groovy"), None);
assert_eq!(
count(&parser, &["call".to_string()]).0,
2,
"is_call must count method_invocation + command_chain only, not the constructor"
);
let ctor = find_first_kind(&parser, Groovy::ObjectCreationExpression as u16)
.expect("object_creation_expression");
assert!(
!GroovyCode::is_call(&ctor),
"object_creation_expression must not be a call"
);
let method =
find_first_kind(&parser, Groovy::MethodInvocation as u16).expect("method_invocation");
assert!(
GroovyCode::is_call(&method),
"method_invocation must be a call"
);
let chain = find_first_kind(&parser, Groovy::CommandChain as u16).expect("command_chain");
assert!(GroovyCode::is_call(&chain), "command_chain must be a call");
}
fn parse_python(src: &str) -> PythonParser {
PythonParser::new(src.as_bytes().to_vec(), &PathBuf::from("test.py"), None)
}
fn find_all_kinds<P: ParserTrait>(parser: &P, target: u16) -> Vec<Node<'_>> {
let mut out = Vec::new();
let mut stack = vec![parser.root()];
while let Some(node) = stack.pop() {
if node.kind_id() == target {
out.push(node);
}
for i in (0..node.child_count()).rev() {
if let Some(c) = node.child(i) {
stack.push(c);
}
}
}
out
}
#[test]
fn python_is_else_if_recognises_if_inside_else_clause() {
let src = "if a:\n pass\nelse:\n if b:\n pass\n";
let parser = parse_python(src);
let outer =
find_first_kind(&parser, Python::IfStatement as u16).expect("outer if_statement");
let inner =
find_python_if_inside_else_block(&parser).expect("inner if_statement under else");
assert!(
PythonCode::is_else_if(&inner),
"if_statement inside else_clause's block must be recognised as else-if"
);
assert!(
!PythonCode::is_else_if(&outer),
"outer if_statement must not be recognised as else-if"
);
}
#[test]
fn python_is_else_if_false_for_standalone_if() {
let src = "if a:\n pass\n";
let parser = parse_python(src);
let node = find_first_kind(&parser, Python::IfStatement as u16).expect("if_statement");
assert!(!PythonCode::is_else_if(&node));
}
#[test]
fn python_is_else_if_false_for_outer_if_with_elif_alternative() {
let src = "if a:\n pass\nelif b:\n pass\n";
let parser = parse_python(src);
let outer = find_first_kind(&parser, Python::IfStatement as u16).expect("if_statement");
assert!(!PythonCode::is_else_if(&outer));
}
fn find_python_if_inside_else_block(parser: &PythonParser) -> Option<Node<'_>> {
find_all_kinds(parser, Python::IfStatement as u16)
.into_iter()
.find(|n| {
n.parent().is_some_and(|p| {
matches!(p.kind_id().into(), Python::Block | Python::Block2)
&& p.parent()
.is_some_and(|gp| gp.kind_id() == Python::ElseClause)
})
})
}
#[test]
fn python_is_else_if_false_when_else_body_has_siblings() {
let src = "if a:\n pass\nelse:\n if b:\n pass\n pass\n";
let parser = parse_python(src);
let inner =
find_python_if_inside_else_block(&parser).expect("inner if_statement under else");
assert!(
!PythonCode::is_else_if(&inner),
"inner if must NOT be recognised as else-if when its block has siblings"
);
}
#[test]
fn python_hidden_block_and_lambda_aliases_stay_unseen() {
let src = "def f(a, b):\n if a:\n return b\n for x in b:\n print(x)\n\nclass C:\n def m(self):\n pass\n\ng = lambda x: x + 1\n";
let parser = parse_python(src);
assert!(
ast_has_kind_id(&parser, Python::Block2 as u16),
"expected Python::Block2 (160, the emitted `block`) in the parse",
);
assert!(
ast_has_kind_id(&parser, Python::Lambda as u16),
"expected Python::Lambda (196, the emitted `lambda`) in the parse",
);
assert!(
!ast_has_kind_id(&parser, Python::Block as u16),
"Python::Block (135) is the hidden `_block` supertype; if it now appears, route it through python_is_block and assert positively (#419)",
);
assert!(
!ast_has_kind_id(&parser, Python::Lambda2 as u16),
"Python::Lambda2 (197) is an unseen lambda alias; if it now appears, cognitive::python_is_lambda (reused by is_closure and the three cognitive lambda-scope sites) must detect it and a positive closure assertion is required (#419/#422)",
);
}
#[test]
fn python_is_lambda_matches_live_lambda_and_agrees_with_is_closure() {
use crate::metrics::cognitive::python_is_lambda;
let parser = parse_python("def f():\n return lambda x: x and x\n");
let lambda = find_first_kind(&parser, Python::Lambda as u16)
.expect("the lambda expression must parse as Python::Lambda (196)");
assert!(
python_is_lambda(&lambda),
"python_is_lambda must accept the emitted Lambda node",
);
assert!(
PythonCode::is_closure(&lambda),
"is_closure must agree with python_is_lambda on the same lambda node",
);
let func = find_first_kind(&parser, Python::FunctionDefinition as u16)
.expect("the def must parse as Python::FunctionDefinition");
assert!(
!python_is_lambda(&func),
"python_is_lambda must reject a non-lambda node",
);
}
fn count_with_parser<P: ParserTrait>(parser: &P) -> usize {
count(parser, &["string".to_string()]).0
}
fn assert_variant_is_string<P: ParserTrait, F: Fn(&Node) -> bool>(
parser: &P,
target: u16,
is_string: F,
lang: &str,
variant: &str,
) {
assert!(
ast_has_kind_id(parser, target),
"{lang}::{variant} (kind_id {target}) did not appear in the parse — fixture broken",
);
assert!(
count_string_matches_for_kind(parser, target, is_string) > 0,
"{lang}::{variant} must route through is_string",
);
}
macro_rules! assert_variants_is_string {
($parser:expr, $lang:ident, $code:ident, [$($variant:ident),+ $(,)?]) => {
$(
assert_variant_is_string(
$parser,
$lang::$variant as u16,
$code::is_string,
stringify!($lang),
stringify!($variant),
);
)+
};
}
macro_rules! assert_no_string_matches {
($parser_ty:ident, $path:expr, $src:expr, $lang:literal $(,)?) => {{
let parser = $parser_ty::new($src.to_vec(), $path, None);
assert_eq!(count_with_parser(&parser), 0, $lang);
}};
}
#[test]
fn simple_is_string_macro_recognises_each_language() {
use crate::langs::{
CcommentParser, CppParser, CsharpParser, ElixirParser, GoParser, GroovyParser,
IrulesParser, JavaParser, KotlinParser, LuaParser, PerlParser, PreprocParser,
PythonParser, RubyParser, RustParser, TclParser,
};
let path = PathBuf::from("test");
let src = b"#include \"foo.h\"\nR\"(raw)\"\n".to_vec();
let parser = PreprocParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Preproc,
PreprocCode,
[StringLiteral, RawStringLiteral]
);
let src = b"\"hello\"\nR\"(raw)\"\n".to_vec();
let parser = CcommentParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Ccomment,
CcommentCode,
[StringLiteral, RawStringLiteral]
);
let src =
b"const char* a = \"hi\";\nconst char* b = \"a\" \"b\";\nconst char* c = R\"(raw)\";\n"
.to_vec();
let parser = CppParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Cpp,
CppCode,
[StringLiteral, ConcatenatedString, RawStringLiteral]
);
let src = b"a = \"hi\"\nb = \"a\" \"b\"\n".to_vec();
let parser = PythonParser::new(src, &path, None);
assert_variants_is_string!(&parser, Python, PythonCode, [String, ConcatenatedString]);
let src = b"class C { String a = \"hi\"; String b = \"\"\"\nmulti\n\"\"\"; }\n".to_vec();
let parser = JavaParser::new(src, &path, None);
assert_variants_is_string!(&parser, Java, JavaCode, [StringLiteral]);
assert!(
!ast_has_kind_id(&parser, Java::MultilineStringLiteral as u16),
"Java::MultilineStringLiteral is documented as the hidden _multiline_string_literal supertype; if it now appears in parses, replace this with a positive variant assertion",
);
let src = b"class C { string a = \"hi\"; string b = @\"verb\"; string c = \"\"\"raw\"\"\"; string d = $\"int{1}\"; }\n".to_vec();
let parser = CsharpParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Csharp,
CsharpCode,
[
StringLiteral,
VerbatimStringLiteral,
RawStringLiteral,
InterpolatedStringExpression,
]
);
let src = b"fn main() { let a = \"hi\"; let b = r\"raw\"; }\n".to_vec();
let parser = RustParser::new(src, &path, None);
assert_variants_is_string!(&parser, Rust, RustCode, [StringLiteral, RawStringLiteral]);
let src = b"package main\nfunc main() { _ = \"hi\"; _ = `raw` }\n".to_vec();
let parser = GoParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Go,
GoCode,
[InterpretedStringLiteral, RawStringLiteral]
);
let src = b"fun main() { val a = \"hi\"; val b = \"\"\"multi\"\"\" }\n".to_vec();
let parser = KotlinParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Kotlin,
KotlinCode,
[StringLiteral, MultilineStringLiteral]
);
let src = b"local a = \"hi\"\nlocal b = [[long]]\n".to_vec();
let parser = LuaParser::new(src, &path, None);
assert_variants_is_string!(&parser, Lua, LuaCode, [String]);
let src = b"my $a = 'single';\nmy $b = \"double\";\nmy $c = q(qquoted);\nmy $d = qq(qqquoted);\nmy $e = `cmd`;\nmy $f = qx(qxcmd);\nmy $g = <<EOT;\nbody\nEOT\n".to_vec();
let parser = PerlParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Perl,
PerlCode,
[
StringSingleQuoted,
StringDoubleQuoted,
StringQQuoted,
StringQqQuoted,
BacktickQuoted,
CommandQxQuoted,
HeredocBodyStatement,
]
);
let src = b"a=\"d\"\nb='r'\nc=$'ansi'\nd=$\"t\"\ncat <<EOF\nbody\nEOF\n".to_vec();
let parser = crate::langs::BashParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Bash,
BashCode,
[
String,
RawString,
AnsiCString,
TranslatedString,
HeredocBody2
]
);
let src = b"set a \"quoted\"\nset b {braced}\nproc p {x y} { return $x }\n".to_vec();
let parser = TclParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Tcl,
TclCode,
[QuotedWord, BracedWordSimple, BracedWord]
);
let src = b"set a \"quoted\"\nset b {braced}\nproc p {x y} { return $x }\n".to_vec();
let parser = IrulesParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Irules,
IrulesCode,
[QuotedWord, BracedWordSimple, BracedWord]
);
let src = b"<?php function f(): string { $a = 'single'; $b = \"double\"; $c = <<<EOT\nbody\nEOT;\n$d = <<<'EOT'\nnow\nEOT;\n$e = `ls`; return $a; }\n".to_vec();
let parser = PhpParser::new(src, &path, None);
assert_variants_is_string!(&parser, Php, PhpCode, [String, String2]);
assert!(
!ast_has_kind_id(&parser, Php::String3 as u16),
"Php::String3 is documented as the hidden _string supertype; if it now appears in parses, add a positive variant assertion",
);
assert_variants_is_string!(
&parser,
Php,
PhpCode,
[EncapsedString, Heredoc, Nowdoc, ShellCommandExpression]
);
let src = b"a = \"hi\"\nb = 'charlist'\nc = ~s(sigil)\n".to_vec();
let parser = ElixirParser::new(src, &path, None);
assert_variants_is_string!(&parser, Elixir, ElixirCode, [String, Charlist, Sigil]);
let src = b"a = \"hi\"\nb = \"x\" \"y\"\nc = `cmd`\nd = /re/\ne = <<EOT\nbody\nEOT\nf = :sym\ng = :\"dsym\"\nh = %w[bare1 bare2]\ni = %i[s1 s2]\nj = ?A\n".to_vec();
let parser = RubyParser::new(src, &path, None);
assert_variants_is_string!(
&parser,
Ruby,
RubyCode,
[
String,
ChainedString,
BareString,
Subshell,
Regex,
HeredocBody,
DelimitedSymbol,
SimpleSymbol,
StringArray,
SymbolArray,
Character,
]
);
let src =
b"def m() { def a = \"hi\"; def b = \"\"\"multi\"\"\"; def c = /pat/ }\n".to_vec();
let parser = GroovyParser::new(src, &path, None);
assert_variants_is_string!(&parser, Groovy, GroovyCode, [StringLiteral]);
}
#[test]
fn simple_is_string_macro_rejects_non_string_nodes() {
use crate::langs::{
BashParser, CcommentParser, CppParser, CsharpParser, ElixirParser, GoParser,
GroovyParser, IrulesParser, JavaParser, KotlinParser, LuaParser, PerlParser,
PreprocParser, PythonParser, RubyParser, RustParser, TclParser,
};
let path = PathBuf::from("test");
assert_no_string_matches!(PreprocParser, &path, b"#define FOO 1\n", "Preproc");
assert_no_string_matches!(CcommentParser, &path, b"// just a comment\n", "Ccomment");
assert_no_string_matches!(CppParser, &path, b"int main() { return x; }\n", "Cpp");
assert_no_string_matches!(PythonParser, &path, b"x = y\n", "Python");
assert_no_string_matches!(JavaParser, &path, b"class C { int x = y; }\n", "Java");
assert_no_string_matches!(CsharpParser, &path, b"class C { int x = y; }\n", "Csharp");
assert_no_string_matches!(RustParser, &path, b"fn main() { let x = y; }\n", "Rust");
assert_no_string_matches!(
GoParser,
&path,
b"package main\nfunc main() { _ = x }\n",
"Go"
);
assert_no_string_matches!(KotlinParser, &path, b"fun main() { val x = y }\n", "Kotlin");
assert_no_string_matches!(PerlParser, &path, b"my $x = $y;\n", "Perl");
assert_no_string_matches!(LuaParser, &path, b"local x = y\n", "Lua");
assert_no_string_matches!(BashParser, &path, b"s=$y\n", "Bash");
assert_no_string_matches!(TclParser, &path, b"set x $y\n", "Tcl");
assert_no_string_matches!(IrulesParser, &path, b"set x $y\n", "Irules");
assert_no_string_matches!(PhpParser, &path, b"<?php $x = $y;\n", "Php");
assert_no_string_matches!(ElixirParser, &path, b"x = 1\n", "Elixir");
assert_no_string_matches!(RubyParser, &path, b"x = y\n", "Ruby");
assert_no_string_matches!(GroovyParser, &path, b"def m() { def x = y }\n", "Groovy");
}
#[test]
fn mozjs_parses_using_declaration() {
let src = "function f() {\n using r = acquire();\n return r;\n}\n";
let parser = MozjsParser::new(src.as_bytes().to_vec(), &PathBuf::from("t.js"), None);
assert!(
ast_has_kind_id(&parser, Mozjs::UsingDeclaration as u16),
"expected Mozjs::UsingDeclaration to appear in the parse",
);
}
#[test]
fn go_rune_literal_is_not_a_string() {
use crate::langs::GoParser;
let path = PathBuf::from("test.go");
let src = b"package main\nfunc main() { r := 'x'; _ = r }\n".to_vec();
let parser = GoParser::new(src, &path, None);
let rune_id = Go::RuneLiteral as u16;
assert!(
ast_has_kind_id(&parser, rune_id),
"fixture should produce a rune_literal node",
);
assert_eq!(
count_string_matches_for_kind(&parser, rune_id, GoCode::is_string),
0,
"Go rune_literal must not match is_string (a rune is a char, not a string)",
);
}
}