perl_parser/lib.rs
1//! # perl-parser — Production-grade Perl parser and Language Server Protocol engine
2//!
3//! A comprehensive Perl parser built on recursive descent principles, providing robust AST
4//! generation, LSP feature providers, workspace indexing, and test-driven development support.
5//!
6//! ## Key Features
7//!
8//! - **Tree-sitter Compatible**: AST with kinds, fields, and position tracking compatible with tree-sitter grammar
9//! - **Comprehensive Parsing**: ~100% edge case coverage for Perl 5.8-5.40 syntax
10//! - **LSP Integration**: Full Language Server Protocol feature set (~82% coverage in v0.8.6)
11//! - **TDD Workflow**: Intelligent test generation with return value analysis
12//! - **Incremental Parsing**: Efficient re-parsing for real-time editing
13//! - **Error Recovery**: Graceful handling of malformed input with detailed diagnostics
14//! - **Workspace Navigation**: Cross-file symbol resolution and reference tracking
15//!
16//! ## Quick Start
17//!
18//! ### Basic Parsing
19//!
20//! ```rust
21//! use perl_parser::Parser;
22//!
23//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! let code = r#"sub hello { print "Hello, world!\n"; }"#;
25//! let mut parser = Parser::new(code);
26//!
27//! match parser.parse() {
28//! Ok(ast) => {
29//! println!("AST: {}", ast.to_sexp());
30//! println!("Parsed {} nodes", ast.count_nodes());
31//! }
32//! Err(e) => eprintln!("Parse error: {}", e),
33//! }
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! ### Test-Driven Development
39//!
40//! Generate tests automatically from parsed code:
41//!
42//! ```rust
43//! use perl_parser::Parser;
44//! use perl_parser::tdd::test_generator::{TestGenerator, TestFramework};
45//!
46//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
47//! let code = r#"sub add { my ($a, $b) = @_; return $a + $b; }"#;
48//! let mut parser = Parser::new(code);
49//! let ast = parser.parse()?;
50//!
51//! let generator = TestGenerator::new(TestFramework::TestMore);
52//! let tests = generator.generate_tests(&ast, code);
53//!
54//! // Returns test cases with intelligent assertions
55//! assert!(!tests.is_empty());
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! ### LSP Integration
61//!
62//! Use as a library for LSP features (see `perl-lsp` for the standalone server):
63//!
64//! ```rust
65//! use perl_parser::Parser;
66//! use perl_parser::analysis::semantic::SemanticAnalyzer;
67//!
68//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
69//! let code = "my $x = 42;";
70//! let mut parser = Parser::new(code);
71//! let ast = parser.parse()?;
72//!
73//! // Semantic analysis for hover, completion, etc.
74//! let model = SemanticAnalyzer::analyze(&ast);
75//! # Ok(())
76//! # }
77//! ```
78//!
79//! ## Architecture
80//!
81//! The parser is organized into distinct layers for maintainability and testability:
82//!
83//! ### Core Engine ([`engine`])
84//!
85//! - **[`parser`]**: Recursive descent parser with operator precedence
86//! - **[`ast`]**: Abstract Syntax Tree definitions and node types
87//! - **[`error`]**: Error classification, recovery strategies, and diagnostics
88//! - **[`position`]**: UTF-16 position mapping for LSP protocol compliance
89//! - **[`quote_parser`]**: Specialized parser for quote-like operators
90//! - **[`heredoc_collector`]**: FIFO heredoc collection with indent stripping
91//!
92//! ### IDE Integration (LSP Provider Modules)
93//!
94//! - **[`completion`]**: Context-aware completion providers
95//! - **[`diagnostics`]**: Diagnostics generation and formatting
96//! - **[`references`]**: Reference search providers
97//! - **[`rename`]**: Rename providers with validation
98//! - **[`semantic_tokens`]**: Semantic token generation
99//! - **[`type_definition`]**: Type definition providers
100//! - **[`workspace_symbols`]**: Workspace symbol search
101//!
102//! ### Analysis ([`analysis`])
103//!
104//! - **[`scope_analyzer`]**: Variable and subroutine scoping resolution
105//! - **[`type_inference`]**: Perl type inference engine
106//! - **[`semantic`]**: Semantic model with hover information
107//! - **[`symbol`]**: Symbol table and reference tracking
108//! - **[`dead_code_detector`]**: Unused code detection
109//!
110//! ### Workspace ([`workspace`])
111//!
112//! - **[`workspace_index`]**: Cross-file symbol indexing
113//! - **[`workspace_rename`]**: Multi-file refactoring
114//! - **[`document_store`]**: Document state management
115//!
116//! ### Refactoring ([`refactor`])
117//!
118//! - **[`refactoring`]**: Unified refactoring engine
119//! - **[`modernize`]**: Code modernization utilities
120//! - **[`import_optimizer`]**: Import statement analysis and optimization
121//!
122//! ### Test Support ([`tdd`])
123//!
124//! - **[`test_generator`]**: Intelligent test case generation
125//! - **[`test_runner`]**: Test execution and validation
126//! - **`tdd_workflow`** *(test-only)*: TDD cycle management and coverage tracking
127//!
128//! ## LSP Feature Support
129//!
130//! This crate provides the engine for LSP features. The public standalone server is in
131//! `perllsp`, backed by the `perl-lsp-rs` implementation crate.
132//!
133//! ### Implemented Features
134//!
135//! - **Completion**: Context-aware code completion with type inference
136//! - **Hover**: Documentation and type information on hover
137//! - **Definition**: Go-to-definition with cross-file support
138//! - **References**: Find all references with workspace indexing
139//! - **Rename**: Symbol renaming with conflict detection
140//! - **Diagnostics**: Syntax errors and semantic warnings
141//! - **Formatting**: Code formatting via perltidy integration
142//! - **Folding**: Code folding for blocks and regions
143//! - **Semantic Tokens**: Fine-grained syntax highlighting
144//! - **Call Hierarchy**: Function call navigation
145//! - **Type Hierarchy**: Class inheritance navigation
146//!
147//! See `docs/reference/LSP_CAPABILITY_POLICY.md` for the complete capability matrix.
148//!
149//! ## Incremental Parsing
150//!
151//! Enable efficient re-parsing for real-time editing:
152//!
153//! ```rust,ignore
154//! use perl_parser::{IncrementalState, apply_edits, Edit};
155//!
156//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
157//! let mut state = IncrementalState::new("my $x = 1;");
158//! let ast = state.parse()?;
159//!
160//! // Apply an edit
161//! let edit = Edit {
162//! start_byte: 3,
163//! old_end_byte: 5,
164//! new_end_byte: 5,
165//! text: "$y".to_string(),
166//! };
167//! apply_edits(&mut state, vec![edit]);
168//!
169//! // Incremental re-parse reuses unchanged nodes
170//! let new_ast = state.parse()?;
171//! # Ok(())
172//! # }
173//! ```
174//!
175//! ## Error Recovery
176//!
177//! The parser uses intelligent error recovery to continue parsing after errors:
178//!
179//! ```rust
180//! use perl_parser::Parser;
181//!
182//! let code = "sub broken { if ("; // Incomplete code
183//! let mut parser = Parser::new(code);
184//!
185//! // Parser recovers and builds partial AST
186//! let result = parser.parse();
187//! assert!(result.is_ok());
188//!
189//! // Check recorded errors
190//! let errors = parser.errors();
191//! assert!(!errors.is_empty());
192//! ```
193//!
194//! ## Workspace Indexing
195//!
196//! Build cross-file indexes for workspace-wide navigation:
197//!
198//! ```rust,ignore
199//! use perl_parser::workspace_index::WorkspaceIndex;
200//!
201//! let mut index = WorkspaceIndex::new();
202//! index.index_file("lib/Foo.pm", "package Foo; sub bar { }");
203//! index.index_file("lib/Baz.pm", "use Foo; Foo::bar();");
204//!
205//! // Find all references to Foo::bar
206//! let refs = index.find_references("Foo::bar");
207//! ```
208//!
209//! ## Testing with perl-corpus
210//!
211//! The parser is tested against the comprehensive `perl-corpus` test suite:
212//!
213//! ```bash
214//! # Run parser tests with full corpus coverage
215//! cargo test -p perl-parser
216//!
217//! # Run specific test category
218//! cargo test -p perl-parser --test regex_tests
219//!
220//! # Validate documentation examples
221//! cargo test --doc
222//! ```
223//!
224//! ## Command-Line Tools
225//!
226//! Build and install the LSP server binary:
227//!
228//! ```bash
229//! # Build LSP server
230//! cargo build -p perllsp --release
231//!
232//! # Install globally
233//! cargo install --path crates/perllsp
234//!
235//! # Run LSP server
236//! perllsp --stdio
237//!
238//! # Check server health
239//! perllsp --health
240//! ```
241//!
242//! ## Integration Examples
243//!
244//! ### VSCode Extension
245//!
246//! Configure the LSP server in VSCode settings:
247//!
248//! ```json
249//! {
250//! "perl.lsp.path": "/path/to/perllsp",
251//! "perl.lsp.args": ["--stdio"]
252//! }
253//! ```
254//!
255//! ### Neovim Integration
256//!
257//! ```lua
258//! require'lspconfig'.perl.setup{
259//! cmd = { "/path/to/perllsp", "--stdio" },
260//! }
261//! ```
262//!
263//! ## Performance Characteristics
264//!
265//! - **Single-pass parsing**: O(n) complexity for well-formed input
266//! - **UTF-16 mapping**: Fast bidirectional offset conversion for LSP
267//! - **Incremental updates**: Reuses unchanged AST nodes for efficiency
268//! - **Memory efficiency**: Streaming token processing with bounded lookahead
269//!
270//! ## Compatibility
271//!
272//! - **Perl Versions**: 5.8 through 5.40 (covers 99% of CPAN)
273//! - **LSP Protocol**: LSP 3.17 specification
274//! - **Tree-sitter**: Compatible AST format and position tracking
275//! - **UTF-16**: Full Unicode support with correct LSP position mapping
276//!
277//! ## Related Crates
278//!
279//! - `perllsp`: Public Cargo entry point for the standalone LSP server
280//! - `perl-lsp-rs`: Standalone LSP server runtime implementation (moved from this crate)
281//! - `perl-lexer`: Context-aware Perl tokenizer
282//! - `perl-corpus`: Comprehensive test corpus and generators
283//! - `perl-dap`: Debug Adapter Protocol implementation
284//!
285//! ## Documentation
286//!
287//! - **API Docs**: See module documentation below
288//! - **LSP Guide**: `docs/reference/LSP_IMPLEMENTATION_GUIDE.md`
289//! - **Capability Policy**: `docs/reference/LSP_CAPABILITY_POLICY.md`
290//! - **Commands**: `docs/reference/COMMANDS_REFERENCE.md`
291//! - **Current Status**: `docs/project/CURRENT_STATUS.md`
292
293#![deny(unsafe_code)]
294#![deny(unreachable_pub)] // prevent stray pub items from escaping
295#![warn(rust_2018_idioms)]
296// NOTE: missing_docs enabled with baseline enforcement (Issue #197)
297// Baseline enforced via ci/missing_docs_baseline.txt
298#![warn(missing_docs)]
299#![warn(clippy::all)]
300#![allow(
301 // Core allows for parser/lexer code
302 clippy::too_many_lines,
303 clippy::module_name_repetitions,
304 clippy::cast_possible_truncation,
305 clippy::cast_sign_loss,
306 clippy::cast_precision_loss,
307 clippy::cast_possible_wrap,
308 clippy::must_use_candidate,
309 clippy::missing_errors_doc,
310 clippy::missing_panics_doc,
311
312 // Parser-specific patterns that are fine
313 clippy::wildcard_imports,
314 clippy::enum_glob_use,
315 clippy::match_same_arms,
316 clippy::if_not_else,
317 clippy::struct_excessive_bools,
318 clippy::items_after_statements,
319 clippy::return_self_not_must_use,
320 clippy::unused_self,
321 clippy::collapsible_match,
322 clippy::collapsible_if,
323 clippy::only_used_in_recursion,
324 clippy::items_after_test_module,
325 clippy::while_let_loop,
326 clippy::single_range_in_vec_init,
327 clippy::arc_with_non_send_sync,
328 clippy::needless_range_loop,
329 clippy::result_large_err,
330 clippy::if_same_then_else,
331 clippy::should_implement_trait,
332 clippy::manual_flatten,
333
334 // String handling in parsers
335 clippy::needless_raw_string_hashes,
336 clippy::single_char_pattern,
337 clippy::uninlined_format_args
338)]
339//! ## Architecture
340//!
341//! The parser follows a recursive descent design with operator precedence handling,
342//! maintaining a clean separation from the lexing phase. This modular approach
343//! enables:
344//!
345//! - Independent testing of parsing logic
346//! - Easy integration with different lexer implementations
347//! - Clear error boundaries between lexing and parsing phases
348//! - Optimal performance through single-pass parsing
349//!
350//! ## Example
351//!
352//! ```rust
353//! use perl_parser::Parser;
354//!
355//! let code = "my $x = 42;";
356//! let mut parser = Parser::new(code);
357//!
358//! match parser.parse() {
359//! Ok(ast) => println!("AST: {}", ast.to_sexp()),
360//! Err(e) => eprintln!("Parse error: {}", e),
361//! }
362//! ```
363
364/// Parser engine components and supporting utilities.
365pub mod engine;
366/// Legacy module aliases for moved engine components.
367pub use engine::{error, parser, position};
368
369/// Abstract Syntax Tree (AST) definitions for Perl parsing.
370pub use engine::ast;
371/// Experimental second-generation AST (work in progress).
372pub use engine::ast_v2;
373/// Edit tracking for incremental parsing.
374pub use engine::edit;
375/// Heredoc content collector with FIFO ordering and indent stripping.
376pub use engine::heredoc_collector;
377/// Recursive descent Perl parser with error recovery and AST generation.
378pub use engine::parser::Parser;
379/// Parser context with error recovery support.
380pub use engine::parser_context;
381/// Pragma tracking for `use` and related directives.
382pub use engine::pragma_tracker;
383/// Parser for Perl quote and quote-like operators.
384pub use engine::quote_parser;
385#[cfg(not(target_arch = "wasm32"))]
386/// Error classification and recovery strategies for parse failures.
387pub use error::classifier as error_classifier;
388/// Error recovery strategies for resilient parsing.
389pub use error::recovery as error_recovery;
390/// Parser utilities and helpers.
391pub use perl_parser_core::util;
392
393/// Line-to-byte offset index for fast position lookups.
394pub use perl_parser_core::line_index;
395/// Line ending detection and UTF-16 position mapping for LSP compliance.
396pub use position::{LineEnding, PositionMapper};
397
398/// Semantic analysis, scope resolution, and type inference.
399pub mod analysis;
400/// Perl builtin function signatures and metadata.
401pub mod builtins;
402#[cfg(feature = "incremental")]
403/// Incremental parsing for efficient re-parsing during editing.
404pub mod incremental;
405/// Code refactoring, modernization, and import optimization.
406pub mod refactor;
407/// Test-driven development support and test generation.
408pub mod tdd;
409/// Token stream, trivia, and token wrapper utilities.
410pub mod tokens;
411/// External tooling integration (perltidy, perlcritic, performance).
412pub mod tooling;
413/// Workspace indexing, document store, and cross-file operations.
414pub mod workspace;
415
416/// Variable and subroutine declaration analysis.
417pub use analysis::declaration;
418#[cfg(not(target_arch = "wasm32"))]
419/// File and symbol indexing for workspace-wide navigation.
420pub use analysis::index;
421/// Scope analysis for variable and subroutine resolution.
422pub use analysis::scope_analyzer;
423/// Semantic model with hover information and token classification.
424pub use analysis::semantic;
425/// Symbol table, extraction, and reference tracking.
426pub use analysis::symbol;
427/// Type inference engine for Perl variable analysis.
428pub use analysis::type_inference;
429/// Builtin function signature lookup tables.
430pub use builtins::builtin_signatures;
431/// Perfect hash function (PHF) based builtin signature lookup.
432pub use builtins::builtin_signatures_phf;
433/// Dead code detection for Perl workspaces.
434#[cfg(not(target_arch = "wasm32"))]
435pub use perl_dead_code as dead_code_detector;
436
437// Re-exports from extracted microcrates
438/// LSP code actions for automated refactoring and fixes.
439pub mod code_actions {
440 pub use perl_lsp_code_actions::*;
441}
442/// Enhanced code actions provider with workspace-aware refactoring.
443pub use perl_lsp_code_actions::EnhancedCodeActionsProvider;
444/// LSP completion for code suggestions.
445pub mod completion {
446 pub use perl_lsp_completion::*;
447}
448/// LSP diagnostics for error reporting.
449pub mod diagnostics {
450 pub use perl_lsp_diagnostics::*;
451}
452/// LSP document links provider for file and URL navigation.
453pub mod document_links {
454 pub use perl_lsp_navigation::*;
455}
456/// LSP implementation provider.
457pub mod implementation_provider {
458 pub use perl_lsp_navigation::*;
459}
460/// LSP inlay hints for inline type and parameter information.
461pub mod inlay_hints {
462 pub use perl_lsp_inlay_hints::*;
463}
464/// LSP inlay hints provider implementation.
465pub mod inlay_hints_provider {
466 pub use perl_lsp_inlay_hints::*;
467}
468/// LSP references provider for symbol usage analysis.
469pub mod references {
470 pub use perl_lsp_navigation::*;
471}
472/// LSP rename for symbol renaming.
473pub mod rename {
474 pub use perl_lsp_rename::*;
475}
476/// LSP semantic tokens provider for syntax highlighting.
477pub mod semantic_tokens {
478 pub use perl_lsp_semantic_tokens::*;
479}
480/// LSP semantic tokens provider implementation.
481pub mod semantic_tokens_provider {
482 pub use perl_lsp_semantic_tokens::*;
483}
484/// LSP type definition provider.
485#[cfg(feature = "lsp-compat")]
486pub mod type_definition {
487 pub use perl_lsp_navigation::*;
488}
489/// LSP type hierarchy provider for inheritance navigation.
490pub mod type_hierarchy {
491 pub use perl_lsp_navigation::*;
492}
493/// LSP workspace symbols provider.
494pub mod workspace_symbols {
495 pub use perl_lsp_navigation::*;
496}
497
498/// Import statement analysis and optimization.
499pub use refactor::import_optimizer;
500/// Code modernization utilities for Perl best practices.
501pub use refactor::modernize;
502/// Enhanced code modernization with refactoring capabilities.
503pub use refactor::modernize_refactored;
504/// Unified refactoring engine for comprehensive code transformations.
505pub use refactor::refactoring;
506/// Token stream with position-aware iteration.
507pub use tokens::token_stream;
508/// Lightweight token wrapper for AST integration.
509pub use tokens::token_wrapper;
510/// Trivia (whitespace and comments) representation.
511pub use tokens::trivia;
512/// Parser that preserves trivia tokens for formatting.
513pub use tokens::trivia_parser;
514/// Performance measurement and caching utilities.
515pub use tooling::performance;
516/// Perl::Critic integration for lint diagnostics.
517pub use tooling::perl_critic;
518/// Perltidy integration for code formatting.
519pub use tooling::perltidy;
520
521#[cfg(feature = "incremental")]
522/// Advanced AST node reuse strategies for incremental parsing.
523pub use incremental::incremental_advanced_reuse;
524#[cfg(feature = "incremental")]
525/// Checkpoint-based incremental parsing with rollback support.
526pub use incremental::incremental_checkpoint;
527#[cfg(feature = "incremental")]
528/// Document-level incremental parsing state management.
529pub use incremental::incremental_document;
530#[cfg(feature = "incremental")]
531/// Edit representation and application for incremental updates.
532pub use incremental::incremental_edit;
533#[cfg(feature = "incremental")]
534#[deprecated(note = "LSP server moved to perl-lsp; perl-parser no longer handles didChange")]
535/// Legacy incremental handler (deprecated, use `perl-lsp` crate instead).
536pub use incremental::incremental_handler_v2;
537#[cfg(feature = "incremental")]
538/// Integration layer connecting incremental parsing with the full parser.
539pub use incremental::incremental_integration;
540#[cfg(feature = "incremental")]
541/// Simplified incremental parsing interface for common use cases.
542pub use incremental::incremental_simple;
543#[cfg(feature = "incremental")]
544/// Second-generation incremental parsing with improved node reuse.
545pub use incremental::incremental_v2;
546
547/// Basic TDD utilities and test helpers.
548pub use tdd::tdd_basic;
549#[cfg(test)]
550/// TDD workflow integration for Test-Driven Development support.
551pub use tdd::tdd_workflow;
552/// Intelligent test case generation from parsed Perl code.
553pub use tdd::test_generator;
554/// Test execution and TDD support functionality.
555pub use tdd::test_runner;
556
557/// In-memory document storage for open editor buffers.
558pub use workspace::document_store;
559/// Cross-file symbol index for workspace-wide navigation.
560pub use workspace::workspace_index;
561#[cfg(not(target_arch = "wasm32"))]
562/// Multi-file refactoring operations across a workspace.
563pub use workspace::workspace_refactor;
564/// Cross-file symbol renaming with conflict detection.
565pub use workspace::workspace_rename;
566
567/// AST node, node kind enum, and source location types.
568pub use ast::{Node, NodeKind, SourceLocation};
569/// Parse error and result types for parser output.
570pub use error::{ParseError, ParseResult};
571#[cfg(feature = "incremental")]
572/// Checkpointed incremental parser with simple edit tracking.
573pub use incremental_checkpoint::{CheckpointedIncrementalParser, SimpleEdit};
574/// Pragma state tracking for `use strict`, `use warnings`, etc.
575pub use pragma_tracker::{PragmaState, PragmaTracker};
576/// Token types and token stream for lexer output.
577pub use token_stream::{Token, TokenKind, TokenStream};
578/// Trivia (whitespace/comments) attached to AST nodes.
579pub use trivia::{NodeWithTrivia, Trivia, TriviaToken};
580/// Trivia-preserving parser and formatting utilities.
581pub use trivia_parser::{TriviaPreservingParser, format_with_trivia};
582
583// Incremental parsing exports (feature-gated)
584#[cfg(feature = "incremental")]
585/// Core incremental parsing types: edit representation, state, and application.
586pub use incremental::{Edit, IncrementalState, apply_edits};
587
588/// Semantic analysis types for hover, tokens, and code understanding.
589pub use semantic::{
590 HoverInfo, SemanticAnalyzer, SemanticModel, SemanticToken, SemanticTokenModifier,
591 SemanticTokenType,
592};
593/// Symbol extraction, table, and reference types for navigation.
594pub use symbol::{Symbol, SymbolExtractor, SymbolKind, SymbolReference, SymbolTable};
595
596// =============================================================================
597// LSP Feature Exports (DEPRECATED - migrated to perl-lsp crate)
598// =============================================================================
599// These exports are commented out during the migration period.
600// Use `perl_lsp` crate for LSP functionality instead.
601//
602// pub use code_actions::{CodeAction, CodeActionEdit, CodeActionKind, CodeActionsProvider};
603// pub use code_actions_enhanced::EnhancedCodeActionsProvider;
604// pub use code_actions_provider::{...};
605// pub use code_lens_provider::{CodeLens, CodeLensProvider, ...};
606// pub use completion::{CompletionContext, CompletionItem, CompletionItemKind, CompletionProvider};
607// pub use diagnostics::{Diagnostic, DiagnosticSeverity, DiagnosticTag, ...};
608// pub use document_links::compute_links;
609// pub use folding::{FoldingRange, FoldingRangeExtractor, FoldingRangeKind};
610// pub use formatting::{CodeFormatter, FormatTextEdit, FormattingOptions};
611// pub use inlay_hints::{parameter_hints, trivial_type_hints};
612// pub use lsp::protocol::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
613// pub use lsp_server::LspServer;
614// pub use on_type_formatting::compute_on_type_edit;
615// pub use rename::{RenameOptions, RenameProvider, RenameResult, TextEdit, apply_rename_edits};
616// pub use selection_range::{build_parent_map, selection_chain};
617// pub use semantic_tokens::{...};
618// pub use semantic_tokens_provider::{...};
619// pub use signature_help::{ParameterInfo, SignatureHelp, SignatureHelpProvider, SignatureInfo};
620// pub use workspace_symbols::{WorkspaceSymbol, WorkspaceSymbolsProvider};
621// =============================================================================
622
623/// Import analysis, optimization, and unused import detection.
624pub use import_optimizer::{
625 DuplicateImport, ImportAnalysis, ImportEntry, ImportOptimizer, MissingImport,
626 OrganizationSuggestion, SuggestionPriority, UnusedImport,
627};
628/// Scope analysis issue types and analyzer.
629pub use scope_analyzer::{IssueKind, ScopeAnalyzer, ScopeIssue};
630#[cfg(test)]
631/// Test generation, coverage reporting, and refactoring suggestions.
632pub use test_generator::{
633 CoverageReport, Priority, RefactoringCategory, RefactoringSuggester, RefactoringSuggestion,
634 TestCase, TestFramework, TestGenerator, TestGeneratorOptions, TestResults, TestRunner,
635};
636/// Type inference types: Perl types, constraints, and inference engine.
637pub use type_inference::{
638 PerlType, ScalarType, TypeBasedCompletion, TypeConstraint, TypeEnvironment,
639 TypeInferenceEngine, TypeLocation,
640};
641
642/// Refactoring engine types: configuration, operations, and results.
643pub use refactoring::{
644 ModernizationPattern, RefactoringConfig, RefactoringEngine, RefactoringOperation,
645 RefactoringResult, RefactoringScope, RefactoringType,
646};
647#[cfg(test)]
648/// TDD workflow types: actions, configuration, and cycle management.
649pub use tdd_workflow::{
650 AnnotationSeverity, CoverageAnnotation, TddAction, TddConfig, TddCycleResult, TddWorkflow,
651 TestType, WorkflowState, WorkflowStatus,
652};
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657 use perl_tdd_support::must;
658
659 #[test]
660 fn test_basic_parsing() {
661 let mut parser = Parser::new("my $x = 42;");
662 let result = parser.parse();
663 assert!(result.is_ok());
664
665 let ast = must(result);
666 assert!(matches!(ast.kind, NodeKind::Program { .. }));
667 }
668
669 #[test]
670 fn test_variable_declaration() {
671 let cases = vec![
672 ("my $x;", "my"),
673 ("our $y;", "our"),
674 ("local $z;", "local"),
675 ("state $w;", "state"),
676 ];
677
678 for (code, declarator) in cases {
679 let mut parser = Parser::new(code);
680 let result = parser.parse();
681 assert!(result.is_ok(), "Failed to parse: {}", code);
682
683 let ast = must(result);
684 if let NodeKind::Program { statements } = &ast.kind {
685 assert_eq!(statements.len(), 1);
686 let is_var_decl =
687 matches!(statements[0].kind, NodeKind::VariableDeclaration { .. });
688 assert!(is_var_decl, "Expected VariableDeclaration for: {}", code);
689 if let NodeKind::VariableDeclaration { declarator: decl, .. } = &statements[0].kind
690 {
691 assert_eq!(decl, declarator);
692 }
693 }
694 }
695 }
696
697 #[test]
698 fn test_operators() {
699 // Test operators that work correctly
700 let cases = vec![
701 ("$a + $b", "+"),
702 ("$a - $b", "-"),
703 ("$a * $b", "*"),
704 ("$a . $b", "."),
705 ("$a && $b", "&&"),
706 ("$a || $b", "||"),
707 ];
708
709 for (code, expected_op) in cases {
710 let mut parser = Parser::new(code);
711 let result = parser.parse();
712 assert!(result.is_ok(), "Failed to parse: {}", code);
713
714 let ast = must(result);
715 if let NodeKind::Program { statements } = &ast.kind {
716 assert!(!statements.is_empty(), "No statements found in AST for: {}", code);
717
718 // Find the binary node, which might be wrapped in an ExpressionStatement
719 let binary_node = match &statements[0].kind {
720 NodeKind::ExpressionStatement { expression } => match &expression.kind {
721 NodeKind::Binary { op, left, right } => Some((op, left, right)),
722 _ => None,
723 },
724 NodeKind::Binary { op, left, right } => Some((op, left, right)),
725 _ => None,
726 };
727
728 assert!(
729 binary_node.is_some(),
730 "Expected Binary operator for: {}. Found: {:?}",
731 code,
732 statements[0].kind
733 );
734 if let Some((op, left, right)) = binary_node {
735 assert_eq!(op, expected_op, "Operator mismatch for: {}", code);
736
737 // Additional diagnostic information
738 println!("Parsing: {}", code);
739 println!("Left node: {:?}", left);
740 println!("Right node: {:?}", right);
741 }
742 }
743 assert!(
744 matches!(ast.kind, NodeKind::Program { .. }),
745 "Expected Program node, found: {:?}",
746 ast.kind
747 );
748 }
749 }
750
751 #[test]
752 fn test_operators_with_context() {
753 // These operators require context-aware parsing to disambiguate from similar syntax:
754 // - `/` could be division or regex delimiter
755 // - `%` could be modulo or hash sigil
756 // - `**` could be exponent or glob pattern
757 // - `//` could be defined-or or regex delimiter
758 // The lexer handles disambiguation via LexerMode::ExpectTerm tracking.
759 let cases: Vec<(&str, &str)> = vec![
760 ("2 / 3", "/"), // Division (not regex)
761 ("$a % $b", "%"), // Modulo (not hash sigil)
762 ("$a ** $b", "**"), // Exponent (not glob)
763 ("$a // $b", "//"), // Defined-or (not regex)
764 ];
765
766 for (code, expected_op) in cases {
767 let mut parser = Parser::new(code);
768 let result = parser.parse();
769 assert!(result.is_ok(), "Failed to parse: {}", code);
770
771 let ast = must(result);
772 if let NodeKind::Program { statements } = &ast.kind {
773 assert!(!statements.is_empty(), "No statements found in AST for: {}", code);
774
775 // Find the binary node, which might be wrapped in an ExpressionStatement
776 let binary_node = match &statements[0].kind {
777 NodeKind::ExpressionStatement { expression } => match &expression.kind {
778 NodeKind::Binary { op, .. } => Some(op),
779 _ => None,
780 },
781 NodeKind::Binary { op, .. } => Some(op),
782 _ => None,
783 };
784
785 assert!(
786 binary_node.is_some(),
787 "Expected Binary operator for: {}. Found: {:?}",
788 code,
789 statements[0].kind
790 );
791 if let Some(op) = binary_node {
792 assert_eq!(op, expected_op, "Operator mismatch for: {}", code);
793 }
794 }
795 assert!(
796 matches!(ast.kind, NodeKind::Program { .. }),
797 "Expected Program node, found: {:?}",
798 ast.kind
799 );
800 }
801 }
802
803 #[test]
804 fn test_string_literals() {
805 let cases = vec![r#""hello""#, r#"'world'"#, r#"qq{foo}"#, r#"q{bar}"#];
806
807 for code in cases {
808 let mut parser = Parser::new(code);
809 let result = parser.parse();
810 assert!(result.is_ok(), "Failed to parse: {}", code);
811 }
812 }
813
814 #[test]
815 fn test_arrays_and_hashes() {
816 let cases = vec![
817 "@array",
818 "%hash",
819 "$array[0]",
820 "$hash{key}",
821 "@array[1, 2, 3]",
822 "@hash{'a', 'b'}",
823 ];
824
825 for code in cases {
826 let mut parser = Parser::new(code);
827 let result = parser.parse();
828 assert!(result.is_ok(), "Failed to parse: {}", code);
829 }
830 }
831
832 #[test]
833 fn test_subroutines() {
834 let cases = vec![
835 "sub foo { }",
836 "sub bar { return 42; }",
837 "sub baz ($x, $y) { $x + $y }",
838 "sub qux :method { }",
839 ];
840
841 for code in cases {
842 let mut parser = Parser::new(code);
843 let result = parser.parse();
844 assert!(result.is_ok(), "Failed to parse: {}", code);
845
846 let ast = must(result);
847 if let NodeKind::Program { statements } = &ast.kind {
848 assert_eq!(statements.len(), 1);
849 assert!(matches!(statements[0].kind, NodeKind::Subroutine { .. }));
850 }
851 }
852 }
853
854 #[test]
855 fn test_control_flow() {
856 let cases = vec![
857 "if ($x) { }",
858 "if ($x) { } else { }",
859 "if ($x) { } elsif ($y) { } else { }",
860 "unless ($x) { }",
861 "while ($x) { }",
862 "until ($x) { }",
863 "for (my $i = 0; $i < 10; $i++) { }",
864 "foreach my $x (@array) { }",
865 ];
866
867 for code in cases {
868 let mut parser = Parser::new(code);
869 let result = parser.parse();
870 assert!(result.is_ok(), "Failed to parse: {}", code);
871 }
872 }
873
874 #[test]
875 fn test_regex() {
876 let cases = vec![
877 "/pattern/",
878 "m/pattern/",
879 "s/old/new/",
880 "tr/a-z/A-Z/",
881 r#"qr/\d+/"#,
882 "$x =~ /foo/",
883 "$x !~ /bar/",
884 ];
885
886 for code in cases {
887 let mut parser = Parser::new(code);
888 let result = parser.parse();
889 assert!(result.is_ok(), "Failed to parse: {}", code);
890 }
891 }
892
893 #[test]
894 fn test_error_cases() {
895 let cases = vec![
896 ("if (", "Unexpected end of input"),
897 ("sub (", "Unexpected end of input"),
898 ("my (", "Unexpected end of input"),
899 ("{", "Unexpected end of input"),
900 ];
901
902 for (code, _expected_error) in cases {
903 let mut parser = Parser::new(code);
904 let result = parser.parse();
905
906 // With error recovery, parse() succeeds but collects errors
907 assert!(result.is_ok(), "Parser should recover from errors for: {}", code);
908
909 // Check that errors were recorded
910 let errors = parser.errors();
911 assert!(!errors.is_empty(), "Expected recorded errors for: {}", code);
912 }
913 }
914
915 #[test]
916 fn test_modern_perl_features() {
917 let cases = vec![
918 "class Point { }",
919 "method new { }",
920 "try { } catch ($e) { }",
921 // "defer { }", // defer is not yet supported by the lexer
922 "my $x :shared = 42;",
923 ];
924
925 for code in cases {
926 let mut parser = Parser::new(code);
927 let result = parser.parse();
928 assert!(result.is_ok(), "Failed to parse: {}", code);
929 }
930 }
931
932 #[test]
933 fn test_edge_cases() {
934 let cases = vec![
935 // Indirect object syntax
936 "print STDOUT 'hello';",
937 "new Class;",
938 // Multi-variable declarations
939 "my ($x, $y) = (1, 2);",
940 "my ($a :shared, $b :locked);",
941 // Complex expressions
942 "$x->@*",
943 "$x->%*",
944 "$x->$*",
945 // Defined-or
946 "$x // 'default'",
947 // ISA operator
948 "$obj ISA 'Class'",
949 ];
950
951 for code in cases {
952 let mut parser = Parser::new(code);
953 let result = parser.parse();
954 assert!(result.is_ok(), "Failed to parse edge case: {}", code);
955 }
956 }
957}