dprint_plugin_typescript/generation/
context.rs1use deno_ast::swc::common::comments::Comment;
2use deno_ast::swc::parser::token::TokenAndSpan;
3use deno_ast::view::*;
4use deno_ast::MediaType;
5use deno_ast::SourcePos;
6use deno_ast::SourceRange;
7use deno_ast::SourceRanged;
8use deno_ast::SourceRangedForSpanned;
9use dprint_core::formatting::ConditionReference;
10use dprint_core::formatting::IndentLevel;
11use dprint_core::formatting::IsStartOfLine;
12use dprint_core::formatting::LineNumber;
13use dprint_core::formatting::LineStartIndentLevel;
14use rustc_hash::FxHashMap;
15use rustc_hash::FxHashSet;
16
17use super::*;
18use crate::configuration::*;
19use crate::utils::Stack;
20
21pub type ExternalFormatter = dyn Fn(&str, String, &Configuration) -> anyhow::Result<Option<String>>;
50
51pub(crate) struct GenerateDiagnostic {
52 pub message: String,
53}
54
55pub struct Context<'a> {
56 pub media_type: MediaType,
57 pub program: Program<'a>,
58 pub config: &'a Configuration,
59 pub comments: CommentTracker<'a>,
60 pub external_formatter: Option<&'a ExternalFormatter>,
61 pub token_finder: TokenFinder<'a>,
62 pub current_node: Node<'a>,
63 pub parent_stack: Stack<Node<'a>>,
64 consistent_quote_props_stack: Stack<bool>,
66 handled_comments: FxHashSet<SourcePos>,
67 stored_ln_ranges: FxHashMap<(SourcePos, SourcePos), (LineNumber, LineNumber)>,
68 stored_lsil: FxHashMap<(SourcePos, SourcePos), LineStartIndentLevel>,
69 stored_ln: FxHashMap<(SourcePos, SourcePos), LineNumber>,
70 stored_il: FxHashMap<(SourcePos, SourcePos), IndentLevel>,
71 pub end_statement_or_member_lns: Stack<LineNumber>,
72 before_comments_start_info_stack: Stack<(SourceRange, LineNumber, IsStartOfLine)>,
73 if_stmt_last_brace_condition_ref: Option<ConditionReference>,
74 expr_stmt_single_line_parent_brace_ref: Option<ConditionReference>,
75 #[cfg(debug_assertions)]
77 pub last_generated_node_pos: SourcePos,
78 pub diagnostics: Vec<GenerateDiagnostic>,
79}
80
81impl<'a> Context<'a> {
82 pub fn new(
83 media_type: MediaType,
84 tokens: &'a [TokenAndSpan],
85 current_node: Node<'a>,
86 program: Program<'a>,
87 config: &'a Configuration,
88 external_formatter: Option<&'a ExternalFormatter>,
89 ) -> Context<'a> {
90 Context {
91 media_type,
92 program,
93 config,
94 comments: CommentTracker::new(program, tokens),
95 external_formatter,
96 token_finder: TokenFinder::new(program),
97 current_node,
98 parent_stack: Default::default(),
99 consistent_quote_props_stack: Default::default(),
100 handled_comments: FxHashSet::default(),
101 stored_ln_ranges: FxHashMap::default(),
102 stored_lsil: FxHashMap::default(),
103 stored_ln: FxHashMap::default(),
104 stored_il: FxHashMap::default(),
105 end_statement_or_member_lns: Default::default(),
106 before_comments_start_info_stack: Default::default(),
107 if_stmt_last_brace_condition_ref: None,
108 expr_stmt_single_line_parent_brace_ref: None,
109 #[cfg(debug_assertions)]
110 last_generated_node_pos: deno_ast::SourceTextInfoProvider::text_info(&program).range().start.into(),
111 diagnostics: Vec::new(),
112 }
113 }
114
115 pub fn is_jsx(&self) -> bool {
116 matches!(self.media_type, MediaType::Tsx | MediaType::Jsx | MediaType::JavaScript)
117 }
118
119 pub fn parent(&self) -> Node<'a> {
120 *self.parent_stack.peek().unwrap()
121 }
122
123 pub fn has_handled_comment(&self, comment: &Comment) -> bool {
124 self.handled_comments.contains(&comment.start())
125 }
126
127 pub fn mark_comment_handled(&mut self, comment: &Comment) {
128 self.handled_comments.insert(comment.start());
129 }
130
131 pub fn store_info_range_for_node(&mut self, node: &impl SourceRanged, lns: (LineNumber, LineNumber)) {
132 self.stored_ln_ranges.insert((node.start(), node.end()), lns);
133 }
134
135 pub fn get_ln_range_for_node(&self, node: &impl SourceRanged) -> Option<(LineNumber, LineNumber)> {
136 self.stored_ln_ranges.get(&(node.start(), node.end())).map(|x| x.to_owned())
137 }
138
139 pub fn store_lsil_for_node(&mut self, node: &impl SourceRanged, lsil: LineStartIndentLevel) {
140 self.stored_lsil.insert((node.start(), node.end()), lsil);
141 }
142
143 pub fn get_lsil_for_node(&self, node: &impl SourceRanged) -> Option<LineStartIndentLevel> {
144 self.stored_lsil.get(&(node.start(), node.end())).map(|x| x.to_owned())
145 }
146
147 pub fn store_ln_for_node(&mut self, node: &impl SourceRanged, ln: LineNumber) {
148 self.stored_ln.insert((node.start(), node.end()), ln);
149 }
150
151 pub fn get_ln_for_node(&self, node: &impl SourceRanged) -> Option<LineNumber> {
152 self.stored_ln.get(&(node.start(), node.end())).map(|x| x.to_owned())
153 }
154
155 pub fn store_il_for_node(&mut self, node: &impl SourceRanged, il: IndentLevel) {
156 self.stored_il.insert((node.start(), node.end()), il);
157 }
158
159 pub fn get_il_for_node(&self, node: &impl SourceRanged) -> Option<IndentLevel> {
160 self.stored_il.get(&(node.start(), node.end())).map(|x| x.to_owned())
161 }
162
163 pub fn store_if_stmt_last_brace_condition_ref(&mut self, condition_reference: ConditionReference) {
164 self.if_stmt_last_brace_condition_ref = Some(condition_reference);
165 }
166
167 pub fn take_if_stmt_last_brace_condition_ref(&mut self) -> Option<ConditionReference> {
168 self.if_stmt_last_brace_condition_ref.take()
169 }
170
171 pub fn store_expr_stmt_single_line_parent_brace_ref(&mut self, condition_reference: ConditionReference) {
172 self.expr_stmt_single_line_parent_brace_ref = Some(condition_reference);
173 }
174
175 pub fn take_expr_stmt_single_line_parent_brace_ref(&mut self) -> Option<ConditionReference> {
176 self.expr_stmt_single_line_parent_brace_ref.take()
177 }
178
179 pub fn get_or_create_current_before_comments_start_info(&mut self) -> (LineNumber, IsStartOfLine) {
180 let current_range = self.current_node.range();
181 if let Some((range, ln, isol)) = self.before_comments_start_info_stack.peek() {
182 if *range == current_range {
183 return (*ln, *isol);
184 }
185 }
186
187 let new_ln = LineNumber::new("beforeComments");
188 let new_isol = IsStartOfLine::new("beforeComments");
189 self.before_comments_start_info_stack.push((current_range, new_ln, new_isol));
190 (new_ln, new_isol)
191 }
192
193 pub fn take_current_before_comments_start_info(&mut self) -> Option<(LineNumber, IsStartOfLine)> {
194 let mut had_range = false;
195 if let Some((range, _, _)) = self.before_comments_start_info_stack.peek() {
196 if *range == self.current_node.range() {
197 had_range = true;
198 }
199 }
200
201 if had_range {
202 let (_, ln, isol) = self.before_comments_start_info_stack.pop();
203 Some((ln, isol))
204 } else {
205 None
206 }
207 }
208
209 pub fn use_consistent_quote_props(&self) -> Option<bool> {
210 self.consistent_quote_props_stack.peek().copied()
211 }
212
213 pub fn with_maybe_consistent_props<TState, TReturn>(
214 &mut self,
215 state: TState,
216 use_consistent_quotes: impl FnOnce(&TState) -> bool,
217 action: impl FnOnce(&mut Self, TState) -> TReturn,
218 ) -> TReturn {
219 if self.config.quote_props == QuoteProps::Consistent {
220 self.consistent_quote_props_stack.push((use_consistent_quotes)(&state));
221 let result = action(self, state);
222 self.consistent_quote_props_stack.pop();
223 result
224 } else {
225 action(self, state)
226 }
227 }
228
229 #[cfg(debug_assertions)]
231 pub fn assert_end_of_file_state(&self) {
232 if self.before_comments_start_info_stack.iter().next().is_some() {
233 panic!("Debug panic! There were infos in the before comments start info stack.");
234 }
235 }
236
237 #[cfg(debug_assertions)]
238 pub fn assert_text(&self, range: SourceRange, expected_text: &str) {
239 let actual_text = range.text_fast(self.program);
240 if actual_text != expected_text {
241 panic!("Debug Panic Expected text `{expected_text}`, but found `{actual_text}`")
242 }
243 }
244}