summaryrefslogtreecommitdiff
path: root/cli/tools/vendor/analyze.rs
blob: 2b00f6bf478adb8a903997035430fdd2abe664ed (plain)
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
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use deno_ast::swc::ast::ExportDefaultDecl;
use deno_ast::swc::ast::ExportSpecifier;
use deno_ast::swc::ast::ModuleExportName;
use deno_ast::swc::ast::NamedExport;
use deno_ast::swc::ast::Program;
use deno_ast::swc::visit::noop_visit_type;
use deno_ast::swc::visit::Visit;
use deno_ast::swc::visit::VisitWith;
use deno_ast::ParsedSource;

/// Gets if the parsed source has a default export.
pub fn has_default_export(source: &ParsedSource) -> bool {
  let mut visitor = DefaultExportFinder {
    has_default_export: false,
  };
  let program = source.program();
  let program: &Program = &program;
  program.visit_with(&mut visitor);
  visitor.has_default_export
}

struct DefaultExportFinder {
  has_default_export: bool,
}

impl Visit for DefaultExportFinder {
  noop_visit_type!();

  fn visit_export_default_decl(&mut self, _: &ExportDefaultDecl) {
    self.has_default_export = true;
  }

  fn visit_named_export(&mut self, named_export: &NamedExport) {
    if named_export
      .specifiers
      .iter()
      .any(export_specifier_has_default)
    {
      self.has_default_export = true;
    }
  }
}

fn export_specifier_has_default(s: &ExportSpecifier) -> bool {
  match s {
    ExportSpecifier::Default(_) => true,
    ExportSpecifier::Namespace(_) => false,
    ExportSpecifier::Named(named) => {
      let export_name = named.exported.as_ref().unwrap_or(&named.orig);

      match export_name {
        ModuleExportName::Str(_) => false,
        ModuleExportName::Ident(ident) => &*ident.sym == "default",
      }
    }
  }
}

#[cfg(test)]
mod test {
  use deno_ast::MediaType;
  use deno_ast::ModuleSpecifier;
  use deno_ast::ParseParams;
  use deno_ast::ParsedSource;
  use deno_ast::SourceTextInfo;

  use super::has_default_export;

  #[test]
  fn has_default_when_export_default_decl() {
    let parsed_source = parse_module("export default class Class {}");
    assert!(has_default_export(&parsed_source));
  }

  #[test]
  fn has_default_when_named_export() {
    let parsed_source = parse_module("export {default} from './test.ts';");
    assert!(has_default_export(&parsed_source));
  }

  #[test]
  fn has_default_when_named_export_alias() {
    let parsed_source =
      parse_module("export {test as default} from './test.ts';");
    assert!(has_default_export(&parsed_source));
  }

  #[test]
  fn not_has_default_when_named_export_not_exported() {
    let parsed_source =
      parse_module("export {default as test} from './test.ts';");
    assert!(!has_default_export(&parsed_source));
  }

  #[test]
  fn not_has_default_when_not() {
    let parsed_source = parse_module("export {test} from './test.ts'; export class Test{} export * from './test';");
    assert!(!has_default_export(&parsed_source));
  }

  fn parse_module(text: &str) -> ParsedSource {
    deno_ast::parse_module(ParseParams {
      specifier: ModuleSpecifier::parse("file:///mod.ts").unwrap(),
      capture_tokens: false,
      maybe_syntax: None,
      media_type: MediaType::TypeScript,
      scope_analysis: false,
      text_info: SourceTextInfo::from_string(text.to_string()),
    })
    .unwrap()
  }
}