tokensave 3.4.2

Code intelligence tool that builds a semantic knowledge graph from Rust, Go, Java, Scala, TypeScript, Python, C, C++, Kotlin, C#, Swift, and many more codebases
#[cfg(feature = "lang-php")]
mod php_tests {

use tokensave::extraction::LanguageExtractor;
use tokensave::extraction::PhpExtractor;
use tokensave::types::*;

#[test]
fn test_php_file_node() {
    let source = r#"<?php
function hello() {}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("test.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let files: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::File).collect();
    assert_eq!(files.len(), 1);
    assert_eq!(files[0].name, "test.php");
}

#[test]
fn test_php_function() {
    let source = r#"<?php
function add(int $a, int $b): int {
    return $a + $b;
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("math.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let fns: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Function).collect();
    assert_eq!(fns.len(), 1);
    assert_eq!(fns[0].name, "add");
}

#[test]
fn test_php_class_with_methods() {
    let source = r#"<?php
class User {
    private string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    public function getName(): string {
        return $this->name;
    }

    private function validate(): bool {
        return true;
    }
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("user.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);

    let classes: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Class).collect();
    assert_eq!(classes.len(), 1);
    assert_eq!(classes[0].name, "User");

    let methods: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Method).collect();
    assert!(methods.len() >= 2, "expected >= 2 methods, got {}", methods.len());
    assert!(methods.iter().any(|m| m.name == "getName"));

    // Visibility
    assert!(
        result.nodes.iter().any(|n| n.visibility == Visibility::Private),
        "expected private members"
    );
    assert!(
        result.nodes.iter().any(|n| n.visibility == Visibility::Pub),
        "expected public members"
    );

    // Fields (properties)
    let fields: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Field).collect();
    assert!(!fields.is_empty(), "expected field nodes for class properties");

    // Contains edges
    assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}

#[test]
fn test_php_interface() {
    let source = r#"<?php
interface Loggable {
    public function log(string $message): void;
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("loggable.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let traits: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Trait).collect();
    assert_eq!(traits.len(), 1, "interface should map to Trait node");
    assert_eq!(traits[0].name, "Loggable");
}

#[test]
fn test_php_trait_declaration() {
    let source = r#"<?php
trait Timestamps {
    public function createdAt(): string {
        return $this->created;
    }
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("timestamps.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let traits: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Trait).collect();
    assert_eq!(traits.len(), 1);
    assert_eq!(traits[0].name, "Timestamps");
}

#[test]
fn test_php_namespace() {
    let source = r#"<?php
namespace App\Models;

class Item {}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("item.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    assert!(
        result.nodes.iter().any(|n| n.kind == NodeKind::Module),
        "namespace should produce a Module node"
    );
}

#[test]
fn test_php_enum() {
    let source = r#"<?php
enum Status {
    case Active;
    case Inactive;
    case Pending;
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("status.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let enums: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
    assert_eq!(enums.len(), 1);
    assert_eq!(enums[0].name, "Status");
    let variants: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::EnumVariant).collect();
    assert_eq!(variants.len(), 3, "expected 3 enum cases");
}

#[test]
fn test_php_class_inheritance() {
    let source = r#"<?php
class Base {
    public function id(): int { return 1; }
}
class Child extends Base {
    public function name(): string { return "x"; }
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("inherit.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    assert!(
        result.unresolved_refs.iter().any(|r| r.reference_kind == EdgeKind::Extends),
        "expected Extends ref for class inheritance"
    );
}

#[test]
fn test_php_trait_use_inside_class() {
    let source = r#"<?php
trait Logger {
    public function log(): void {}
}
class Service {
    use Logger;
    public function run(): void {}
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("service.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let uses: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Use).collect();
    assert!(!uses.is_empty(), "expected Use node for `use Logger` inside class");
}

#[test]
fn test_php_constructor_as_method() {
    let source = r#"<?php
class Widget {
    public function __construct(private string $name) {}
}
"#;
    let extractor = PhpExtractor;
    let result = extractor.extract("widget.php", source);
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    // PHP extractor maps __construct as a regular Method
    let methods: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::Method).collect();
    assert!(methods.iter().any(|m| m.name == "__construct"), "expected __construct method");
}

#[test]
fn test_php_empty_source() {
    let extractor = PhpExtractor;
    let result = extractor.extract("empty.php", "<?php\n");
    assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
    let files: Vec<_> = result.nodes.iter().filter(|n| n.kind == NodeKind::File).collect();
    assert_eq!(files.len(), 1);
}

} // mod php_tests