diff --git a/crates/dialects/src/base.rs b/crates/dialects/src/base.rs index 65036956..8b64b391 100644 --- a/crates/dialects/src/base.rs +++ b/crates/dialects/src/base.rs @@ -18,7 +18,7 @@ use crate::lang::executor::{ use crate::lang::{check_defs, into_exec_compiler_error, replace_sender_placeholder}; use crate::shared::errors::{CompilerError, ExecCompilerError, FileSourceMap, ProjectSourceMap}; use crate::shared::results::{ChainStateChanges, ResourceChange}; -use crate::shared::ProvidedAccountAddress; +use crate::shared::{line_endings, ProvidedAccountAddress}; pub trait Dialect { fn name(&self) -> &str; @@ -34,9 +34,9 @@ pub trait Dialect { file: MoveFile, sender: &ProvidedAccountAddress, ) -> Result<(Vec, FileSourceMap), ExecCompilerError> { - let (fname, mut source_text) = file; + let (fname, source_text) = file; - let mut file_source_map = FileSourceMap::default(); + let (mut source_text, mut file_source_map) = line_endings::normalize(source_text); source_text = replace_sender_placeholder( source_text, &sender.normalized_original, diff --git a/crates/dialects/src/shared/line_endings.rs b/crates/dialects/src/shared/line_endings.rs new file mode 100644 index 00000000..5181cc41 --- /dev/null +++ b/crates/dialects/src/shared/line_endings.rs @@ -0,0 +1,62 @@ +use crate::shared::errors::FileSourceMap; +use std::str::Chars; + +struct NewNormalized<'a> { + chars: Chars<'a>, + prev_was_carriage_return: bool, + pos: usize, + source_map: &'a mut FileSourceMap, +} + +impl<'a> NewNormalized<'a> { + fn inner_next(&mut self) -> Option { + self.pos += 1; + self.chars.next() + } +} + +const PATTERN_LENGTH: usize = 2; + +impl Iterator for NewNormalized<'_> { + type Item = char; + + fn next(&mut self) -> Option { + match self.inner_next() { + Some('\n') if self.prev_was_carriage_return => { + self.source_map.insert_layer(self.pos - PATTERN_LENGTH, 1); + + self.prev_was_carriage_return = false; + match self.inner_next() { + Some('\r') => { + self.prev_was_carriage_return = true; + Some('\n') + } + any => { + self.prev_was_carriage_return = false; + any + } + } + } + Some('\r') => { + self.prev_was_carriage_return = true; + Some('\n') + } + any => { + self.prev_was_carriage_return = false; + any + } + } + } +} + +pub fn normalize(s: String) -> (String, FileSourceMap) { + let mut source_map = FileSourceMap::default(); + let normalized = NewNormalized { + chars: s.chars(), + prev_was_carriage_return: false, + pos: 0, + source_map: &mut source_map, + }; + let normalized_string = normalized.collect::(); + (normalized_string, source_map) +} diff --git a/crates/dialects/src/shared/mod.rs b/crates/dialects/src/shared/mod.rs index ce338074..c1e3af36 100644 --- a/crates/dialects/src/shared/mod.rs +++ b/crates/dialects/src/shared/mod.rs @@ -4,6 +4,7 @@ use move_lang::shared::Address; pub mod addresses; pub mod bech32; pub mod errors; +pub mod line_endings; pub mod results; #[derive(Debug, Clone)] diff --git a/crates/integration_tests/tests/test_compilation_check.rs b/crates/integration_tests/tests/test_compilation_check.rs index 5b72b955..258a93ad 100644 --- a/crates/integration_tests/tests/test_compilation_check.rs +++ b/crates/integration_tests/tests/test_compilation_check.rs @@ -644,4 +644,24 @@ module DFI { "Invalid \'use\'. Unbound module: \'0x0::UnknownPayments\'" ); } + + #[test] + fn test_windows_line_endings_are_allowed() { + let script_text = "script { fun main() {} \r\n } \r\n"; + let errors = diagnostics(script_text); + assert!(errors.is_empty(), "{:#?}", errors); + } + + #[test] + fn test_windows_line_endings_do_not_offset_errors() { + let script_text = "script {\r\n func main() {} \r\n }"; + let errors = diagnostics(script_text); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].range, range((1, 1), (1, 5))); + + let script_text = "script {\r\n\r\n\r\n func main() {} \r\n }"; + let errors = diagnostics(script_text); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].range, range((3, 1), (3, 5))); + } }