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: Vec<(Range<usize>, PragmaSnapshot)>,
155}
156
157impl CompileTimePragmaEnvironment {
158 #[must_use]
160 pub fn build(ast: &Node) -> Self {
161 let mut ranges = Vec::new();
162 let mut current_state = PragmaState::default();
163 PragmaTracker::build_ranges(ast, &mut current_state, &mut ranges);
164 ranges.sort_by_key(|(range, _)| range.start);
165
166 let map =
167 ranges.into_iter().map(|(range, state)| (range, PragmaSnapshot::from(state))).collect();
168
169 Self { map }
170 }
171
172 #[must_use]
174 pub fn query_at(&self, offset: usize) -> PragmaStateQuery {
175 PragmaStateQuery { offset, snapshot: self.snapshot_at(offset) }
176 }
177
178 #[must_use]
180 pub fn snapshot_at(&self, offset: usize) -> PragmaSnapshot {
181 let idx = self.map.partition_point(|(range, _)| range.start <= offset);
182 let mut snapshot =
183 if idx > 0 { self.map[idx - 1].1.clone() } else { PragmaSnapshot::default() };
184
185 if snapshot.state.signatures_strict {
186 snapshot.state.strict_vars = true;
187 snapshot.state.strict_subs = true;
188 snapshot.state.strict_refs = true;
189 }
190
191 snapshot
192 }
193
194 #[must_use]
196 pub fn as_map(&self) -> &[(Range<usize>, PragmaSnapshot)] {
197 &self.map
198 }
199}
200
201impl PragmaState {
202 pub fn all_strict() -> Self {
204 Self {
205 strict_vars: true,
206 strict_subs: true,
207 strict_refs: true,
208 warnings: false,
209 utf8: false,
210 encoding: None,
211 unicode_strings: false,
212 locale: false,
213 locale_scope: None,
214 disabled_warning_categories: Vec::new(),
215 signatures_strict: false,
216 features: Vec::new(),
217 builtin_imports: Vec::new(),
218 }
219 }
220
221 #[must_use]
231 pub fn is_warning_active(&self, category: &str) -> bool {
232 self.warnings && !self.disabled_warning_categories.iter().any(|c| c == category)
233 }
234
235 #[must_use]
237 pub fn has_feature(&self, feature: &str) -> bool {
238 self.features.contains(&feature)
239 }
240
241 #[must_use]
243 pub fn has_builtin_import(&self, name: &str) -> bool {
244 self.builtin_imports.iter().any(|import| import == name)
245 }
246}
247
248pub fn parse_perl_version(module: &str) -> Option<PerlVersion> {
257 let s = module.strip_prefix('v').unwrap_or(module);
258 let mut parts = s.splitn(3, '.');
259
260 let major = parse_version_component(parts.next()?)?;
261 let minor = match parts.next() {
262 Some(part) => parse_version_component(part)?,
263 None => 0,
264 };
265
266 Some(PerlVersion::new(major, minor))
267}
268
269fn parse_version_component(component: &str) -> Option<u32> {
270 let component = component.split_once('_').map_or(component, |(head, _)| head);
271 component.parse().ok()
272}
273
274#[must_use]
276pub fn version_implies_strict(version: PerlVersion) -> bool {
277 version >= PerlVersion::new(5, 12)
278}
279
280#[must_use]
282pub fn version_implies_warnings(version: PerlVersion) -> bool {
283 version >= PerlVersion::new(5, 35)
284}
285
286#[must_use]
295pub fn features_enabled_by_version(version: PerlVersion) -> Vec<&'static str> {
296 let mut features = Vec::new();
297
298 if version >= PerlVersion::new(5, 10) {
300 features.extend_from_slice(&["say", "state", "switch"]);
301 }
302
303 if version >= PerlVersion::new(5, 12) {
305 features.push("unicode_strings");
306 }
307
308 if version >= PerlVersion::new(5, 16) {
310 features.extend_from_slice(&["unicode_eval", "evalbytes", "current_sub", "fc"]);
311 }
312
313 if version >= PerlVersion::new(5, 20) {
316 features.push("postfix_deref");
317 }
318
319 if version >= PerlVersion::new(5, 34) {
321 features.push("try");
322 }
323
324 if version >= PerlVersion::new(5, 36) {
326 features.extend_from_slice(&["signatures", "defer", "isa"]);
327 }
328
329 if version >= PerlVersion::new(5, 38) {
331 features.extend_from_slice(&["class", "field", "method"]);
332 features.retain(|&f| f != "switch");
333 }
334
335 if version >= PerlVersion::new(5, 40) {
337 features.push("builtin");
338 }
339
340 features
341}
342
343fn enable_effective_version_semantics(state: &mut PragmaState, version: PerlVersion) {
344 if version_implies_strict(version) {
345 state.strict_vars = true;
346 state.strict_subs = true;
347 state.strict_refs = true;
348 }
349 if version_implies_warnings(version) {
350 state.warnings = true;
351 }
352 state.features = features_enabled_by_version(version);
355 state.unicode_strings = state.has_feature("unicode_strings");
356 state.signatures_strict = false;
357}
358
359fn feature_items(arg: &str) -> Vec<String> {
360 pragma_arg_items(arg)
361}
362
363fn known_feature_name(name: &str) -> Option<&'static str> {
364 match name {
365 "say" => Some("say"),
366 "state" => Some("state"),
367 "switch" => Some("switch"),
368 "unicode_strings" => Some("unicode_strings"),
369 "unicode_eval" => Some("unicode_eval"),
370 "evalbytes" => Some("evalbytes"),
371 "current_sub" => Some("current_sub"),
372 "fc" => Some("fc"),
373 "postfix_deref" => Some("postfix_deref"),
374 "try" => Some("try"),
375 "signatures" => Some("signatures"),
376 "defer" => Some("defer"),
377 "isa" => Some("isa"),
378 "class" => Some("class"),
379 "field" => Some("field"),
380 "method" => Some("method"),
381 "builtin" => Some("builtin"),
382 _ => None,
383 }
384}
385
386const ALL_KNOWN_FEATURES: &[&str] = &[
387 "say",
388 "state",
389 "switch",
390 "unicode_strings",
391 "unicode_eval",
392 "evalbytes",
393 "current_sub",
394 "fc",
395 "postfix_deref",
396 "try",
397 "signatures",
398 "defer",
399 "isa",
400 "class",
401 "field",
402 "method",
403 "builtin",
404];
405
406fn enable_feature_name(state: &mut PragmaState, name: &str) -> bool {
407 if name == "signatures" {
408 state.signatures_strict = true;
409 }
410 if name == "unicode_strings" {
411 state.unicode_strings = true;
412 }
413
414 if let Some(feature) = known_feature_name(name) {
415 if state.features.iter().all(|existing| existing != &feature) {
416 state.features.push(feature);
417 }
418 true
419 } else {
420 false
421 }
422}
423
424fn disable_feature_name(state: &mut PragmaState, name: &str) -> bool {
425 if name == "signatures" {
426 state.signatures_strict = false;
427 }
428 if name == "unicode_strings" {
429 state.unicode_strings = false;
430 }
431
432 if let Some(feature) = known_feature_name(name) {
433 let before = state.features.len();
434 state.features.retain(|existing| *existing != feature);
435 before != state.features.len()
436 } else {
437 false
438 }
439}
440
441fn apply_feature_state(state: &mut PragmaState, args: &[String], enabled: bool) -> bool {
442 if !enabled && args.is_empty() {
443 let changed =
444 !state.features.is_empty() || state.unicode_strings || state.signatures_strict;
445 state.features.clear();
446 state.unicode_strings = false;
447 state.signatures_strict = false;
448 return changed;
449 }
450
451 let mut changed = false;
452
453 for arg in args {
454 for item in feature_items(arg) {
455 if enabled && item == ":all" {
456 for feature in ALL_KNOWN_FEATURES {
457 changed |= enable_feature_name(state, feature);
458 }
459 continue;
460 }
461
462 if !enabled && item == ":all" {
463 let had_features =
464 !state.features.is_empty() || state.unicode_strings || state.signatures_strict;
465 state.features.clear();
466 state.unicode_strings = false;
467 state.signatures_strict = false;
468 changed |= had_features;
469 continue;
470 }
471
472 if let Some(version) = item.strip_prefix(':').and_then(parse_perl_version) {
473 for feature in features_enabled_by_version(version) {
474 changed |= if enabled {
475 enable_feature_name(state, feature)
476 } else {
477 disable_feature_name(state, feature)
478 };
479 }
480 continue;
481 }
482
483 changed |= if enabled {
484 enable_feature_name(state, &item)
485 } else {
486 disable_feature_name(state, &item)
487 };
488 }
489 }
490
491 changed
492}
493
494fn builtin_import_names(arg: &str) -> Vec<String> {
495 let trimmed = arg.trim();
496
497 if let Some(inner) = trimmed.strip_prefix("qw(").and_then(|s| s.strip_suffix(')')) {
498 return inner
499 .split_whitespace()
500 .filter(|name| !name.is_empty())
501 .map(|name| name.trim_matches('\'').trim_matches('"').to_string())
502 .collect();
503 }
504
505 let name = trimmed.trim_matches('\'').trim_matches('"');
506 if name.is_empty() { Vec::new() } else { vec![name.to_string()] }
507}
508
509fn apply_builtin_imports(state: &mut PragmaState, args: &[String]) {
510 for arg in args {
511 for name in builtin_import_names(arg) {
512 if !state.builtin_imports.iter().any(|import| import == &name) {
513 state.builtin_imports.push(name);
514 }
515 }
516 }
517}
518
519fn add_disabled_warning_category(state: &mut PragmaState, category: &str) {
526 if category.is_empty() {
527 return;
528 }
529
530 if state.disabled_warning_categories.iter().any(|c| c == category) {
531 return;
532 }
533
534 if state.disabled_warning_categories.len() >= MAX_DISABLED_WARNING_CATEGORIES {
535 return;
536 }
537
538 state.disabled_warning_categories.push(category.to_string());
539}
540
541fn remove_builtin_imports(state: &mut PragmaState, args: &[String]) {
542 if args.is_empty() {
543 state.builtin_imports.clear();
544 return;
545 }
546
547 let names_to_remove: Vec<String> =
548 args.iter().flat_map(|arg| builtin_import_names(arg)).collect();
549 state.builtin_imports.retain(|import| !names_to_remove.iter().any(|name| name == import));
550}
551
552fn pragma_arg_items(arg: &str) -> Vec<String> {
553 let trimmed = arg.trim().trim_matches('\'').trim_matches('"');
554
555 if let Some(inner) = trimmed.strip_prefix("qw(").and_then(|s| s.strip_suffix(')')) {
556 return inner.split_whitespace().map(|item| item.to_string()).collect();
557 }
558
559 if trimmed.contains(char::is_whitespace) {
560 return trimmed.split_whitespace().map(|item| item.to_string()).collect();
561 }
562
563 vec![trimmed.to_string()]
564}
565
566fn normalized_pragma_token(arg: &str) -> &str {
567 arg.trim().trim_matches('\'').trim_matches('"')
568}
569
570fn is_tracked_pragma_module(module: &str) -> bool {
571 matches!(module, "strict" | "warnings" | "utf8" | "encoding" | "locale" | "feature" | "builtin")
572}
573
574fn valid_strict_args(args: &[String]) -> bool {
575 args.iter()
576 .flat_map(|arg| pragma_arg_items(arg))
577 .all(|item| matches!(item.as_str(), "vars" | "subs" | "refs"))
578}
579
580fn conditional_target_tail_is_valid(module: &str, tail: &[String]) -> bool {
581 if parse_perl_version(module).is_some() {
582 return tail.is_empty();
583 }
584
585 match module {
586 "strict" => tail.is_empty() || valid_strict_args(tail),
587 "warnings" => true,
588 "utf8" => tail.is_empty(),
589 "encoding" => tail.len() == 1 && !normalized_pragma_token(&tail[0]).is_empty(),
590 "locale" => {
591 tail.is_empty() || (tail.len() == 1 && !normalized_pragma_token(&tail[0]).is_empty())
592 }
593 "feature" => !tail.is_empty(),
594 "builtin" => tail.iter().any(|arg| !builtin_import_names(arg).is_empty()),
595 _ => false,
596 }
597}
598
599fn conditional_pragma_target(args: &[String]) -> Option<(&str, &[String])> {
600 args.iter().enumerate().find_map(|(idx, arg)| {
601 let module = normalized_pragma_token(arg);
602 let tail = &args[idx + 1..];
603 if (is_tracked_pragma_module(module) || parse_perl_version(module).is_some())
604 && conditional_target_tail_is_valid(module, tail)
605 {
606 Some((module, tail))
607 } else {
608 None
609 }
610 })
611}
612
613pub struct PragmaTracker;
615
616#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
621pub struct PragmaQueryCursor {
622 index: usize,
623}
624
625impl PragmaQueryCursor {
626 #[must_use]
628 pub fn new() -> Self {
629 Self::default()
630 }
631
632 pub fn state_for_offset(
637 &mut self,
638 pragma_map: &[(Range<usize>, PragmaState)],
639 offset: usize,
640 ) -> PragmaState {
641 if pragma_map.is_empty() {
642 return PragmaState::default();
643 }
644
645 if self.index >= pragma_map.len() {
646 self.index = pragma_map.len() - 1;
647 }
648
649 if pragma_map[self.index].0.start > offset {
650 self.index = pragma_map.partition_point(|(range, _)| range.start <= offset);
651 if self.index > 0 {
652 self.index -= 1;
653 }
654 } else {
655 while self.index + 1 < pragma_map.len() && pragma_map[self.index + 1].0.start <= offset
656 {
657 self.index += 1;
658 }
659 }
660
661 let mut state = if pragma_map[self.index].0.start <= offset {
662 pragma_map[self.index].1.clone()
663 } else {
664 PragmaState::default()
665 };
666
667 if state.signatures_strict {
668 state.strict_vars = true;
669 state.strict_subs = true;
670 state.strict_refs = true;
671 }
672
673 state
674 }
675}
676
677impl PragmaTracker {
678 pub fn build(ast: &Node) -> Vec<(Range<usize>, PragmaState)> {
680 CompileTimePragmaEnvironment::build(ast)
681 .as_map()
682 .iter()
683 .map(|(range, snapshot)| (range.clone(), snapshot.clone().into()))
684 .collect()
685 }
686
687 pub fn state_for_offset(
689 pragma_map: &[(Range<usize>, PragmaState)],
690 offset: usize,
691 ) -> PragmaState {
692 let map = pragma_map
693 .iter()
694 .map(|(range, state)| (range.clone(), PragmaSnapshot::from(state.clone())))
695 .collect();
696 let environment = CompileTimePragmaEnvironment { map };
697 environment.snapshot_at(offset).into()
698 }
699
700 #[must_use]
702 pub fn final_state(pragma_map: &[(Range<usize>, PragmaState)]) -> PragmaState {
703 let mut state = pragma_map.last().map_or_else(PragmaState::default, |(_, s)| s.clone());
704
705 if state.signatures_strict {
706 state.strict_vars = true;
707 state.strict_subs = true;
708 state.strict_refs = true;
709 }
710
711 state
712 }
713
714 fn build_scoped_body(
720 body: &Node,
721 current_state: &mut PragmaState,
722 ranges: &mut Vec<(Range<usize>, PragmaState)>,
723 ) {
724 let saved_state = current_state.clone();
725 Self::build_ranges(body, current_state, ranges);
726 *current_state = saved_state;
727 ranges.push((body.location.end..body.location.end, current_state.clone()));
728 }
729
730 fn build_ranges(
731 node: &Node,
732 current_state: &mut PragmaState,
733 ranges: &mut Vec<(Range<usize>, PragmaState)>,
734 ) {
735 match &node.kind {
736 NodeKind::Use { module, args, .. } => {
737 if (module == "if" || module == "unless")
738 && let Some((conditional_module, conditional_args)) =
739 conditional_pragma_target(args)
740 {
741 match conditional_module {
742 "strict" => {
743 if conditional_args.is_empty() {
744 current_state.strict_vars = true;
745 current_state.strict_subs = true;
746 current_state.strict_refs = true;
747 } else {
748 for arg in conditional_args {
749 for item in pragma_arg_items(arg) {
750 match item.as_str() {
751 "vars" => current_state.strict_vars = true,
752 "subs" => current_state.strict_subs = true,
753 "refs" => current_state.strict_refs = true,
754 _ => {}
755 }
756 }
757 }
758 }
759 ranges.push((
760 node.location.start..node.location.end,
761 current_state.clone(),
762 ));
763 return;
764 }
765 "warnings" => {
766 current_state.warnings = true;
767 current_state.disabled_warning_categories.clear();
768 ranges.push((
769 node.location.start..node.location.end,
770 current_state.clone(),
771 ));
772 return;
773 }
774 "utf8" => {
775 current_state.utf8 = true;
776 ranges.push((
777 node.location.start..node.location.end,
778 current_state.clone(),
779 ));
780 return;
781 }
782 "encoding" => {
783 current_state.encoding = conditional_args
784 .first()
785 .map(|arg| normalized_pragma_token(arg).to_string());
786 ranges.push((
787 node.location.start..node.location.end,
788 current_state.clone(),
789 ));
790 return;
791 }
792 "locale" => {
793 current_state.locale = true;
794 current_state.locale_scope = conditional_args
795 .first()
796 .map(|arg| normalized_pragma_token(arg).to_string());
797 ranges.push((
798 node.location.start..node.location.end,
799 current_state.clone(),
800 ));
801 return;
802 }
803 "feature" => {
804 if apply_feature_state(current_state, conditional_args, true) {
805 ranges.push((
806 node.location.start..node.location.end,
807 current_state.clone(),
808 ));
809 }
810 return;
811 }
812 "builtin" => {
813 apply_builtin_imports(current_state, conditional_args);
814 ranges.push((
815 node.location.start..node.location.end,
816 current_state.clone(),
817 ));
818 return;
819 }
820 _ => {
821 if let Some(version) = parse_perl_version(conditional_module) {
822 enable_effective_version_semantics(current_state, version);
823 ranges.push((
824 node.location.start..node.location.end,
825 current_state.clone(),
826 ));
827 }
828 return;
829 }
830 }
831 }
832
833 match module.as_str() {
835 "strict" => {
836 if args.is_empty() {
837 current_state.strict_vars = true;
839 current_state.strict_subs = true;
840 current_state.strict_refs = true;
841 } else {
842 for arg in args {
844 for item in pragma_arg_items(arg) {
845 match item.as_str() {
846 "vars" => {
847 current_state.strict_vars = true;
848 }
849 "subs" => {
850 current_state.strict_subs = true;
851 }
852 "refs" => {
853 current_state.strict_refs = true;
854 }
855 _ => {}
856 }
857 }
858 }
859 }
860
861 ranges
863 .push((node.location.start..node.location.end, current_state.clone()));
864 }
865 "warnings" => {
866 current_state.warnings = true;
867 current_state.disabled_warning_categories.clear();
870 ranges
871 .push((node.location.start..node.location.end, current_state.clone()));
872 }
873 "utf8" => {
874 current_state.utf8 = true;
875 ranges
876 .push((node.location.start..node.location.end, current_state.clone()));
877 }
878 "encoding" => {
879 current_state.encoding = args
880 .first()
881 .map(|arg| arg.trim().trim_matches('\'').trim_matches('"').to_string());
882 ranges
883 .push((node.location.start..node.location.end, current_state.clone()));
884 }
885 "locale" => {
886 current_state.locale = true;
887 current_state.locale_scope = args
888 .first()
889 .map(|arg| arg.trim().trim_matches('\'').trim_matches('"').to_string());
890 ranges
891 .push((node.location.start..node.location.end, current_state.clone()));
892 }
893 "feature" => {
894 if apply_feature_state(current_state, args, true) {
895 ranges.push((
896 node.location.start..node.location.end,
897 current_state.clone(),
898 ));
899 }
900 }
901 "builtin" => {
902 apply_builtin_imports(current_state, args);
903 ranges
904 .push((node.location.start..node.location.end, current_state.clone()));
905 }
906 _ => {
907 if let Some(version) = parse_perl_version(module) {
908 enable_effective_version_semantics(current_state, version);
909 ranges.push((
910 node.location.start..node.location.end,
911 current_state.clone(),
912 ));
913 }
914 }
915 }
916 }
917 NodeKind::No { module, args, .. } => {
918 if (module == "if" || module == "unless")
919 && let Some((conditional_module, conditional_args)) =
920 conditional_pragma_target(args)
921 {
922 match conditional_module {
923 "strict" => {
924 if conditional_args.is_empty() {
925 current_state.strict_vars = false;
926 current_state.strict_subs = false;
927 current_state.strict_refs = false;
928 } else {
929 for arg in conditional_args {
930 for item in pragma_arg_items(arg) {
931 match item.as_str() {
932 "vars" => current_state.strict_vars = false,
933 "subs" => current_state.strict_subs = false,
934 "refs" => current_state.strict_refs = false,
935 _ => {}
936 }
937 }
938 }
939 }
940 ranges.push((
941 node.location.start..node.location.end,
942 current_state.clone(),
943 ));
944 return;
945 }
946 "warnings" => {
947 if conditional_args.is_empty() {
948 current_state.warnings = false;
949 current_state.disabled_warning_categories.clear();
950 } else {
951 for arg in conditional_args {
952 let category = normalized_pragma_token(arg);
953 add_disabled_warning_category(current_state, category);
954 }
955 }
956 ranges.push((
957 node.location.start..node.location.end,
958 current_state.clone(),
959 ));
960 return;
961 }
962 "utf8" => {
963 current_state.utf8 = false;
964 ranges.push((
965 node.location.start..node.location.end,
966 current_state.clone(),
967 ));
968 return;
969 }
970 "encoding" => {
971 current_state.encoding = None;
972 ranges.push((
973 node.location.start..node.location.end,
974 current_state.clone(),
975 ));
976 return;
977 }
978 "locale" => {
979 current_state.locale = false;
980 current_state.locale_scope = None;
981 ranges.push((
982 node.location.start..node.location.end,
983 current_state.clone(),
984 ));
985 return;
986 }
987 "feature" => {
988 if apply_feature_state(current_state, conditional_args, false) {
989 ranges.push((
990 node.location.start..node.location.end,
991 current_state.clone(),
992 ));
993 }
994 return;
995 }
996 "builtin" => {
997 remove_builtin_imports(current_state, conditional_args);
998 ranges.push((
999 node.location.start..node.location.end,
1000 current_state.clone(),
1001 ));
1002 return;
1003 }
1004 _ => return,
1005 }
1006 }
1007
1008 match module.as_str() {
1010 "strict" => {
1011 if args.is_empty() {
1012 current_state.strict_vars = false;
1014 current_state.strict_subs = false;
1015 current_state.strict_refs = false;
1016 } else {
1017 for arg in args {
1019 for item in pragma_arg_items(arg) {
1020 match item.as_str() {
1021 "vars" => {
1022 current_state.strict_vars = false;
1023 }
1024 "subs" => {
1025 current_state.strict_subs = false;
1026 }
1027 "refs" => {
1028 current_state.strict_refs = false;
1029 }
1030 _ => {}
1031 }
1032 }
1033 }
1034 }
1035
1036 ranges
1038 .push((node.location.start..node.location.end, current_state.clone()));
1039 }
1040 "warnings" => {
1041 let warnings_before = current_state.warnings;
1042 let had_disabled_before =
1043 !current_state.disabled_warning_categories.is_empty();
1044 let before = current_state.disabled_warning_categories.len();
1045 if args.is_empty() {
1046 current_state.warnings = false;
1048 current_state.disabled_warning_categories.clear();
1049 } else {
1050 for arg in args {
1054 let category = arg.trim_matches('\'').trim_matches('"');
1057 add_disabled_warning_category(current_state, category);
1058 }
1059 }
1060 let changed = if args.is_empty() {
1061 warnings_before || had_disabled_before
1062 } else {
1063 current_state.disabled_warning_categories.len() != before
1064 };
1065 if changed {
1066 ranges.push((
1067 node.location.start..node.location.end,
1068 current_state.clone(),
1069 ));
1070 }
1071 }
1072 "utf8" => {
1073 current_state.utf8 = false;
1074 ranges
1075 .push((node.location.start..node.location.end, current_state.clone()));
1076 }
1077 "encoding" => {
1078 current_state.encoding = None;
1079 ranges
1080 .push((node.location.start..node.location.end, current_state.clone()));
1081 }
1082 "locale" => {
1083 current_state.locale = false;
1084 current_state.locale_scope = None;
1085 ranges
1086 .push((node.location.start..node.location.end, current_state.clone()));
1087 }
1088 "feature" => {
1089 if apply_feature_state(current_state, args, false) {
1090 ranges.push((
1091 node.location.start..node.location.end,
1092 current_state.clone(),
1093 ));
1094 }
1095 }
1096 "builtin" => {
1097 remove_builtin_imports(current_state, args);
1098 ranges
1099 .push((node.location.start..node.location.end, current_state.clone()));
1100 }
1101 _ => {}
1102 }
1103 }
1104 NodeKind::Block { statements } => {
1105 let saved_state = current_state.clone();
1107
1108 for stmt in statements {
1110 Self::build_ranges(stmt, current_state, ranges);
1111 }
1112
1113 *current_state = saved_state;
1115 ranges.push((node.location.end..node.location.end, current_state.clone()));
1116 }
1117 NodeKind::Program { statements } => {
1118 for stmt in statements {
1120 Self::build_ranges(stmt, current_state, ranges);
1121 }
1122 }
1123 NodeKind::Subroutine { body, .. } => {
1125 Self::build_scoped_body(body, current_state, ranges);
1126 }
1127 NodeKind::Method { body, .. } => {
1128 Self::build_scoped_body(body, current_state, ranges);
1129 }
1130 NodeKind::Class { body, .. } => {
1131 Self::build_scoped_body(body, current_state, ranges);
1132 }
1133 NodeKind::If { then_branch, elsif_branches, else_branch, .. } => {
1134 Self::build_scoped_body(then_branch, current_state, ranges);
1135 for (_, elsif_body) in elsif_branches {
1136 Self::build_scoped_body(elsif_body, current_state, ranges);
1137 }
1138 if let Some(else_b) = else_branch {
1139 Self::build_scoped_body(else_b, current_state, ranges);
1140 }
1141 }
1142 NodeKind::While { body, continue_block, .. }
1143 | NodeKind::For { body, continue_block, .. }
1144 | NodeKind::Foreach { body, continue_block, .. } => {
1145 Self::build_scoped_body(body, current_state, ranges);
1146 if let Some(continue_block) = continue_block {
1147 Self::build_scoped_body(continue_block, current_state, ranges);
1148 }
1149 }
1150 NodeKind::Eval { block } => {
1151 if matches!(block.kind, NodeKind::Block { .. }) {
1152 Self::build_scoped_body(block, current_state, ranges);
1153 }
1154 }
1155 NodeKind::Do { block } | NodeKind::Defer { block } => {
1156 Self::build_scoped_body(block, current_state, ranges);
1157 }
1158 NodeKind::PhaseBlock { block, .. } => {
1159 Self::build_scoped_body(block, current_state, ranges);
1160 }
1161 NodeKind::Given { body, .. }
1162 | NodeKind::When { body, .. }
1163 | NodeKind::Default { body } => {
1164 Self::build_scoped_body(body, current_state, ranges);
1165 }
1166 NodeKind::Try { body, catch_blocks, finally_block } => {
1167 Self::build_scoped_body(body, current_state, ranges);
1168 for (_, catch_body) in catch_blocks {
1169 Self::build_scoped_body(catch_body, current_state, ranges);
1170 }
1171 if let Some(finally_block) = finally_block {
1172 Self::build_scoped_body(finally_block, current_state, ranges);
1173 }
1174 }
1175 NodeKind::LabeledStatement { statement, .. } => {
1176 Self::build_ranges(statement, current_state, ranges);
1177 }
1178 NodeKind::StatementModifier { statement, condition, .. } => {
1179 Self::build_ranges(statement, current_state, ranges);
1180 Self::build_ranges(condition, current_state, ranges);
1181 }
1182 NodeKind::Package { block: Some(pkg_block), .. } => {
1189 Self::build_scoped_body(pkg_block, current_state, ranges);
1190 }
1191 _ => {}
1193 }
1194 }
1195}