Skip to main content

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