use perl_ast::ast::{Node, NodeKind};
use std::ops::Range;
const MAX_DISABLED_WARNING_CATEGORIES: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct PerlVersion {
pub major: u32,
pub minor: u32,
}
impl PerlVersion {
pub const fn new(major: u32, minor: u32) -> Self {
Self { major, minor }
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PragmaState {
pub strict_vars: bool,
pub strict_subs: bool,
pub strict_refs: bool,
pub warnings: bool,
pub utf8: bool,
pub encoding: Option<String>,
pub unicode_strings: bool,
pub locale: bool,
pub locale_scope: Option<String>,
pub disabled_warning_categories: Vec<String>,
pub signatures_strict: bool,
pub features: Vec<&'static str>,
pub builtin_imports: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PragmaSnapshot {
state: PragmaState,
}
impl PragmaSnapshot {
#[must_use]
pub fn from_state(state: PragmaState) -> Self {
Self { state }
}
#[must_use]
pub fn state(&self) -> &PragmaState {
&self.state
}
#[must_use]
pub fn strict_enabled(&self) -> bool {
self.state.strict_vars && self.state.strict_subs && self.state.strict_refs
}
#[must_use]
pub fn warnings_enabled(&self) -> bool {
self.state.warnings
}
#[must_use]
pub fn has_feature(&self, feature: &str) -> bool {
self.state.has_feature(feature)
}
#[must_use]
pub fn is_warning_active(&self, category: &str) -> bool {
self.state.is_warning_active(category)
}
}
impl From<PragmaState> for PragmaSnapshot {
fn from(state: PragmaState) -> Self {
Self::from_state(state)
}
}
impl From<PragmaSnapshot> for PragmaState {
fn from(snapshot: PragmaSnapshot) -> Self {
snapshot.state
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PragmaStateQuery {
offset: usize,
snapshot: PragmaSnapshot,
}
impl PragmaStateQuery {
#[must_use]
pub fn offset(&self) -> usize {
self.offset
}
#[must_use]
pub fn snapshot(&self) -> &PragmaSnapshot {
&self.snapshot
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CompileTimePragmaEnvironment {
map: Vec<(Range<usize>, PragmaSnapshot)>,
}
impl CompileTimePragmaEnvironment {
#[must_use]
pub fn build(ast: &Node) -> Self {
let mut ranges = Vec::new();
let mut current_state = PragmaState::default();
PragmaTracker::build_ranges(ast, &mut current_state, &mut ranges);
ranges.sort_by_key(|(range, _)| range.start);
let map =
ranges.into_iter().map(|(range, state)| (range, PragmaSnapshot::from(state))).collect();
Self { map }
}
#[must_use]
pub fn query_at(&self, offset: usize) -> PragmaStateQuery {
PragmaStateQuery { offset, snapshot: self.snapshot_at(offset) }
}
#[must_use]
pub fn snapshot_at(&self, offset: usize) -> PragmaSnapshot {
let idx = self.map.partition_point(|(range, _)| range.start <= offset);
let mut snapshot =
if idx > 0 { self.map[idx - 1].1.clone() } else { PragmaSnapshot::default() };
if snapshot.state.signatures_strict {
snapshot.state.strict_vars = true;
snapshot.state.strict_subs = true;
snapshot.state.strict_refs = true;
}
snapshot
}
#[must_use]
pub fn as_map(&self) -> &[(Range<usize>, PragmaSnapshot)] {
&self.map
}
}
impl PragmaState {
pub fn all_strict() -> Self {
Self {
strict_vars: true,
strict_subs: true,
strict_refs: true,
warnings: false,
utf8: false,
encoding: None,
unicode_strings: false,
locale: false,
locale_scope: None,
disabled_warning_categories: Vec::new(),
signatures_strict: false,
features: Vec::new(),
builtin_imports: Vec::new(),
}
}
#[must_use]
pub fn is_warning_active(&self, category: &str) -> bool {
self.warnings && !self.disabled_warning_categories.iter().any(|c| c == category)
}
#[must_use]
pub fn has_feature(&self, feature: &str) -> bool {
self.features.contains(&feature)
}
#[must_use]
pub fn has_builtin_import(&self, name: &str) -> bool {
self.builtin_imports.iter().any(|import| import == name)
}
}
pub fn parse_perl_version(module: &str) -> Option<PerlVersion> {
let s = module.strip_prefix('v').unwrap_or(module);
let mut parts = s.splitn(3, '.');
let major = parse_version_component(parts.next()?)?;
let minor = match parts.next() {
Some(part) => parse_version_component(part)?,
None => 0,
};
Some(PerlVersion::new(major, minor))
}
fn parse_version_component(component: &str) -> Option<u32> {
let component = component.split_once('_').map_or(component, |(head, _)| head);
component.parse().ok()
}
#[must_use]
pub fn version_implies_strict(version: PerlVersion) -> bool {
version >= PerlVersion::new(5, 12)
}
#[must_use]
pub fn version_implies_warnings(version: PerlVersion) -> bool {
version >= PerlVersion::new(5, 35)
}
#[must_use]
pub fn features_enabled_by_version(version: PerlVersion) -> Vec<&'static str> {
let mut features = Vec::new();
if version >= PerlVersion::new(5, 10) {
features.extend_from_slice(&["say", "state", "switch"]);
}
if version >= PerlVersion::new(5, 12) {
features.push("unicode_strings");
}
if version >= PerlVersion::new(5, 16) {
features.extend_from_slice(&["unicode_eval", "evalbytes", "current_sub", "fc"]);
}
if version >= PerlVersion::new(5, 20) {
features.push("postfix_deref");
}
if version >= PerlVersion::new(5, 34) {
features.push("try");
}
if version >= PerlVersion::new(5, 36) {
features.extend_from_slice(&["signatures", "defer", "isa"]);
}
if version >= PerlVersion::new(5, 38) {
features.extend_from_slice(&["class", "field", "method"]);
features.retain(|&f| f != "switch");
}
if version >= PerlVersion::new(5, 40) {
features.push("builtin");
}
features
}
fn enable_effective_version_semantics(state: &mut PragmaState, version: PerlVersion) {
if version_implies_strict(version) {
state.strict_vars = true;
state.strict_subs = true;
state.strict_refs = true;
}
if version_implies_warnings(version) {
state.warnings = true;
}
state.features = features_enabled_by_version(version);
state.unicode_strings = state.has_feature("unicode_strings");
state.signatures_strict = false;
}
fn feature_items(arg: &str) -> Vec<String> {
pragma_arg_items(arg)
}
fn known_feature_name(name: &str) -> Option<&'static str> {
match name {
"say" => Some("say"),
"state" => Some("state"),
"switch" => Some("switch"),
"unicode_strings" => Some("unicode_strings"),
"unicode_eval" => Some("unicode_eval"),
"evalbytes" => Some("evalbytes"),
"current_sub" => Some("current_sub"),
"fc" => Some("fc"),
"postfix_deref" => Some("postfix_deref"),
"try" => Some("try"),
"signatures" => Some("signatures"),
"defer" => Some("defer"),
"isa" => Some("isa"),
"class" => Some("class"),
"field" => Some("field"),
"method" => Some("method"),
"builtin" => Some("builtin"),
_ => None,
}
}
const ALL_KNOWN_FEATURES: &[&str] = &[
"say",
"state",
"switch",
"unicode_strings",
"unicode_eval",
"evalbytes",
"current_sub",
"fc",
"postfix_deref",
"try",
"signatures",
"defer",
"isa",
"class",
"field",
"method",
"builtin",
];
fn enable_feature_name(state: &mut PragmaState, name: &str) -> bool {
if name == "signatures" {
state.signatures_strict = true;
}
if name == "unicode_strings" {
state.unicode_strings = true;
}
if let Some(feature) = known_feature_name(name) {
if state.features.iter().all(|existing| existing != &feature) {
state.features.push(feature);
}
true
} else {
false
}
}
fn disable_feature_name(state: &mut PragmaState, name: &str) -> bool {
if name == "signatures" {
state.signatures_strict = false;
}
if name == "unicode_strings" {
state.unicode_strings = false;
}
if let Some(feature) = known_feature_name(name) {
let before = state.features.len();
state.features.retain(|existing| *existing != feature);
before != state.features.len()
} else {
false
}
}
fn apply_feature_state(state: &mut PragmaState, args: &[String], enabled: bool) -> bool {
if !enabled && args.is_empty() {
let changed =
!state.features.is_empty() || state.unicode_strings || state.signatures_strict;
state.features.clear();
state.unicode_strings = false;
state.signatures_strict = false;
return changed;
}
let mut changed = false;
for arg in args {
for item in feature_items(arg) {
if enabled && item == ":all" {
for feature in ALL_KNOWN_FEATURES {
changed |= enable_feature_name(state, feature);
}
continue;
}
if !enabled && item == ":all" {
let had_features =
!state.features.is_empty() || state.unicode_strings || state.signatures_strict;
state.features.clear();
state.unicode_strings = false;
state.signatures_strict = false;
changed |= had_features;
continue;
}
if let Some(version) = item.strip_prefix(':').and_then(parse_perl_version) {
for feature in features_enabled_by_version(version) {
changed |= if enabled {
enable_feature_name(state, feature)
} else {
disable_feature_name(state, feature)
};
}
continue;
}
changed |= if enabled {
enable_feature_name(state, &item)
} else {
disable_feature_name(state, &item)
};
}
}
changed
}
fn builtin_import_names(arg: &str) -> Vec<String> {
let trimmed = arg.trim();
if let Some(inner) = trimmed.strip_prefix("qw(").and_then(|s| s.strip_suffix(')')) {
return inner
.split_whitespace()
.filter(|name| !name.is_empty())
.map(|name| name.trim_matches('\'').trim_matches('"').to_string())
.collect();
}
let name = trimmed.trim_matches('\'').trim_matches('"');
if name.is_empty() { Vec::new() } else { vec![name.to_string()] }
}
fn apply_builtin_imports(state: &mut PragmaState, args: &[String]) {
for arg in args {
for name in builtin_import_names(arg) {
if !state.builtin_imports.iter().any(|import| import == &name) {
state.builtin_imports.push(name);
}
}
}
}
fn add_disabled_warning_category(state: &mut PragmaState, category: &str) {
if category.is_empty() {
return;
}
if state.disabled_warning_categories.iter().any(|c| c == category) {
return;
}
if state.disabled_warning_categories.len() >= MAX_DISABLED_WARNING_CATEGORIES {
return;
}
state.disabled_warning_categories.push(category.to_string());
}
fn remove_builtin_imports(state: &mut PragmaState, args: &[String]) {
if args.is_empty() {
state.builtin_imports.clear();
return;
}
let names_to_remove: Vec<String> =
args.iter().flat_map(|arg| builtin_import_names(arg)).collect();
state.builtin_imports.retain(|import| !names_to_remove.iter().any(|name| name == import));
}
fn pragma_arg_items(arg: &str) -> Vec<String> {
let trimmed = arg.trim().trim_matches('\'').trim_matches('"');
if let Some(inner) = trimmed.strip_prefix("qw(").and_then(|s| s.strip_suffix(')')) {
return inner.split_whitespace().map(|item| item.to_string()).collect();
}
if trimmed.contains(char::is_whitespace) {
return trimmed.split_whitespace().map(|item| item.to_string()).collect();
}
vec![trimmed.to_string()]
}
fn normalized_pragma_token(arg: &str) -> &str {
arg.trim().trim_matches('\'').trim_matches('"')
}
fn is_tracked_pragma_module(module: &str) -> bool {
matches!(module, "strict" | "warnings" | "utf8" | "encoding" | "locale" | "feature" | "builtin")
}
fn valid_strict_args(args: &[String]) -> bool {
args.iter()
.flat_map(|arg| pragma_arg_items(arg))
.all(|item| matches!(item.as_str(), "vars" | "subs" | "refs"))
}
fn conditional_target_tail_is_valid(module: &str, tail: &[String]) -> bool {
if parse_perl_version(module).is_some() {
return tail.is_empty();
}
match module {
"strict" => tail.is_empty() || valid_strict_args(tail),
"warnings" => true,
"utf8" => tail.is_empty(),
"encoding" => tail.len() == 1 && !normalized_pragma_token(&tail[0]).is_empty(),
"locale" => {
tail.is_empty() || (tail.len() == 1 && !normalized_pragma_token(&tail[0]).is_empty())
}
"feature" => !tail.is_empty(),
"builtin" => tail.iter().any(|arg| !builtin_import_names(arg).is_empty()),
_ => false,
}
}
fn conditional_pragma_target(args: &[String]) -> Option<(&str, &[String])> {
args.iter().enumerate().find_map(|(idx, arg)| {
let module = normalized_pragma_token(arg);
let tail = &args[idx + 1..];
if (is_tracked_pragma_module(module) || parse_perl_version(module).is_some())
&& conditional_target_tail_is_valid(module, tail)
{
Some((module, tail))
} else {
None
}
})
}
pub struct PragmaTracker;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct PragmaQueryCursor {
index: usize,
}
impl PragmaQueryCursor {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn state_for_offset(
&mut self,
pragma_map: &[(Range<usize>, PragmaState)],
offset: usize,
) -> PragmaState {
if pragma_map.is_empty() {
return PragmaState::default();
}
if self.index >= pragma_map.len() {
self.index = pragma_map.len() - 1;
}
if pragma_map[self.index].0.start > offset {
self.index = pragma_map.partition_point(|(range, _)| range.start <= offset);
if self.index > 0 {
self.index -= 1;
}
} else {
while self.index + 1 < pragma_map.len() && pragma_map[self.index + 1].0.start <= offset
{
self.index += 1;
}
}
let mut state = if pragma_map[self.index].0.start <= offset {
pragma_map[self.index].1.clone()
} else {
PragmaState::default()
};
if state.signatures_strict {
state.strict_vars = true;
state.strict_subs = true;
state.strict_refs = true;
}
state
}
}
impl PragmaTracker {
pub fn build(ast: &Node) -> Vec<(Range<usize>, PragmaState)> {
CompileTimePragmaEnvironment::build(ast)
.as_map()
.iter()
.map(|(range, snapshot)| (range.clone(), snapshot.clone().into()))
.collect()
}
pub fn state_for_offset(
pragma_map: &[(Range<usize>, PragmaState)],
offset: usize,
) -> PragmaState {
let map = pragma_map
.iter()
.map(|(range, state)| (range.clone(), PragmaSnapshot::from(state.clone())))
.collect();
let environment = CompileTimePragmaEnvironment { map };
environment.snapshot_at(offset).into()
}
#[must_use]
pub fn final_state(pragma_map: &[(Range<usize>, PragmaState)]) -> PragmaState {
let mut state = pragma_map.last().map_or_else(PragmaState::default, |(_, s)| s.clone());
if state.signatures_strict {
state.strict_vars = true;
state.strict_subs = true;
state.strict_refs = true;
}
state
}
fn build_scoped_body(
body: &Node,
current_state: &mut PragmaState,
ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
let saved_state = current_state.clone();
Self::build_ranges(body, current_state, ranges);
*current_state = saved_state;
ranges.push((body.location.end..body.location.end, current_state.clone()));
}
fn build_ranges(
node: &Node,
current_state: &mut PragmaState,
ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
match &node.kind {
NodeKind::Use { module, args, .. } => {
if (module == "if" || module == "unless")
&& let Some((conditional_module, conditional_args)) =
conditional_pragma_target(args)
{
match conditional_module {
"strict" => {
if conditional_args.is_empty() {
current_state.strict_vars = true;
current_state.strict_subs = true;
current_state.strict_refs = true;
} else {
for arg in conditional_args {
for item in pragma_arg_items(arg) {
match item.as_str() {
"vars" => current_state.strict_vars = true,
"subs" => current_state.strict_subs = true,
"refs" => current_state.strict_refs = true,
_ => {}
}
}
}
}
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"warnings" => {
current_state.warnings = true;
current_state.disabled_warning_categories.clear();
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"utf8" => {
current_state.utf8 = true;
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"encoding" => {
current_state.encoding = conditional_args
.first()
.map(|arg| normalized_pragma_token(arg).to_string());
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"locale" => {
current_state.locale = true;
current_state.locale_scope = conditional_args
.first()
.map(|arg| normalized_pragma_token(arg).to_string());
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"feature" => {
if apply_feature_state(current_state, conditional_args, true) {
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
return;
}
"builtin" => {
apply_builtin_imports(current_state, conditional_args);
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
_ => {
if let Some(version) = parse_perl_version(conditional_module) {
enable_effective_version_semantics(current_state, version);
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
return;
}
}
}
match module.as_str() {
"strict" => {
if args.is_empty() {
current_state.strict_vars = true;
current_state.strict_subs = true;
current_state.strict_refs = true;
} else {
for arg in args {
for item in pragma_arg_items(arg) {
match item.as_str() {
"vars" => {
current_state.strict_vars = true;
}
"subs" => {
current_state.strict_subs = true;
}
"refs" => {
current_state.strict_refs = true;
}
_ => {}
}
}
}
}
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"warnings" => {
current_state.warnings = true;
current_state.disabled_warning_categories.clear();
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"utf8" => {
current_state.utf8 = true;
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"encoding" => {
current_state.encoding = args
.first()
.map(|arg| arg.trim().trim_matches('\'').trim_matches('"').to_string());
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"locale" => {
current_state.locale = true;
current_state.locale_scope = args
.first()
.map(|arg| arg.trim().trim_matches('\'').trim_matches('"').to_string());
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"feature" => {
if apply_feature_state(current_state, args, true) {
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
}
"builtin" => {
apply_builtin_imports(current_state, args);
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
_ => {
if let Some(version) = parse_perl_version(module) {
enable_effective_version_semantics(current_state, version);
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
}
}
}
NodeKind::No { module, args, .. } => {
if (module == "if" || module == "unless")
&& let Some((conditional_module, conditional_args)) =
conditional_pragma_target(args)
{
match conditional_module {
"strict" => {
if conditional_args.is_empty() {
current_state.strict_vars = false;
current_state.strict_subs = false;
current_state.strict_refs = false;
} else {
for arg in conditional_args {
for item in pragma_arg_items(arg) {
match item.as_str() {
"vars" => current_state.strict_vars = false,
"subs" => current_state.strict_subs = false,
"refs" => current_state.strict_refs = false,
_ => {}
}
}
}
}
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"warnings" => {
if conditional_args.is_empty() {
current_state.warnings = false;
current_state.disabled_warning_categories.clear();
} else {
for arg in conditional_args {
let category = normalized_pragma_token(arg);
add_disabled_warning_category(current_state, category);
}
}
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"utf8" => {
current_state.utf8 = false;
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"encoding" => {
current_state.encoding = None;
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"locale" => {
current_state.locale = false;
current_state.locale_scope = None;
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
"feature" => {
if apply_feature_state(current_state, conditional_args, false) {
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
return;
}
"builtin" => {
remove_builtin_imports(current_state, conditional_args);
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
return;
}
_ => return,
}
}
match module.as_str() {
"strict" => {
if args.is_empty() {
current_state.strict_vars = false;
current_state.strict_subs = false;
current_state.strict_refs = false;
} else {
for arg in args {
for item in pragma_arg_items(arg) {
match item.as_str() {
"vars" => {
current_state.strict_vars = false;
}
"subs" => {
current_state.strict_subs = false;
}
"refs" => {
current_state.strict_refs = false;
}
_ => {}
}
}
}
}
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"warnings" => {
let warnings_before = current_state.warnings;
let had_disabled_before =
!current_state.disabled_warning_categories.is_empty();
let before = current_state.disabled_warning_categories.len();
if args.is_empty() {
current_state.warnings = false;
current_state.disabled_warning_categories.clear();
} else {
for arg in args {
let category = arg.trim_matches('\'').trim_matches('"');
add_disabled_warning_category(current_state, category);
}
}
let changed = if args.is_empty() {
warnings_before || had_disabled_before
} else {
current_state.disabled_warning_categories.len() != before
};
if changed {
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
}
"utf8" => {
current_state.utf8 = false;
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"encoding" => {
current_state.encoding = None;
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"locale" => {
current_state.locale = false;
current_state.locale_scope = None;
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
"feature" => {
if apply_feature_state(current_state, args, false) {
ranges.push((
node.location.start..node.location.end,
current_state.clone(),
));
}
}
"builtin" => {
remove_builtin_imports(current_state, args);
ranges
.push((node.location.start..node.location.end, current_state.clone()));
}
_ => {}
}
}
NodeKind::Block { statements } => {
let saved_state = current_state.clone();
for stmt in statements {
Self::build_ranges(stmt, current_state, ranges);
}
*current_state = saved_state;
ranges.push((node.location.end..node.location.end, current_state.clone()));
}
NodeKind::Program { statements } => {
for stmt in statements {
Self::build_ranges(stmt, current_state, ranges);
}
}
NodeKind::Subroutine { body, .. } => {
Self::build_scoped_body(body, current_state, ranges);
}
NodeKind::Method { body, .. } => {
Self::build_scoped_body(body, current_state, ranges);
}
NodeKind::Class { body, .. } => {
Self::build_scoped_body(body, current_state, ranges);
}
NodeKind::If { then_branch, elsif_branches, else_branch, .. } => {
Self::build_scoped_body(then_branch, current_state, ranges);
for (_, elsif_body) in elsif_branches {
Self::build_scoped_body(elsif_body, current_state, ranges);
}
if let Some(else_b) = else_branch {
Self::build_scoped_body(else_b, current_state, ranges);
}
}
NodeKind::While { body, continue_block, .. }
| NodeKind::For { body, continue_block, .. }
| NodeKind::Foreach { body, continue_block, .. } => {
Self::build_scoped_body(body, current_state, ranges);
if let Some(continue_block) = continue_block {
Self::build_scoped_body(continue_block, current_state, ranges);
}
}
NodeKind::Eval { block } => {
if matches!(block.kind, NodeKind::Block { .. }) {
Self::build_scoped_body(block, current_state, ranges);
}
}
NodeKind::Do { block } | NodeKind::Defer { block } => {
Self::build_scoped_body(block, current_state, ranges);
}
NodeKind::PhaseBlock { block, .. } => {
Self::build_scoped_body(block, current_state, ranges);
}
NodeKind::Given { body, .. }
| NodeKind::When { body, .. }
| NodeKind::Default { body } => {
Self::build_scoped_body(body, current_state, ranges);
}
NodeKind::Try { body, catch_blocks, finally_block } => {
Self::build_scoped_body(body, current_state, ranges);
for (_, catch_body) in catch_blocks {
Self::build_scoped_body(catch_body, current_state, ranges);
}
if let Some(finally_block) = finally_block {
Self::build_scoped_body(finally_block, current_state, ranges);
}
}
NodeKind::LabeledStatement { statement, .. } => {
Self::build_ranges(statement, current_state, ranges);
}
NodeKind::StatementModifier { statement, condition, .. } => {
Self::build_ranges(statement, current_state, ranges);
Self::build_ranges(condition, current_state, ranges);
}
NodeKind::Package { block: Some(pkg_block), .. } => {
Self::build_scoped_body(pkg_block, current_state, ranges);
}
_ => {}
}
}
}