summaryrefslogtreecommitdiff
path: root/cli/ast.rs
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2020-10-20 14:10:42 +1100
committerGitHub <noreply@github.com>2020-10-20 14:10:42 +1100
commit57e95032c898cfdfe795e6924d402fdbe2ae59a8 (patch)
treee548b283d0129b5a1c00828f5a456cc094276bd1 /cli/ast.rs
parent034ab48086557af00216ffe311c71ad4eb0ec4d5 (diff)
feat(cli): add support for bundle --no-check (#8023)
Fixes #6686
Diffstat (limited to 'cli/ast.rs')
-rw-r--r--cli/ast.rs136
1 files changed, 122 insertions, 14 deletions
diff --git a/cli/ast.rs b/cli/ast.rs
index e3b45a6fd..95f243717 100644
--- a/cli/ast.rs
+++ b/cli/ast.rs
@@ -1,13 +1,14 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::media_type::MediaType;
+use crate::tsc_config;
use deno_core::error::AnyError;
+use deno_core::serde_json;
use deno_core::ModuleSpecifier;
use std::error::Error;
use std::fmt;
use std::rc::Rc;
-use std::result;
use std::sync::Arc;
use std::sync::RwLock;
use swc_common::chain;
@@ -21,6 +22,7 @@ use swc_common::errors::HandlerFlags;
use swc_common::FileName;
use swc_common::Globals;
use swc_common::Loc;
+use swc_common::SourceFile;
use swc_common::SourceMap;
use swc_common::Span;
use swc_ecmascript::ast::Module;
@@ -38,13 +40,11 @@ use swc_ecmascript::parser::TsConfig;
use swc_ecmascript::transforms::fixer;
use swc_ecmascript::transforms::helpers;
use swc_ecmascript::transforms::pass::Optional;
-use swc_ecmascript::transforms::proposals::decorators;
+use swc_ecmascript::transforms::proposals;
use swc_ecmascript::transforms::react;
use swc_ecmascript::transforms::typescript;
use swc_ecmascript::visit::FoldWith;
-type Result<V> = result::Result<V, AnyError>;
-
static TARGET: JscTarget = JscTarget::Es2020;
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -173,7 +173,10 @@ pub fn get_syntax(media_type: &MediaType) -> Syntax {
/// Options which can be adjusted when transpiling a module.
#[derive(Debug, Clone)]
-pub struct TranspileOptions {
+pub struct EmitOptions {
+ /// Indicate if JavaScript is being checked/transformed as well, or if it is
+ /// only TypeScript.
+ pub check_js: bool,
/// When emitting a legacy decorator, also emit experimental decorator meta
/// data. Defaults to `false`.
pub emit_metadata: bool,
@@ -190,9 +193,10 @@ pub struct TranspileOptions {
pub transform_jsx: bool,
}
-impl Default for TranspileOptions {
+impl Default for EmitOptions {
fn default() -> Self {
- TranspileOptions {
+ EmitOptions {
+ check_js: false,
emit_metadata: false,
inline_source_map: true,
jsx_factory: "React.createElement".into(),
@@ -202,6 +206,21 @@ impl Default for TranspileOptions {
}
}
+impl From<tsc_config::TsConfig> for EmitOptions {
+ fn from(config: tsc_config::TsConfig) -> Self {
+ let options: tsc_config::EmitConfigOptions =
+ serde_json::from_value(config.0).unwrap();
+ EmitOptions {
+ check_js: options.check_js,
+ emit_metadata: options.emit_decorator_metadata,
+ inline_source_map: true,
+ jsx_factory: options.jsx_factory,
+ jsx_fragment_factory: options.jsx_fragment_factory,
+ transform_jsx: options.jsx == "react",
+ }
+ }
+}
+
/// A logical structure to hold the value of a parsed module for further
/// processing.
#[derive(Clone)]
@@ -245,8 +264,8 @@ impl ParsedModule {
/// The result is a tuple of the code and optional source map as strings.
pub fn transpile(
self,
- options: &TranspileOptions,
- ) -> Result<(String, Option<String>)> {
+ options: &EmitOptions,
+ ) -> Result<(String, Option<String>), AnyError> {
let program = Program::Module(self.module);
let jsx_pass = react::react(
@@ -263,7 +282,7 @@ impl ParsedModule {
);
let mut passes = chain!(
Optional::new(jsx_pass, options.transform_jsx),
- decorators::decorators(decorators::Config {
+ proposals::decorators::decorators(proposals::decorators::Config {
legacy: true,
emit_metadata: options.emit_metadata
}),
@@ -329,7 +348,7 @@ pub fn parse(
specifier: &ModuleSpecifier,
source: &str,
media_type: &MediaType,
-) -> Result<ParsedModule> {
+) -> Result<ParsedModule, AnyError> {
let source_map = SourceMap::default();
let source_file = source_map.new_source_file(
FileName::Custom(specifier.to_string()),
@@ -372,6 +391,95 @@ pub fn parse(
})
}
+/// A low level function which transpiles a source module into an swc
+/// SourceFile.
+pub fn transpile_module(
+ filename: &str,
+ src: &str,
+ media_type: &MediaType,
+ emit_options: &EmitOptions,
+ cm: Rc<SourceMap>,
+) -> Result<(Rc<SourceFile>, Module), AnyError> {
+ // TODO(@kitsonk) DRY-up with ::parse()
+ let error_buffer = ErrorBuffer::new();
+ let handler = Handler::with_emitter_and_flags(
+ Box::new(error_buffer.clone()),
+ HandlerFlags {
+ can_emit_warnings: true,
+ dont_buffer_diagnostics: true,
+ ..HandlerFlags::default()
+ },
+ );
+ let comments = SingleThreadedComments::default();
+ let syntax = get_syntax(media_type);
+ let source_file =
+ cm.new_source_file(FileName::Custom(filename.to_string()), src.to_string());
+ let lexer = Lexer::new(
+ syntax,
+ TARGET,
+ StringInput::from(&*source_file),
+ Some(&comments),
+ );
+ let mut parser = swc_ecmascript::parser::Parser::new_from(lexer);
+ let sm = cm.clone();
+ let module = parser.parse_module().map_err(move |err| {
+ let mut diagnostic = err.into_diagnostic(&handler);
+ diagnostic.emit();
+
+ DiagnosticBuffer::from_error_buffer(error_buffer, |span| {
+ sm.lookup_char_pos(span.lo)
+ })
+ })?;
+ // TODO(@kitsonk) DRY-up with ::transpile()
+ let jsx_pass = react::react(
+ cm,
+ Some(&comments),
+ react::Options {
+ pragma: emit_options.jsx_factory.clone(),
+ pragma_frag: emit_options.jsx_fragment_factory.clone(),
+ // this will use `Object.assign()` instead of the `_extends` helper
+ // when spreading props.
+ use_builtins: true,
+ ..Default::default()
+ },
+ );
+ let mut passes = chain!(
+ Optional::new(jsx_pass, emit_options.transform_jsx),
+ proposals::decorators::decorators(proposals::decorators::Config {
+ legacy: true,
+ emit_metadata: emit_options.emit_metadata
+ }),
+ typescript::strip(),
+ fixer(Some(&comments)),
+ );
+ let module = module.fold_with(&mut passes);
+
+ Ok((source_file, module))
+}
+
+pub struct BundleHook;
+
+impl swc_bundler::Hook for BundleHook {
+ fn get_import_meta_url(
+ &self,
+ span: swc_common::Span,
+ file: &swc_common::FileName,
+ ) -> Result<Option<swc_ecmascript::ast::Expr>, AnyError> {
+ // we use custom file names, and swc "wraps" these in `<` and `>` so, we
+ // want to strip those back out.
+ let mut value = file.to_string();
+ value.pop();
+ value.remove(0);
+ Ok(Some(swc_ecmascript::ast::Expr::Lit(
+ swc_ecmascript::ast::Lit::Str(swc_ecmascript::ast::Str {
+ span,
+ value: value.into(),
+ has_escape: false,
+ }),
+ )))
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -436,7 +544,7 @@ mod tests {
let module = parse(&specifier, source, &MediaType::TypeScript)
.expect("could not parse module");
let (code, maybe_map) = module
- .transpile(&TranspileOptions::default())
+ .transpile(&EmitOptions::default())
.expect("could not strip types");
assert!(code.starts_with("var D;\n(function(D) {\n"));
assert!(
@@ -460,7 +568,7 @@ mod tests {
let module = parse(&specifier, source, &MediaType::TSX)
.expect("could not parse module");
let (code, _) = module
- .transpile(&TranspileOptions::default())
+ .transpile(&EmitOptions::default())
.expect("could not strip types");
assert!(code.contains("React.createElement(\"div\", null"));
}
@@ -491,7 +599,7 @@ mod tests {
let module = parse(&specifier, source, &MediaType::TypeScript)
.expect("could not parse module");
let (code, _) = module
- .transpile(&TranspileOptions::default())
+ .transpile(&EmitOptions::default())
.expect("could not strip types");
assert!(code.contains("_applyDecoratedDescriptor("));
}