mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 13:00:36 -05:00
feat(check): compiler options from workspace members
This commit is contained in:
parent
9c0e6369b2
commit
d52ccd2834
11 changed files with 286 additions and 8 deletions
|
@ -68,6 +68,7 @@ use once_cell::sync::Lazy;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
@ -790,6 +791,15 @@ struct CliOptionOverrides {
|
||||||
import_map_specifier: Option<Option<ModuleSpecifier>>,
|
import_map_specifier: Option<Option<ModuleSpecifier>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overrides for the options below that when set will
|
||||||
|
/// use these values over the values derived from the
|
||||||
|
/// CLI flags or config file.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ScopeOptions {
|
||||||
|
pub scope: Option<Arc<ModuleSpecifier>>,
|
||||||
|
pub all_scopes: Arc<BTreeSet<Arc<ModuleSpecifier>>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds the resolved options of many sources used by subcommands
|
/// Holds the resolved options of many sources used by subcommands
|
||||||
/// and provides some helper function for creating common objects.
|
/// and provides some helper function for creating common objects.
|
||||||
pub struct CliOptions {
|
pub struct CliOptions {
|
||||||
|
@ -804,6 +814,7 @@ pub struct CliOptions {
|
||||||
overrides: CliOptionOverrides,
|
overrides: CliOptionOverrides,
|
||||||
pub start_dir: Arc<WorkspaceDirectory>,
|
pub start_dir: Arc<WorkspaceDirectory>,
|
||||||
pub deno_dir_provider: Arc<DenoDirProvider>,
|
pub deno_dir_provider: Arc<DenoDirProvider>,
|
||||||
|
pub scope_options: Option<Arc<ScopeOptions>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliOptions {
|
impl CliOptions {
|
||||||
|
@ -814,6 +825,7 @@ impl CliOptions {
|
||||||
npmrc: Arc<ResolvedNpmRc>,
|
npmrc: Arc<ResolvedNpmRc>,
|
||||||
start_dir: Arc<WorkspaceDirectory>,
|
start_dir: Arc<WorkspaceDirectory>,
|
||||||
force_global_cache: bool,
|
force_global_cache: bool,
|
||||||
|
scope_options: Option<Arc<ScopeOptions>>,
|
||||||
) -> Result<Self, AnyError> {
|
) -> Result<Self, AnyError> {
|
||||||
if let Some(insecure_allowlist) =
|
if let Some(insecure_allowlist) =
|
||||||
flags.unsafely_ignore_certificate_errors.as_ref()
|
flags.unsafely_ignore_certificate_errors.as_ref()
|
||||||
|
@ -853,6 +865,7 @@ impl CliOptions {
|
||||||
main_module_cell: std::sync::OnceLock::new(),
|
main_module_cell: std::sync::OnceLock::new(),
|
||||||
start_dir,
|
start_dir,
|
||||||
deno_dir_provider,
|
deno_dir_provider,
|
||||||
|
scope_options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -937,6 +950,7 @@ impl CliOptions {
|
||||||
npmrc,
|
npmrc,
|
||||||
Arc::new(start_dir),
|
Arc::new(start_dir),
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1410,9 +1410,17 @@ impl ConfigData {
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ts_config = LspTsConfig::new(
|
// TODO(nayeemrmn): This is a hack to get member-specific compiler options.
|
||||||
member_dir.workspace.root_deno_json().map(|c| c.as_ref()),
|
let ts_config = if let Some(config_file) = member_dir
|
||||||
);
|
.maybe_deno_json()
|
||||||
|
.filter(|c| c.json.compiler_options.is_some())
|
||||||
|
{
|
||||||
|
LspTsConfig::new(Some(config_file))
|
||||||
|
} else {
|
||||||
|
LspTsConfig::new(
|
||||||
|
member_dir.workspace.root_deno_json().map(|c| c.as_ref()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let deno_lint_config =
|
let deno_lint_config =
|
||||||
if ts_config.inner.0.get("jsx").and_then(|v| v.as_str()) == Some("react")
|
if ts_config.inner.0.get("jsx").and_then(|v| v.as_str()) == Some("react")
|
||||||
|
|
|
@ -3681,6 +3681,7 @@ impl Inner {
|
||||||
.unwrap_or_else(create_default_npmrc),
|
.unwrap_or_else(create_default_npmrc),
|
||||||
workspace,
|
workspace,
|
||||||
force_global_cache,
|
force_global_cache,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let open_docs = self.documents.documents(DocumentsFilter::OpenDiagnosable);
|
let open_docs = self.documents.documents(DocumentsFilter::OpenDiagnosable);
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
|
use deno_config::deno_json::get_ts_config_for_emit;
|
||||||
|
use deno_config::glob::PathOrPattern;
|
||||||
|
use deno_core::anyhow::anyhow;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_graph::Module;
|
use deno_graph::Module;
|
||||||
use deno_graph::ModuleGraph;
|
use deno_graph::ModuleGraph;
|
||||||
|
@ -15,9 +20,15 @@ use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::args::check_warn_tsconfig;
|
use crate::args::check_warn_tsconfig;
|
||||||
|
use crate::args::discover_npmrc_from_workspace;
|
||||||
use crate::args::CheckFlags;
|
use crate::args::CheckFlags;
|
||||||
|
use crate::args::CliLockfile;
|
||||||
use crate::args::CliOptions;
|
use crate::args::CliOptions;
|
||||||
|
use crate::args::ConfigFlag;
|
||||||
|
use crate::args::FileFlags;
|
||||||
use crate::args::Flags;
|
use crate::args::Flags;
|
||||||
|
use crate::args::LintFlags;
|
||||||
|
use crate::args::ScopeOptions;
|
||||||
use crate::args::TsConfig;
|
use crate::args::TsConfig;
|
||||||
use crate::args::TsConfigType;
|
use crate::args::TsConfigType;
|
||||||
use crate::args::TsTypeLib;
|
use crate::args::TsTypeLib;
|
||||||
|
@ -40,6 +51,159 @@ pub async fn check(
|
||||||
flags: Arc<Flags>,
|
flags: Arc<Flags>,
|
||||||
check_flags: CheckFlags,
|
check_flags: CheckFlags,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let is_discovered_config = match flags.config_flag {
|
||||||
|
ConfigFlag::Discover => true,
|
||||||
|
ConfigFlag::Path(_) => false,
|
||||||
|
ConfigFlag::Disabled => false,
|
||||||
|
};
|
||||||
|
if is_discovered_config {
|
||||||
|
let factory = CliFactory::from_flags(flags.clone());
|
||||||
|
let cli_options = factory.cli_options()?;
|
||||||
|
let (remote_files, files) = check_flags
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.partition::<Vec<_>, _>(|f| {
|
||||||
|
f.starts_with("http://")
|
||||||
|
|| f.starts_with("https://")
|
||||||
|
|| f.starts_with("npm:")
|
||||||
|
|| f.starts_with("jsr:")
|
||||||
|
});
|
||||||
|
let cwd_prefix = format!(
|
||||||
|
"{}{}",
|
||||||
|
cli_options.initial_cwd().to_string_lossy(),
|
||||||
|
std::path::MAIN_SEPARATOR
|
||||||
|
);
|
||||||
|
// TODO(nayeemrmn): Using lint options for now. Add proper API to deno_config.
|
||||||
|
let mut by_workspace_directory = cli_options
|
||||||
|
.resolve_lint_options_for_members(&LintFlags {
|
||||||
|
files: FileFlags {
|
||||||
|
ignore: Default::default(),
|
||||||
|
include: files,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(d, o)| {
|
||||||
|
let files = o
|
||||||
|
.files
|
||||||
|
.include
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| {
|
||||||
|
p.inner().iter().flat_map(|p| match p {
|
||||||
|
PathOrPattern::NegatedPath(_) => None,
|
||||||
|
PathOrPattern::Path(p) => Some(p.to_string_lossy().to_string()),
|
||||||
|
PathOrPattern::Pattern(p) => {
|
||||||
|
// TODO(nayeemrmn): Absolute globs don't work for specifier
|
||||||
|
// collection, we make them relative here for now.
|
||||||
|
let s = p.as_str();
|
||||||
|
Some(s.strip_prefix(&cwd_prefix).unwrap_or(&s).to_string())
|
||||||
|
}
|
||||||
|
PathOrPattern::RemoteUrl(_) => None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
(d.dir_url().clone(), (Arc::new(d), files))
|
||||||
|
})
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
if !remote_files.is_empty() {
|
||||||
|
by_workspace_directory
|
||||||
|
.entry(cli_options.start_dir.dir_url().clone())
|
||||||
|
.or_insert((cli_options.start_dir.clone(), vec![]))
|
||||||
|
.1
|
||||||
|
.extend(remote_files);
|
||||||
|
}
|
||||||
|
let all_scopes = Arc::new(
|
||||||
|
by_workspace_directory
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, (d, _))| d.has_deno_or_pkg_json())
|
||||||
|
.map(|(s, (_, _))| s.clone())
|
||||||
|
.collect::<BTreeSet<_>>(),
|
||||||
|
);
|
||||||
|
let dir_count = by_workspace_directory.len();
|
||||||
|
let mut check_errors = vec![];
|
||||||
|
let mut found_specifiers = false;
|
||||||
|
for (dir_url, (workspace_directory, files)) in by_workspace_directory {
|
||||||
|
let check_flags = CheckFlags {
|
||||||
|
files,
|
||||||
|
doc: check_flags.doc,
|
||||||
|
doc_only: check_flags.doc_only,
|
||||||
|
};
|
||||||
|
let (npmrc, _) =
|
||||||
|
discover_npmrc_from_workspace(&workspace_directory.workspace)?;
|
||||||
|
let lockfile =
|
||||||
|
CliLockfile::discover(&flags, &workspace_directory.workspace)?;
|
||||||
|
let scope_options = (dir_count > 1).then(|| ScopeOptions {
|
||||||
|
scope: workspace_directory
|
||||||
|
.has_deno_or_pkg_json()
|
||||||
|
.then(|| dir_url.clone()),
|
||||||
|
all_scopes: all_scopes.clone(),
|
||||||
|
});
|
||||||
|
let cli_options = CliOptions::new(
|
||||||
|
flags.clone(),
|
||||||
|
cli_options.initial_cwd().to_path_buf(),
|
||||||
|
lockfile.map(Arc::new),
|
||||||
|
npmrc,
|
||||||
|
workspace_directory,
|
||||||
|
false,
|
||||||
|
scope_options.map(Arc::new),
|
||||||
|
)?;
|
||||||
|
let factory = CliFactory::from_cli_options(Arc::new(cli_options));
|
||||||
|
let main_graph_container = factory.main_module_graph_container().await?;
|
||||||
|
let specifiers =
|
||||||
|
main_graph_container.collect_specifiers(&check_flags.files)?;
|
||||||
|
if specifiers.is_empty() {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
found_specifiers = true;
|
||||||
|
}
|
||||||
|
let specifiers_for_typecheck = if check_flags.doc || check_flags.doc_only
|
||||||
|
{
|
||||||
|
let file_fetcher = factory.file_fetcher()?;
|
||||||
|
let root_permissions = factory.root_permissions_container()?;
|
||||||
|
let mut specifiers_for_typecheck = if check_flags.doc {
|
||||||
|
specifiers.clone()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
for s in specifiers {
|
||||||
|
let file = file_fetcher.fetch(&s, root_permissions).await?;
|
||||||
|
let snippet_files = extract::extract_snippet_files(file)?;
|
||||||
|
for snippet_file in snippet_files {
|
||||||
|
specifiers_for_typecheck.push(snippet_file.specifier.clone());
|
||||||
|
file_fetcher.insert_memory_files(snippet_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
specifiers_for_typecheck
|
||||||
|
} else {
|
||||||
|
specifiers
|
||||||
|
};
|
||||||
|
if let Err(err) = main_graph_container
|
||||||
|
.check_specifiers(&specifiers_for_typecheck, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
check_errors.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_specifiers {
|
||||||
|
log::warn!("{} No matching files found.", colors::yellow("Warning"));
|
||||||
|
}
|
||||||
|
if !check_errors.is_empty() {
|
||||||
|
// TODO(nayeemrmn): More integrated way of concatenating diagnostics from
|
||||||
|
// different checks.
|
||||||
|
return Err(anyhow!(
|
||||||
|
"{}",
|
||||||
|
check_errors
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n\n"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let factory = CliFactory::from_flags(flags);
|
let factory = CliFactory::from_flags(flags);
|
||||||
|
|
||||||
let main_graph_container = factory.main_module_graph_container().await?;
|
let main_graph_container = factory.main_module_graph_container().await?;
|
||||||
|
@ -168,9 +332,22 @@ impl TypeChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("Type checking.");
|
log::debug!("Type checking.");
|
||||||
let ts_config_result = self
|
// TODO(nayeemrmn): This is a hack to get member-specific compiler options.
|
||||||
|
let ts_config_result = if let Some(config_file) = self
|
||||||
.cli_options
|
.cli_options
|
||||||
.resolve_ts_config_for_emit(TsConfigType::Check { lib: options.lib })?;
|
.start_dir
|
||||||
|
.maybe_deno_json()
|
||||||
|
.filter(|c| c.json.compiler_options.is_some())
|
||||||
|
{
|
||||||
|
get_ts_config_for_emit(
|
||||||
|
TsConfigType::Check { lib: options.lib },
|
||||||
|
Some(config_file),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
.cli_options
|
||||||
|
.resolve_ts_config_for_emit(TsConfigType::Check { lib: options.lib })?
|
||||||
|
};
|
||||||
if options.log_ignored_options {
|
if options.log_ignored_options {
|
||||||
check_warn_tsconfig(&ts_config_result);
|
check_warn_tsconfig(&ts_config_result);
|
||||||
}
|
}
|
||||||
|
@ -259,10 +436,33 @@ impl TypeChecker {
|
||||||
|
|
||||||
let mut diagnostics = response.diagnostics.filter(|d| {
|
let mut diagnostics = response.diagnostics.filter(|d| {
|
||||||
if self.is_remote_diagnostic(d) {
|
if self.is_remote_diagnostic(d) {
|
||||||
type_check_mode == TypeCheckMode::All && d.include_when_remote()
|
return type_check_mode == TypeCheckMode::All
|
||||||
} else {
|
&& d.include_when_remote()
|
||||||
true
|
&& self
|
||||||
|
.cli_options
|
||||||
|
.scope_options
|
||||||
|
.as_ref()
|
||||||
|
.map(|o| o.scope.is_none())
|
||||||
|
.unwrap_or(true);
|
||||||
}
|
}
|
||||||
|
let Some(scope_options) = &self.cli_options.scope_options else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
let Some(specifier) = d
|
||||||
|
.file_name
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| ModuleSpecifier::parse(s).ok())
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if specifier.scheme() != "file" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let scope = scope_options
|
||||||
|
.all_scopes
|
||||||
|
.iter()
|
||||||
|
.rfind(|s| specifier.as_str().starts_with(s.as_str()));
|
||||||
|
scope == scope_options.scope.as_ref()
|
||||||
});
|
});
|
||||||
|
|
||||||
diagnostics.apply_fast_check_source_maps(&graph);
|
diagnostics.apply_fast_check_source_maps(&graph);
|
||||||
|
|
14
tests/specs/check/check_workspace/__test__.jsonc
Normal file
14
tests/specs/check/check_workspace/__test__.jsonc
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"tests": {
|
||||||
|
"discover": {
|
||||||
|
"args": "check --quiet main.ts member/mod.ts",
|
||||||
|
"output": "check_discover.out",
|
||||||
|
"exitCode": 1
|
||||||
|
},
|
||||||
|
"config_flag": {
|
||||||
|
"args": "check --quiet --config deno.json main.ts member/mod.ts",
|
||||||
|
"output": "check_config_flag.out",
|
||||||
|
"exitCode": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
tests/specs/check/check_workspace/check_config_flag.out
Normal file
11
tests/specs/check/check_workspace/check_config_flag.out
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
error: TS2304 [ERROR]: Cannot find name 'onmessage'.
|
||||||
|
onmessage;
|
||||||
|
~~~~~~~~~
|
||||||
|
at file:///home/nayeem/projects/deno/tests/specs/check/check_workspace/main.ts:8:1
|
||||||
|
|
||||||
|
TS2304 [ERROR]: Cannot find name 'onmessage'.
|
||||||
|
onmessage;
|
||||||
|
~~~~~~~~~
|
||||||
|
at file:///home/nayeem/projects/deno/tests/specs/check/check_workspace/member/mod.ts:5:1
|
||||||
|
|
||||||
|
Found 2 errors.
|
9
tests/specs/check/check_workspace/check_discover.out
Normal file
9
tests/specs/check/check_workspace/check_discover.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
error: TS2304 [ERROR]: Cannot find name 'onmessage'.
|
||||||
|
onmessage;
|
||||||
|
~~~~~~~~~
|
||||||
|
at file:///[WILDCARD]/main.ts:8:1
|
||||||
|
|
||||||
|
TS2304 [ERROR]: Cannot find name 'localStorage'.
|
||||||
|
localStorage;
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
at file:///[WILDCARD]/member/mod.ts:2:1
|
3
tests/specs/check/check_workspace/deno.json
Normal file
3
tests/specs/check/check_workspace/deno.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"workspace": ["member"]
|
||||||
|
}
|
8
tests/specs/check/check_workspace/main.ts
Normal file
8
tests/specs/check/check_workspace/main.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// We shouldn't get diagnostics from this import under this check scope.
|
||||||
|
import "./member/mod.ts";
|
||||||
|
|
||||||
|
// Only defined for window.
|
||||||
|
localStorage;
|
||||||
|
|
||||||
|
// Only defined for worker.
|
||||||
|
onmessage;
|
5
tests/specs/check/check_workspace/member/deno.json
Normal file
5
tests/specs/check/check_workspace/member/deno.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["deno.worker"]
|
||||||
|
}
|
||||||
|
}
|
5
tests/specs/check/check_workspace/member/mod.ts
Normal file
5
tests/specs/check/check_workspace/member/mod.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Only defined for window.
|
||||||
|
localStorage;
|
||||||
|
|
||||||
|
// Only defined for worker.
|
||||||
|
onmessage;
|
Loading…
Add table
Reference in a new issue