mxmlextrema_as3parser/compilation_unit/
compilation_unit.rs

1use std::{any::Any, cell::RefMut};
2use std::fmt::{Debug, Formatter};
3use crate::ns::*;
4use realhydroper_sourcetext::SourceText;
5
6/// `CompilationUnit` identifies an AS3 compilation unit and contains
7/// a source text.
8pub struct CompilationUnit {
9    pub(crate) file_path: Option<String>,
10    pub(crate) source_text: SourceText,
11    pub(crate) compiler_options: RefCell<Option<Rc<dyn Any>>>,
12    pub(crate) diagnostics: RefCell<Vec<Diagnostic>>,
13    pub(crate) error_count: Cell<u32>,
14    pub(crate) warning_count: Cell<u32>,
15    pub(crate) invalidated: Cell<bool>,
16    pub(crate) comments: RefCell<Vec<Rc<Comment>>>,
17    pub(crate) included_from: RefCell<Option<Rc<CompilationUnit>>>,
18    pub(crate) nested_compilation_units: RefCell<Vec<Rc<CompilationUnit>>>,
19}
20
21impl Debug for CompilationUnit {
22    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23        f.write_str("CompilationUnit")
24    }
25}
26
27impl Default for CompilationUnit {
28    fn default() -> Self {
29        Self {
30            file_path: None,
31            source_text: SourceText::new("".into()),
32            compiler_options: RefCell::new(None),
33            diagnostics: RefCell::new(vec![]),
34            invalidated: Cell::new(false),
35            error_count: Cell::new(0),
36            warning_count: Cell::new(0),
37            comments: RefCell::new(vec![]),
38            nested_compilation_units: RefCell::new(vec![]),
39            included_from: RefCell::new(None),
40        }
41    }
42}
43
44impl CompilationUnit {
45    /// Constructs a source file in unparsed and non verified state.
46    pub fn new(file_path: Option<String>, text: String) -> Rc<Self> {
47        Rc::new(Self {
48            file_path,
49            source_text: SourceText::new(text),
50            compiler_options: RefCell::new(None),
51            diagnostics: RefCell::new(vec![]),
52            invalidated: Cell::new(false),
53            error_count: Cell::new(0),
54            warning_count: Cell::new(0),
55            comments: RefCell::new(vec![]),
56            nested_compilation_units: RefCell::new(vec![]),
57            included_from: RefCell::new(None),
58        })
59    }
60
61    /// File path of the source or `None` if not a file.
62    pub fn file_path(&self) -> Option<String> {
63        self.file_path.clone()
64    }
65
66    /// Source text.
67    pub fn text(&self) -> &String {
68        &self.source_text.contents
69    }
70
71    /// Compiler options.
72    pub fn compiler_options(&self) -> Option<Rc<dyn Any>> {
73        self.compiler_options.borrow().clone()
74    }
75
76    /// Set compiler options.
77    pub fn set_compiler_options(&self, options: Option<Rc<dyn Any>>) {
78        self.compiler_options.replace(options);
79    }
80
81    /// Whether the source contains any errors after parsing
82    /// and/or verification.
83    pub fn invalidated(&self) -> bool {
84        self.invalidated.get()
85    }
86
87    /// The comments present in the source file. To get mutable access to the
88    /// collection of comments, use the `comments_mut()` method instead.
89    pub fn comments(&self) -> Vec<Rc<Comment>> {
90        let mut collection = vec![];
91        for c in self.comments.borrow().iter() {
92            collection.push(c.clone());
93        }
94        collection
95    }
96
97    /// The comments present in the source file, as a mutable collection.
98    pub fn comments_mut(&self) -> RefMut<Vec<Rc<Comment>>> {
99        self.comments.borrow_mut()
100    }
101
102    /// Contributes a comment if there is no other comment
103    /// in the same location.
104    pub fn add_comment(&self, comment: Rc<Comment>) {
105        let mut dup = false;
106        let i = comment.location.borrow().first_offset();
107        for c1 in self.comments.borrow().iter() {
108            if c1.location.borrow().first_offset == i {
109                dup = true;
110                break;
111            }
112        }
113        if !dup {
114            self.comments.borrow_mut().push(comment);
115        }
116    }
117
118    /// Diagnostics of the source file after parsing and/or
119    /// verification.
120    pub fn diagnostics(&self) -> Vec<Diagnostic> {
121        self.diagnostics.borrow().clone()
122    }
123
124    /// Diagnostics of the source file after parsing and/or
125    /// verification, including those of nested compilation units.
126    pub fn nested_diagnostics(&self) -> Vec<Diagnostic> {
127        let mut result = self.diagnostics();
128        for unit in self.nested_compilation_units.borrow().iter() {
129            result.extend(unit.nested_diagnostics());
130        }
131        result
132    }
133
134    /// Sort diagnostics from the compilation unit
135    /// and any nested compilation units.
136    pub fn sort_diagnostics(&self) {
137        self.diagnostics.borrow_mut().sort();
138        for unit in self.nested_compilation_units.borrow().iter() {
139            unit.sort_diagnostics();
140        }
141    }
142
143    /// Determines whether to skip contributing an error when it
144    /// occurs at the same offset of another error.
145    pub fn prevent_equal_offset_error(&self, location: &Location) -> bool {
146        let diag_list = self.diagnostics.borrow();
147        for diag in diag_list.iter() {
148            if diag.is_warning() {
149                continue;
150            }
151            if diag.location.first_offset == location.first_offset {
152                return true;
153            }
154        }
155        false
156    }
157
158    /// Determines whether to skip contributing a warning when it
159    /// occurs at the same offset of another warning.
160    pub fn prevent_equal_offset_warning(&self, location: &Location) -> bool {
161        let diag_list = self.diagnostics.borrow();
162        for diag in diag_list.iter() {
163            if diag.is_error() {
164                continue;
165            }
166            if diag.location.first_offset == location.first_offset {
167                return true;
168            }
169        }
170        false
171    }
172
173    /// If this compilation unit is subsequent of an include directive in another
174    /// compilation unit, returns the compilation unit of that include directive.
175    pub fn included_from(&self) -> Option<Rc<CompilationUnit>> {
176        self.included_from.borrow().clone()
177    }
178
179    pub(crate) fn set_included_from(&self, included_from: Option<Rc<CompilationUnit>>) {
180        self.included_from.replace(included_from);
181    }
182
183    pub(crate) fn include_directive_is_circular(&self, file_path: &str) -> bool {
184        if normalize_path(&self.file_path.clone().unwrap_or("".into())) == normalize_path(file_path) {
185            return true;
186        }
187        if let Some(included_from) = self.included_from() {
188            return included_from.include_directive_is_circular(file_path);
189        }
190        return false;
191    }
192
193    pub fn nested_compilation_units(&self) -> Vec<Rc<CompilationUnit>> {
194        let mut result = vec![];
195        for unit in self.nested_compilation_units.borrow().iter() {
196            result.push(unit.clone());
197        }
198        result
199    }
200
201    pub fn add_nested_compilation_unit(self: &Rc<Self>, unit: Rc<CompilationUnit>) {
202        self.nested_compilation_units.borrow_mut().push(unit.clone());
203        unit.set_included_from(Some(self.clone()));
204    }
205
206    pub fn add_diagnostic(&self, diagnostic: Diagnostic) {
207        if diagnostic.is_warning() {
208            self.warning_count.set(self.warning_count.get() + 1);
209        } else {
210            self.error_count.set(self.error_count.get() + 1);
211            self.invalidated.set(true);
212        }
213        self.diagnostics.borrow_mut().push(diagnostic);
214    }
215
216    pub fn error_count(&self) -> u32 {
217        self.error_count.get()
218    }
219
220    pub fn warning_count(&self) -> u32 {
221        self.warning_count.get()
222    }
223
224    /// Retrieves line number from an offset. The resulting line number
225    /// is counted from one.
226    pub fn get_line_number(&self, offset: usize) -> usize {
227        self.source_text.get_line_number(offset)
228    }
229
230    /// Returns the zero based column of an offset.
231    pub fn get_column(&self, offset: usize) -> usize {
232        self.source_text.get_column(offset)
233    }
234
235    /// Retrieves offset from line number (counted from one).
236    pub fn get_line_offset(&self, line: usize) -> Option<usize> {
237        self.source_text.get_line_offset(line)
238    }
239
240    /// Retrieves the offset from the corresponding line of an offset.
241    pub fn get_line_offset_from_offset(&self, offset: usize) -> usize {
242        self.source_text.get_line_offset_from_offset(offset)
243    }
244
245    pub fn get_line_indent(&self, line: usize) -> usize {
246        let line_offset = self.get_line_offset(line).unwrap();
247        CharacterValidator::indent_count(&self.source_text.contents[line_offset..])
248    }
249}
250
251fn normalize_path(path: &str) -> String {
252    realhydroper_path::normalize_path(std::path::Path::new(path)).to_string_lossy().into_owned()
253}