Skip to content

Commit 140572d

Browse files
unhappychoiceclaude
andcommitted
feat: add PHP language support
Add comprehensive PHP language support to gittype, enabling users to practice typing PHP code patterns and syntax. ## Changes - Add Language::Php enum variant with display/extension handling - Implement PhpExtractor with tree-sitter-php integration - Support multiple PHP file extensions (.php, .phtml, .php3, .php4, .php5) - Add query patterns for PHP constructs: - Functions and methods - Classes, interfaces, and traits - Namespaces - Add PHP comment detection (// and /* */ comments, # shell-style comments) - Register PHP in parser registry and extraction options - Add comprehensive test coverage for: - Function extraction - Class/interface/trait extraction - Namespace handling - Complex PHP code patterns ## Implementation Details - Uses tree-sitter-php v0.21 for AST parsing - Supports PHP language features: classes, functions, methods, interfaces, traits, namespaces - Handles PHP-specific syntax patterns including variables with $ prefix - Compatible with opening/closing PHP tags (<?php, ?>) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 414c584 commit 140572d

File tree

9 files changed

+350
-0
lines changed

9 files changed

+350
-0
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ tree-sitter-go = "0.20"
3737
tree-sitter-swift = "0.4.3"
3838
tree-sitter-kotlin = "0.2"
3939
tree-sitter-java = "0.20"
40+
tree-sitter-php = "0.21"
4041
crossterm = "0.27"
4142
ratatui = "0.26"
4243
rusqlite = { version = "0.29", features = ["bundled"] }

src/extractor/core/extractor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ impl CommonExtractor {
9595
Language::Swift => node_kind == "comment" || node_kind == "multiline_comment",
9696
Language::Kotlin => node_kind == "line_comment" || node_kind == "multiline_comment",
9797
Language::Java => node_kind == "line_comment" || node_kind == "block_comment",
98+
Language::Php => node_kind == "comment" || node_kind == "shell_comment_line",
9899
}
99100
}
100101

src/extractor/models/language.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub enum Language {
88
Swift,
99
Kotlin,
1010
Java,
11+
Php,
1112
}
1213

1314
impl std::fmt::Display for Language {
@@ -21,6 +22,7 @@ impl std::fmt::Display for Language {
2122
Language::Swift => "swift",
2223
Language::Kotlin => "kotlin",
2324
Language::Java => "java",
25+
Language::Php => "php",
2426
};
2527
write!(f, "{}", s)
2628
}
@@ -37,6 +39,7 @@ impl Language {
3739
"swift" => Some(Language::Swift),
3840
"kt" | "kts" => Some(Language::Kotlin),
3941
"java" => Some(Language::Java),
42+
"php" | "phtml" | "php3" | "php4" | "php5" => Some(Language::Php),
4043
_ => None,
4144
}
4245
}
@@ -51,6 +54,7 @@ impl Language {
5154
Language::Swift => "swift",
5255
Language::Kotlin => "kt",
5356
Language::Java => "java",
57+
Language::Php => "php",
5458
}
5559
}
5660

@@ -65,6 +69,7 @@ impl Language {
6569
Some("swift") => "swift".to_string(),
6670
Some("kt") | Some("kts") => "kotlin".to_string(),
6771
Some("java") => "java".to_string(),
72+
Some("php") | Some("phtml") | Some("php3") | Some("php4") | Some("php5") => "php".to_string(),
6873
_ => "text".to_string(),
6974
}
7075
}

src/extractor/models/options.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ impl Default for ExtractionOptions {
1818
"**/*.kt".to_string(),
1919
"**/*.kts".to_string(),
2020
"**/*.java".to_string(),
21+
"**/*.php".to_string(),
22+
"**/*.phtml".to_string(),
23+
"**/*.php3".to_string(),
24+
"**/*.php4".to_string(),
25+
"**/*.php5".to_string(),
2126
],
2227
exclude_patterns: vec![
2328
"**/target/**".to_string(),

src/extractor/parsers/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use tree_sitter::{Node, Parser, Query};
77
pub mod go;
88
pub mod java;
99
pub mod kotlin;
10+
pub mod php;
1011
pub mod python;
1112
pub mod ruby;
1213
pub mod rust;
@@ -79,6 +80,10 @@ impl ParserRegistry {
7980
Box::new(java::JavaExtractor)
8081
});
8182

83+
registry.register(Language::Php, php::PhpExtractor::create_parser, || {
84+
Box::new(php::PhpExtractor)
85+
});
86+
8287
registry
8388
}
8489

src/extractor/parsers/php.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use super::LanguageExtractor;
2+
use crate::extractor::models::{ChunkType, Language};
3+
use crate::{GitTypeError, Result};
4+
use tree_sitter::{Node, Parser};
5+
6+
pub struct PhpExtractor;
7+
8+
impl LanguageExtractor for PhpExtractor {
9+
fn language(&self) -> Language {
10+
Language::Php
11+
}
12+
13+
fn file_extensions(&self) -> &[&str] {
14+
&["php", "phtml", "php3", "php4", "php5"]
15+
}
16+
17+
fn tree_sitter_language(&self) -> tree_sitter::Language {
18+
tree_sitter_php::language_php()
19+
}
20+
21+
fn query_patterns(&self) -> &str {
22+
"
23+
(function_definition name: (name) @name) @function
24+
(method_declaration name: (name) @name) @method
25+
(class_declaration name: (name) @name) @class
26+
(interface_declaration name: (name) @name) @interface
27+
(trait_declaration name: (name) @name) @trait
28+
(namespace_definition name: (namespace_name (name) @name)) @namespace
29+
"
30+
}
31+
32+
fn comment_query(&self) -> &str {
33+
"
34+
(comment) @comment
35+
"
36+
}
37+
38+
fn capture_name_to_chunk_type(&self, capture_name: &str) -> Option<ChunkType> {
39+
match capture_name {
40+
"function" => Some(ChunkType::Function),
41+
"method" => Some(ChunkType::Function),
42+
"class" => Some(ChunkType::Class),
43+
"interface" => Some(ChunkType::Class),
44+
"trait" => Some(ChunkType::Class),
45+
"namespace" => Some(ChunkType::Function),
46+
"name" => None, // name captures are not chunks themselves
47+
_ => None,
48+
}
49+
}
50+
51+
fn extract_name(&self, node: Node, source_code: &str, capture_name: &str) -> Option<String> {
52+
// For @name captures, the node is already the name node
53+
if capture_name == "name" {
54+
let start = node.start_byte();
55+
let end = node.end_byte();
56+
return Some(source_code[start..end].to_string());
57+
}
58+
59+
// Fallback to searching for name child
60+
self.extract_name_from_node(node, source_code)
61+
}
62+
}
63+
64+
impl PhpExtractor {
65+
fn extract_name_from_node(&self, node: Node, source_code: &str) -> Option<String> {
66+
let mut cursor = node.walk();
67+
if cursor.goto_first_child() {
68+
loop {
69+
let child = cursor.node();
70+
if child.kind() == "name" {
71+
let start = child.start_byte();
72+
let end = child.end_byte();
73+
return Some(source_code[start..end].to_string());
74+
}
75+
if !cursor.goto_next_sibling() {
76+
break;
77+
}
78+
}
79+
}
80+
None
81+
}
82+
83+
pub fn create_parser() -> Result<Parser> {
84+
let mut parser = Parser::new();
85+
parser
86+
.set_language(tree_sitter_php::language_php())
87+
.map_err(|e| {
88+
GitTypeError::ExtractionFailed(format!("Failed to set PHP language: {}", e))
89+
})?;
90+
Ok(parser)
91+
}
92+
}

tests/integration/languages/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod go;
22
pub mod java;
33
pub mod kotlin;
4+
pub mod php;
45
pub mod python;
56
pub mod ruby;
67
pub mod rust;

0 commit comments

Comments
 (0)