summaryrefslogtreecommitdiff
path: root/cli/tools/lint/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools/lint/mod.rs')
-rw-r--r--cli/tools/lint/mod.rs429
1 files changed, 273 insertions, 156 deletions
diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs
index 0d9868cf2..e3f2844a7 100644
--- a/cli/tools/lint/mod.rs
+++ b/cli/tools/lint/mod.rs
@@ -9,13 +9,21 @@ use deno_ast::ParsedSource;
use deno_ast::SourceRange;
use deno_ast::SourceTextInfo;
use deno_config::glob::FilePatterns;
+use deno_config::workspace::Workspace;
+use deno_config::workspace::WorkspaceMemberContext;
+use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
+use deno_core::futures::future::LocalBoxFuture;
+use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex;
use deno_core::serde_json;
+use deno_core::unsync::future::LocalFutureExt;
+use deno_core::unsync::future::SharedLocal;
use deno_graph::FastCheckDiagnostic;
+use deno_graph::ModuleGraph;
use deno_lint::diagnostic::LintDiagnostic;
use deno_lint::linter::LintConfig;
use deno_lint::linter::LintFileOptions;
@@ -33,6 +41,7 @@ use std::io::stdin;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
+use std::rc::Rc;
use std::sync::Arc;
use crate::args::CliOptions;
@@ -41,9 +50,12 @@ use crate::args::LintFlags;
use crate::args::LintOptions;
use crate::args::LintReporterKind;
use crate::args::LintRulesConfig;
+use crate::args::WorkspaceLintOptions;
+use crate::cache::Caches;
use crate::cache::IncrementalCache;
use crate::colors;
use crate::factory::CliFactory;
+use crate::graph_util::ModuleGraphCreator;
use crate::tools::fmt::run_parallelized;
use crate::util::file_watcher;
use crate::util::fs::canonicalize_path;
@@ -79,35 +91,49 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> {
Ok(async move {
let factory = CliFactory::from_flags(flags)?;
let cli_options = factory.cli_options();
- let lint_options = cli_options.resolve_lint_options(lint_flags)?;
let lint_config = cli_options.resolve_lint_config()?;
- let files =
- collect_lint_files(cli_options, lint_options.files.clone())
- .and_then(|files| {
- if files.is_empty() {
- Err(generic_error("No target files found."))
- } else {
- Ok(files)
- }
- })?;
- _ = watcher_communicator.watch_paths(files.clone());
-
- let lint_paths = if let Some(paths) = changed_paths {
- // lint all files on any changed (https://github.com/denoland/deno/issues/12446)
- files
- .iter()
- .any(|path| {
- canonicalize_path(path)
- .map(|p| paths.contains(&p))
- .unwrap_or(false)
- })
- .then_some(files)
- .unwrap_or_else(|| [].to_vec())
- } else {
- files
- };
-
- lint_files(factory, lint_options, lint_config, lint_paths).await?;
+ let mut paths_with_options_batches =
+ resolve_paths_with_options_batches(cli_options, &lint_flags)?;
+ for paths_with_options in &mut paths_with_options_batches {
+ _ = watcher_communicator
+ .watch_paths(paths_with_options.paths.clone());
+
+ let files = std::mem::take(&mut paths_with_options.paths);
+ paths_with_options.paths = if let Some(paths) = &changed_paths {
+ // lint all files on any changed (https://github.com/denoland/deno/issues/12446)
+ files
+ .iter()
+ .any(|path| {
+ canonicalize_path(path)
+ .map(|p| paths.contains(&p))
+ .unwrap_or(false)
+ })
+ .then_some(files)
+ .unwrap_or_else(|| [].to_vec())
+ } else {
+ files
+ };
+ }
+
+ let mut linter = WorkspaceLinter::new(
+ factory.caches()?.clone(),
+ factory.module_graph_creator().await?.clone(),
+ cli_options.workspace.clone(),
+ &cli_options.resolve_workspace_lint_options(&lint_flags)?,
+ );
+ for paths_with_options in paths_with_options_batches {
+ linter
+ .lint_files(
+ paths_with_options.options,
+ lint_config.clone(),
+ paths_with_options.ctx,
+ paths_with_options.paths,
+ )
+ .await?;
+ }
+
+ linter.finish();
+
Ok(())
})
},
@@ -117,15 +143,19 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> {
let factory = CliFactory::from_flags(flags)?;
let cli_options = factory.cli_options();
let is_stdin = lint_flags.is_stdin();
- let lint_options = cli_options.resolve_lint_options(lint_flags)?;
let lint_config = cli_options.resolve_lint_config()?;
- let files = &lint_options.files;
+ let workspace_lint_options =
+ cli_options.resolve_workspace_lint_options(&lint_flags)?;
let success = if is_stdin {
- let reporter_kind = lint_options.reporter_kind;
- let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind)));
+ let start_ctx = cli_options.workspace.resolve_start_ctx();
+ let reporter_lock = Arc::new(Mutex::new(create_reporter(
+ workspace_lint_options.reporter_kind,
+ )));
+ let lint_options =
+ cli_options.resolve_lint_options(lint_flags, &start_ctx)?;
let lint_rules = get_config_rules_err_empty(
lint_options.rules,
- cli_options.maybe_config_file().as_ref(),
+ start_ctx.maybe_deno_json().map(|c| c.as_ref()),
)?;
let file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME);
let r = lint_stdin(&file_path, lint_rules.rules, lint_config);
@@ -137,16 +167,25 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> {
reporter_lock.lock().close(1);
success
} else {
- let target_files = collect_lint_files(cli_options, files.clone())
- .and_then(|files| {
- if files.is_empty() {
- Err(generic_error("No target files found."))
- } else {
- Ok(files)
- }
- })?;
- debug!("Found {} files", target_files.len());
- lint_files(factory, lint_options, lint_config, target_files).await?
+ let mut linter = WorkspaceLinter::new(
+ factory.caches()?.clone(),
+ factory.module_graph_creator().await?.clone(),
+ cli_options.workspace.clone(),
+ &workspace_lint_options,
+ );
+ let paths_with_options_batches =
+ resolve_paths_with_options_batches(cli_options, &lint_flags)?;
+ for paths_with_options in paths_with_options_batches {
+ linter
+ .lint_files(
+ paths_with_options.options,
+ lint_config.clone(),
+ paths_with_options.ctx,
+ paths_with_options.paths,
+ )
+ .await?;
+ }
+ linter.finish()
};
if !success {
std::process::exit(1);
@@ -156,121 +195,202 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> {
Ok(())
}
-async fn lint_files(
- factory: CliFactory,
- lint_options: LintOptions,
- lint_config: LintConfig,
+struct PathsWithOptions {
+ ctx: WorkspaceMemberContext,
paths: Vec<PathBuf>,
-) -> Result<bool, AnyError> {
- let caches = factory.caches()?;
- let maybe_config_file = factory.cli_options().maybe_config_file().as_ref();
- let lint_rules =
- get_config_rules_err_empty(lint_options.rules, maybe_config_file)?;
- let incremental_cache = Arc::new(IncrementalCache::new(
- caches.lint_incremental_cache_db(),
- &lint_rules.incremental_cache_state(),
- &paths,
- ));
- let target_files_len = paths.len();
- let reporter_kind = lint_options.reporter_kind;
- // todo(dsherret): abstract away this lock behind a performant interface
- let reporter_lock =
- Arc::new(Mutex::new(create_reporter(reporter_kind.clone())));
- let has_error = Arc::new(AtomicFlag::default());
-
- let mut futures = Vec::with_capacity(2);
- if lint_rules.no_slow_types {
- if let Some(config_file) = maybe_config_file {
- let members = config_file.to_workspace_members()?;
- let has_error = has_error.clone();
- let reporter_lock = reporter_lock.clone();
- let module_graph_creator = factory.module_graph_creator().await?.clone();
- let path_urls = paths
- .iter()
- .filter_map(|p| ModuleSpecifier::from_file_path(p).ok())
- .collect::<HashSet<_>>();
- futures.push(deno_core::unsync::spawn(async move {
- let graph = module_graph_creator
- .create_and_validate_publish_graph(&members, true)
- .await?;
- // todo(dsherret): this isn't exactly correct as linting isn't properly
- // setup to handle workspaces. Iterating over the workspace members
- // should be done at a higher level because it also needs to take into
- // account the config per workspace member.
- for member in &members {
- let export_urls = member.config_file.resolve_export_value_urls()?;
- if !export_urls.iter().any(|url| path_urls.contains(url)) {
- continue; // entrypoint is not specified, so skip
+ options: LintOptions,
+}
+
+fn resolve_paths_with_options_batches(
+ cli_options: &CliOptions,
+ lint_flags: &LintFlags,
+) -> Result<Vec<PathsWithOptions>, AnyError> {
+ let members_lint_options =
+ cli_options.resolve_lint_options_for_members(lint_flags)?;
+ let mut paths_with_options_batches =
+ Vec::with_capacity(members_lint_options.len());
+ for (ctx, lint_options) in members_lint_options {
+ let files = collect_lint_files(cli_options, lint_options.files.clone())?;
+ if !files.is_empty() {
+ paths_with_options_batches.push(PathsWithOptions {
+ ctx,
+ paths: files,
+ options: lint_options,
+ });
+ }
+ }
+ if paths_with_options_batches.is_empty() {
+ return Err(generic_error("No target files found."));
+ }
+ Ok(paths_with_options_batches)
+}
+
+type WorkspaceModuleGraphFuture =
+ SharedLocal<LocalBoxFuture<'static, Result<Rc<ModuleGraph>, Rc<AnyError>>>>;
+
+struct WorkspaceLinter {
+ caches: Arc<Caches>,
+ module_graph_creator: Arc<ModuleGraphCreator>,
+ workspace: Arc<Workspace>,
+ reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
+ workspace_module_graph: Option<WorkspaceModuleGraphFuture>,
+ has_error: Arc<AtomicFlag>,
+ file_count: usize,
+}
+
+impl WorkspaceLinter {
+ pub fn new(
+ caches: Arc<Caches>,
+ module_graph_creator: Arc<ModuleGraphCreator>,
+ workspace: Arc<Workspace>,
+ workspace_options: &WorkspaceLintOptions,
+ ) -> Self {
+ let reporter_lock =
+ Arc::new(Mutex::new(create_reporter(workspace_options.reporter_kind)));
+ Self {
+ caches,
+ module_graph_creator,
+ workspace,
+ reporter_lock,
+ workspace_module_graph: None,
+ has_error: Default::default(),
+ file_count: 0,
+ }
+ }
+
+ pub async fn lint_files(
+ &mut self,
+ lint_options: LintOptions,
+ lint_config: LintConfig,
+ member_ctx: WorkspaceMemberContext,
+ paths: Vec<PathBuf>,
+ ) -> Result<(), AnyError> {
+ self.file_count += paths.len();
+
+ let lint_rules = get_config_rules_err_empty(
+ lint_options.rules,
+ member_ctx.maybe_deno_json().map(|c| c.as_ref()),
+ )?;
+ let incremental_cache = Arc::new(IncrementalCache::new(
+ self.caches.lint_incremental_cache_db(),
+ &lint_rules.incremental_cache_state(),
+ &paths,
+ ));
+
+ let mut futures = Vec::with_capacity(2);
+ if lint_rules.no_slow_types {
+ if self.workspace_module_graph.is_none() {
+ let module_graph_creator = self.module_graph_creator.clone();
+ let packages = self.workspace.jsr_packages_for_publish();
+ self.workspace_module_graph = Some(
+ async move {
+ module_graph_creator
+ .create_and_validate_publish_graph(&packages, true)
+ .await
+ .map(Rc::new)
+ .map_err(Rc::new)
}
- let diagnostics = no_slow_types::collect_no_slow_type_diagnostics(
- &export_urls,
- &graph,
- );
- if !diagnostics.is_empty() {
- has_error.raise();
- let mut reporter = reporter_lock.lock();
- for diagnostic in &diagnostics {
- reporter
- .visit_diagnostic(LintOrCliDiagnostic::FastCheck(diagnostic));
+ .boxed_local()
+ .shared_local(),
+ );
+ }
+ let workspace_module_graph_future =
+ self.workspace_module_graph.as_ref().unwrap().clone();
+ let publish_config = member_ctx.maybe_package_config();
+ if let Some(publish_config) = publish_config {
+ let has_error = self.has_error.clone();
+ let reporter_lock = self.reporter_lock.clone();
+ let path_urls = paths
+ .iter()
+ .filter_map(|p| ModuleSpecifier::from_file_path(p).ok())
+ .collect::<HashSet<_>>();
+ futures.push(
+ async move {
+ let graph = workspace_module_graph_future
+ .await
+ .map_err(|err| anyhow!("{:#}", err))?;
+ let export_urls =
+ publish_config.config_file.resolve_export_value_urls()?;
+ if !export_urls.iter().any(|url| path_urls.contains(url)) {
+ return Ok(()); // entrypoint is not specified, so skip
}
+ let diagnostics = no_slow_types::collect_no_slow_type_diagnostics(
+ &export_urls,
+ &graph,
+ );
+ if !diagnostics.is_empty() {
+ has_error.raise();
+ let mut reporter = reporter_lock.lock();
+ for diagnostic in &diagnostics {
+ reporter
+ .visit_diagnostic(LintOrCliDiagnostic::FastCheck(diagnostic));
+ }
+ }
+ Ok(())
}
- }
- Ok(())
- }));
- }
- }
-
- futures.push({
- let has_error = has_error.clone();
- let linter = create_linter(lint_rules.rules);
- let reporter_lock = reporter_lock.clone();
- let incremental_cache = incremental_cache.clone();
- let lint_config = lint_config.clone();
- let fix = lint_options.fix;
- deno_core::unsync::spawn(async move {
- run_parallelized(paths, {
- move |file_path| {
- let file_text = deno_ast::strip_bom(fs::read_to_string(&file_path)?);
-
- // don't bother rechecking this file if it didn't have any diagnostics before
- if incremental_cache.is_file_same(&file_path, &file_text) {
- return Ok(());
- }
+ .boxed_local(),
+ );
+ }
+ }
- let r = lint_file(&linter, &file_path, file_text, lint_config, fix);
- if let Ok((file_source, file_diagnostics)) = &r {
- if file_diagnostics.is_empty() {
- // update the incremental cache if there were no diagnostics
- incremental_cache.update_file(
- &file_path,
- // ensure the returned text is used here as it may have been modified via --fix
- file_source.text(),
- )
+ futures.push({
+ let has_error = self.has_error.clone();
+ let linter = create_linter(lint_rules.rules);
+ let reporter_lock = self.reporter_lock.clone();
+ let incremental_cache = incremental_cache.clone();
+ let lint_config = lint_config.clone();
+ let fix = lint_options.fix;
+ async move {
+ run_parallelized(paths, {
+ move |file_path| {
+ let file_text =
+ deno_ast::strip_bom(fs::read_to_string(&file_path)?);
+
+ // don't bother rechecking this file if it didn't have any diagnostics before
+ if incremental_cache.is_file_same(&file_path, &file_text) {
+ return Ok(());
}
- }
- let success = handle_lint_result(
- &file_path.to_string_lossy(),
- r,
- reporter_lock.clone(),
- );
- if !success {
- has_error.raise();
- }
+ let r = lint_file(&linter, &file_path, file_text, lint_config, fix);
+ if let Ok((file_source, file_diagnostics)) = &r {
+ if file_diagnostics.is_empty() {
+ // update the incremental cache if there were no diagnostics
+ incremental_cache.update_file(
+ &file_path,
+ // ensure the returned text is used here as it may have been modified via --fix
+ file_source.text(),
+ )
+ }
+ }
- Ok(())
- }
- })
- .await
- })
- });
+ let success = handle_lint_result(
+ &file_path.to_string_lossy(),
+ r,
+ reporter_lock.clone(),
+ );
+ if !success {
+ has_error.raise();
+ }
- deno_core::futures::future::try_join_all(futures).await?;
+ Ok(())
+ }
+ })
+ .await
+ }
+ .boxed_local()
+ });
- incremental_cache.wait_completion().await;
- reporter_lock.lock().close(target_files_len);
+ deno_core::futures::future::try_join_all(futures).await?;
- Ok(!has_error.is_raised())
+ incremental_cache.wait_completion().await;
+ Ok(())
+ }
+
+ pub fn finish(self) -> bool {
+ debug!("Found {} files", self.file_count);
+ self.reporter_lock.lock().close(self.file_count);
+ !self.has_error.is_raised() // success
+ }
}
fn collect_lint_files(
@@ -692,9 +812,8 @@ impl LintReporter for PrettyLintReporter {
}
match check_count {
- n if n <= 1 => info!("Checked {} file", n),
- n if n > 1 => info!("Checked {} files", n),
- _ => unreachable!(),
+ 1 => info!("Checked 1 file"),
+ n => info!("Checked {} files", n),
}
}
}
@@ -744,9 +863,8 @@ impl LintReporter for CompactLintReporter {
}
match check_count {
- n if n <= 1 => info!("Checked {} file", n),
- n if n > 1 => info!("Checked {} files", n),
- _ => unreachable!(),
+ 1 => info!("Checked 1 file"),
+ n => info!("Checked {} files", n),
}
}
}
@@ -910,9 +1028,8 @@ pub fn get_configured_rules(
maybe_config_file: Option<&deno_config::ConfigFile>,
) -> ConfiguredRules {
const NO_SLOW_TYPES_NAME: &str = "no-slow-types";
- let implicit_no_slow_types = maybe_config_file
- .map(|c| c.is_package() || c.json.workspace.is_some())
- .unwrap_or(false);
+ let implicit_no_slow_types =
+ maybe_config_file.map(|c| c.is_package()).unwrap_or(false);
let no_slow_types = implicit_no_slow_types
&& !rules
.exclude