1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

refactor: add TypeChecker struct (#18709)

Adds a `TypeChecker` struct and pushes more shared functionality into
it.
This commit is contained in:
David Sherret 2023-04-14 18:05:46 -04:00 committed by GitHub
parent f6a28e3e62
commit 0a67a3965f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 185 additions and 206 deletions

View file

@ -2,19 +2,17 @@
use crate::args::CliOptions; use crate::args::CliOptions;
use crate::args::Lockfile; use crate::args::Lockfile;
use crate::args::TsConfigType;
use crate::args::TsTypeLib; use crate::args::TsTypeLib;
use crate::args::TypeCheckMode; use crate::args::TypeCheckMode;
use crate::cache; use crate::cache;
use crate::cache::DenoDir;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
use crate::cache::TypeCheckCache;
use crate::colors; use crate::colors;
use crate::errors::get_error_class_name; use crate::errors::get_error_class_name;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolver;
use crate::tools::check; use crate::tools::check;
use crate::tools::check::TypeChecker;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_core::error::custom_error; use deno_core::error::custom_error;
@ -170,10 +168,9 @@ pub struct ModuleGraphBuilder {
npm_resolver: Arc<NpmPackageResolver>, npm_resolver: Arc<NpmPackageResolver>,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
lockfile: Option<Arc<Mutex<Lockfile>>>, lockfile: Option<Arc<Mutex<Lockfile>>>,
caches: Arc<cache::Caches>,
emit_cache: cache::EmitCache, emit_cache: cache::EmitCache,
file_fetcher: Arc<FileFetcher>, file_fetcher: Arc<FileFetcher>,
deno_dir: DenoDir, type_checker: Arc<TypeChecker>,
} }
impl ModuleGraphBuilder { impl ModuleGraphBuilder {
@ -184,10 +181,9 @@ impl ModuleGraphBuilder {
npm_resolver: Arc<NpmPackageResolver>, npm_resolver: Arc<NpmPackageResolver>,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
lockfile: Option<Arc<Mutex<Lockfile>>>, lockfile: Option<Arc<Mutex<Lockfile>>>,
caches: Arc<cache::Caches>,
emit_cache: cache::EmitCache, emit_cache: cache::EmitCache,
file_fetcher: Arc<FileFetcher>, file_fetcher: Arc<FileFetcher>,
deno_dir: DenoDir, type_checker: Arc<TypeChecker>,
) -> Self { ) -> Self {
Self { Self {
options, options,
@ -195,10 +191,9 @@ impl ModuleGraphBuilder {
npm_resolver, npm_resolver,
parsed_source_cache, parsed_source_cache,
lockfile, lockfile,
caches,
emit_cache, emit_cache,
file_fetcher, file_fetcher,
deno_dir, type_checker,
} }
} }
@ -270,51 +265,24 @@ impl ModuleGraphBuilder {
) )
.await?; .await?;
graph_valid_with_cli_options(&graph, &graph.roots, &self.options)?;
let graph = Arc::new(graph); let graph = Arc::new(graph);
graph_valid_with_cli_options(&graph, &graph.roots, &self.options)?;
if let Some(lockfile) = &self.lockfile { if let Some(lockfile) = &self.lockfile {
graph_lock_or_exit(&graph, &mut lockfile.lock()); graph_lock_or_exit(&graph, &mut lockfile.lock());
} }
if self.options.type_check_mode() != TypeCheckMode::None { if self.options.type_check_mode() != TypeCheckMode::None {
// node built-in specifiers use the @types/node package to determine self
// types, so inject that now after the lockfile has been written .type_checker
if graph.has_node_specifier { .check(
self graph.clone(),
.npm_resolver check::CheckOptions {
.inject_synthetic_types_node_package()
.await?;
}
let ts_config_result =
self
.options
.resolve_ts_config_for_emit(TsConfigType::Check {
lib: self.options.ts_type_lib_window(), lib: self.options.ts_type_lib_window(),
})?; log_ignored_options: true,
if let Some(ignored_options) = ts_config_result.maybe_ignored_options { reload: self.options.reload_flag(),
log::warn!("{}", ignored_options); },
} )
let maybe_config_specifier = self.options.maybe_config_file_specifier(); .await?;
let cache =
TypeCheckCache::new(self.caches.type_checking_cache_db(&self.deno_dir));
let check_result = check::check(
graph.clone(),
&cache,
self.npm_resolver.clone(),
check::CheckOptions {
type_check_mode: self.options.type_check_mode(),
debug: self.options.log_level() == Some(log::Level::Debug),
maybe_config_specifier,
ts_config: ts_config_result.ts_config,
log_checks: true,
reload: self.options.reload_flag(),
},
)?;
log::debug!("{}", check_result.stats);
if !check_result.diagnostics.is_empty() {
return Err(check_result.diagnostics.into());
}
} }
Ok(graph) Ok(graph)

View file

@ -2,13 +2,9 @@
use crate::args::CliOptions; use crate::args::CliOptions;
use crate::args::DenoSubcommand; use crate::args::DenoSubcommand;
use crate::args::TsConfigType;
use crate::args::TsTypeLib; use crate::args::TsTypeLib;
use crate::args::TypeCheckMode; use crate::args::TypeCheckMode;
use crate::cache::Caches;
use crate::cache::DenoDir;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
use crate::cache::TypeCheckCache;
use crate::emit::Emitter; use crate::emit::Emitter;
use crate::graph_util::graph_lock_or_exit; use crate::graph_util::graph_lock_or_exit;
use crate::graph_util::graph_valid_with_cli_options; use crate::graph_util::graph_valid_with_cli_options;
@ -24,6 +20,7 @@ use crate::proc_state::FileWatcherReporter;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolver;
use crate::tools::check; use crate::tools::check;
use crate::tools::check::TypeChecker;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use crate::util::text_encoding::code_without_source_map; use crate::util::text_encoding::code_without_source_map;
use crate::util::text_encoding::source_map_from_code; use crate::util::text_encoding::source_map_from_code;
@ -66,45 +63,39 @@ use std::sync::Arc;
pub struct ModuleLoadPreparer { pub struct ModuleLoadPreparer {
options: Arc<CliOptions>, options: Arc<CliOptions>,
caches: Arc<Caches>,
deno_dir: DenoDir,
graph_container: Arc<ModuleGraphContainer>, graph_container: Arc<ModuleGraphContainer>,
lockfile: Option<Arc<Mutex<Lockfile>>>, lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>, maybe_file_watcher_reporter: Option<FileWatcherReporter>,
module_graph_builder: Arc<ModuleGraphBuilder>, module_graph_builder: Arc<ModuleGraphBuilder>,
npm_resolver: Arc<NpmPackageResolver>,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
progress_bar: ProgressBar, progress_bar: ProgressBar,
resolver: Arc<CliGraphResolver>, resolver: Arc<CliGraphResolver>,
type_checker: Arc<TypeChecker>,
} }
impl ModuleLoadPreparer { impl ModuleLoadPreparer {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
options: Arc<CliOptions>, options: Arc<CliOptions>,
caches: Arc<Caches>,
deno_dir: DenoDir,
graph_container: Arc<ModuleGraphContainer>, graph_container: Arc<ModuleGraphContainer>,
lockfile: Option<Arc<Mutex<Lockfile>>>, lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>, maybe_file_watcher_reporter: Option<FileWatcherReporter>,
module_graph_builder: Arc<ModuleGraphBuilder>, module_graph_builder: Arc<ModuleGraphBuilder>,
npm_resolver: Arc<NpmPackageResolver>,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
progress_bar: ProgressBar, progress_bar: ProgressBar,
resolver: Arc<CliGraphResolver>, resolver: Arc<CliGraphResolver>,
type_checker: Arc<TypeChecker>,
) -> Self { ) -> Self {
Self { Self {
options, options,
caches,
deno_dir,
graph_container, graph_container,
lockfile, lockfile,
maybe_file_watcher_reporter, maybe_file_watcher_reporter,
module_graph_builder, module_graph_builder,
npm_resolver,
parsed_source_cache, parsed_source_cache,
progress_bar, progress_bar,
resolver, resolver,
type_checker,
} }
} }
@ -166,61 +157,40 @@ impl ModuleLoadPreparer {
) )
.await?; .await?;
// If there is a lockfile, validate the integrity of all the modules. graph_valid_with_cli_options(graph, &roots, &self.options)?;
// If there is a lockfile...
if let Some(lockfile) = &self.lockfile { if let Some(lockfile) = &self.lockfile {
graph_lock_or_exit(graph, &mut lockfile.lock()); let mut lockfile = lockfile.lock();
// validate the integrity of all the modules
graph_lock_or_exit(graph, &mut lockfile);
// update it with anything new
lockfile.write()?;
} }
graph_valid_with_cli_options(graph, &roots, &self.options)?;
// save the graph and get a reference to the new graph // save the graph and get a reference to the new graph
let graph = graph_update_permit.commit(); let graph = graph_update_permit.commit();
if graph.has_node_specifier
&& self.options.type_check_mode() != TypeCheckMode::None
{
self
.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}
drop(_pb_clear_guard); drop(_pb_clear_guard);
// type check if necessary // type check if necessary
if self.options.type_check_mode() != TypeCheckMode::None if self.options.type_check_mode() != TypeCheckMode::None
&& !self.graph_container.is_type_checked(&roots, lib) && !self.graph_container.is_type_checked(&roots, lib)
{ {
// todo(dsherret): consolidate this with what's done in graph_util
log::debug!("Type checking.");
let maybe_config_specifier = self.options.maybe_config_file_specifier();
let graph = Arc::new(graph.segment(&roots)); let graph = Arc::new(graph.segment(&roots));
let options = check::CheckOptions { self
type_check_mode: self.options.type_check_mode(), .type_checker
debug: self.options.log_level() == Some(log::Level::Debug), .check(
maybe_config_specifier, graph,
ts_config: self check::CheckOptions {
.options lib,
.resolve_ts_config_for_emit(TsConfigType::Check { lib })? log_ignored_options: false,
.ts_config, reload: self.options.reload_flag()
log_checks: true, && !roots.iter().all(|r| reload_exclusions.contains(r)),
reload: self.options.reload_flag() },
&& !roots.iter().all(|r| reload_exclusions.contains(r)), )
}; .await?;
let check_cache =
TypeCheckCache::new(self.caches.type_checking_cache_db(&self.deno_dir));
let check_result =
check::check(graph, &check_cache, self.npm_resolver.clone(), options)?;
self.graph_container.set_type_checked(&roots, lib); self.graph_container.set_type_checked(&roots, lib);
if !check_result.diagnostics.is_empty() {
return Err(anyhow!(check_result.diagnostics));
}
log::debug!("{}", check_result.stats);
}
// any updates to the lockfile should be updated now
if let Some(ref lockfile) = self.lockfile {
let g = lockfile.lock();
g.write()?;
} }
log::debug!("Prepared module load."); log::debug!("Prepared module load.");

View file

@ -25,6 +25,7 @@ use crate::npm::NpmPackageResolver;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
use crate::npm::PackageJsonDepsInstaller; use crate::npm::PackageJsonDepsInstaller;
use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolver;
use crate::tools::check::TypeChecker;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle; use crate::util::progress_bar::ProgressBarStyle;
@ -305,30 +306,33 @@ impl ProcState {
file_fetcher.clone(), file_fetcher.clone(),
npm_resolver.clone(), npm_resolver.clone(),
)); ));
let type_checker = Arc::new(TypeChecker::new(
dir.clone(),
caches.clone(),
cli_options.clone(),
npm_resolver.clone(),
));
let module_graph_builder = Arc::new(ModuleGraphBuilder::new( let module_graph_builder = Arc::new(ModuleGraphBuilder::new(
cli_options.clone(), cli_options.clone(),
resolver.clone(), resolver.clone(),
npm_resolver.clone(), npm_resolver.clone(),
parsed_source_cache.clone(), parsed_source_cache.clone(),
lockfile.clone(), lockfile.clone(),
caches.clone(),
emit_cache.clone(), emit_cache.clone(),
file_fetcher.clone(), file_fetcher.clone(),
dir.clone(), type_checker.clone(),
)); ));
let graph_container: Arc<ModuleGraphContainer> = Default::default(); let graph_container: Arc<ModuleGraphContainer> = Default::default();
let module_load_preparer = Arc::new(ModuleLoadPreparer::new( let module_load_preparer = Arc::new(ModuleLoadPreparer::new(
cli_options.clone(), cli_options.clone(),
caches.clone(),
dir.clone(),
graph_container.clone(), graph_container.clone(),
lockfile.clone(), lockfile.clone(),
maybe_file_watcher_reporter.clone(), maybe_file_watcher_reporter.clone(),
module_graph_builder.clone(), module_graph_builder.clone(),
npm_resolver.clone(),
parsed_source_cache.clone(), parsed_source_cache.clone(),
progress_bar.clone(), progress_bar.clone(),
resolver.clone(), resolver.clone(),
type_checker,
)); ));
Ok(ProcState(Arc::new(Inner { Ok(ProcState(Arc::new(Inner {

View file

@ -12,136 +12,172 @@ use deno_runtime::colors;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use crate::args::CliOptions;
use crate::args::TsConfig; use crate::args::TsConfig;
use crate::args::TsConfigType;
use crate::args::TsTypeLib;
use crate::args::TypeCheckMode; use crate::args::TypeCheckMode;
use crate::cache::Caches;
use crate::cache::DenoDir;
use crate::cache::FastInsecureHasher; use crate::cache::FastInsecureHasher;
use crate::cache::TypeCheckCache; use crate::cache::TypeCheckCache;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
use crate::tsc; use crate::tsc;
use crate::tsc::Diagnostics;
use crate::tsc::Stats;
use crate::version; use crate::version;
/// Options for performing a check of a module graph. Note that the decision to /// Options for performing a check of a module graph. Note that the decision to
/// emit or not is determined by the `ts_config` settings. /// emit or not is determined by the `ts_config` settings.
pub struct CheckOptions { pub struct CheckOptions {
/// The check flag from the option which can effect the filtering of /// Default type library to type check with.
/// diagnostics in the emit result. pub lib: TsTypeLib,
pub type_check_mode: TypeCheckMode, /// Whether to log about any ignored compiler options.
/// Set the debug flag on the TypeScript type checker. pub log_ignored_options: bool,
pub debug: bool,
/// The module specifier to the configuration file, passed to tsc so that
/// configuration related diagnostics are properly formed.
pub maybe_config_specifier: Option<ModuleSpecifier>,
/// The derived tsconfig that should be used when checking.
pub ts_config: TsConfig,
/// If true, `Check <specifier>` will be written to stdout for each root.
pub log_checks: bool,
/// If true, valid `.tsbuildinfo` files will be ignored and type checking /// If true, valid `.tsbuildinfo` files will be ignored and type checking
/// will always occur. /// will always occur.
pub reload: bool, pub reload: bool,
} }
/// The result of a check of a module graph. pub struct TypeChecker {
#[derive(Debug, Default)] deno_dir: DenoDir,
pub struct CheckResult { caches: Arc<Caches>,
pub diagnostics: Diagnostics, cli_options: Arc<CliOptions>,
pub stats: Stats, npm_resolver: Arc<NpmPackageResolver>,
} }
/// Given a set of roots and graph data, type check the module graph. impl TypeChecker {
/// pub fn new(
/// It is expected that it is determined if a check and/or emit is validated deno_dir: DenoDir,
/// before the function is called. caches: Arc<Caches>,
pub fn check( cli_options: Arc<CliOptions>,
graph: Arc<ModuleGraph>, npm_resolver: Arc<NpmPackageResolver>,
cache: &TypeCheckCache, ) -> Self {
npm_resolver: Arc<NpmPackageResolver>, Self {
options: CheckOptions, deno_dir,
) -> Result<CheckResult, AnyError> { caches,
let check_js = options.ts_config.get_check_js(); cli_options,
let check_hash = match get_check_hash(&graph, &options) { npm_resolver,
CheckHashResult::NoFiles => return Ok(Default::default()), }
CheckHashResult::Hash(hash) => hash,
};
// do not type check if we know this is type checked
if !options.reload && cache.has_check_hash(check_hash) {
return Ok(Default::default());
} }
if options.log_checks { /// Type check the module graph.
///
/// It is expected that it is determined if a check and/or emit is validated
/// before the function is called.
pub async fn check(
&self,
graph: Arc<ModuleGraph>,
options: CheckOptions,
) -> Result<(), AnyError> {
// node built-in specifiers use the @types/node package to determine
// types, so inject that now (the caller should do this after the lockfile
// has been written)
if graph.has_node_specifier {
self
.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}
log::debug!("Type checking.");
let ts_config_result = self
.cli_options
.resolve_ts_config_for_emit(TsConfigType::Check { lib: options.lib })?;
if options.log_ignored_options {
if let Some(ignored_options) = ts_config_result.maybe_ignored_options {
log::warn!("{}", ignored_options);
}
}
let ts_config = ts_config_result.ts_config;
let type_check_mode = self.cli_options.type_check_mode();
let debug = self.cli_options.log_level() == Some(log::Level::Debug);
let cache =
TypeCheckCache::new(self.caches.type_checking_cache_db(&self.deno_dir));
let check_js = ts_config.get_check_js();
let check_hash = match get_check_hash(&graph, type_check_mode, &ts_config) {
CheckHashResult::NoFiles => return Ok(()),
CheckHashResult::Hash(hash) => hash,
};
// do not type check if we know this is type checked
if !options.reload && cache.has_check_hash(check_hash) {
return Ok(());
}
for root in &graph.roots { for root in &graph.roots {
let root_str = root.as_str(); let root_str = root.as_str();
log::info!("{} {}", colors::green("Check"), root_str); log::info!("{} {}", colors::green("Check"), root_str);
} }
}
let root_names = get_tsc_roots(&graph, check_js); let root_names = get_tsc_roots(&graph, check_js);
// while there might be multiple roots, we can't "merge" the build info, so we // while there might be multiple roots, we can't "merge" the build info, so we
// try to retrieve the build info for first root, which is the most common use // try to retrieve the build info for first root, which is the most common use
// case. // case.
let maybe_tsbuildinfo = if options.reload { let maybe_tsbuildinfo = if options.reload {
None None
} else { } else {
cache.get_tsbuildinfo(&graph.roots[0]) cache.get_tsbuildinfo(&graph.roots[0])
}; };
// to make tsc build info work, we need to consistently hash modules, so that // to make tsc build info work, we need to consistently hash modules, so that
// tsc can better determine if an emit is still valid or not, so we provide // tsc can better determine if an emit is still valid or not, so we provide
// that data here. // that data here.
let hash_data = { let hash_data = {
let mut hasher = FastInsecureHasher::new(); let mut hasher = FastInsecureHasher::new();
hasher.write(&options.ts_config.as_bytes()); hasher.write(&ts_config.as_bytes());
hasher.write_str(version::deno()); hasher.write_str(version::deno());
hasher.finish() hasher.finish()
}; };
let response = tsc::exec(tsc::Request { let response = tsc::exec(tsc::Request {
config: options.ts_config, config: ts_config,
debug: options.debug, debug,
graph: graph.clone(), graph: graph.clone(),
hash_data, hash_data,
maybe_npm_resolver: Some(npm_resolver.clone()), maybe_npm_resolver: Some(self.npm_resolver.clone()),
maybe_tsbuildinfo, maybe_tsbuildinfo,
root_names, root_names,
check_mode: options.type_check_mode, check_mode: type_check_mode,
})?; })?;
let diagnostics = if options.type_check_mode == TypeCheckMode::Local { let diagnostics = if type_check_mode == TypeCheckMode::Local {
response.diagnostics.filter(|d| { response.diagnostics.filter(|d| {
if let Some(file_name) = &d.file_name { if let Some(file_name) = &d.file_name {
if !file_name.starts_with("http") { if !file_name.starts_with("http") {
if ModuleSpecifier::parse(file_name) if ModuleSpecifier::parse(file_name)
.map(|specifier| !npm_resolver.in_npm_package(&specifier)) .map(|specifier| !self.npm_resolver.in_npm_package(&specifier))
.unwrap_or(true) .unwrap_or(true)
{ {
Some(d.clone()) Some(d.clone())
} else {
None
}
} else { } else {
None None
} }
} else { } else {
None Some(d.clone())
} }
} else { })
Some(d.clone()) } else {
} response.diagnostics
}) };
} else {
response.diagnostics
};
if let Some(tsbuildinfo) = response.maybe_tsbuildinfo { if let Some(tsbuildinfo) = response.maybe_tsbuildinfo {
cache.set_tsbuildinfo(&graph.roots[0], &tsbuildinfo); cache.set_tsbuildinfo(&graph.roots[0], &tsbuildinfo);
}
if diagnostics.is_empty() {
cache.add_check_hash(check_hash);
}
log::debug!("{}", response.stats);
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics.into())
}
} }
if diagnostics.is_empty() {
cache.add_check_hash(check_hash);
}
Ok(CheckResult {
diagnostics,
stats: response.stats,
})
} }
enum CheckHashResult { enum CheckHashResult {
@ -153,17 +189,18 @@ enum CheckHashResult {
/// be used to tell /// be used to tell
fn get_check_hash( fn get_check_hash(
graph: &ModuleGraph, graph: &ModuleGraph,
options: &CheckOptions, type_check_mode: TypeCheckMode,
ts_config: &TsConfig,
) -> CheckHashResult { ) -> CheckHashResult {
let mut hasher = FastInsecureHasher::new(); let mut hasher = FastInsecureHasher::new();
hasher.write_u8(match options.type_check_mode { hasher.write_u8(match type_check_mode {
TypeCheckMode::All => 0, TypeCheckMode::All => 0,
TypeCheckMode::Local => 1, TypeCheckMode::Local => 1,
TypeCheckMode::None => 2, TypeCheckMode::None => 2,
}); });
hasher.write(&options.ts_config.as_bytes()); hasher.write(&ts_config.as_bytes());
let check_js = options.ts_config.get_check_js(); let check_js = ts_config.get_check_js();
let mut sorted_modules = graph.modules().collect::<Vec<_>>(); let mut sorted_modules = graph.modules().collect::<Vec<_>>();
sorted_modules.sort_by_key(|m| m.specifier().as_str()); // make it deterministic sorted_modules.sort_by_key(|m| m.specifier().as_str()); // make it deterministic
let mut has_file = false; let mut has_file = false;