1use std::{future::Future, sync::Arc};
2
3use anyhow::{Context, Result, anyhow};
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, 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(format!(
288 "failed to parse {}",
289 source.ident().to_string().await?
290 ))),
291 }
292}
293
294async fn parse_internal(
295 source: ResolvedVc<Box<dyn Source>>,
296 ty: EcmascriptModuleAssetType,
297 transforms: ResolvedVc<EcmascriptInputTransforms>,
298 loose_errors: bool,
299 inline_helpers: bool,
300) -> Result<Vc<ParseResult>> {
301 let content = source.content();
302 let fs_path = source.ident().path().owned().await?;
303 let ident = &*source.ident().to_string().await?;
304 let file_path_hash = hash_xxh3_hash64(&*source.ident().to_string().await?) as u128;
305 let content = match content.await {
306 Ok(content) => content,
307 Err(error) => {
308 let error: RcStr = PrettyPrintError(&error).to_string().into();
309 ReadSourceIssue {
310 source: IssueSource::from_source_only(source),
311 error: error.clone(),
312 severity: if loose_errors {
313 IssueSeverity::Warning
314 } else {
315 IssueSeverity::Error
316 },
317 }
318 .resolved_cell()
319 .emit();
320
321 return Ok(ParseResult::Unparsable {
322 messages: Some(vec![error]),
323 }
324 .cell());
325 }
326 };
327 Ok(match &*content {
328 AssetContent::File(file) => match &*file.await? {
329 FileContent::NotFound => ParseResult::NotFound.cell(),
330 FileContent::Content(file) => {
331 match BytesStr::from_utf8(file.content().clone().into_bytes()) {
332 Ok(string) => {
333 let transforms = &*transforms.await?;
334 match parse_file_content(
335 string,
336 &fs_path,
337 ident,
338 source.ident().await?.query.clone(),
339 file_path_hash,
340 source,
341 ty,
342 transforms,
343 loose_errors,
344 inline_helpers,
345 )
346 .await
347 {
348 Ok(result) => result,
349 Err(e) => {
350 return Err(e).context(anyhow!(
351 "Transforming and/or parsing of {} failed",
352 source.ident().to_string().await?
353 ));
354 }
355 }
356 }
357 Err(error) => {
358 let error: RcStr = PrettyPrintError(
359 &anyhow::anyhow!(error).context("failed to convert rope into string"),
360 )
361 .to_string()
362 .into();
363 ReadSourceIssue {
364 source: IssueSource::from_source_only(source),
369 error: error.clone(),
370 severity: if loose_errors {
371 IssueSeverity::Warning
372 } else {
373 IssueSeverity::Error
374 },
375 }
376 .resolved_cell()
377 .emit();
378 ParseResult::Unparsable {
379 messages: Some(vec![error]),
380 }
381 .cell()
382 }
383 }
384 }
385 },
386 AssetContent::Redirect { .. } => ParseResult::Unparsable { messages: None }.cell(),
387 })
388}
389
390async fn parse_file_content(
391 string: BytesStr,
392 fs_path: &FileSystemPath,
393 ident: &str,
394 query: RcStr,
395 file_path_hash: u128,
396 source: ResolvedVc<Box<dyn Source>>,
397 ty: EcmascriptModuleAssetType,
398 transforms: &[EcmascriptInputTransform],
399 loose_errors: bool,
400 inline_helpers: bool,
401) -> Result<Vc<ParseResult>> {
402 let source_map: Arc<swc_core::common::SourceMap> = Default::default();
403 let (emitter, collector) = IssueEmitter::new(
404 source,
405 source_map.clone(),
406 Some(rcstr!("Ecmascript file had an error")),
407 );
408 let handler = Handler::with_emitter(true, false, Box::new(emitter));
409
410 let (emitter, collector_parse) = IssueEmitter::new(
411 source,
412 source_map.clone(),
413 Some(rcstr!("Parsing ecmascript source code failed")),
414 );
415 let parser_handler = Handler::with_emitter(true, false, Box::new(emitter));
416 let globals = Arc::new(Globals::new());
417 let globals_ref = &globals;
418
419 let mut result = WrapFuture::new(
420 async {
421 let file_name = FileName::Custom(ident.to_string());
422 let fm = source_map.new_source_file(file_name.clone().into(), string);
423
424 let comments = SwcComments::default();
425
426 let mut parsed_program = {
427 let lexer = Lexer::new(
428 match ty {
429 EcmascriptModuleAssetType::Ecmascript
430 | EcmascriptModuleAssetType::EcmascriptExtensionless => {
431 Syntax::Es(EsSyntax {
432 jsx: true,
433 fn_bind: true,
434 decorators: true,
435 decorators_before_export: true,
436 export_default_from: true,
437 import_attributes: true,
438 allow_super_outside_method: true,
439 allow_return_outside_function: true,
440 auto_accessors: true,
441 explicit_resource_management: true,
442 })
443 }
444 EcmascriptModuleAssetType::Typescript { tsx, .. } => {
445 Syntax::Typescript(TsSyntax {
446 decorators: true,
447 dts: false,
448 tsx,
449 ..Default::default()
450 })
451 }
452 EcmascriptModuleAssetType::TypescriptDeclaration => {
453 Syntax::Typescript(TsSyntax {
454 decorators: true,
455 dts: true,
456 tsx: false,
457 ..Default::default()
458 })
459 }
460 },
461 EsVersion::latest(),
462 StringInput::from(&*fm),
463 Some(&comments),
464 );
465
466 let mut parser = Parser::new_from(lexer);
467 let span = tracing::trace_span!("swc_parse").entered();
468 let program_result = parser.parse_program();
469 drop(span);
470
471 let mut has_errors = vec![];
472 for e in parser.take_errors() {
473 let mut e = e.into_diagnostic(&parser_handler);
474 has_errors.extend(e.message.iter().map(|m| m.0.as_str().into()));
475 e.emit();
476 }
477
478 if !has_errors.is_empty() {
479 return Ok(ParseResult::Unparsable {
480 messages: Some(has_errors),
481 });
482 }
483
484 match program_result {
485 Ok(parsed_program) => parsed_program,
486 Err(e) => {
487 let mut e = e.into_diagnostic(&parser_handler);
488 let messages = e.message.iter().map(|m| m.0.as_str().into()).collect();
489
490 e.emit();
491
492 return Ok(ParseResult::Unparsable {
493 messages: Some(messages),
494 });
495 }
496 }
497 };
498
499 let unresolved_mark = Mark::new();
500 let top_level_mark = Mark::new();
501
502 let is_typescript = matches!(
503 ty,
504 EcmascriptModuleAssetType::Typescript { .. }
505 | EcmascriptModuleAssetType::TypescriptDeclaration
506 );
507
508 let helpers = Helpers::new(!inline_helpers);
509 let span = tracing::trace_span!("swc_resolver").entered();
510
511 parsed_program.visit_mut_with(&mut resolver(
512 unresolved_mark,
513 top_level_mark,
514 is_typescript,
515 ));
516 drop(span);
517
518 let span = tracing::trace_span!("swc_lint").entered();
519
520 let lint_config = LintConfig::default();
521 let rules = lints::rules::all(LintParams {
522 program: &parsed_program,
523 lint_config: &lint_config,
524 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
525 top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
526 es_version: EsVersion::latest(),
527 source_map: source_map.clone(),
528 });
529
530 parsed_program.mutate(lints::rules::lint_pass(rules));
531 drop(span);
532
533 HELPERS.set(&helpers, || {
534 parsed_program.mutate(explicit_resource_management());
535 });
536
537 let var_with_ts_declare = if is_typescript {
538 VarDeclWithTsDeclareCollector::collect(&parsed_program)
539 } else {
540 FxHashSet::default()
541 };
542
543 let mut helpers = helpers.data();
544 let transform_context = TransformContext {
545 comments: &comments,
546 source_map: &source_map,
547 top_level_mark,
548 unresolved_mark,
549 file_path_str: &fs_path.path,
550 file_name_str: fs_path.file_name(),
551 file_name_hash: file_path_hash,
552 query_str: query,
553 file_path: fs_path.clone(),
554 source,
555 };
556 let span = tracing::trace_span!("transforms");
557 async {
558 for transform in transforms.iter() {
559 helpers = transform
560 .apply(&mut parsed_program, &transform_context, helpers)
561 .await?;
562 }
563 anyhow::Ok(())
564 }
565 .instrument(span)
566 .await?;
567
568 if parser_handler.has_errors() {
569 let messages = if let Some(error) = collector_parse.last_emitted_issue() {
570 if let StyledString::Text(xx) = &*error.await?.message.await? {
572 Some(vec![xx.clone()])
573 } else {
574 None
575 }
576 } else {
577 None
578 };
579 let messages = Some(messages.unwrap_or_else(|| vec![fm.src.clone().into()]));
580 return Ok(ParseResult::Unparsable { messages });
581 }
582
583 let helpers = Helpers::from_data(helpers);
584 HELPERS.set(&helpers, || {
585 parsed_program.mutate(swc_core::ecma::transforms::base::helpers::inject_helpers(
586 unresolved_mark,
587 ));
588 });
589
590 let eval_context = EvalContext::new(
591 Some(&parsed_program),
592 unresolved_mark,
593 top_level_mark,
594 Arc::new(var_with_ts_declare),
595 Some(&comments),
596 Some(source),
597 );
598
599 let (comments, source_mapping_url) =
600 ImmutableComments::new_with_source_mapping_url(comments);
601
602 Ok::<ParseResult, anyhow::Error>(ParseResult::Ok {
603 program: parsed_program,
604 comments: Arc::new(comments),
605 eval_context,
606 globals: Arc::new(Globals::new()),
609 source_map,
610 source_mapping_url: source_mapping_url.map(|s| s.into()),
611 })
612 },
613 |f, cx| GLOBALS.set(globals_ref, || HANDLER.set(&handler, || f.poll(cx))),
614 )
615 .await?;
616 if let ParseResult::Ok {
617 globals: ref mut g, ..
618 } = result
619 {
620 *g = globals;
622 }
623 collector.emit(loose_errors).await?;
624 collector_parse.emit(loose_errors).await?;
625 Ok(result.cell())
626}
627
628#[turbo_tasks::value]
629struct ReadSourceIssue {
630 source: IssueSource,
631 error: RcStr,
632 severity: IssueSeverity,
633}
634
635#[turbo_tasks::value_impl]
636impl Issue for ReadSourceIssue {
637 #[turbo_tasks::function]
638 fn file_path(&self) -> Vc<FileSystemPath> {
639 self.source.file_path()
640 }
641
642 #[turbo_tasks::function]
643 fn title(&self) -> Vc<StyledString> {
644 StyledString::Text(rcstr!("Reading source code for parsing failed")).cell()
645 }
646
647 #[turbo_tasks::function]
648 fn description(&self) -> Vc<OptionStyledString> {
649 Vc::cell(Some(
650 StyledString::Text(
651 format!(
652 "An unexpected error happened while trying to read the source code to parse: \
653 {}",
654 self.error
655 )
656 .into(),
657 )
658 .resolved_cell(),
659 ))
660 }
661
662 fn severity(&self) -> IssueSeverity {
663 self.severity
664 }
665
666 #[turbo_tasks::function]
667 fn stage(&self) -> Vc<IssueStage> {
668 IssueStage::Load.cell()
669 }
670
671 #[turbo_tasks::function]
672 fn source(&self) -> Vc<OptionIssueSource> {
673 Vc::cell(Some(self.source))
674 }
675}
676
677struct VarDeclWithTsDeclareCollector {
678 id_with_no_ts_declare: FxHashSet<Id>,
679 id_with_ts_declare: FxHashSet<Id>,
680}
681
682impl VarDeclWithTsDeclareCollector {
683 fn collect<N: VisitWith<VarDeclWithTsDeclareCollector>>(n: &N) -> FxHashSet<Id> {
684 let mut collector = VarDeclWithTsDeclareCollector {
685 id_with_no_ts_declare: Default::default(),
686 id_with_ts_declare: Default::default(),
687 };
688 n.visit_with(&mut collector);
689 collector
690 .id_with_ts_declare
691 .retain(|id| !collector.id_with_no_ts_declare.contains(id));
692 collector.id_with_ts_declare
693 }
694
695 fn handle_pat(&mut self, pat: &Pat, declare: bool) {
696 match pat {
697 Pat::Ident(binding_ident) => {
698 if declare {
699 self.id_with_ts_declare.insert(binding_ident.to_id());
700 } else {
701 self.id_with_no_ts_declare.insert(binding_ident.to_id());
702 }
703 }
704 Pat::Array(array_pat) => {
705 for pat in array_pat.elems.iter().flatten() {
706 self.handle_pat(pat, declare);
707 }
708 }
709 Pat::Object(object_pat) => {
710 for prop in object_pat.props.iter() {
711 match prop {
712 ObjectPatProp::KeyValue(key_value_pat_prop) => {
713 self.handle_pat(&key_value_pat_prop.value, declare);
714 }
715 ObjectPatProp::Assign(assign_pat_prop) => {
716 if declare {
717 self.id_with_ts_declare.insert(assign_pat_prop.key.to_id());
718 } else {
719 self.id_with_no_ts_declare
720 .insert(assign_pat_prop.key.to_id());
721 }
722 }
723 _ => {}
724 }
725 }
726 }
727 _ => {}
728 }
729 }
730}
731
732impl Visit for VarDeclWithTsDeclareCollector {
733 noop_visit_type!();
734
735 fn visit_var_decl(&mut self, node: &VarDecl) {
736 for decl in node.decls.iter() {
737 self.handle_pat(&decl.name, node.declare);
738 }
739 }
740
741 fn visit_ts_module_decl(&mut self, node: &TsModuleDecl) {
742 if node.declare
743 && let TsModuleName::Ident(id) = &node.id
744 {
745 self.id_with_ts_declare.insert(id.to_id());
746 }
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use swc_core::{
753 common::{FileName, GLOBALS, SourceMap, sync::Lrc},
754 ecma::parser::{Parser, Syntax, TsSyntax, lexer::Lexer},
755 };
756
757 use super::VarDeclWithTsDeclareCollector;
758
759 fn parse_and_collect(code: &str) -> Vec<String> {
760 GLOBALS.set(&Default::default(), || {
761 let cm: Lrc<SourceMap> = Default::default();
762 let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());
763
764 let lexer = Lexer::new(
765 Syntax::Typescript(TsSyntax {
766 tsx: false,
767 decorators: true,
768 ..Default::default()
769 }),
770 Default::default(),
771 (&*fm).into(),
772 None,
773 );
774
775 let mut parser = Parser::new_from(lexer);
776 let module = parser.parse_module().expect("Failed to parse");
777
778 let ids = VarDeclWithTsDeclareCollector::collect(&module);
779 let mut result: Vec<_> = ids.iter().map(|id| id.0.to_string()).collect();
780 result.sort();
781 result
782 })
783 }
784
785 #[test]
786 fn test_collect_declare_const() {
787 let ids = parse_and_collect("declare const Foo: number;");
788 assert_eq!(ids, vec!["Foo"]);
789 }
790
791 #[test]
792 fn test_collect_declare_global() {
793 let ids = parse_and_collect("declare global {}");
794 assert_eq!(ids, vec!["global"]);
795 }
796
797 #[test]
798 fn test_collect_declare_global_with_content() {
799 let ids = parse_and_collect(
800 r#"
801 declare global {
802 interface Window {
803 foo: string;
804 }
805 }
806 "#,
807 );
808 assert_eq!(ids, vec!["global"]);
809 }
810
811 #[test]
812 fn test_collect_multiple_declares() {
813 let ids = parse_and_collect(
814 r#"
815 declare const Foo: number;
816 declare global {}
817 declare const Bar: string;
818 "#,
819 );
820 assert_eq!(ids, vec!["Bar", "Foo", "global"]);
821 }
822
823 #[test]
824 fn test_no_collect_non_declare() {
825 let ids = parse_and_collect("const Foo = 1;");
826 assert!(ids.is_empty());
827 }
828
829 #[test]
830 fn test_collect_declare_namespace() {
831 let ids = parse_and_collect("declare namespace Foo {}");
833 assert_eq!(ids, vec!["Foo"]);
834 }
835}