mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 04:52:26 -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::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::io::BufReader;
|
||||
|
@ -790,6 +791,15 @@ struct CliOptionOverrides {
|
|||
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
|
||||
/// and provides some helper function for creating common objects.
|
||||
pub struct CliOptions {
|
||||
|
@ -804,6 +814,7 @@ pub struct CliOptions {
|
|||
overrides: CliOptionOverrides,
|
||||
pub start_dir: Arc<WorkspaceDirectory>,
|
||||
pub deno_dir_provider: Arc<DenoDirProvider>,
|
||||
pub scope_options: Option<Arc<ScopeOptions>>,
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
|
@ -814,6 +825,7 @@ impl CliOptions {
|
|||
npmrc: Arc<ResolvedNpmRc>,
|
||||
start_dir: Arc<WorkspaceDirectory>,
|
||||
force_global_cache: bool,
|
||||
scope_options: Option<Arc<ScopeOptions>>,
|
||||
) -> Result<Self, AnyError> {
|
||||
if let Some(insecure_allowlist) =
|
||||
flags.unsafely_ignore_certificate_errors.as_ref()
|
||||
|
@ -853,6 +865,7 @@ impl CliOptions {
|
|||
main_module_cell: std::sync::OnceLock::new(),
|
||||
start_dir,
|
||||
deno_dir_provider,
|
||||
scope_options,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -937,6 +950,7 @@ impl CliOptions {
|
|||
npmrc,
|
||||
Arc::new(start_dir),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1410,9 +1410,17 @@ impl ConfigData {
|
|||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
let ts_config = LspTsConfig::new(
|
||||
member_dir.workspace.root_deno_json().map(|c| c.as_ref()),
|
||||
);
|
||||
// TODO(nayeemrmn): This is a hack to get member-specific compiler options.
|
||||
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 =
|
||||
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),
|
||||
workspace,
|
||||
force_global_cache,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let open_docs = self.documents.documents(DocumentsFilter::OpenDiagnosable);
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
// 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::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_ast::MediaType;
|
||||
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_graph::Module;
|
||||
use deno_graph::ModuleGraph;
|
||||
|
@ -15,9 +20,15 @@ use once_cell::sync::Lazy;
|
|||
use regex::Regex;
|
||||
|
||||
use crate::args::check_warn_tsconfig;
|
||||
use crate::args::discover_npmrc_from_workspace;
|
||||
use crate::args::CheckFlags;
|
||||
use crate::args::CliLockfile;
|
||||
use crate::args::CliOptions;
|
||||
use crate::args::ConfigFlag;
|
||||
use crate::args::FileFlags;
|
||||
use crate::args::Flags;
|
||||
use crate::args::LintFlags;
|
||||
use crate::args::ScopeOptions;
|
||||
use crate::args::TsConfig;
|
||||
use crate::args::TsConfigType;
|
||||
use crate::args::TsTypeLib;
|
||||
|
@ -40,6 +51,159 @@ pub async fn check(
|
|||
flags: Arc<Flags>,
|
||||
check_flags: CheckFlags,
|
||||
) -> 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 main_graph_container = factory.main_module_graph_container().await?;
|
||||
|
@ -168,9 +332,22 @@ impl TypeChecker {
|
|||
}
|
||||
|
||||
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
|
||||
.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 {
|
||||
check_warn_tsconfig(&ts_config_result);
|
||||
}
|
||||
|
@ -259,10 +436,33 @@ impl TypeChecker {
|
|||
|
||||
let mut diagnostics = response.diagnostics.filter(|d| {
|
||||
if self.is_remote_diagnostic(d) {
|
||||
type_check_mode == TypeCheckMode::All && d.include_when_remote()
|
||||
} else {
|
||||
true
|
||||
return type_check_mode == TypeCheckMode::All
|
||||
&& d.include_when_remote()
|
||||
&& 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);
|
||||
|
|
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