summaryrefslogtreecommitdiff
path: root/cli/doc/parser.rs
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-03-28 19:16:57 +0100
committerGitHub <noreply@github.com>2020-03-28 14:16:57 -0400
commit3fac487461abf055165fe0e2bb962573950277b8 (patch)
treec0ba09a2049975f9eb8625365e3ba395e67d8b50 /cli/doc/parser.rs
parentbced52505f32d6cca4f944bb610a8a26767908a8 (diff)
feat: Add "deno doc" subcommand (#4500)
Diffstat (limited to 'cli/doc/parser.rs')
-rw-r--r--cli/doc/parser.rs189
1 files changed, 189 insertions, 0 deletions
diff --git a/cli/doc/parser.rs b/cli/doc/parser.rs
new file mode 100644
index 000000000..bd3a64806
--- /dev/null
+++ b/cli/doc/parser.rs
@@ -0,0 +1,189 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use regex::Regex;
+use std::sync::Arc;
+use std::sync::RwLock;
+use swc_common;
+use swc_common::comments::CommentKind;
+use swc_common::comments::Comments;
+use swc_common::errors::Diagnostic;
+use swc_common::errors::DiagnosticBuilder;
+use swc_common::errors::Emitter;
+use swc_common::errors::Handler;
+use swc_common::errors::HandlerFlags;
+use swc_common::FileName;
+use swc_common::Globals;
+use swc_common::SourceMap;
+use swc_common::Span;
+use swc_ecma_parser::lexer::Lexer;
+use swc_ecma_parser::JscTarget;
+use swc_ecma_parser::Parser;
+use swc_ecma_parser::Session;
+use swc_ecma_parser::SourceFileInput;
+use swc_ecma_parser::Syntax;
+use swc_ecma_parser::TsConfig;
+
+use super::DocNode;
+
+pub type SwcDiagnostics = Vec<Diagnostic>;
+
+#[derive(Clone, Default)]
+pub struct BufferedError(Arc<RwLock<SwcDiagnostics>>);
+
+impl Emitter for BufferedError {
+ fn emit(&mut self, db: &DiagnosticBuilder) {
+ self.0.write().unwrap().push((**db).clone());
+ }
+}
+
+impl From<BufferedError> for Vec<Diagnostic> {
+ fn from(buf: BufferedError) -> Self {
+ let s = buf.0.read().unwrap();
+ s.clone()
+ }
+}
+
+pub struct DocParser {
+ pub buffered_error: BufferedError,
+ pub source_map: Arc<SourceMap>,
+ pub handler: Handler,
+ pub comments: Comments,
+ pub globals: Globals,
+}
+
+impl DocParser {
+ pub fn default() -> Self {
+ let buffered_error = BufferedError::default();
+
+ let handler = Handler::with_emitter_and_flags(
+ Box::new(buffered_error.clone()),
+ HandlerFlags {
+ dont_buffer_diagnostics: true,
+ can_emit_warnings: true,
+ ..Default::default()
+ },
+ );
+
+ DocParser {
+ buffered_error,
+ source_map: Arc::new(SourceMap::default()),
+ handler,
+ comments: Comments::default(),
+ globals: Globals::new(),
+ }
+ }
+
+ pub fn parse(
+ &self,
+ file_name: String,
+ source_code: String,
+ ) -> Result<Vec<DocNode>, SwcDiagnostics> {
+ swc_common::GLOBALS.set(&self.globals, || {
+ let swc_source_file = self
+ .source_map
+ .new_source_file(FileName::Custom(file_name), source_code);
+
+ let buffered_err = self.buffered_error.clone();
+ let session = Session {
+ handler: &self.handler,
+ };
+
+ let mut ts_config = TsConfig::default();
+ ts_config.dynamic_import = true;
+ let syntax = Syntax::Typescript(ts_config);
+
+ let lexer = Lexer::new(
+ session,
+ syntax,
+ JscTarget::Es2019,
+ SourceFileInput::from(&*swc_source_file),
+ Some(&self.comments),
+ );
+
+ let mut parser = Parser::new_from(session, lexer);
+
+ let module =
+ parser
+ .parse_module()
+ .map_err(move |mut err: DiagnosticBuilder| {
+ err.cancel();
+ SwcDiagnostics::from(buffered_err)
+ })?;
+
+ let doc_entries = self.get_doc_nodes_for_module_body(module.body);
+ Ok(doc_entries)
+ })
+ }
+
+ pub fn get_doc_nodes_for_module_decl(
+ &self,
+ module_decl: &swc_ecma_ast::ModuleDecl,
+ ) -> Vec<DocNode> {
+ use swc_ecma_ast::ModuleDecl;
+
+ match module_decl {
+ ModuleDecl::ExportDecl(export_decl) => {
+ vec![super::module::get_doc_node_for_export_decl(
+ self,
+ export_decl,
+ )]
+ }
+ ModuleDecl::ExportNamed(_named_export) => {
+ vec![]
+ // TODO(bartlomieju):
+ // super::module::get_doc_nodes_for_named_export(self, named_export)
+ }
+ ModuleDecl::ExportDefaultDecl(_) => vec![],
+ ModuleDecl::ExportDefaultExpr(_) => vec![],
+ ModuleDecl::ExportAll(_) => vec![],
+ ModuleDecl::TsExportAssignment(_) => vec![],
+ ModuleDecl::TsNamespaceExport(_) => vec![],
+ _ => vec![],
+ }
+ }
+
+ pub fn get_doc_nodes_for_module_body(
+ &self,
+ module_body: Vec<swc_ecma_ast::ModuleItem>,
+ ) -> Vec<DocNode> {
+ let mut doc_entries: Vec<DocNode> = vec![];
+ for node in module_body.iter() {
+ if let swc_ecma_ast::ModuleItem::ModuleDecl(module_decl) = node {
+ doc_entries.extend(self.get_doc_nodes_for_module_decl(module_decl));
+ }
+ }
+ doc_entries
+ }
+
+ pub fn js_doc_for_span(&self, span: Span) -> Option<String> {
+ let comments = self.comments.take_leading_comments(span.lo())?;
+ let js_doc_comment = comments.iter().find(|comment| {
+ comment.kind == CommentKind::Block && comment.text.starts_with('*')
+ })?;
+
+ let mut margin_pat = String::from("");
+ if let Some(margin) = self.source_map.span_to_margin(span) {
+ for _ in 0..margin {
+ margin_pat.push(' ');
+ }
+ }
+
+ let js_doc_re = Regex::new(r#" ?\* ?"#).unwrap();
+ let txt = js_doc_comment
+ .text
+ .split('\n')
+ .map(|line| js_doc_re.replace(line, "").to_string())
+ .map(|line| {
+ if line.starts_with(&margin_pat) {
+ line[margin_pat.len()..].to_string()
+ } else {
+ line
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n");
+
+ let txt = txt.trim_start().trim_end().to_string();
+
+ Some(txt)
+ }
+}