diff --git a/CONFIGURING.md b/CONFIGURING.md index 34a59dc0..ec9da9f6 100644 --- a/CONFIGURING.md +++ b/CONFIGURING.md @@ -95,6 +95,7 @@ The `[code_standards]` section has the following options: * `disallow_relative_proc_definitions` - Raised on relative pathed proc definitions * `disallow_relative_type_definitions` - Raised on relative pathed subtype defintions +* `disallow_turf_contents_iteration` - Raised on attempting to use a for loop over a turf or turf.contents ### DM Doc diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index 6173615c..53f397df 100644 --- a/crates/dreamchecker/src/lib.rs +++ b/crates/dreamchecker/src/lib.rs @@ -692,7 +692,7 @@ impl<'o> AnalyzeObjectTree<'o> { .with_blocking_builtins(self.sleeping_procs.get_violators(*child_violator).unwrap()) .register(self.context) } - } + } if let Some(calledvec) = self.call_tree.get(&nextproc) { for (proccalled, location, new_context) in calledvec.iter() { let mut newstack = callstack.clone(); @@ -743,7 +743,7 @@ impl<'o> AnalyzeObjectTree<'o> { .with_blocking_builtins(self.impure_procs.get_violators(*child_violator).unwrap()) .register(self.context) } - } + } if let Some(calledvec) = self.call_tree.get(&nextproc) { for (proccalled, location, new_context) in calledvec.iter() { let mut newstack = callstack.clone(); @@ -1385,6 +1385,28 @@ impl<'o, 's> AnalyzeProc<'o, 's> { let ForListStatement { var_type, name, input_type, in_list, block } = &**for_list; let mut scoped_locals = local_vars.clone(); if let Some(in_list) = in_list { + if self.context.config().code_standards.disallow_turf_contents_iteration { + if let Expression::Base { term, follow } = in_list { + let ty = self.visit_term(term.location, &term.elem, None, &mut scoped_locals); + if ty.static_ty == StaticType::Type(self.objtree.expect("/turf")) { + if follow.is_empty() { + error(term.location, "iterating over turf contents") + .set_severity(Severity::Error) + .register(self.context); + } + + if follow.len() == 1 { + if let Follow::Field(_, ident) = &follow.first().unwrap().elem { + if ident == "contents" { + error(term.location, "iterating over turf contents") + .set_severity(Severity::Error) + .register(self.context); + } + } + } + } + } + } let list = self.visit_expression(location, in_list, None, &mut scoped_locals); match list.static_ty { StaticType::None => { diff --git a/crates/dreamchecker/src/test_helpers.rs b/crates/dreamchecker/src/test_helpers.rs index e0ea5e0c..82f6f95c 100644 --- a/crates/dreamchecker/src/test_helpers.rs +++ b/crates/dreamchecker/src/test_helpers.rs @@ -5,9 +5,11 @@ use crate::{run_inner}; pub const NO_ERRORS: &[(u32, u16, &str)] = &[]; -pub fn parse_a_file_for_test>>(buffer: S) -> Context { +pub fn parse_a_file_for_test>>(buffer: S, config: String) -> Context { let context = Context::default(); + context.set_config(config); + let pp = dm::preprocessor::Preprocessor::from_buffer(&context, "unit_tests.rs".into(), buffer.into()); let indents = dm::indents::IndentProcessor::new(&context, pp); @@ -22,7 +24,11 @@ pub fn parse_a_file_for_test>>(buffer: S) -> Context { } pub fn check_errors_match>>(buffer: S, errorlist: &[(u32, u16, &str)]) { - let context = parse_a_file_for_test(buffer); + check_errors_match_with_config(buffer, errorlist, String::new()); +} + +pub fn check_errors_match_with_config>>(buffer: S, errorlist: &[(u32, u16, &str)], config: String) { + let context = parse_a_file_for_test(buffer, config); let errors = context.errors(); let mut iter = errors.iter(); for (line, column, desc) in errorlist { diff --git a/crates/dreamchecker/tests/turf_iteration_tests.rs b/crates/dreamchecker/tests/turf_iteration_tests.rs new file mode 100644 index 00000000..48482dbe --- /dev/null +++ b/crates/dreamchecker/tests/turf_iteration_tests.rs @@ -0,0 +1,27 @@ + +extern crate dreamchecker as dc; + +use dc::test_helpers::*; + +pub const CONST_EVAL_ERRORS: &[(u32, u16, &str)] = &[ + (3, 18, "iterating over turf contents"), + (5, 18, "iterating over turf contents"), +]; + +#[test] +fn const_eval() { + let code = r##" +/proc/test() + var/turf/T = new /turf + for(var/a in T) + world.log << a + for(var/a in T.contents) + world.log << a +"##.trim(); + let config = r##" +[code_standards] +disallow_turf_contents_iteration = true +"##.trim(); + check_errors_match_with_config(code, CONST_EVAL_ERRORS, config.to_string()); +} + diff --git a/crates/dreammaker/src/config.rs b/crates/dreammaker/src/config.rs index 4adcb258..afd68be8 100644 --- a/crates/dreammaker/src/config.rs +++ b/crates/dreammaker/src/config.rs @@ -48,6 +48,7 @@ pub struct Langserver { pub struct CodeStandards { pub disallow_relative_proc_definitions: bool, pub disallow_relative_type_definitions: bool, + pub disallow_turf_contents_iteration: bool, } /// DMDoc config options @@ -119,6 +120,10 @@ impl Config { Ok(toml::from_str(&config_toml)?) } + pub fn read_toml_string(toml: String) -> Result { + Ok(toml::from_str(&toml)?) + } + fn config_warninglevel(&self, error: &DMError) -> Option<&WarningLevel> { if let Some(errortype) = error.errortype() { return self.diagnostics.get(errortype) diff --git a/crates/dreammaker/src/error.rs b/crates/dreammaker/src/error.rs index 05c7aa29..04a70bd7 100644 --- a/crates/dreammaker/src/error.rs +++ b/crates/dreammaker/src/error.rs @@ -122,6 +122,10 @@ impl Context { // ------------------------------------------------------------------------ // Configuration + pub fn set_config(&self, config: String) { + *self.config.borrow_mut() = Config::read_toml_string(config).unwrap(); + } + pub fn force_config(&self, toml: &Path) { match Config::read_toml(toml) { Ok(config) => *self.config.borrow_mut() = config,