1use std::{future::Future, sync::Arc};
2
3use anyhow::{Context, Result};
4use bytes_str::BytesStr;
5use rustc_hash::{FxHashMap, FxHashSet};
6use swc_core::{
7 atoms::Atom,
8 base::SwcComments,
9 common::{
10 BytePos, FileName, GLOBALS, Globals, LineCol, Mark, SyntaxContext,
11 errors::{HANDLER, Handler},
12 input::StringInput,
13 source_map::{Files, SourceMapGenConfig, build_source_map},
14 },
15 ecma::{
16 ast::{
17 EsVersion, Id, Ident, IdentName, ObjectPatProp, Pat, Program, TsModuleDecl,
18 TsModuleName, VarDecl,
19 },
20 lints::{self, config::LintConfig, rules::LintParams},
21 parser::{EsSyntax, Parser, Syntax, TsSyntax, lexer::Lexer},
22 transforms::{
23 base::{
24 helpers::{HELPERS, Helpers},
25 resolver,
26 },
27 proposal::explicit_resource_management::explicit_resource_management,
28 },
29 visit::{Visit, VisitMutWith, VisitWith, noop_visit_type},
30 },
31};
32use tracing::{Instrument, instrument};
33use turbo_rcstr::{RcStr, rcstr};
34use turbo_tasks::{PrettyPrintError, ResolvedVc, ValueToString, Vc, turbofmt, util::WrapFuture};
35use turbo_tasks_fs::{FileContent, FileSystemPath, rope::Rope};
36use turbo_tasks_hash::hash_xxh3_hash64;
37use turbopack_core::{
38 SOURCE_URL_PROTOCOL,
39 asset::{Asset, AssetContent},
40 issue::{
41 Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
42 OptionStyledString, StyledString,
43 },
44 source::Source,
45 source_map::utils::add_default_ignore_list,
46};
47use turbopack_swc_utils::emitter::IssueEmitter;
48
49use super::EcmascriptModuleAssetType;
50use crate::{
51 EcmascriptInputTransform,
52 analyzer::graph::EvalContext,
53 magic_identifier,
54 swc_comments::ImmutableComments,
55 transform::{EcmascriptInputTransforms, TransformContext},
56};
57
58pub struct IdentCollector {
62 names_vec: Vec<(BytePos, Atom)>,
63 class_stack: Vec<Atom>,
65}
66
67impl IdentCollector {
68 pub fn into_map(self) -> FxHashMap<BytePos, Atom> {
70 FxHashMap::from_iter(self.names_vec)
71 }
72}
73impl Default for IdentCollector {
74 fn default() -> Self {
75 Self {
76 names_vec: Vec::with_capacity(128),
77 class_stack: Vec::new(),
78 }
79 }
80}
81
82fn unmangle_atom(name: &Atom) -> Atom {
84 magic_identifier::unmangle(name)
85 .map(Atom::from)
86 .unwrap_or_else(|| name.clone())
87}
88
89impl Visit for IdentCollector {
90 noop_visit_type!();
91
92 fn visit_ident(&mut self, ident: &Ident) {
93 if !ident.span.lo.is_dummy() {
95 self.names_vec
97 .push((ident.span.lo, unmangle_atom(&ident.sym)));
98 }
99 }
100
101 fn visit_ident_name(&mut self, ident: &IdentName) {
102 if ident.span.lo.is_dummy() {
103 return;
104 }
105
106 let mut sym = &ident.sym;
108 if ident.sym == "constructor" {
109 if let Some(class_name) = self.class_stack.last() {
110 sym = class_name;
111 } else {
112 return;
114 }
115 }
116
117 self.names_vec.push((ident.span.lo, unmangle_atom(sym)));
118 }
119
120 fn visit_class_decl(&mut self, decl: &swc_core::ecma::ast::ClassDecl) {
121 self.class_stack.push(decl.ident.sym.clone());
123
124 self.visit_ident(&decl.ident);
126 self.visit_class(&decl.class);
127
128 self.class_stack.pop();
130 }
131
132 fn visit_class_expr(&mut self, expr: &swc_core::ecma::ast::ClassExpr) {
133 if let Some(ref ident) = expr.ident {
135 self.class_stack.push(ident.sym.clone());
136 self.visit_ident(ident);
137 }
138
139 self.visit_class(&expr.class);
141
142 if expr.ident.is_some() {
144 self.class_stack.pop();
145 }
146 }
147}
148
149#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
150#[allow(clippy::large_enum_variant)]
151pub enum ParseResult {
152 Ok {
154 #[turbo_tasks(debug_ignore, trace_ignore)]
155 program: Program,
156 #[turbo_tasks(debug_ignore, trace_ignore)]
157 comments: Arc<ImmutableComments>,
158 #[turbo_tasks(debug_ignore, trace_ignore)]
159 eval_context: EvalContext,
160 #[turbo_tasks(debug_ignore, trace_ignore)]
161 globals: Arc<Globals>,
162 #[turbo_tasks(debug_ignore, trace_ignore)]
163 source_map: Arc<swc_core::common::SourceMap>,
164 source_mapping_url: Option<RcStr>,
165 },
166 Unparsable {
167 messages: Option<Vec<RcStr>>,
168 },
169 NotFound,
170}
171
172#[instrument(level = "info", name = "generate source map", skip_all)]
175pub fn generate_js_source_map<'a>(
176 files_map: &impl Files,
177 mappings: Vec<(BytePos, LineCol)>,
178 original_source_maps: impl IntoIterator<Item = &'a Rope>,
179 original_source_maps_complete: bool,
180 inline_sources_content: bool,
181 names: FxHashMap<BytePos, Atom>,
182) -> Result<Rope> {
183 let original_source_maps = original_source_maps
184 .into_iter()
185 .map(|map| map.to_bytes())
186 .collect::<Vec<_>>();
187 let original_source_maps = original_source_maps
188 .iter()
189 .map(|map| Ok(swc_sourcemap::lazy::decode(map)?.into_source_map()?))
190 .collect::<Result<Vec<_>>>()?;
191
192 let fast_path_single_original_source_map =
193 original_source_maps.len() == 1 && original_source_maps_complete;
194
195 let mut new_mappings = build_source_map(
196 files_map,
197 &mappings,
198 None,
199 &InlineSourcesContentConfig {
200 inline_sources_content: inline_sources_content && !fast_path_single_original_source_map,
207 names,
208 },
209 );
210
211 if original_source_maps.is_empty() {
212 add_default_ignore_list(&mut new_mappings);
216
217 let mut result = vec![];
218 new_mappings.to_writer(&mut result)?;
219 Ok(Rope::from(result))
220 } else if fast_path_single_original_source_map {
221 let mut map = original_source_maps.into_iter().next().unwrap();
222 map.adjust_mappings(new_mappings);
224
225 let map = map.into_raw_sourcemap();
228 let result = serde_json::to_vec(&map)?;
229 Ok(Rope::from(result))
230 } else {
231 let mut map = new_mappings.adjust_mappings_from_multiple(original_source_maps);
232
233 add_default_ignore_list(&mut map);
234
235 let mut result = vec![];
236 map.to_writer(&mut result)?;
237 Ok(Rope::from(result))
238 }
239}
240
241pub struct InlineSourcesContentConfig {
245 inline_sources_content: bool,
246 names: FxHashMap<BytePos, Atom>,
247}
248
249impl SourceMapGenConfig for InlineSourcesContentConfig {
250 fn file_name_to_source(&self, f: &FileName) -> String {
251 match f {
252 FileName::Custom(s) => {
253 format!("{SOURCE_URL_PROTOCOL}///{s}")
254 }
255 _ => f.to_string(),
256 }
257 }
258
259 fn inline_sources_content(&self, _f: &FileName) -> bool {
260 self.inline_sources_content
261 }
262
263 fn name_for_bytepos(&self, pos: BytePos) -> Option<&str> {
264 self.names.get(&pos).map(|v| &**v)
265 }
266}
267
268#[turbo_tasks::function]
269pub async fn parse(
270 source: ResolvedVc<Box<dyn Source>>,
271 ty: EcmascriptModuleAssetType,
272 transforms: ResolvedVc<EcmascriptInputTransforms>,
273 is_external_tracing: bool,
274 inline_helpers: bool,
275) -> Result<Vc<ParseResult>> {
276 let span = tracing::info_span!(
277 "parse ecmascript",
278 name = display(source.ident().to_string().await?),
279 ty = display(&ty)
280 );
281
282 match parse_internal(source, ty, transforms, is_external_tracing, inline_helpers)
283 .instrument(span)
284 .await
285 {
286 Ok(result) => Ok(result),
287 Err(error) => Err(error.context(turbofmt!("failed to parse {}", source.ident()).await?)),
289 }
290}
291
292async fn parse_internal(
293 source: ResolvedVc<Box<dyn Source>>,
294 ty: EcmascriptModuleAssetType,
295 transforms: ResolvedVc<EcmascriptInputTransforms>,
296 loose_errors: bool,
297 inline_helpers: bool,
298) -> Result<Vc<ParseResult>> {
299 let content = source.content();
300 let fs_path = source.ident().path().owned().await?;
301 let ident = &*source.ident().to_string().await?;
302 let file_path_hash = hash_xxh3_hash64(&*source.ident().to_string().await?) as u128;
303 let content = match content.await {
304 Ok(content) => content,
305 Err(error) => {
306 let error: RcStr = PrettyPrintError(&error).to_string().into();
307 ReadSourceIssue {
308 source: IssueSource::from_source_only(source),
309 error: error.clone(),
310 severity: if loose_errors {
311 IssueSeverity::Warning
312 } else {
313 IssueSeverity::Error
314 },
315 }
316 .resolved_cell()
317 .emit();
318
319 return Ok(ParseResult::Unparsable {
320 messages: Some(vec![error]),
321 }
322 .cell());
323 }
324 };
325 Ok(match &*content {
326 AssetContent::File(file) => match &*file.await? {
327 FileContent::NotFound => ParseResult::NotFound.cell(),
328 FileContent::Content(file) => {
329 match BytesStr::from_utf8(file.content().clone().into_bytes()) {
330 Ok(string) => {
331 let transforms = &*transforms.await?;
332 match parse_file_content(
333 string,
334 &fs_path,
335 ident,
336 source.ident().await?.query.clone(),
337 file_path_hash,
338 source,
339 ty,
340 transforms,
341 loose_errors,
342 inline_helpers,
343 )
344 .await
345 {
346 Ok(result) => result,
347 Err(e) => {
348 return Err(e).context(
350 turbofmt!(
351 "Transforming and/or parsing of {} failed",
352 source.ident()
353 )
354 .await?,
355 );
356 }
357 }
358 }
359 Err(error) => {
360 let error: RcStr = PrettyPrintError(
361 &anyhow::anyhow!(error).context("failed to convert rope into string"),
362 )
363 .to_string()
364 .into();
365 ReadSourceIssue {
366 source: IssueSource::from_source_only(source),
371 error: error.clone(),
372 severity: if loose_errors {
373 IssueSeverity::Warning
374 } else {
375 IssueSeverity::Error
376 },
377 }
378 .resolved_cell()
379 .emit();
380 ParseResult::Unparsable {
381 messages: Some(vec![error]),
382 }
383 .cell()
384 }
385 }
386 }
387 },
388 AssetContent::Redirect { .. } => ParseResult::Unparsable { messages: None }.cell(),
389 })
390}
391
392async fn parse_file_content(
393 string: BytesStr,
394 fs_path: &FileSystemPath,
395 ident: &str,
396 query: RcStr,
397 file_path_hash: u128,
398 source: ResolvedVc<Box<dyn Source>>,
399 ty: EcmascriptModuleAssetType,
400 transforms: &[EcmascriptInputTransform],
401 loose_errors: bool,
402 inline_helpers: bool,
403) -> Result<Vc<ParseResult>> {
404 let source_map: Arc<swc_core::common::SourceMap> = Default::default();
405 let (emitter, collector) = IssueEmitter::new(
406 source,
407 source_map.clone(),
408 Some(rcstr!("Ecmascript file had an error")),
409 );
410 let handler = Handler::with_emitter(true, false, Box::new(emitter));
411
412 let (emitter, collector_parse) = IssueEmitter::new(
413 source,
414 source_map.clone(),
415 Some(rcstr!("Parsing ecmascript source code failed")),
416 );
417 let parser_handler = Handler::with_emitter(true, false, Box::new(emitter));
418 let globals = Arc::new(Globals::new());
419 let globals_ref = &globals;
420
421 let mut result = WrapFuture::new(
422 async {
423 let file_name = FileName::Custom(ident.to_string());
424 let fm = source_map.new_source_file(file_name.clone().into(), string);
425
426 let comments = SwcComments::default();
427
428 let mut parsed_program = {
429 let lexer = Lexer::new(
430 match ty {
431 EcmascriptModuleAssetType::Ecmascript
432 | EcmascriptModuleAssetType::EcmascriptExtensionless => {
433 Syntax::Es(EsSyntax {
434 jsx: true,
435 fn_bind: true,
436 decorators: true,
437 decorators_before_export: true,
438 export_default_from: true,
439 import_attributes: true,
440 allow_super_outside_method: true,
441 allow_return_outside_function: true,
442 auto_accessors: true,
443 explicit_resource_management: true,
444 })
445 }
446 EcmascriptModuleAssetType::Typescript { tsx, .. } => {
447 Syntax::Typescript(TsSyntax {
448 decorators: true,
449 dts: false,
450 tsx,
451 ..Default::default()
452 })
453 }
454 EcmascriptModuleAssetType::TypescriptDeclaration => {
455 Syntax::Typescript(TsSyntax {
456 decorators: true,
457 dts: true,
458 tsx: false,
459 ..Default::default()
460 })
461 }
462 },
463 EsVersion::latest(),
464 StringInput::from(&*fm),
465 Some(&comments),
466 );
467
468 let mut parser = Parser::new_from(lexer);
469 let span = tracing::trace_span!("swc_parse").entered();
470 let program_result = parser.parse_program();
471 drop(span);
472
473 let mut has_errors = vec![];
474 for e in parser.take_errors() {
475 let mut e = e.into_diagnostic(&parser_handler);
476 has_errors.extend(e.message.iter().map(|m| m.0.as_str().into()));
477 e.emit();
478 }
479
480 if !has_errors.is_empty() {
481 return Ok(ParseResult::Unparsable {
482 messages: Some(has_errors),
483 });
484 }
485
486 match program_result {
487 Ok(parsed_program) => parsed_program,
488 Err(e) => {
489 let mut e = e.into_diagnostic(&parser_handler);
490 let messages = e.message.iter().map(|m| m.0.as_str().into()).collect();
491
492 e.emit();
493
494 return Ok(ParseResult::Unparsable {
495 messages: Some(messages),
496 });
497 }
498 }
499 };
500
501 let unresolved_mark = Mark::new();
502 let top_level_mark = Mark::new();
503
504 let is_typescript = matches!(
505 ty,
506 EcmascriptModuleAssetType::Typescript { .. }
507 | EcmascriptModuleAssetType::TypescriptDeclaration
508 );
509
510 let helpers = Helpers::new(!inline_helpers);
511 let span = tracing::trace_span!("swc_resolver").entered();
512
513 parsed_program.visit_mut_with(&mut resolver(
514 unresolved_mark,
515 top_level_mark,
516 is_typescript,
517 ));
518 drop(span);
519
520 let span = tracing::trace_span!("swc_lint").entered();
521
522 let lint_config = LintConfig::default();
523 let rules = lints::rules::all(LintParams {
524 program: &parsed_program,
525 lint_config: &lint_config,
526 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
527 top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
528 es_version: EsVersion::latest(),
529 source_map: source_map.clone(),
530 });
531
532 parsed_program.mutate(lints::rules::lint_pass(rules));
533 drop(span);
534
535 HELPERS.set(&helpers, || {
536 parsed_program.mutate(explicit_resource_management());
537 });
538
539 let var_with_ts_declare = if is_typescript {
540 VarDeclWithTsDeclareCollector::collect(&parsed_program)
541 } else {
542 FxHashSet::default()
543 };
544
545 let mut helpers = helpers.data();
546 let transform_context = TransformContext {
547 comments: &comments,
548 source_map: &source_map,
549 top_level_mark,
550 unresolved_mark,
551 file_path_str: &fs_path.path,
552 file_name_str: fs_path.file_name(),
553 file_name_hash: file_path_hash,
554 query_str: query,
555 file_path: fs_path.clone(),
556 source,
557 };
558 let span = tracing::trace_span!("transforms");
559 async {
560 for transform in transforms.iter() {
561 helpers = transform
562 .apply(&mut parsed_program, &transform_context, helpers)
563 .await?;
564 }
565 anyhow::Ok(())
566 }
567 .instrument(span)
568 .await?;
569
570 if parser_handler.has_errors() {
571 let messages = if let Some(error) = collector_parse.last_emitted_issue() {
572 if let StyledString::Text(xx) = &*error.await?.message.await? {
574 Some(vec![xx.clone()])
575 } else {
576 None
577 }
578 } else {
579 None
580 };
581 let messages = Some(messages.unwrap_or_else(|| vec![fm.src.clone().into()]));
582 return Ok(ParseResult::Unparsable { messages });
583 }
584
585 let helpers = Helpers::from_data(helpers);
586 HELPERS.set(&helpers, || {
587 parsed_program.mutate(swc_core::ecma::transforms::base::helpers::inject_helpers(
588 unresolved_mark,
589 ));
590 });
591
592 let eval_context = EvalContext::new(
593 Some(&parsed_program),
594 unresolved_mark,
595 top_level_mark,
596 Arc::new(var_with_ts_declare),
597 Some(&comments),
598 Some(source),
599 );
600
601 let (comments, source_mapping_url) =
602 ImmutableComments::new_with_source_mapping_url(comments);
603
604 Ok::<ParseResult, anyhow::Error>(ParseResult::Ok {
605 program: parsed_program,
606 comments: Arc::new(comments),
607 eval_context,
608 globals: Arc::new(Globals::new()),
611 source_map,
612 source_mapping_url: source_mapping_url.map(|s| s.into()),
613 })
614 },
615 |f, cx| GLOBALS.set(globals_ref, || HANDLER.set(&handler, || f.poll(cx))),
616 )
617 .await?;
618 if let ParseResult::Ok {
619 globals: ref mut g, ..
620 } = result
621 {
622 *g = globals;
624 }
625 collector.emit(loose_errors).await?;
626 collector_parse.emit(loose_errors).await?;
627 Ok(result.cell())
628}
629
630#[turbo_tasks::value]
631struct ReadSourceIssue {
632 source: IssueSource,
633 error: RcStr,
634 severity: IssueSeverity,
635}
636
637#[turbo_tasks::value_impl]
638impl Issue for ReadSourceIssue {
639 #[turbo_tasks::function]
640 fn file_path(&self) -> Vc<FileSystemPath> {
641 self.source.file_path()
642 }
643
644 #[turbo_tasks::function]
645 fn title(&self) -> Vc<StyledString> {
646 StyledString::Text(rcstr!("Reading source code for parsing failed")).cell()
647 }
648
649 #[turbo_tasks::function]
650 fn description(&self) -> Vc<OptionStyledString> {
651 Vc::cell(Some(
652 StyledString::Text(
653 format!(
654 "An unexpected error happened while trying to read the source code to parse: \
655 {}",
656 self.error
657 )
658 .into(),
659 )
660 .resolved_cell(),
661 ))
662 }
663
664 fn severity(&self) -> IssueSeverity {
665 self.severity
666 }
667
668 #[turbo_tasks::function]
669 fn stage(&self) -> Vc<IssueStage> {
670 IssueStage::Load.cell()
671 }
672
673 #[turbo_tasks::function]
674 fn source(&self) -> Vc<OptionIssueSource> {
675 Vc::cell(Some(self.source))
676 }
677}
678
679struct VarDeclWithTsDeclareCollector {
680 id_with_no_ts_declare: FxHashSet<Id>,
681 id_with_ts_declare: FxHashSet<Id>,
682}
683
684impl VarDeclWithTsDeclareCollector {
685 fn collect<N: VisitWith<VarDeclWithTsDeclareCollector>>(n: &N) -> FxHashSet<Id> {
686 let mut collector = VarDeclWithTsDeclareCollector {
687 id_with_no_ts_declare: Default::default(),
688 id_with_ts_declare: Default::default(),
689 };
690 n.visit_with(&mut collector);
691 collector
692 .id_with_ts_declare
693 .retain(|id| !collector.id_with_no_ts_declare.contains(id));
694 collector.id_with_ts_declare
695 }
696
697 fn handle_pat(&mut self, pat: &Pat, declare: bool) {
698 match pat {
699 Pat::Ident(binding_ident) => {
700 if declare {
701 self.id_with_ts_declare.insert(binding_ident.to_id());
702 } else {
703 self.id_with_no_ts_declare.insert(binding_ident.to_id());
704 }
705 }
706 Pat::Array(array_pat) => {
707 for pat in array_pat.elems.iter().flatten() {
708 self.handle_pat(pat, declare);
709 }
710 }
711 Pat::Object(object_pat) => {
712 for prop in object_pat.props.iter() {
713 match prop {
714 ObjectPatProp::KeyValue(key_value_pat_prop) => {
715 self.handle_pat(&key_value_pat_prop.value, declare);
716 }
717 ObjectPatProp::Assign(assign_pat_prop) => {
718 if declare {
719 self.id_with_ts_declare.insert(assign_pat_prop.key.to_id());
720 } else {
721 self.id_with_no_ts_declare
722 .insert(assign_pat_prop.key.to_id());
723 }
724 }
725 _ => {}
726 }
727 }
728 }
729 _ => {}
730 }
731 }
732}
733
734impl Visit for VarDeclWithTsDeclareCollector {
735 noop_visit_type!();
736
737 fn visit_var_decl(&mut self, node: &VarDecl) {
738 for decl in node.decls.iter() {
739 self.handle_pat(&decl.name, node.declare);
740 }
741 }
742
743 fn visit_ts_module_decl(&mut self, node: &TsModuleDecl) {
744 if node.declare
745 && let TsModuleName::Ident(id) = &node.id
746 {
747 self.id_with_ts_declare.insert(id.to_id());
748 }
749 }
750}
751
752#[cfg(test)]
753mod tests {
754 use swc_core::{
755 common::{FileName, GLOBALS, SourceMap, sync::Lrc},
756 ecma::parser::{Parser, Syntax, TsSyntax, lexer::Lexer},
757 };
758
759 use super::VarDeclWithTsDeclareCollector;
760
761 fn parse_and_collect(code: &str) -> Vec<String> {
762 GLOBALS.set(&Default::default(), || {
763 let cm: Lrc<SourceMap> = Default::default();
764 let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());
765
766 let lexer = Lexer::new(
767 Syntax::Typescript(TsSyntax {
768 tsx: false,
769 decorators: true,
770 ..Default::default()
771 }),
772 Default::default(),
773 (&*fm).into(),
774 None,
775 );
776
777 let mut parser = Parser::new_from(lexer);
778 let module = parser.parse_module().expect("Failed to parse");
779
780 let ids = VarDeclWithTsDeclareCollector::collect(&module);
781 let mut result: Vec<_> = ids.iter().map(|id| id.0.to_string()).collect();
782 result.sort();
783 result
784 })
785 }
786
787 #[test]
788 fn test_collect_declare_const() {
789 let ids = parse_and_collect("declare const Foo: number;");
790 assert_eq!(ids, vec!["Foo"]);
791 }
792
793 #[test]
794 fn test_collect_declare_global() {
795 let ids = parse_and_collect("declare global {}");
796 assert_eq!(ids, vec!["global"]);
797 }
798
799 #[test]
800 fn test_collect_declare_global_with_content() {
801 let ids = parse_and_collect(
802 r#"
803 declare global {
804 interface Window {
805 foo: string;
806 }
807 }
808 "#,
809 );
810 assert_eq!(ids, vec!["global"]);
811 }
812
813 #[test]
814 fn test_collect_multiple_declares() {
815 let ids = parse_and_collect(
816 r#"
817 declare const Foo: number;
818 declare global {}
819 declare const Bar: string;
820 "#,
821 );
822 assert_eq!(ids, vec!["Bar", "Foo", "global"]);
823 }
824
825 #[test]
826 fn test_no_collect_non_declare() {
827 let ids = parse_and_collect("const Foo = 1;");
828 assert!(ids.is_empty());
829 }
830
831 #[test]
832 fn test_collect_declare_namespace() {
833 let ids = parse_and_collect("declare namespace Foo {}");
835 assert_eq!(ids, vec!["Foo"]);
836 }
837}