mxmlextrema_as3parser/compilation_unit/
compilation_unit.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
use std::{any::Any, cell::RefMut};
use std::fmt::{Debug, Formatter};
use crate::ns::*;
use hydroperfox_sourcetext::SourceText;

/// `CompilationUnit` identifies an AS3 compilation unit and contains
/// a source text.
pub struct CompilationUnit {
    pub(crate) file_path: Option<String>,
    pub(crate) source_text: SourceText,
    pub(crate) compiler_options: RefCell<Option<Rc<dyn Any>>>,
    pub(crate) diagnostics: RefCell<Vec<Diagnostic>>,
    pub(crate) error_count: Cell<u32>,
    pub(crate) warning_count: Cell<u32>,
    pub(crate) invalidated: Cell<bool>,
    pub(crate) comments: RefCell<Vec<Rc<Comment>>>,
    pub(crate) included_from: RefCell<Option<Rc<CompilationUnit>>>,
    pub(crate) nested_compilation_units: RefCell<Vec<Rc<CompilationUnit>>>,
}

impl Debug for CompilationUnit {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str("CompilationUnit")
    }
}

impl Default for CompilationUnit {
    fn default() -> Self {
        Self {
            file_path: None,
            source_text: SourceText::new("".into()),
            compiler_options: RefCell::new(None),
            diagnostics: RefCell::new(vec![]),
            invalidated: Cell::new(false),
            error_count: Cell::new(0),
            warning_count: Cell::new(0),
            comments: RefCell::new(vec![]),
            nested_compilation_units: RefCell::new(vec![]),
            included_from: RefCell::new(None),
        }
    }
}

impl CompilationUnit {
    /// Constructs a source file in unparsed and non verified state.
    pub fn new(file_path: Option<String>, text: String) -> Rc<Self> {
        Rc::new(Self {
            file_path,
            source_text: SourceText::new(text),
            compiler_options: RefCell::new(None),
            diagnostics: RefCell::new(vec![]),
            invalidated: Cell::new(false),
            error_count: Cell::new(0),
            warning_count: Cell::new(0),
            comments: RefCell::new(vec![]),
            nested_compilation_units: RefCell::new(vec![]),
            included_from: RefCell::new(None),
        })
    }

    /// File path of the source or `None` if not a file.
    pub fn file_path(&self) -> Option<String> {
        self.file_path.clone()
    }

    /// Source text.
    pub fn text(&self) -> &String {
        &self.source_text.contents
    }

    /// Compiler options.
    pub fn compiler_options(&self) -> Option<Rc<dyn Any>> {
        self.compiler_options.borrow().clone()
    }

    /// Set compiler options.
    pub fn set_compiler_options(&self, options: Option<Rc<dyn Any>>) {
        self.compiler_options.replace(options);
    }

    /// Whether the source contains any errors after parsing
    /// and/or verification.
    pub fn invalidated(&self) -> bool {
        self.invalidated.get()
    }

    /// The comments present in the source file. To get mutable access to the
    /// collection of comments, use the `comments_mut()` method instead.
    pub fn comments(&self) -> Vec<Rc<Comment>> {
        let mut collection = vec![];
        for c in self.comments.borrow().iter() {
            collection.push(c.clone());
        }
        collection
    }

    /// The comments present in the source file, as a mutable collection.
    pub fn comments_mut(&self) -> RefMut<Vec<Rc<Comment>>> {
        self.comments.borrow_mut()
    }

    /// Contributes a comment if there is no other comment
    /// in the same location.
    pub fn add_comment(&self, comment: Rc<Comment>) {
        let mut dup = false;
        let i = comment.location.borrow().first_offset();
        for c1 in self.comments.borrow().iter() {
            if c1.location.borrow().first_offset == i {
                dup = true;
                break;
            }
        }
        if !dup {
            self.comments.borrow_mut().push(comment);
        }
    }

    /// Diagnostics of the source file after parsing and/or
    /// verification.
    pub fn diagnostics(&self) -> Vec<Diagnostic> {
        self.diagnostics.borrow().clone()
    }

    /// Diagnostics of the source file after parsing and/or
    /// verification, including those of nested compilation units.
    pub fn nested_diagnostics(&self) -> Vec<Diagnostic> {
        let mut result = self.diagnostics();
        for unit in self.nested_compilation_units.borrow().iter() {
            result.extend(unit.nested_diagnostics());
        }
        result
    }

    /// Sort diagnostics from the compilation unit
    /// and any nested compilation units.
    pub fn sort_diagnostics(&self) {
        self.diagnostics.borrow_mut().sort();
        for unit in self.nested_compilation_units.borrow().iter() {
            unit.sort_diagnostics();
        }
    }

    /// Determines whether to skip contributing an error when it
    /// occurs at the same offset of another error.
    pub fn prevent_equal_offset_error(&self, location: &Location) -> bool {
        let diag_list = self.diagnostics.borrow();
        for diag in diag_list.iter() {
            if diag.is_warning() {
                continue;
            }
            if diag.location.first_offset == location.first_offset {
                return true;
            }
        }
        false
    }

    /// Determines whether to skip contributing a warning when it
    /// occurs at the same offset of another warning.
    pub fn prevent_equal_offset_warning(&self, location: &Location) -> bool {
        let diag_list = self.diagnostics.borrow();
        for diag in diag_list.iter() {
            if diag.is_error() {
                continue;
            }
            if diag.location.first_offset == location.first_offset {
                return true;
            }
        }
        false
    }

    /// If this compilation unit is subsequent of an include directive in another
    /// compilation unit, returns the compilation unit of that include directive.
    pub fn included_from(&self) -> Option<Rc<CompilationUnit>> {
        self.included_from.borrow().clone()
    }

    pub(crate) fn set_included_from(&self, included_from: Option<Rc<CompilationUnit>>) {
        self.included_from.replace(included_from);
    }

    pub(crate) fn include_directive_is_circular(&self, file_path: &str) -> bool {
        if canonicalize_path(&self.file_path.clone().unwrap_or("".into())) == canonicalize_path(file_path) {
            return true;
        }
        if let Some(included_from) = self.included_from() {
            return included_from.include_directive_is_circular(file_path);
        }
        return false;
    }

    pub fn nested_compilation_units(&self) -> Vec<Rc<CompilationUnit>> {
        let mut result = vec![];
        for unit in self.nested_compilation_units.borrow().iter() {
            result.push(unit.clone());
        }
        result
    }

    pub fn add_nested_compilation_unit(self: &Rc<Self>, unit: Rc<CompilationUnit>) {
        self.nested_compilation_units.borrow_mut().push(unit.clone());
        unit.set_included_from(Some(self.clone()));
    }

    pub fn add_diagnostic(&self, diagnostic: Diagnostic) {
        if diagnostic.is_warning() {
            self.warning_count.set(self.warning_count.get() + 1);
        } else {
            self.error_count.set(self.error_count.get() + 1);
            self.invalidated.set(true);
        }
        self.diagnostics.borrow_mut().push(diagnostic);
    }

    pub fn error_count(&self) -> u32 {
        self.error_count.get()
    }

    pub fn warning_count(&self) -> u32 {
        self.warning_count.get()
    }

    /// Retrieves line number from an offset. The resulting line number
    /// is counted from one.
    pub fn get_line_number(&self, offset: usize) -> usize {
        self.source_text.get_line_number(offset)
    }

    /// Returns the zero based column of an offset.
    pub fn get_column(&self, offset: usize) -> usize {
        self.source_text.get_column(offset)
    }

    /// Retrieves offset from line number (counted from one).
    pub fn get_line_offset(&self, line: usize) -> Option<usize> {
        self.source_text.get_line_offset(line)
    }

    /// Retrieves the offset from the corresponding line of an offset.
    pub fn get_line_offset_from_offset(&self, offset: usize) -> usize {
        self.source_text.get_line_offset_from_offset(offset)
    }

    pub fn get_line_indent(&self, line: usize) -> usize {
        let line_offset = self.get_line_offset(line).unwrap();
        CharacterValidator::indent_count(&self.source_text.contents[line_offset..])
    }
}

fn canonicalize_path(path: &str) -> String {
    std::path::Path::new(path).canonicalize().unwrap_or(std::path::PathBuf::new()).to_string_lossy().into_owned()
}