1use perl_ast::ast::{Node, NodeKind};
7use std::ops::Range;
8
9const MAX_DISABLED_WARNING_CATEGORIES: usize = 256;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub struct PerlVersion {
14 pub major: u32,
16 pub minor: u32,
18}
19
20impl PerlVersion {
21 pub const fn new(major: u32, minor: u32) -> Self {
23 Self { major, minor }
24 }
25}
26
27#[derive(Debug, Clone, Default, PartialEq)]
29pub struct PragmaState {
30 pub strict_vars: bool,
32 pub strict_subs: bool,
34 pub strict_refs: bool,
36 pub warnings: bool,
38 pub utf8: bool,
40 pub encoding: Option<String>,
42 pub unicode_strings: bool,
44 pub locale: bool,
46 pub locale_scope: Option<String>,
48 pub disabled_warning_categories: Vec<String>,
55 pub signatures_strict: bool,
61 pub features: Vec<&'static str>,
66 pub builtin_imports: Vec<String>,
68}
69
70#[derive(Debug, Clone, Default, PartialEq)]
75pub struct PragmaSnapshot {
76 state: PragmaState,
77}
78
79impl PragmaSnapshot {
80 #[must_use]
82 pub fn from_state(state: PragmaState) -> Self {
83 Self { state }
84 }
85
86 #[must_use]
88 pub fn state(&self) -> &PragmaState {
89 &self.state
90 }
91
92 #[must_use]
94 pub fn strict_enabled(&self) -> bool {
95 self.state.strict_vars && self.state.strict_subs && self.state.strict_refs
96 }
97
98 #[must_use]
100 pub fn warnings_enabled(&self) -> bool {
101 self.state.warnings
102 }
103
104 #[must_use]
106 pub fn has_feature(&self, feature: &str) -> bool {
107 self.state.has_feature(feature)
108 }
109
110 #[must_use]
112 pub fn is_warning_active(&self, category: &str) -> bool {
113 self.state.is_warning_active(category)
114 }
115}
116
117impl From<PragmaState> for PragmaSnapshot {
118 fn from(state: PragmaState) -> Self {
119 Self::from_state(state)
120 }
121}
122
123impl From<PragmaSnapshot> for PragmaState {
124 fn from(snapshot: PragmaSnapshot) -> Self {
125 snapshot.state
126 }
127}
128
129#[derive(Debug, Clone, PartialEq)]
131pub struct PragmaStateQuery {
132 offset: usize,
133 snapshot: PragmaSnapshot,
134}
135
136impl PragmaStateQuery {
137 #[must_use]
139 pub fn offset(&self) -> usize {
140 self.offset
141 }
142
143 #[must_use]
145 pub fn snapshot(&self) -> &PragmaSnapshot {
146 &self.snapshot
147 }
148}
149
150#[derive(Debug, Clone, Default, PartialEq)]
153pub struct CompileTimePragmaEnvironment {
154 map: PragmaMap,
155}
156
157#[derive(Debug, Clone, PartialEq)]
159#[non_exhaustive]
160pub struct PragmaEntry {
161 pub range: Range<usize>,
166 pub snapshot: PragmaSnapshot,
168}
169
170#[derive(Debug, Clone, Default, PartialEq)]
172#[non_exhaustive]
173pub struct PragmaMap {
174 entries: Box<[PragmaEntry]>,
175}
176
177impl CompileTimePragmaEnvironment {
178 #[must_use]
180 pub fn build(ast: &Node) -> Self {
181 let mut ranges = Vec::new();
182 let mut current_state = PragmaState::default();
183 PragmaTracker::build_ranges(ast, &mut current_state, &mut ranges);
184 ranges.sort_by_key(|(range, _)| range.start);
185
186 let entries = ranges
187 .into_iter()
188 .map(|(range, state)| PragmaEntry { range, snapshot: PragmaSnapshot::from(state) })
189 .collect::<Vec<_>>()
190 .into_boxed_slice();
191
192 Self { map: PragmaMap { entries } }
193 }
194
195 #[must_use]
197 pub fn query_at(&self, offset: usize) -> PragmaStateQuery {
198 PragmaStateQuery { offset, snapshot: self.snapshot_at(offset) }
199 }
200
201 #[must_use]
203 pub fn snapshot_at(&self, offset: usize) -> PragmaSnapshot {
204 self.map.snapshot_at(offset)
205 }
206
207 #[must_use]
209 pub fn map(&self) -> &PragmaMap {
210 &self.map
211 }
212
213 #[must_use]
215 pub fn as_map(&self) -> Vec<(Range<usize>, PragmaSnapshot)> {
216 self.map.to_tuples()
217 }
218}
219
220impl PragmaMap {
221 #[must_use]
223 pub fn snapshot_at(&self, offset: usize) -> PragmaSnapshot {
224 let idx = self.entries.partition_point(|entry| entry.range.start <= offset);
225 let snapshot = if idx > 0 {
226 self.entries[idx - 1].snapshot.clone()
227 } else {
228 PragmaSnapshot::default()
229 };
230
231 normalize_snapshot(snapshot)
232 }
233
234 #[must_use]
236 pub fn state_at(&self, offset: usize) -> PragmaState {
237 self.snapshot_at(offset).into()
238 }
239
240 #[must_use]
242 pub fn final_state(&self) -> PragmaState {
243 let state = self
244 .entries
245 .last()
246 .map_or_else(PragmaState::default, |entry| entry.snapshot.clone().into());
247
248 normalize_state(state)
249 }
250
251 #[must_use]
253 pub fn cursor(&self) -> PragmaQueryCursor {
254 PragmaQueryCursor::new()
255 }
256
257 #[must_use]
259 pub fn entries(&self) -> &[PragmaEntry] {
260 &self.entries
261 }
262
263 #[must_use]
265 pub fn to_tuples(&self) -> Vec<(Range<usize>, PragmaSnapshot)> {
266 self.entries.iter().map(|e| (e.range.clone(), e.snapshot.clone())).collect()
267 }
268}
269
270fn normalize_snapshot(mut snapshot: PragmaSnapshot) -> PragmaSnapshot {
271 if snapshot.state.signatures_strict {
272 snapshot.state.strict_vars = true;
273 snapshot.state.strict_subs = true;
274 snapshot.state.strict_refs = true;
275 }
276
277 snapshot
278}
279
280fn normalize_state(mut state: PragmaState) -> PragmaState {
281 if state.signatures_strict {
282 state.strict_vars = true;
283 state.strict_subs = true;
284 state.strict_refs = true;
285 }
286
287 state
288}
289
290impl PragmaState {
291 pub fn all_strict() -> Self {
293 Self {
294 strict_vars: true,
295 strict_subs: true,
296 strict_refs: true,
297 warnings: false,
298 utf8: false,
299 encoding: None,
300 unicode_strings: false,
301 locale: false,
302 locale_scope: None,
303 disabled_warning_categories: Vec::new(),
304 signatures_strict: false,
305 features: Vec::new(),
306 builtin_imports: Vec::new(),
307 }
308 }
309
310 #[must_use]
320 pub fn is_warning_active(&self, category: &str) -> bool {
321 self.warnings && !self.disabled_warning_categories.iter().any(|c| c == category)
322 }
323
324 #[must_use]
326 pub fn has_feature(&self, feature: &str) -> bool {
327 self.features.contains(&feature)
328 }
329
330 #[must_use]
332 pub fn has_builtin_import(&self, name: &str) -> bool {
333 self.builtin_imports.iter().any(|import| import == name)
334 }
335}
336
337pub fn parse_perl_version(module: &str) -> Option<PerlVersion> {
346 let s = module.strip_prefix('v').unwrap_or(module);
347 let mut parts = s.splitn(3, '.');
348
349 let major = parse_version_component(parts.next()?)?;
350 let minor = match parts.next() {
351 Some(part) => parse_version_component(part)?,
352 None => 0,
353 };
354
355 Some(PerlVersion::new(major, minor))
356}
357
358fn parse_version_component(component: &str) -> Option<u32> {
359 let component = component.split_once('_').map_or(component, |(head, _)| head);
360 component.parse().ok()
361}
362
363#[must_use]
365pub fn version_implies_strict(version: PerlVersion) -> bool {
366 version >= PerlVersion::new(5, 12)
367}
368
369#[must_use]
371pub fn version_implies_warnings(version: PerlVersion) -> bool {
372 version >= PerlVersion::new(5, 35)
373}
374
375#[must_use]
384pub fn features_enabled_by_version(version: PerlVersion) -> Vec<&'static str> {
385 let mut features = Vec::new();
386
387 if version >= PerlVersion::new(5, 10) {
389 features.extend_from_slice(&["say", "state", "switch"]);
390 }
391
392 if version >= PerlVersion::new(5, 12) {
394 features.push("unicode_strings");
395 }
396
397 if version >= PerlVersion::new(5, 16) {
399 features.extend_from_slice(&["unicode_eval", "evalbytes", "current_sub", "fc"]);
400 }
401
402 if version >= PerlVersion::new(5, 20) {
405 features.push("postfix_deref");
406 }
407
408 if version >= PerlVersion::new(5, 34) {
410 features.push("try");
411 }
412
413 if version >= PerlVersion::new(5, 36) {
415 features.extend_from_slice(&["signatures", "defer", "isa"]);
416 }
417
418 if version >= PerlVersion::new(5, 38) {
420 features.extend_from_slice(&["class", "field", "method"]);
421 features.retain(|&f| f != "switch");
422 }
423
424 if version >= PerlVersion::new(5, 40) {
426 features.push("builtin");
427 }
428
429 features
430}
431
432fn enable_effective_version_semantics(state: &mut PragmaState, version: PerlVersion) {
433 if version_implies_strict(version) {
434 state.strict_vars = true;
435 state.strict_subs = true;
436 state.strict_refs = true;
437 }
438 if version_implies_warnings(version) {
439 state.warnings = true;
440 }
441 state.features = features_enabled_by_version(version);
444 state.unicode_strings = state.has_feature("unicode_strings");
445 state.signatures_strict = false;
446}
447
448fn feature_items(arg: &str) -> Vec<String> {
449 pragma_arg_items(arg)
450}
451
452fn known_feature_name(name: &str) -> Option<&'static str> {
453 match name {
454 "say" => Some("say"),
455 "state" => Some("state"),
456 "switch" => Some("switch"),
457 "unicode_strings" => Some("unicode_strings"),
458 "unicode_eval" => Some("unicode_eval"),
459 "evalbytes" => Some("evalbytes"),
460 "current_sub" => Some("current_sub"),
461 "fc" => Some("fc"),
462 "postfix_deref" => Some("postfix_deref"),
463 "try" => Some("try"),
464 "signatures" => Some("signatures"),
465 "defer" => Some("defer"),
466 "isa" => Some("isa"),
467 "class" => Some("class"),
468 "field" => Some("field"),
469 "method" => Some("method"),
470 "builtin" => Some("builtin"),
471 _ => None,
472 }
473}
474
475const ALL_KNOWN_FEATURES: &[&str] = &[
476 "say",
477 "state",
478 "switch",
479 "unicode_strings",
480 "unicode_eval",
481 "evalbytes",
482 "current_sub",
483 "fc",
484 "postfix_deref",
485 "try",
486 "signatures",
487 "defer",
488 "isa",
489 "class",
490 "field",
491 "method",
492 "builtin",
493];
494
495fn enable_feature_name(state: &mut PragmaState, name: &str) -> bool {
496 if name == "signatures" {
497 state.signatures_strict = true;
498 }
499 if name == "unicode_strings" {
500 state.unicode_strings = true;
501 }
502
503 if let Some(feature) = known_feature_name(name) {
504 if state.features.iter().all(|existing| existing != &feature) {
505 state.features.push(feature);
506 }
507 true
508 } else {
509 false
510 }
511}
512
513fn disable_feature_name(state: &mut PragmaState, name: &str) -> bool {
514 if name == "signatures" {
515 state.signatures_strict = false;
516 }
517 if name == "unicode_strings" {
518 state.unicode_strings = false;
519 }
520
521 if let Some(feature) = known_feature_name(name) {
522 let before = state.features.len();
523 state.features.retain(|existing| *existing != feature);
524 before != state.features.len()
525 } else {
526 false
527 }
528}
529
530fn apply_feature_state(state: &mut PragmaState, args: &[String], enabled: bool) -> bool {
531 if !enabled && args.is_empty() {
532 let changed =
533 !state.features.is_empty() || state.unicode_strings || state.signatures_strict;
534 state.features.clear();
535 state.unicode_strings = false;
536 state.signatures_strict = false;
537 return changed;
538 }
539
540 let mut changed = false;
541
542 for arg in args {
543 for item in feature_items(arg) {
544 if enabled && item == ":all" {
545 for feature in ALL_KNOWN_FEATURES {
546 changed |= enable_feature_name(state, feature);
547 }
548 continue;
549 }
550
551 if !enabled && item == ":all" {
552 let had_features =
553 !state.features.is_empty() || state.unicode_strings || state.signatures_strict;
554 state.features.clear();
555 state.unicode_strings = false;
556 state.signatures_strict = false;
557 changed |= had_features;
558 continue;
559 }
560
561 if let Some(version) = item.strip_prefix(':').and_then(parse_perl_version) {
562 for feature in features_enabled_by_version(version) {
563 changed |= if enabled {
564 enable_feature_name(state, feature)
565 } else {
566 disable_feature_name(state, feature)
567 };
568 }
569 continue;
570 }
571
572 changed |= if enabled {
573 enable_feature_name(state, &item)
574 } else {
575 disable_feature_name(state, &item)
576 };
577 }
578 }
579
580 changed
581}
582
583fn builtin_import_names(arg: &str) -> Vec<String> {
584 let trimmed = arg.trim();
585
586 if let Some(inner) = trimmed.strip_prefix("qw(").and_then(|s| s.strip_suffix(')')) {
587 return inner
588 .split_whitespace()
589 .filter(|name| !name.is_empty())
590 .map(|name| name.trim_matches('\'').trim_matches('"').to_string())
591 .collect();
592 }
593
594 let name = trimmed.trim_matches('\'').trim_matches('"');
595 if name.is_empty() { Vec::new() } else { vec![name.to_string()] }
596}
597
598fn apply_builtin_imports(state: &mut PragmaState, args: &[String]) {
599 for arg in args {
600 for name in builtin_import_names(arg) {
601 if !state.builtin_imports.iter().any(|import| import == &name) {
602 state.builtin_imports.push(name);
603 }
604 }
605 }
606}
607
608fn add_disabled_warning_category(state: &mut PragmaState, category: &str) {
615 if category.is_empty() {
616 return;
617 }
618
619 if state.disabled_warning_categories.iter().any(|c| c == category) {
620 return;
621 }
622
623 if state.disabled_warning_categories.len() >= MAX_DISABLED_WARNING_CATEGORIES {
624 return;
625 }
626
627 state.disabled_warning_categories.push(category.to_string());
628}
629
630fn remove_builtin_imports(state: &mut PragmaState, args: &[String]) {
631 if args.is_empty() {
632 state.builtin_imports.clear();
633 return;
634 }
635
636 let names_to_remove: Vec<String> =
637 args.iter().flat_map(|arg| builtin_import_names(arg)).collect();
638 state.builtin_imports.retain(|import| !names_to_remove.iter().any(|name| name == import));
639}
640
641fn pragma_arg_items(arg: &str) -> Vec<String> {
642 let trimmed = arg.trim().trim_matches('\'').trim_matches('"');
643
644 if let Some(inner) = trimmed.strip_prefix("qw(").and_then(|s| s.strip_suffix(')')) {
645 return inner.split_whitespace().map(|item| item.to_string()).collect();
646 }
647
648 if trimmed.contains(char::is_whitespace) {
649 return trimmed.split_whitespace().map(|item| item.to_string()).collect();
650 }
651
652 vec![trimmed.to_string()]
653}
654
655fn normalized_pragma_token(arg: &str) -> &str {
656 arg.trim().trim_matches('\'').trim_matches('"')
657}
658
659fn is_tracked_pragma_module(module: &str) -> bool {
660 matches!(module, "strict" | "warnings" | "utf8" | "encoding" | "locale" | "feature" | "builtin")
661}
662
663fn valid_strict_args(args: &[String]) -> bool {
664 args.iter()
665 .flat_map(|arg| pragma_arg_items(arg))
666 .all(|item| matches!(item.as_str(), "vars" | "subs" | "refs"))
667}
668
669fn conditional_target_tail_is_valid(module: &str, tail: &[String]) -> bool {
670 if parse_perl_version(module).is_some() {
671 return tail.is_empty();
672 }
673
674 match module {
675 "strict" => tail.is_empty() || valid_strict_args(tail),
676 "warnings" => true,
677 "utf8" => tail.is_empty(),
678 "encoding" => tail.len() == 1 && !normalized_pragma_token(&tail[0]).is_empty(),
679 "locale" => {
680 tail.is_empty() || (tail.len() == 1 && !normalized_pragma_token(&tail[0]).is_empty())
681 }
682 "feature" => !tail.is_empty(),
683 "builtin" => tail.iter().any(|arg| !builtin_import_names(arg).is_empty()),
684 _ => false,
685 }
686}
687
688fn conditional_pragma_target(args: &[String]) -> Option<(&str, &[String])> {
689 args.iter().enumerate().find_map(|(idx, arg)| {
690 let module = normalized_pragma_token(arg);
691 let tail = &args[idx + 1..];
692 if (is_tracked_pragma_module(module) || parse_perl_version(module).is_some())
693 && conditional_target_tail_is_valid(module, tail)
694 {
695 Some((module, tail))
696 } else {
697 None
698 }
699 })
700}
701
702pub struct PragmaTracker;
704
705#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
710pub struct PragmaQueryCursor {
711 index: usize,
712}
713
714impl PragmaQueryCursor {
715 #[must_use]
717 pub fn new() -> Self {
718 Self::default()
719 }
720
721 pub fn snapshot_at(&mut self, pragma_map: &PragmaMap, offset: usize) -> PragmaSnapshot {
727 let snapshot = self
728 .entry_for_offset(pragma_map.entries(), offset)
729 .map_or_else(PragmaSnapshot::default, |entry| entry.snapshot.clone());
730
731 normalize_snapshot(snapshot)
732 }
733
734 pub fn state_at(&mut self, pragma_map: &PragmaMap, offset: usize) -> PragmaState {
736 self.snapshot_at(pragma_map, offset).into()
737 }
738
739 pub fn state_for_offset(
745 &mut self,
746 pragma_map: &[(Range<usize>, PragmaState)],
747 offset: usize,
748 ) -> PragmaState {
749 if pragma_map.is_empty() {
750 return PragmaState::default();
751 }
752
753 if self.index >= pragma_map.len() {
754 self.index = pragma_map.len() - 1;
755 }
756
757 if pragma_map[self.index].0.start > offset {
758 self.index = pragma_map.partition_point(|(range, _)| range.start <= offset);
759 if self.index > 0 {
760 self.index -= 1;
761 }
762 } else {
763 while self.index + 1 < pragma_map.len() && pragma_map[self.index + 1].0.start <= offset
764 {
765 self.index += 1;
766 }
767 }
768
769 let state = if pragma_map[self.index].0.start <= offset {
770 pragma_map[self.index].1.clone()
771 } else {
772 PragmaState::default()
773 };
774
775 normalize_state(state)
776 }
777
778 fn entry_for_offset<'a>(
779 &mut self,
780 entries: &'a [PragmaEntry],
781 offset: usize,
782 ) -> Option<&'a PragmaEntry> {
783 if entries.is_empty() {
784 return None;
785 }
786
787 if self.index >= entries.len() {
788 self.index = entries.len() - 1;
789 }
790
791 if entries[self.index].range.start > offset {
792 self.index = entries.partition_point(|entry| entry.range.start <= offset);
793 if self.index > 0 {
794 self.index -= 1;
795 }
796 } else {
797 while self.index + 1 < entries.len() && entries[self.index + 1].range.start <= offset {
798 self.index += 1;
799 }
800 }
801
802 if entries[self.index].range.start <= offset { Some(&entries[self.index]) } else { None }
803 }
804}
805
806impl PragmaTracker {
807 pub fn build(ast: &Node) -> Vec<(Range<usize>, PragmaState)> {
809 CompileTimePragmaEnvironment::build(ast)
810 .as_map()
811 .iter()
812 .map(|(range, snapshot)| (range.clone(), snapshot.clone().into()))
813 .collect()
814 }
815
816 pub fn state_for_offset(
818 pragma_map: &[(Range<usize>, PragmaState)],
819 offset: usize,
820 ) -> PragmaState {
821 let idx = pragma_map.partition_point(|(range, _)| range.start <= offset);
822 let state = if idx > 0 { pragma_map[idx - 1].1.clone() } else { PragmaState::default() };
823
824 normalize_state(state)
825 }
826
827 #[must_use]
829 pub fn final_state(pragma_map: &[(Range<usize>, PragmaState)]) -> PragmaState {
830 let state = pragma_map.last().map_or_else(PragmaState::default, |(_, s)| s.clone());
831
832 normalize_state(state)
833 }
834
835 fn build_scoped_body(
841 body: &Node,
842 current_state: &mut PragmaState,
843 ranges: &mut Vec<(Range<usize>, PragmaState)>,
844 ) {
845 let saved_state = current_state.clone();
846 Self::build_ranges(body, current_state, ranges);
847 *current_state = saved_state;
848 ranges.push((body.location.end..body.location.end, current_state.clone()));
849 }
850
851 fn build_ranges(
852 node: &Node,
853 current_state: &mut PragmaState,
854 ranges: &mut Vec<(Range<usize>, PragmaState)>,
855 ) {
856 match &node.kind {
857 NodeKind::Use { module, args, .. } => {
858 if (module == "if" || module == "unless")
859 && let Some((conditional_module, conditional_args)) =
860 conditional_pragma_target(args)
861 {
862 match conditional_module {
863 "strict" => {
864 if conditional_args.is_empty() {
865 current_state.strict_vars = true;
866 current_state.strict_subs = true;
867 current_state.strict_refs = true;
868 } else {
869 for arg in conditional_args {
870 for item in pragma_arg_items(arg) {
871 match item.as_str() {
872 "vars" => current_state.strict_vars = true,
873 "subs" => current_state.strict_subs = true,
874 "refs" => current_state.strict_refs = true,
875 _ => {}
876 }
877 }
878 }
879 }
880 ranges.push((
881 node.location.start..node.location.end,
882 current_state.clone(),
883 ));
884 return;
885 }
886 "warnings" => {
887 current_state.warnings = true;
888 current_state.disabled_warning_categories.clear();
889 ranges.push((
890 node.location.start..node.location.end,
891 current_state.clone(),
892 ));
893 return;
894 }
895 "utf8" => {
896 current_state.utf8 = true;
897 ranges.push((
898 node.location.start..node.location.end,
899 current_state.clone(),
900 ));
901 return;
902 }
903 "encoding" => {
904 current_state.encoding = conditional_args
905 .first()
906 .map(|arg| normalized_pragma_token(arg).to_string());
907 ranges.push((
908 node.location.start..node.location.end,
909 current_state.clone(),
910 ));
911 return;
912 }
913 "locale" => {
914 current_state.locale = true;
915 current_state.locale_scope = conditional_args
916 .first()
917 .map(|arg| normalized_pragma_token(arg).to_string());
918 ranges.push((
919 node.location.start..node.location.end,
920 current_state.clone(),
921 ));
922 return;
923 }
924 "feature" => {
925 if apply_feature_state(current_state, conditional_args, true) {
926 ranges.push((
927 node.location.start..node.location.end,
928 current_state.clone(),
929 ));
930 }
931 return;
932 }
933 "builtin" => {
934 apply_builtin_imports(current_state, conditional_args);
935 ranges.push((
936 node.location.start..node.location.end,
937 current_state.clone(),
938 ));
939 return;
940 }
941 _ => {
942 if let Some(version) = parse_perl_version(conditional_module) {
943 enable_effective_version_semantics(current_state, version);
944 ranges.push((
945 node.location.start..node.location.end,
946 current_state.clone(),
947 ));
948 }
949 return;
950 }
951 }
952 }
953
954 match module.as_str() {
956 "strict" => {
957 if args.is_empty() {
958 current_state.strict_vars = true;
960 current_state.strict_subs = true;
961 current_state.strict_refs = true;
962 } else {
963 for arg in args {
965 for item in pragma_arg_items(arg) {
966 match item.as_str() {
967 "vars" => {
968 current_state.strict_vars = true;
969 }
970 "subs" => {
971 current_state.strict_subs = true;
972 }
973 "refs" => {
974 current_state.strict_refs = true;
975 }
976 _ => {}
977 }
978 }
979 }
980 }
981
982 ranges
984 .push((node.location.start..node.location.end, current_state.clone()));
985 }
986 "warnings" => {
987 current_state.warnings = true;
988 current_state.disabled_warning_categories.clear();
991 ranges
992 .push((node.location.start..node.location.end, current_state.clone()));
993 }
994 "utf8" => {
995 current_state.utf8 = true;
996 ranges
997 .push((node.location.start..node.location.end, current_state.clone()));
998 }
999 "encoding" => {
1000 current_state.encoding = args
1001 .first()
1002 .map(|arg| arg.trim().trim_matches('\'').trim_matches('"').to_string());
1003 ranges
1004 .push((node.location.start..node.location.end, current_state.clone()));
1005 }
1006 "locale" => {
1007 current_state.locale = true;
1008 current_state.locale_scope = args
1009 .first()
1010 .map(|arg| arg.trim().trim_matches('\'').trim_matches('"').to_string());
1011 ranges
1012 .push((node.location.start..node.location.end, current_state.clone()));
1013 }
1014 "feature" => {
1015 if apply_feature_state(current_state, args, true) {
1016 ranges.push((
1017 node.location.start..node.location.end,
1018 current_state.clone(),
1019 ));
1020 }
1021 }
1022 "builtin" => {
1023 apply_builtin_imports(current_state, args);
1024 ranges
1025 .push((node.location.start..node.location.end, current_state.clone()));
1026 }
1027 _ => {
1028 if let Some(version) = parse_perl_version(module) {
1029 enable_effective_version_semantics(current_state, version);
1030 ranges.push((
1031 node.location.start..node.location.end,
1032 current_state.clone(),
1033 ));
1034 }
1035 }
1036 }
1037 }
1038 NodeKind::No { module, args, .. } => {
1039 if (module == "if" || module == "unless")
1040 && let Some((conditional_module, conditional_args)) =
1041 conditional_pragma_target(args)
1042 {
1043 match conditional_module {
1044 "strict" => {
1045 if conditional_args.is_empty() {
1046 current_state.strict_vars = false;
1047 current_state.strict_subs = false;
1048 current_state.strict_refs = false;
1049 } else {
1050 for arg in conditional_args {
1051 for item in pragma_arg_items(arg) {
1052 match item.as_str() {
1053 "vars" => current_state.strict_vars = false,
1054 "subs" => current_state.strict_subs = false,
1055 "refs" => current_state.strict_refs = false,
1056 _ => {}
1057 }
1058 }
1059 }
1060 }
1061 ranges.push((
1062 node.location.start..node.location.end,
1063 current_state.clone(),
1064 ));
1065 return;
1066 }
1067 "warnings" => {
1068 if conditional_args.is_empty() {
1069 current_state.warnings = false;
1070 current_state.disabled_warning_categories.clear();
1071 } else {
1072 for arg in conditional_args {
1073 let category = normalized_pragma_token(arg);
1074 add_disabled_warning_category(current_state, category);
1075 }
1076 }
1077 ranges.push((
1078 node.location.start..node.location.end,
1079 current_state.clone(),
1080 ));
1081 return;
1082 }
1083 "utf8" => {
1084 current_state.utf8 = false;
1085 ranges.push((
1086 node.location.start..node.location.end,
1087 current_state.clone(),
1088 ));
1089 return;
1090 }
1091 "encoding" => {
1092 current_state.encoding = None;
1093 ranges.push((
1094 node.location.start..node.location.end,
1095 current_state.clone(),
1096 ));
1097 return;
1098 }
1099 "locale" => {
1100 current_state.locale = false;
1101 current_state.locale_scope = None;
1102 ranges.push((
1103 node.location.start..node.location.end,
1104 current_state.clone(),
1105 ));
1106 return;
1107 }
1108 "feature" => {
1109 if apply_feature_state(current_state, conditional_args, false) {
1110 ranges.push((
1111 node.location.start..node.location.end,
1112 current_state.clone(),
1113 ));
1114 }
1115 return;
1116 }
1117 "builtin" => {
1118 remove_builtin_imports(current_state, conditional_args);
1119 ranges.push((
1120 node.location.start..node.location.end,
1121 current_state.clone(),
1122 ));
1123 return;
1124 }
1125 _ => return,
1126 }
1127 }
1128
1129 match module.as_str() {
1131 "strict" => {
1132 if args.is_empty() {
1133 current_state.strict_vars = false;
1135 current_state.strict_subs = false;
1136 current_state.strict_refs = false;
1137 } else {
1138 for arg in args {
1140 for item in pragma_arg_items(arg) {
1141 match item.as_str() {
1142 "vars" => {
1143 current_state.strict_vars = false;
1144 }
1145 "subs" => {
1146 current_state.strict_subs = false;
1147 }
1148 "refs" => {
1149 current_state.strict_refs = false;
1150 }
1151 _ => {}
1152 }
1153 }
1154 }
1155 }
1156
1157 ranges
1159 .push((node.location.start..node.location.end, current_state.clone()));
1160 }
1161 "warnings" => {
1162 let warnings_before = current_state.warnings;
1163 let had_disabled_before =
1164 !current_state.disabled_warning_categories.is_empty();
1165 let before = current_state.disabled_warning_categories.len();
1166 if args.is_empty() {
1167 current_state.warnings = false;
1169 current_state.disabled_warning_categories.clear();
1170 } else {
1171 for arg in args {
1175 let category = arg.trim_matches('\'').trim_matches('"');
1178 add_disabled_warning_category(current_state, category);
1179 }
1180 }
1181 let changed = if args.is_empty() {
1182 warnings_before || had_disabled_before
1183 } else {
1184 current_state.disabled_warning_categories.len() != before
1185 };
1186 if changed {
1187 ranges.push((
1188 node.location.start..node.location.end,
1189 current_state.clone(),
1190 ));
1191 }
1192 }
1193 "utf8" => {
1194 current_state.utf8 = false;
1195 ranges
1196 .push((node.location.start..node.location.end, current_state.clone()));
1197 }
1198 "encoding" => {
1199 current_state.encoding = None;
1200 ranges
1201 .push((node.location.start..node.location.end, current_state.clone()));
1202 }
1203 "locale" => {
1204 current_state.locale = false;
1205 current_state.locale_scope = None;
1206 ranges
1207 .push((node.location.start..node.location.end, current_state.clone()));
1208 }
1209 "feature" => {
1210 if apply_feature_state(current_state, args, false) {
1211 ranges.push((
1212 node.location.start..node.location.end,
1213 current_state.clone(),
1214 ));
1215 }
1216 }
1217 "builtin" => {
1218 remove_builtin_imports(current_state, args);
1219 ranges
1220 .push((node.location.start..node.location.end, current_state.clone()));
1221 }
1222 _ => {}
1223 }
1224 }
1225 NodeKind::Block { statements } => {
1226 let saved_state = current_state.clone();
1228
1229 for stmt in statements {
1231 Self::build_ranges(stmt, current_state, ranges);
1232 }
1233
1234 *current_state = saved_state;
1236 ranges.push((node.location.end..node.location.end, current_state.clone()));
1237 }
1238 NodeKind::Program { statements } => {
1239 for stmt in statements {
1241 Self::build_ranges(stmt, current_state, ranges);
1242 }
1243 }
1244 NodeKind::Subroutine { body, .. } => {
1246 Self::build_scoped_body(body, current_state, ranges);
1247 }
1248 NodeKind::Method { body, .. } => {
1249 Self::build_scoped_body(body, current_state, ranges);
1250 }
1251 NodeKind::Class { body, .. } => {
1252 Self::build_scoped_body(body, current_state, ranges);
1253 }
1254 NodeKind::If { then_branch, elsif_branches, else_branch, .. } => {
1255 Self::build_scoped_body(then_branch, current_state, ranges);
1256 for (_, elsif_body) in elsif_branches {
1257 Self::build_scoped_body(elsif_body, current_state, ranges);
1258 }
1259 if let Some(else_b) = else_branch {
1260 Self::build_scoped_body(else_b, current_state, ranges);
1261 }
1262 }
1263 NodeKind::While { body, continue_block, .. }
1264 | NodeKind::For { body, continue_block, .. }
1265 | NodeKind::Foreach { body, continue_block, .. } => {
1266 Self::build_scoped_body(body, current_state, ranges);
1267 if let Some(continue_block) = continue_block {
1268 Self::build_scoped_body(continue_block, current_state, ranges);
1269 }
1270 }
1271 NodeKind::Eval { block } => {
1272 if matches!(block.kind, NodeKind::Block { .. }) {
1273 Self::build_scoped_body(block, current_state, ranges);
1274 }
1275 }
1276 NodeKind::Do { block } | NodeKind::Defer { block } => {
1277 Self::build_scoped_body(block, current_state, ranges);
1278 }
1279 NodeKind::PhaseBlock { block, .. } => {
1280 Self::build_scoped_body(block, current_state, ranges);
1281 }
1282 NodeKind::Given { body, .. }
1283 | NodeKind::When { body, .. }
1284 | NodeKind::Default { body } => {
1285 Self::build_scoped_body(body, current_state, ranges);
1286 }
1287 NodeKind::Try { body, catch_blocks, finally_block } => {
1288 Self::build_scoped_body(body, current_state, ranges);
1289 for (_, catch_body) in catch_blocks {
1290 Self::build_scoped_body(catch_body, current_state, ranges);
1291 }
1292 if let Some(finally_block) = finally_block {
1293 Self::build_scoped_body(finally_block, current_state, ranges);
1294 }
1295 }
1296 NodeKind::LabeledStatement { statement, .. } => {
1297 Self::build_ranges(statement, current_state, ranges);
1298 }
1299 NodeKind::StatementModifier { statement, condition, .. } => {
1300 Self::build_ranges(statement, current_state, ranges);
1301 Self::build_ranges(condition, current_state, ranges);
1302 }
1303 NodeKind::Package { block: Some(pkg_block), .. } => {
1310 Self::build_scoped_body(pkg_block, current_state, ranges);
1311 }
1312 _ => {}
1314 }
1315 }
1316}