From c80f4120b5fa7400c425b94e41ff1bc0c67172d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 29 May 2020 20:15:57 +0200 Subject: [PATCH] add AstParser helper (#52) --- src/linter.rs | 126 +++++++----------------------- src/main.rs | 1 + src/swc_util.rs | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+), 99 deletions(-) create mode 100644 src/swc_util.rs diff --git a/src/linter.rs b/src/linter.rs index 9661d671d..4f45af1c2 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -1,49 +1,18 @@ // Copyright 2020 the Deno authors. All rights reserved. MIT license. +use crate::swc_util; +use crate::swc_util::AstParser; +use crate::swc_util::SwcDiagnosticBuffer; use std::sync::Arc; use std::sync::Mutex; -use std::sync::RwLock; use swc_common; use swc_common::comments::Comment; 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::SourceMap; -use swc_ecma_ast; -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 crate::rules; use crate::rules::LintDiagnostic; use crate::rules::LintRule; -pub type SwcDiagnostics = Vec; - -#[derive(Clone, Default)] -pub(crate) struct BufferedError(Arc>); - -impl Emitter for BufferedError { - fn emit(&mut self, db: &DiagnosticBuilder) { - self.0.write().unwrap().push((**db).clone()); - } -} - -impl From for Vec { - fn from(buf: BufferedError) -> Self { - let s = buf.0.read().unwrap(); - s.clone() - } -} - #[derive(Debug)] pub struct IgnoreDirective { location: rules::Location, @@ -65,33 +34,14 @@ impl IgnoreDirective { } pub struct Linter { - buffered_error: BufferedError, - pub source_map: Arc, - pub handler: Handler, - // After parsing module `comments` are taken from - // `Linter` and passed to `Context` - comments: Option, + pub ast_parser: AstParser, } impl Linter { 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() - }, - ); + let ast_parser = AstParser::new(); - Linter { - buffered_error, - source_map: Arc::new(SourceMap::default()), - handler, - comments: Some(Comments::default()), - } + Linter { ast_parser } } pub fn lint( @@ -99,41 +49,19 @@ impl Linter { file_name: String, source_code: String, rules: Vec>, - ) -> Result, SwcDiagnostics> { - swc_common::GLOBALS.set(&swc_common::Globals::new(), || { - let swc_source_file = self - .source_map - .new_source_file(FileName::Custom(file_name.clone()), 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), - self.comments.as_ref(), - ); - - 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) - })?; - - Ok(self.lint_module(file_name, module, rules)) - }) + ) -> Result, SwcDiagnosticBuffer> { + let syntax = swc_util::get_default_ts_config(); + self.ast_parser.parse_module( + &file_name, + syntax, + &source_code, + |parse_result, comments| { + let module = parse_result?; + let diagnostics = + self.lint_module(file_name.clone(), module, comments, rules); + Ok(diagnostics) + }, + ) } fn parse_ignore_comment(&self, comment: &Comment) -> Option { @@ -153,7 +81,10 @@ impl Linter { return None; }; - let location = self.source_map.lookup_char_pos(comment.span.lo()); + let location = self + .ast_parser + .source_map + .lookup_char_pos(comment.span.lo()); Some(IgnoreDirective { location: location.into(), @@ -162,21 +93,18 @@ impl Linter { } fn lint_module( - &mut self, + &self, file_name: String, module: swc_ecma_ast::Module, + comments: Comments, rules: Vec>, ) -> Vec { - let (leading, trailing) = self - .comments - .take() - .expect("Comments already taken") - .take_all(); + let (leading, trailing) = comments.take_all(); let context = rules::Context { file_name, diagnostics: Arc::new(Mutex::new(vec![])), - source_map: self.source_map.clone(), + source_map: self.ast_parser.source_map.clone(), leading_comments: leading, trailing_comments: trailing, }; diff --git a/src/main.rs b/src/main.rs index 5ab61cfb4..610e867e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ // Copyright 2020 the Deno authors. All rights reserved. MIT license. mod linter; mod rules; +mod swc_util; use linter::Linter; use rules::LintRule; diff --git a/src/swc_util.rs b/src/swc_util.rs new file mode 100644 index 000000000..cdaa1249e --- /dev/null +++ b/src/swc_util.rs @@ -0,0 +1,202 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use std::error::Error; +use std::fmt; +use std::sync::Arc; +use std::sync::RwLock; +use swc_common; +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_ast; +use swc_ecma_parser::lexer::Lexer; +use swc_ecma_parser::EsConfig; +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; + +#[allow(unused)] +fn get_default_es_config() -> Syntax { + let mut config = EsConfig::default(); + config.num_sep = true; + config.class_private_props = false; + config.class_private_methods = false; + config.class_props = false; + config.export_default_from = true; + config.export_namespace_from = true; + config.dynamic_import = true; + config.nullish_coalescing = true; + config.optional_chaining = true; + config.import_meta = true; + config.top_level_await = true; + Syntax::Es(config) +} + +pub fn get_default_ts_config() -> Syntax { + let mut ts_config = TsConfig::default(); + ts_config.dynamic_import = true; + ts_config.decorators = true; + Syntax::Typescript(ts_config) +} + +#[derive(Clone, Debug)] +pub struct SwcDiagnosticBuffer { + pub diagnostics: Vec, +} + +impl Error for SwcDiagnosticBuffer {} + +impl fmt::Display for SwcDiagnosticBuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = self.diagnostics.join(","); + + f.pad(&msg) + } +} + +impl SwcDiagnosticBuffer { + pub fn from_swc_error( + error_buffer: SwcErrorBuffer, + parser: &AstParser, + ) -> Self { + let s = error_buffer.0.read().unwrap().clone(); + + let diagnostics = s + .iter() + .map(|d| { + let mut msg = d.message(); + + if let Some(span) = d.span.primary_span() { + let location = parser.get_span_location(span); + let filename = match &location.file.name { + FileName::Custom(n) => n, + _ => unreachable!(), + }; + msg = format!( + "{} at {}:{}:{}", + msg, filename, location.line, location.col_display + ); + } + + msg + }) + .collect::>(); + + Self { diagnostics } + } +} + +#[derive(Clone)] +pub struct SwcErrorBuffer(Arc>>); + +impl SwcErrorBuffer { + pub fn default() -> Self { + Self(Arc::new(RwLock::new(vec![]))) + } +} + +impl Emitter for SwcErrorBuffer { + fn emit(&mut self, db: &DiagnosticBuilder) { + self.0.write().unwrap().push((**db).clone()); + } +} + +/// Low-level utility structure with common AST parsing functions. +/// +/// Allows to build more complicated parser by providing a callback +/// to `parse_module`. +pub struct AstParser { + pub buffered_error: SwcErrorBuffer, + pub source_map: Arc, + pub handler: Handler, + pub globals: Globals, +} + +impl AstParser { + pub fn new() -> Self { + let buffered_error = SwcErrorBuffer::default(); + + let handler = Handler::with_emitter_and_flags( + Box::new(buffered_error.clone()), + HandlerFlags { + dont_buffer_diagnostics: true, + can_emit_warnings: true, + ..Default::default() + }, + ); + + AstParser { + buffered_error, + source_map: Arc::new(SourceMap::default()), + handler, + globals: Globals::new(), + } + } + + pub fn parse_module( + &self, + file_name: &str, + syntax: Syntax, + source_code: &str, + callback: F, + ) -> R + where + F: FnOnce(Result, Comments) -> R, + { + swc_common::GLOBALS.set(&self.globals, || { + let swc_source_file = self.source_map.new_source_file( + FileName::Custom(file_name.to_string()), + source_code.to_string(), + ); + + let buffered_err = self.buffered_error.clone(); + let session = Session { + handler: &self.handler, + }; + + let comments = Comments::default(); + let lexer = Lexer::new( + session, + syntax, + JscTarget::Es2019, + SourceFileInput::from(&*swc_source_file), + Some(&comments), + ); + + let mut parser = Parser::new_from(session, lexer); + + let parse_result = + parser + .parse_module() + .map_err(move |mut err: DiagnosticBuilder| { + err.emit(); + SwcDiagnosticBuffer::from_swc_error(buffered_err, self) + }); + + callback(parse_result, comments) + }) + } + + pub fn get_span_location(&self, span: Span) -> swc_common::Loc { + self.source_map.lookup_char_pos(span.lo()) + } + + // pub fn get_span_comments( + // &self, + // span: Span, + // ) -> Vec { + // match self.leading_comments.get(&span.lo()) { + // Some(c) => c.clone(), + // None => vec![], + // } + // } +}