summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/emit.rs394
-rw-r--r--cli/main.rs5
-rw-r--r--cli/proc_state.rs6
-rw-r--r--cli/tools/check.rs369
-rw-r--r--cli/tools/mod.rs1
-rw-r--r--cli/tsc.rs43
6 files changed, 415 insertions, 403 deletions
diff --git a/cli/emit.rs b/cli/emit.rs
index eb8a56ad0..f2d890adc 100644
--- a/cli/emit.rs
+++ b/cli/emit.rs
@@ -8,25 +8,14 @@ use crate::args::config_file::IgnoredCompilerOptions;
use crate::args::ConfigFile;
use crate::args::EmitConfigOptions;
use crate::args::TsConfig;
-use crate::args::TypeCheckMode;
use crate::cache::EmitCache;
use crate::cache::FastInsecureHasher;
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::ModuleRecord;
use deno_ast::swc::common::Span;
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::Serializer;
use deno_core::serde_json;
@@ -34,47 +23,10 @@ use deno_core::serde_json::json;
use deno_core::ModuleSpecifier;
use deno_graph::MediaType;
use deno_graph::ModuleGraphError;
-use deno_graph::ModuleKind;
use deno_graph::ResolutionError;
-use once_cell::sync::Lazy;
-use regex::Regex;
use std::fmt;
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
/// checking the code in the module graph. Note that a user provided config
/// 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
/// user provided config and generates a string hash which can be stored to
/// 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
/// in the Deno CLI.
#[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);"
- ));
- }
-}
diff --git a/cli/main.rs b/cli/main.rs
index 391b4ff0d..0c4b5c893 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -73,6 +73,7 @@ use crate::graph_util::graph_valid;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
+use crate::tools::check;
use args::CliOptions;
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 cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path());
- let check_result = emit::check(
+ let check_result = check::check(
&graph.roots,
Arc::new(RwLock::new(graph.as_ref().into())),
&cache,
- emit::CheckOptions {
+ check::CheckOptions {
type_check_mode: ps.options.type_check_mode(),
debug,
maybe_config_specifier,
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index 42c34686f..7d29cd89f 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -12,7 +12,6 @@ use crate::cache::TypeCheckCache;
use crate::compat;
use crate::compat::NodeEsmResolver;
use crate::deno_dir;
-use crate::emit;
use crate::emit::emit_parsed_source;
use crate::emit::TsConfigType;
use crate::emit::TsTypeLib;
@@ -29,6 +28,7 @@ use crate::npm::NpmPackageReference;
use crate::npm::NpmPackageResolver;
use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
+use crate::tools::check;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
@@ -453,7 +453,7 @@ impl ProcState {
if self.options.type_check_mode() != TypeCheckMode::None {
let maybe_config_specifier = self.options.maybe_config_file_specifier();
let roots = roots.clone();
- let options = emit::CheckOptions {
+ let options = check::CheckOptions {
type_check_mode: self.options.type_check_mode(),
debug: self.options.log_level() == Some(log::Level::Debug),
maybe_config_specifier,
@@ -469,7 +469,7 @@ impl ProcState {
TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path());
let graph_data = self.graph_data.clone();
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() {
return Err(anyhow!(check_result.diagnostics));
}
diff --git a/cli/tools/check.rs b/cli/tools/check.rs
new file mode 100644
index 000000000..bb0b873f4
--- /dev/null
+++ b/cli/tools/check.rs
@@ -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);"
+ ));
+ }
+}
diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs
index 11c904776..8ef30189e 100644
--- a/cli/tools/mod.rs
+++ b/cli/tools/mod.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
pub mod bench;
+pub mod check;
pub mod coverage;
pub mod doc;
pub mod fmt;
diff --git a/cli/tsc.rs b/cli/tsc.rs
index 5b99d0456..54989a4ed 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -2,7 +2,6 @@
use crate::args::TsConfig;
use crate::diagnostics::Diagnostics;
-use crate::emit;
use crate::graph_util::GraphData;
use crate::graph_util::ModuleEntry;
@@ -15,7 +14,9 @@ use deno_core::op;
use deno_core::parking_lot::RwLock;
use deno_core::resolve_url_or_path;
use deno_core::serde::Deserialize;
+use deno_core::serde::Deserializer;
use deno_core::serde::Serialize;
+use deno_core::serde::Serializer;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
@@ -28,6 +29,7 @@ use deno_core::Snapshot;
use deno_graph::Resolved;
use once_cell::sync::Lazy;
use std::collections::HashMap;
+use std::fmt;
use std::path::PathBuf;
use std::sync::Arc;
@@ -115,6 +117,40 @@ pub static STATIC_ASSETS: Lazy<HashMap<&'static str, &'static str>> =
.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.
pub fn get_asset(asset: &str) -> Option<&'static str> {
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.
pub maybe_tsbuildinfo: Option<String>,
/// Statistics from the check.
- pub stats: emit::Stats,
+ pub stats: Stats,
}
#[derive(Debug)]
@@ -565,7 +601,7 @@ fn op_resolve(
#[derive(Debug, Deserialize, Eq, PartialEq)]
struct RespondArgs {
pub diagnostics: Diagnostics,
- pub stats: emit::Stats,
+ pub stats: Stats,
}
#[op]
@@ -674,7 +710,6 @@ mod tests {
use crate::args::TsConfig;
use crate::diagnostics::Diagnostic;
use crate::diagnostics::DiagnosticCategory;
- use crate::emit::Stats;
use deno_core::futures::future;
use deno_core::OpState;
use deno_graph::ModuleKind;