mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
parent
e719a02bb0
commit
387300aed0
6 changed files with 415 additions and 403 deletions
394
cli/emit.rs
394
cli/emit.rs
|
@ -8,25 +8,14 @@ use crate::args::config_file::IgnoredCompilerOptions;
|
||||||
use crate::args::ConfigFile;
|
use crate::args::ConfigFile;
|
||||||
use crate::args::EmitConfigOptions;
|
use crate::args::EmitConfigOptions;
|
||||||
use crate::args::TsConfig;
|
use crate::args::TsConfig;
|
||||||
use crate::args::TypeCheckMode;
|
|
||||||
use crate::cache::EmitCache;
|
use crate::cache::EmitCache;
|
||||||
use crate::cache::FastInsecureHasher;
|
use crate::cache::FastInsecureHasher;
|
||||||
use crate::cache::ParsedSourceCache;
|
use crate::cache::ParsedSourceCache;
|
||||||
use crate::cache::TypeCheckCache;
|
|
||||||
use crate::colors;
|
|
||||||
use crate::diagnostics::Diagnostics;
|
|
||||||
use crate::graph_util::GraphData;
|
|
||||||
use crate::graph_util::ModuleEntry;
|
|
||||||
use crate::tsc;
|
|
||||||
use crate::version;
|
|
||||||
|
|
||||||
use deno_ast::swc::bundler::Hook;
|
use deno_ast::swc::bundler::Hook;
|
||||||
use deno_ast::swc::bundler::ModuleRecord;
|
use deno_ast::swc::bundler::ModuleRecord;
|
||||||
use deno_ast::swc::common::Span;
|
use deno_ast::swc::common::Span;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::RwLock;
|
|
||||||
use deno_core::serde::Deserialize;
|
|
||||||
use deno_core::serde::Deserializer;
|
|
||||||
use deno_core::serde::Serialize;
|
use deno_core::serde::Serialize;
|
||||||
use deno_core::serde::Serializer;
|
use deno_core::serde::Serializer;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
|
@ -34,47 +23,10 @@ use deno_core::serde_json::json;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_graph::MediaType;
|
use deno_graph::MediaType;
|
||||||
use deno_graph::ModuleGraphError;
|
use deno_graph::ModuleGraphError;
|
||||||
use deno_graph::ModuleKind;
|
|
||||||
use deno_graph::ResolutionError;
|
use deno_graph::ResolutionError;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use regex::Regex;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A structure representing stats from an emit operation for a graph.
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct Stats(pub Vec<(String, u32)>);
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Stats {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?;
|
|
||||||
Ok(Stats(items))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Stats {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
Serialize::serialize(&self.0, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Stats {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
writeln!(f, "Compilation statistics:")?;
|
|
||||||
for (key, value) in self.0.clone() {
|
|
||||||
writeln!(f, " {}: {}", key, value)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the "default" type library that should be used when type
|
/// Represents the "default" type library that should be used when type
|
||||||
/// checking the code in the module graph. Note that a user provided config
|
/// checking the code in the module graph. Note that a user provided config
|
||||||
/// of `"lib"` would override this value.
|
/// of `"lib"` would override this value.
|
||||||
|
@ -193,40 +145,6 @@ pub fn get_ts_config_for_emit(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform the graph into root specifiers that we can feed `tsc`. We have to
|
|
||||||
/// provide the media type for root modules because `tsc` does not "resolve" the
|
|
||||||
/// media type like other modules, as well as a root specifier needs any
|
|
||||||
/// redirects resolved. We need to include all the emittable files in
|
|
||||||
/// the roots, so they get type checked and optionally emitted,
|
|
||||||
/// otherwise they would be ignored if only imported into JavaScript.
|
|
||||||
fn get_tsc_roots(
|
|
||||||
graph_data: &GraphData,
|
|
||||||
check_js: bool,
|
|
||||||
) -> Vec<(ModuleSpecifier, MediaType)> {
|
|
||||||
graph_data
|
|
||||||
.entries()
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(specifier, module_entry)| match module_entry {
|
|
||||||
ModuleEntry::Module {
|
|
||||||
media_type, code, ..
|
|
||||||
} => match media_type {
|
|
||||||
MediaType::TypeScript
|
|
||||||
| MediaType::Tsx
|
|
||||||
| MediaType::Mts
|
|
||||||
| MediaType::Cts
|
|
||||||
| MediaType::Jsx => Some((specifier.clone(), *media_type)),
|
|
||||||
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs
|
|
||||||
if check_js || has_ts_check(*media_type, code) =>
|
|
||||||
{
|
|
||||||
Some((specifier.clone(), *media_type))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A hashing function that takes the source code, version and optionally a
|
/// A hashing function that takes the source code, version and optionally a
|
||||||
/// user provided config and generates a string hash which can be stored to
|
/// user provided config and generates a string hash which can be stored to
|
||||||
/// determine if the cached emit is valid or not.
|
/// determine if the cached emit is valid or not.
|
||||||
|
@ -266,193 +184,6 @@ pub fn emit_parsed_source(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options for performing a check of a module graph. Note that the decision to
|
|
||||||
/// emit or not is determined by the `ts_config` settings.
|
|
||||||
pub struct CheckOptions {
|
|
||||||
/// The check flag from the option which can effect the filtering of
|
|
||||||
/// diagnostics in the emit result.
|
|
||||||
pub type_check_mode: TypeCheckMode,
|
|
||||||
/// Set the debug flag on the TypeScript type checker.
|
|
||||||
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
|
|
||||||
/// will always occur.
|
|
||||||
pub reload: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of a check of a module graph.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct CheckResult {
|
|
||||||
pub diagnostics: Diagnostics,
|
|
||||||
pub stats: Stats,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a set of roots and graph data, 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 fn check(
|
|
||||||
roots: &[(ModuleSpecifier, ModuleKind)],
|
|
||||||
graph_data: Arc<RwLock<GraphData>>,
|
|
||||||
cache: &TypeCheckCache,
|
|
||||||
options: CheckOptions,
|
|
||||||
) -> Result<CheckResult, AnyError> {
|
|
||||||
let check_js = options.ts_config.get_check_js();
|
|
||||||
let segment_graph_data = {
|
|
||||||
let graph_data = graph_data.read();
|
|
||||||
graph_data.graph_segment(roots).unwrap()
|
|
||||||
};
|
|
||||||
let check_hash = match get_check_hash(&segment_graph_data, &options) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
let root_names = get_tsc_roots(&segment_graph_data, check_js);
|
|
||||||
if options.log_checks {
|
|
||||||
for (root, _) in roots {
|
|
||||||
let root_str = root.to_string();
|
|
||||||
// `$deno` specifiers are internal, don't print them.
|
|
||||||
if !root_str.contains("$deno") {
|
|
||||||
log::info!("{} {}", colors::green("Check"), root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
// case.
|
|
||||||
let maybe_tsbuildinfo = if options.reload {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
cache.get_tsbuildinfo(&roots[0].0)
|
|
||||||
};
|
|
||||||
// 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
|
|
||||||
// that data here.
|
|
||||||
let hash_data = vec![
|
|
||||||
options.ts_config.as_bytes(),
|
|
||||||
version::deno().as_bytes().to_owned(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let response = tsc::exec(tsc::Request {
|
|
||||||
config: options.ts_config,
|
|
||||||
debug: options.debug,
|
|
||||||
graph_data,
|
|
||||||
hash_data,
|
|
||||||
maybe_config_specifier: options.maybe_config_specifier,
|
|
||||||
maybe_tsbuildinfo,
|
|
||||||
root_names,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let diagnostics = if options.type_check_mode == TypeCheckMode::Local {
|
|
||||||
response.diagnostics.filter(|d| {
|
|
||||||
if let Some(file_name) = &d.file_name {
|
|
||||||
!file_name.starts_with("http")
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
response.diagnostics
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(tsbuildinfo) = response.maybe_tsbuildinfo {
|
|
||||||
cache.set_tsbuildinfo(&roots[0].0, &tsbuildinfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if diagnostics.is_empty() {
|
|
||||||
cache.add_check_hash(check_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CheckResult {
|
|
||||||
diagnostics,
|
|
||||||
stats: response.stats,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CheckHashResult {
|
|
||||||
Hash(u64),
|
|
||||||
NoFiles,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a hash of the inputs for type checking. This can then
|
|
||||||
/// be used to tell
|
|
||||||
fn get_check_hash(
|
|
||||||
graph_data: &GraphData,
|
|
||||||
options: &CheckOptions,
|
|
||||||
) -> CheckHashResult {
|
|
||||||
let mut hasher = FastInsecureHasher::new();
|
|
||||||
hasher.write_u8(match options.type_check_mode {
|
|
||||||
TypeCheckMode::All => 0,
|
|
||||||
TypeCheckMode::Local => 1,
|
|
||||||
TypeCheckMode::None => 2,
|
|
||||||
});
|
|
||||||
hasher.write(&options.ts_config.as_bytes());
|
|
||||||
|
|
||||||
let check_js = options.ts_config.get_check_js();
|
|
||||||
let mut sorted_entries = graph_data.entries().collect::<Vec<_>>();
|
|
||||||
sorted_entries.sort_by_key(|(s, _)| s.as_str()); // make it deterministic
|
|
||||||
let mut has_file = false;
|
|
||||||
let mut has_file_to_type_check = false;
|
|
||||||
for (specifier, module_entry) in sorted_entries {
|
|
||||||
if let ModuleEntry::Module {
|
|
||||||
code, media_type, ..
|
|
||||||
} = module_entry
|
|
||||||
{
|
|
||||||
let ts_check = has_ts_check(*media_type, code);
|
|
||||||
if ts_check {
|
|
||||||
has_file_to_type_check = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
match media_type {
|
|
||||||
MediaType::TypeScript
|
|
||||||
| MediaType::Dts
|
|
||||||
| MediaType::Dmts
|
|
||||||
| MediaType::Dcts
|
|
||||||
| MediaType::Mts
|
|
||||||
| MediaType::Cts
|
|
||||||
| MediaType::Tsx => {
|
|
||||||
has_file = true;
|
|
||||||
has_file_to_type_check = true;
|
|
||||||
}
|
|
||||||
MediaType::JavaScript
|
|
||||||
| MediaType::Mjs
|
|
||||||
| MediaType::Cjs
|
|
||||||
| MediaType::Jsx => {
|
|
||||||
has_file = true;
|
|
||||||
if !check_js && !ts_check {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MediaType::Json
|
|
||||||
| MediaType::TsBuildInfo
|
|
||||||
| MediaType::SourceMap
|
|
||||||
| MediaType::Wasm
|
|
||||||
| MediaType::Unknown => continue,
|
|
||||||
}
|
|
||||||
hasher.write_str(specifier.as_str());
|
|
||||||
hasher.write_str(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has_file || !check_js && !has_file_to_type_check {
|
|
||||||
// no files to type check
|
|
||||||
CheckHashResult::NoFiles
|
|
||||||
} else {
|
|
||||||
CheckHashResult::Hash(hasher.finish())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An adapter struct to make a deno_graph::ModuleGraphError display as expected
|
/// An adapter struct to make a deno_graph::ModuleGraphError display as expected
|
||||||
/// in the Deno CLI.
|
/// in the Deno CLI.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -556,128 +287,3 @@ impl From<TsConfig> for deno_ast::EmitOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Matches the `@ts-check` pragma.
|
|
||||||
static TS_CHECK_RE: Lazy<Regex> =
|
|
||||||
Lazy::new(|| Regex::new(r#"(?i)^\s*@ts-check(?:\s+|$)"#).unwrap());
|
|
||||||
|
|
||||||
fn has_ts_check(media_type: MediaType, file_text: &str) -> bool {
|
|
||||||
match &media_type {
|
|
||||||
MediaType::JavaScript
|
|
||||||
| MediaType::Mjs
|
|
||||||
| MediaType::Cjs
|
|
||||||
| MediaType::Jsx => get_leading_comments(file_text)
|
|
||||||
.iter()
|
|
||||||
.any(|text| TS_CHECK_RE.is_match(text)),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_leading_comments(file_text: &str) -> Vec<String> {
|
|
||||||
let mut chars = file_text.chars().peekable();
|
|
||||||
|
|
||||||
// skip over the shebang
|
|
||||||
if file_text.starts_with("#!") {
|
|
||||||
// skip until the end of the line
|
|
||||||
for c in chars.by_ref() {
|
|
||||||
if c == '\n' {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut results = Vec::new();
|
|
||||||
// now handle the comments
|
|
||||||
while chars.peek().is_some() {
|
|
||||||
// skip over any whitespace
|
|
||||||
while chars
|
|
||||||
.peek()
|
|
||||||
.map(|c| char::is_whitespace(*c))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
chars.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if chars.next() != Some('/') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
match chars.next() {
|
|
||||||
Some('/') => {
|
|
||||||
let mut text = String::new();
|
|
||||||
for c in chars.by_ref() {
|
|
||||||
if c == '\n' {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
text.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results.push(text);
|
|
||||||
}
|
|
||||||
Some('*') => {
|
|
||||||
let mut text = String::new();
|
|
||||||
while let Some(c) = chars.next() {
|
|
||||||
if c == '*' && chars.peek() == Some(&'/') {
|
|
||||||
chars.next();
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
text.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results.push(text);
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use deno_ast::MediaType;
|
|
||||||
|
|
||||||
use super::get_leading_comments;
|
|
||||||
use super::has_ts_check;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_leading_comments_test() {
|
|
||||||
assert_eq!(
|
|
||||||
get_leading_comments(
|
|
||||||
"#!/usr/bin/env deno\r\n// test\n/* 1 *//*2*///3\n//\n /**/ /*4 */"
|
|
||||||
),
|
|
||||||
vec![
|
|
||||||
" test".to_string(),
|
|
||||||
" 1 ".to_string(),
|
|
||||||
"2".to_string(),
|
|
||||||
"3".to_string(),
|
|
||||||
"".to_string(),
|
|
||||||
"".to_string(),
|
|
||||||
"4 ".to_string(),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_leading_comments("//1 /* */ \na;"),
|
|
||||||
vec!["1 /* */ ".to_string(),]
|
|
||||||
);
|
|
||||||
assert_eq!(get_leading_comments("//"), vec!["".to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn has_ts_check_test() {
|
|
||||||
assert!(has_ts_check(
|
|
||||||
MediaType::JavaScript,
|
|
||||||
"// @ts-check\nconsole.log(5);"
|
|
||||||
));
|
|
||||||
assert!(has_ts_check(
|
|
||||||
MediaType::JavaScript,
|
|
||||||
"// deno-lint-ignore\n// @ts-check\n"
|
|
||||||
));
|
|
||||||
assert!(!has_ts_check(
|
|
||||||
MediaType::JavaScript,
|
|
||||||
"test;\n// @ts-check\n"
|
|
||||||
));
|
|
||||||
assert!(!has_ts_check(
|
|
||||||
MediaType::JavaScript,
|
|
||||||
"// ts-check\nconsole.log(5);"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ use crate::graph_util::graph_valid;
|
||||||
use crate::proc_state::ProcState;
|
use crate::proc_state::ProcState;
|
||||||
use crate::resolver::ImportMapResolver;
|
use crate::resolver::ImportMapResolver;
|
||||||
use crate::resolver::JsxResolver;
|
use crate::resolver::JsxResolver;
|
||||||
|
use crate::tools::check;
|
||||||
|
|
||||||
use args::CliOptions;
|
use args::CliOptions;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
|
@ -500,11 +501,11 @@ async fn create_graph_and_maybe_check(
|
||||||
}
|
}
|
||||||
let maybe_config_specifier = ps.options.maybe_config_file_specifier();
|
let maybe_config_specifier = ps.options.maybe_config_file_specifier();
|
||||||
let cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path());
|
let cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path());
|
||||||
let check_result = emit::check(
|
let check_result = check::check(
|
||||||
&graph.roots,
|
&graph.roots,
|
||||||
Arc::new(RwLock::new(graph.as_ref().into())),
|
Arc::new(RwLock::new(graph.as_ref().into())),
|
||||||
&cache,
|
&cache,
|
||||||
emit::CheckOptions {
|
check::CheckOptions {
|
||||||
type_check_mode: ps.options.type_check_mode(),
|
type_check_mode: ps.options.type_check_mode(),
|
||||||
debug,
|
debug,
|
||||||
maybe_config_specifier,
|
maybe_config_specifier,
|
||||||
|
|
|
@ -12,7 +12,6 @@ use crate::cache::TypeCheckCache;
|
||||||
use crate::compat;
|
use crate::compat;
|
||||||
use crate::compat::NodeEsmResolver;
|
use crate::compat::NodeEsmResolver;
|
||||||
use crate::deno_dir;
|
use crate::deno_dir;
|
||||||
use crate::emit;
|
|
||||||
use crate::emit::emit_parsed_source;
|
use crate::emit::emit_parsed_source;
|
||||||
use crate::emit::TsConfigType;
|
use crate::emit::TsConfigType;
|
||||||
use crate::emit::TsTypeLib;
|
use crate::emit::TsTypeLib;
|
||||||
|
@ -29,6 +28,7 @@ use crate::npm::NpmPackageReference;
|
||||||
use crate::npm::NpmPackageResolver;
|
use crate::npm::NpmPackageResolver;
|
||||||
use crate::resolver::ImportMapResolver;
|
use crate::resolver::ImportMapResolver;
|
||||||
use crate::resolver::JsxResolver;
|
use crate::resolver::JsxResolver;
|
||||||
|
use crate::tools::check;
|
||||||
|
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_core::anyhow::anyhow;
|
use deno_core::anyhow::anyhow;
|
||||||
|
@ -453,7 +453,7 @@ impl ProcState {
|
||||||
if self.options.type_check_mode() != TypeCheckMode::None {
|
if self.options.type_check_mode() != TypeCheckMode::None {
|
||||||
let maybe_config_specifier = self.options.maybe_config_file_specifier();
|
let maybe_config_specifier = self.options.maybe_config_file_specifier();
|
||||||
let roots = roots.clone();
|
let roots = roots.clone();
|
||||||
let options = emit::CheckOptions {
|
let options = check::CheckOptions {
|
||||||
type_check_mode: self.options.type_check_mode(),
|
type_check_mode: self.options.type_check_mode(),
|
||||||
debug: self.options.log_level() == Some(log::Level::Debug),
|
debug: self.options.log_level() == Some(log::Level::Debug),
|
||||||
maybe_config_specifier,
|
maybe_config_specifier,
|
||||||
|
@ -469,7 +469,7 @@ impl ProcState {
|
||||||
TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path());
|
TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path());
|
||||||
let graph_data = self.graph_data.clone();
|
let graph_data = self.graph_data.clone();
|
||||||
let check_result =
|
let check_result =
|
||||||
emit::check(&roots, graph_data, &check_cache, options)?;
|
check::check(&roots, graph_data, &check_cache, options)?;
|
||||||
if !check_result.diagnostics.is_empty() {
|
if !check_result.diagnostics.is_empty() {
|
||||||
return Err(anyhow!(check_result.diagnostics));
|
return Err(anyhow!(check_result.diagnostics));
|
||||||
}
|
}
|
||||||
|
|
369
cli/tools/check.rs
Normal file
369
cli/tools/check.rs
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use deno_ast::MediaType;
|
||||||
|
use deno_ast::ModuleSpecifier;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_core::parking_lot::RwLock;
|
||||||
|
use deno_graph::ModuleKind;
|
||||||
|
use deno_runtime::colors;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::args::TsConfig;
|
||||||
|
use crate::args::TypeCheckMode;
|
||||||
|
use crate::cache::FastInsecureHasher;
|
||||||
|
use crate::cache::TypeCheckCache;
|
||||||
|
use crate::diagnostics::Diagnostics;
|
||||||
|
use crate::graph_util::GraphData;
|
||||||
|
use crate::graph_util::ModuleEntry;
|
||||||
|
use crate::tsc;
|
||||||
|
use crate::tsc::Stats;
|
||||||
|
use crate::version;
|
||||||
|
|
||||||
|
/// Options for performing a check of a module graph. Note that the decision to
|
||||||
|
/// emit or not is determined by the `ts_config` settings.
|
||||||
|
pub struct CheckOptions {
|
||||||
|
/// The check flag from the option which can effect the filtering of
|
||||||
|
/// diagnostics in the emit result.
|
||||||
|
pub type_check_mode: TypeCheckMode,
|
||||||
|
/// Set the debug flag on the TypeScript type checker.
|
||||||
|
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
|
||||||
|
/// will always occur.
|
||||||
|
pub reload: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of a check of a module graph.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct CheckResult {
|
||||||
|
pub diagnostics: Diagnostics,
|
||||||
|
pub stats: Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a set of roots and graph data, 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 fn check(
|
||||||
|
roots: &[(ModuleSpecifier, ModuleKind)],
|
||||||
|
graph_data: Arc<RwLock<GraphData>>,
|
||||||
|
cache: &TypeCheckCache,
|
||||||
|
options: CheckOptions,
|
||||||
|
) -> Result<CheckResult, AnyError> {
|
||||||
|
let check_js = options.ts_config.get_check_js();
|
||||||
|
let segment_graph_data = {
|
||||||
|
let graph_data = graph_data.read();
|
||||||
|
graph_data.graph_segment(roots).unwrap()
|
||||||
|
};
|
||||||
|
let check_hash = match get_check_hash(&segment_graph_data, &options) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
let root_names = get_tsc_roots(&segment_graph_data, check_js);
|
||||||
|
if options.log_checks {
|
||||||
|
for (root, _) in roots {
|
||||||
|
let root_str = root.to_string();
|
||||||
|
// `$deno` specifiers are internal, don't print them.
|
||||||
|
if !root_str.contains("$deno") {
|
||||||
|
log::info!("{} {}", colors::green("Check"), root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
// case.
|
||||||
|
let maybe_tsbuildinfo = if options.reload {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
cache.get_tsbuildinfo(&roots[0].0)
|
||||||
|
};
|
||||||
|
// 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
|
||||||
|
// that data here.
|
||||||
|
let hash_data = vec![
|
||||||
|
options.ts_config.as_bytes(),
|
||||||
|
version::deno().as_bytes().to_owned(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let response = tsc::exec(tsc::Request {
|
||||||
|
config: options.ts_config,
|
||||||
|
debug: options.debug,
|
||||||
|
graph_data,
|
||||||
|
hash_data,
|
||||||
|
maybe_config_specifier: options.maybe_config_specifier,
|
||||||
|
maybe_tsbuildinfo,
|
||||||
|
root_names,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let diagnostics = if options.type_check_mode == TypeCheckMode::Local {
|
||||||
|
response.diagnostics.filter(|d| {
|
||||||
|
if let Some(file_name) = &d.file_name {
|
||||||
|
!file_name.starts_with("http")
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
response.diagnostics
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tsbuildinfo) = response.maybe_tsbuildinfo {
|
||||||
|
cache.set_tsbuildinfo(&roots[0].0, &tsbuildinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if diagnostics.is_empty() {
|
||||||
|
cache.add_check_hash(check_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CheckResult {
|
||||||
|
diagnostics,
|
||||||
|
stats: response.stats,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CheckHashResult {
|
||||||
|
Hash(u64),
|
||||||
|
NoFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a hash of the inputs for type checking. This can then
|
||||||
|
/// be used to tell
|
||||||
|
fn get_check_hash(
|
||||||
|
graph_data: &GraphData,
|
||||||
|
options: &CheckOptions,
|
||||||
|
) -> CheckHashResult {
|
||||||
|
let mut hasher = FastInsecureHasher::new();
|
||||||
|
hasher.write_u8(match options.type_check_mode {
|
||||||
|
TypeCheckMode::All => 0,
|
||||||
|
TypeCheckMode::Local => 1,
|
||||||
|
TypeCheckMode::None => 2,
|
||||||
|
});
|
||||||
|
hasher.write(&options.ts_config.as_bytes());
|
||||||
|
|
||||||
|
let check_js = options.ts_config.get_check_js();
|
||||||
|
let mut sorted_entries = graph_data.entries().collect::<Vec<_>>();
|
||||||
|
sorted_entries.sort_by_key(|(s, _)| s.as_str()); // make it deterministic
|
||||||
|
let mut has_file = false;
|
||||||
|
let mut has_file_to_type_check = false;
|
||||||
|
for (specifier, module_entry) in sorted_entries {
|
||||||
|
if let ModuleEntry::Module {
|
||||||
|
code, media_type, ..
|
||||||
|
} = module_entry
|
||||||
|
{
|
||||||
|
let ts_check = has_ts_check(*media_type, code);
|
||||||
|
if ts_check {
|
||||||
|
has_file_to_type_check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
match media_type {
|
||||||
|
MediaType::TypeScript
|
||||||
|
| MediaType::Dts
|
||||||
|
| MediaType::Dmts
|
||||||
|
| MediaType::Dcts
|
||||||
|
| MediaType::Mts
|
||||||
|
| MediaType::Cts
|
||||||
|
| MediaType::Tsx => {
|
||||||
|
has_file = true;
|
||||||
|
has_file_to_type_check = true;
|
||||||
|
}
|
||||||
|
MediaType::JavaScript
|
||||||
|
| MediaType::Mjs
|
||||||
|
| MediaType::Cjs
|
||||||
|
| MediaType::Jsx => {
|
||||||
|
has_file = true;
|
||||||
|
if !check_js && !ts_check {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaType::Json
|
||||||
|
| MediaType::TsBuildInfo
|
||||||
|
| MediaType::SourceMap
|
||||||
|
| MediaType::Wasm
|
||||||
|
| MediaType::Unknown => continue,
|
||||||
|
}
|
||||||
|
hasher.write_str(specifier.as_str());
|
||||||
|
hasher.write_str(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_file || !check_js && !has_file_to_type_check {
|
||||||
|
// no files to type check
|
||||||
|
CheckHashResult::NoFiles
|
||||||
|
} else {
|
||||||
|
CheckHashResult::Hash(hasher.finish())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform the graph into root specifiers that we can feed `tsc`. We have to
|
||||||
|
/// provide the media type for root modules because `tsc` does not "resolve" the
|
||||||
|
/// media type like other modules, as well as a root specifier needs any
|
||||||
|
/// redirects resolved. We need to include all the emittable files in
|
||||||
|
/// the roots, so they get type checked and optionally emitted,
|
||||||
|
/// otherwise they would be ignored if only imported into JavaScript.
|
||||||
|
fn get_tsc_roots(
|
||||||
|
graph_data: &GraphData,
|
||||||
|
check_js: bool,
|
||||||
|
) -> Vec<(ModuleSpecifier, MediaType)> {
|
||||||
|
graph_data
|
||||||
|
.entries()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(specifier, module_entry)| match module_entry {
|
||||||
|
ModuleEntry::Module {
|
||||||
|
media_type, code, ..
|
||||||
|
} => match media_type {
|
||||||
|
MediaType::TypeScript
|
||||||
|
| MediaType::Tsx
|
||||||
|
| MediaType::Mts
|
||||||
|
| MediaType::Cts
|
||||||
|
| MediaType::Jsx => Some((specifier.clone(), *media_type)),
|
||||||
|
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs
|
||||||
|
if check_js || has_ts_check(*media_type, code) =>
|
||||||
|
{
|
||||||
|
Some((specifier.clone(), *media_type))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches the `@ts-check` pragma.
|
||||||
|
static TS_CHECK_RE: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r#"(?i)^\s*@ts-check(?:\s+|$)"#).unwrap());
|
||||||
|
|
||||||
|
fn has_ts_check(media_type: MediaType, file_text: &str) -> bool {
|
||||||
|
match &media_type {
|
||||||
|
MediaType::JavaScript
|
||||||
|
| MediaType::Mjs
|
||||||
|
| MediaType::Cjs
|
||||||
|
| MediaType::Jsx => get_leading_comments(file_text)
|
||||||
|
.iter()
|
||||||
|
.any(|text| TS_CHECK_RE.is_match(text)),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_leading_comments(file_text: &str) -> Vec<String> {
|
||||||
|
let mut chars = file_text.chars().peekable();
|
||||||
|
|
||||||
|
// skip over the shebang
|
||||||
|
if file_text.starts_with("#!") {
|
||||||
|
// skip until the end of the line
|
||||||
|
for c in chars.by_ref() {
|
||||||
|
if c == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
// now handle the comments
|
||||||
|
while chars.peek().is_some() {
|
||||||
|
// skip over any whitespace
|
||||||
|
while chars
|
||||||
|
.peek()
|
||||||
|
.map(|c| char::is_whitespace(*c))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if chars.next() != Some('/') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match chars.next() {
|
||||||
|
Some('/') => {
|
||||||
|
let mut text = String::new();
|
||||||
|
for c in chars.by_ref() {
|
||||||
|
if c == '\n' {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
text.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.push(text);
|
||||||
|
}
|
||||||
|
Some('*') => {
|
||||||
|
let mut text = String::new();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if c == '*' && chars.peek() == Some(&'/') {
|
||||||
|
chars.next();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
text.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.push(text);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use deno_ast::MediaType;
|
||||||
|
|
||||||
|
use super::get_leading_comments;
|
||||||
|
use super::has_ts_check;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_leading_comments_test() {
|
||||||
|
assert_eq!(
|
||||||
|
get_leading_comments(
|
||||||
|
"#!/usr/bin/env deno\r\n// test\n/* 1 *//*2*///3\n//\n /**/ /*4 */"
|
||||||
|
),
|
||||||
|
vec![
|
||||||
|
" test".to_string(),
|
||||||
|
" 1 ".to_string(),
|
||||||
|
"2".to_string(),
|
||||||
|
"3".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
"4 ".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_leading_comments("//1 /* */ \na;"),
|
||||||
|
vec!["1 /* */ ".to_string(),]
|
||||||
|
);
|
||||||
|
assert_eq!(get_leading_comments("//"), vec!["".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_ts_check_test() {
|
||||||
|
assert!(has_ts_check(
|
||||||
|
MediaType::JavaScript,
|
||||||
|
"// @ts-check\nconsole.log(5);"
|
||||||
|
));
|
||||||
|
assert!(has_ts_check(
|
||||||
|
MediaType::JavaScript,
|
||||||
|
"// deno-lint-ignore\n// @ts-check\n"
|
||||||
|
));
|
||||||
|
assert!(!has_ts_check(
|
||||||
|
MediaType::JavaScript,
|
||||||
|
"test;\n// @ts-check\n"
|
||||||
|
));
|
||||||
|
assert!(!has_ts_check(
|
||||||
|
MediaType::JavaScript,
|
||||||
|
"// ts-check\nconsole.log(5);"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
pub mod bench;
|
pub mod bench;
|
||||||
|
pub mod check;
|
||||||
pub mod coverage;
|
pub mod coverage;
|
||||||
pub mod doc;
|
pub mod doc;
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
|
|
43
cli/tsc.rs
43
cli/tsc.rs
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use crate::args::TsConfig;
|
use crate::args::TsConfig;
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
use crate::emit;
|
|
||||||
use crate::graph_util::GraphData;
|
use crate::graph_util::GraphData;
|
||||||
use crate::graph_util::ModuleEntry;
|
use crate::graph_util::ModuleEntry;
|
||||||
|
|
||||||
|
@ -15,7 +14,9 @@ use deno_core::op;
|
||||||
use deno_core::parking_lot::RwLock;
|
use deno_core::parking_lot::RwLock;
|
||||||
use deno_core::resolve_url_or_path;
|
use deno_core::resolve_url_or_path;
|
||||||
use deno_core::serde::Deserialize;
|
use deno_core::serde::Deserialize;
|
||||||
|
use deno_core::serde::Deserializer;
|
||||||
use deno_core::serde::Serialize;
|
use deno_core::serde::Serialize;
|
||||||
|
use deno_core::serde::Serializer;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::serde_json::Value;
|
use deno_core::serde_json::Value;
|
||||||
|
@ -28,6 +29,7 @@ use deno_core::Snapshot;
|
||||||
use deno_graph::Resolved;
|
use deno_graph::Resolved;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -115,6 +117,40 @@ pub static STATIC_ASSETS: Lazy<HashMap<&'static str, &'static str>> =
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// A structure representing stats from a type check operation for a graph.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct Stats(pub Vec<(String, u32)>);
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Stats {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?;
|
||||||
|
Ok(Stats(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Stats {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
Serialize::serialize(&self.0, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Stats {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
writeln!(f, "Compilation statistics:")?;
|
||||||
|
for (key, value) in self.0.clone() {
|
||||||
|
writeln!(f, " {}: {}", key, value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve a static asset that are included in the binary.
|
/// Retrieve a static asset that are included in the binary.
|
||||||
pub fn get_asset(asset: &str) -> Option<&'static str> {
|
pub fn get_asset(asset: &str) -> Option<&'static str> {
|
||||||
STATIC_ASSETS.get(asset).map(|s| s.to_owned())
|
STATIC_ASSETS.get(asset).map(|s| s.to_owned())
|
||||||
|
@ -255,7 +291,7 @@ pub struct Response {
|
||||||
/// If there was any build info associated with the exec request.
|
/// If there was any build info associated with the exec request.
|
||||||
pub maybe_tsbuildinfo: Option<String>,
|
pub maybe_tsbuildinfo: Option<String>,
|
||||||
/// Statistics from the check.
|
/// Statistics from the check.
|
||||||
pub stats: emit::Stats,
|
pub stats: Stats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -565,7 +601,7 @@ fn op_resolve(
|
||||||
#[derive(Debug, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Deserialize, Eq, PartialEq)]
|
||||||
struct RespondArgs {
|
struct RespondArgs {
|
||||||
pub diagnostics: Diagnostics,
|
pub diagnostics: Diagnostics,
|
||||||
pub stats: emit::Stats,
|
pub stats: Stats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
|
@ -674,7 +710,6 @@ mod tests {
|
||||||
use crate::args::TsConfig;
|
use crate::args::TsConfig;
|
||||||
use crate::diagnostics::Diagnostic;
|
use crate::diagnostics::Diagnostic;
|
||||||
use crate::diagnostics::DiagnosticCategory;
|
use crate::diagnostics::DiagnosticCategory;
|
||||||
use crate::emit::Stats;
|
|
||||||
use deno_core::futures::future;
|
use deno_core::futures::future;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_graph::ModuleKind;
|
use deno_graph::ModuleKind;
|
||||||
|
|
Loading…
Reference in a new issue