1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-30 16:40:57 -05:00

feat(unstable): repurpose --unstable-detect-cjs to attempt loading more modules as cjs (#27094)

This resurrects the `--unstable-detect-cjs` flag (which became stable),
and repurposes it to attempt loading .js/.jsx/.ts/.tsx files as CJS in
the following additional scenarios:

1. There is no package.json
1. There is a package.json without a "type" field

Also cleans up the implementation of this in the LSP a lot by hanging
`resolution_mode()` off `Document` (didn't think about doing that until
now).
This commit is contained in:
David Sherret 2024-11-27 09:50:38 -05:00 committed by GitHub
parent 1e51b650be
commit 2bbfef137c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 217 additions and 226 deletions

View file

@ -598,6 +598,7 @@ pub struct UnstableConfig {
// TODO(bartlomieju): remove in Deno 2.5
pub legacy_flag_enabled: bool, // --unstable
pub bare_node_builtins: bool,
pub detect_cjs: bool,
pub sloppy_imports: bool,
pub features: Vec<String>, // --unstabe-kv --unstable-cron
}
@ -4373,7 +4374,7 @@ impl CommandExt for Command {
).arg(
Arg::new("unstable-detect-cjs")
.long("unstable-detect-cjs")
.help("Reads the package.json type field in a project to treat .js files as .cjs")
.help("Treats ambiguous .js, .jsx, .ts, .tsx files as CommonJS modules in more cases")
.value_parser(FalseyValueParser::new())
.action(ArgAction::SetTrue)
.hide(true)
@ -5986,6 +5987,7 @@ fn unstable_args_parse(
flags.unstable_config.bare_node_builtins =
matches.get_flag("unstable-bare-node-builtins");
flags.unstable_config.detect_cjs = matches.get_flag("unstable-detect-cjs");
flags.unstable_config.sloppy_imports =
matches.get_flag("unstable-sloppy-imports");

View file

@ -1606,6 +1606,11 @@ impl CliOptions {
|| self.workspace().has_unstable("bare-node-builtins")
}
pub fn unstable_detect_cjs(&self) -> bool {
self.flags.unstable_config.detect_cjs
|| self.workspace().has_unstable("detect-cjs")
}
pub fn detect_cjs(&self) -> bool {
// only enabled when there's a package.json in order to not have a
// perf penalty for non-npm Deno projects of searching for the closest
@ -1675,6 +1680,7 @@ impl CliOptions {
"sloppy-imports",
"byonm",
"bare-node-builtins",
"detect-cjs",
"fmt-component",
"fmt-sql",
])

View file

@ -48,7 +48,6 @@ use crate::resolver::CliNpmReqResolver;
use crate::resolver::CliResolver;
use crate::resolver::CliResolverOptions;
use crate::resolver::CliSloppyImportsResolver;
use crate::resolver::IsCjsResolverOptions;
use crate::resolver::NpmModuleLoader;
use crate::resolver::SloppyImportsCachedFs;
use crate::standalone::DenoCompileBinaryWriter;
@ -72,6 +71,7 @@ use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::FeatureChecker;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_resolver::DenoResolverOptions;
use deno_resolver::NodeAndNpmReqResolver;
@ -845,9 +845,12 @@ impl CliFactory {
Ok(Arc::new(CjsTracker::new(
self.in_npm_pkg_checker()?.clone(),
self.pkg_json_resolver().clone(),
IsCjsResolverOptions {
detect_cjs: options.detect_cjs(),
is_node_main: options.is_node_main(),
if options.is_node_main() || options.unstable_detect_cjs() {
IsCjsResolutionMode::ImplicitTypeCommonJs
} else if options.detect_cjs() {
IsCjsResolutionMode::ExplicitTypeCommonJs
} else {
IsCjsResolutionMode::Disabled
},
)))
})

View file

@ -1281,9 +1281,7 @@ impl CodeActionCollection {
import_start_from_specifier(document, i)
})?;
let referrer = document.specifier();
let referrer_kind = language_server
.is_cjs_resolver
.get_doc_resolution_mode(document);
let resolution_mode = document.resolution_mode();
let file_referrer = document.file_referrer();
let config_data = language_server
.config
@ -1309,7 +1307,7 @@ impl CodeActionCollection {
if !language_server.resolver.is_bare_package_json_dep(
&dep_key,
referrer,
referrer_kind,
resolution_mode,
) {
return None;
}
@ -1329,7 +1327,7 @@ impl CodeActionCollection {
}
if language_server
.resolver
.npm_to_file_url(&npm_ref, referrer, referrer_kind, file_referrer)
.npm_to_file_url(&npm_ref, referrer, resolution_mode, file_referrer)
.is_some()
{
// The package import has types.

View file

@ -9,7 +9,6 @@ use super::jsr::CliJsrSearchApi;
use super::lsp_custom;
use super::npm::CliNpmSearchApi;
use super::registries::ModuleRegistry;
use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver;
use super::search::PackageSearchApi;
use super::tsc;
@ -161,7 +160,6 @@ pub async fn get_import_completions(
jsr_search_api: &CliJsrSearchApi,
npm_search_api: &CliNpmSearchApi,
documents: &Documents,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver,
maybe_import_map: Option<&ImportMap>,
) -> Option<lsp::CompletionResponse> {
@ -171,7 +169,7 @@ pub async fn get_import_completions(
let resolution_mode = graph_range
.resolution_mode
.map(to_node_resolution_mode)
.unwrap_or_else(|| is_cjs_resolver.get_doc_resolution_mode(&document));
.unwrap_or_else(|| document.resolution_mode());
let range = to_narrow_lsp_range(document.text_info(), graph_range.range);
let resolved = resolver
.as_cli_resolver(file_referrer)

View file

@ -1707,7 +1707,6 @@ mod tests {
documents: Arc::new(documents),
assets: Default::default(),
config: Arc::new(config),
is_cjs_resolver: Default::default(),
resolver,
},
)

View file

@ -3,7 +3,6 @@
use super::cache::calculate_fs_version;
use super::cache::LspCache;
use super::config::Config;
use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver;
use super::resolver::ScopeDepInfo;
use super::resolver::SingleReferrerGraphResolver;
@ -313,6 +312,7 @@ pub struct Document {
media_type: MediaType,
/// Present if and only if this is an open document.
open_data: Option<DocumentOpenData>,
resolution_mode: ResolutionMode,
resolver: Arc<LspResolver>,
specifier: ModuleSpecifier,
text: Arc<str>,
@ -328,7 +328,6 @@ impl Document {
maybe_lsp_version: Option<i32>,
maybe_language_id: Option<LanguageId>,
maybe_headers: Option<HashMap<String, String>>,
is_cjs_resolver: &LspIsCjsResolver,
resolver: Arc<LspResolver>,
config: Arc<Config>,
cache: &Arc<LspCache>,
@ -340,7 +339,7 @@ impl Document {
.or(file_referrer);
let media_type =
resolve_media_type(&specifier, maybe_headers.as_ref(), maybe_language_id);
let (maybe_parsed_source, maybe_module) =
let (maybe_parsed_source, maybe_module, resolution_mode) =
if media_type_is_diagnosable(media_type) {
parse_and_analyze_module(
specifier.clone(),
@ -348,11 +347,10 @@ impl Document {
maybe_headers.as_ref(),
media_type,
file_referrer.as_ref(),
is_cjs_resolver,
&resolver,
)
} else {
(None, None)
(None, None, ResolutionMode::Import)
};
let maybe_module = maybe_module.and_then(Result::ok);
let dependencies = maybe_module
@ -387,6 +385,7 @@ impl Document {
maybe_parsed_source,
maybe_semantic_tokens: Default::default(),
}),
resolution_mode,
resolver,
specifier,
text,
@ -396,7 +395,6 @@ impl Document {
fn with_new_config(
&self,
is_cjs_resolver: &LspIsCjsResolver,
resolver: Arc<LspResolver>,
config: Arc<Config>,
) -> Arc<Self> {
@ -408,20 +406,20 @@ impl Document {
let dependencies;
let maybe_types_dependency;
let maybe_parsed_source;
let found_resolution_mode;
let is_script;
let maybe_test_module_fut;
if media_type != self.media_type {
let parsed_source_result =
parse_source(self.specifier.clone(), self.text.clone(), media_type);
let maybe_module = analyze_module(
let (maybe_module_result, resolution_mode) = analyze_module(
self.specifier.clone(),
&parsed_source_result,
self.maybe_headers.as_ref(),
self.file_referrer.as_ref(),
is_cjs_resolver,
&resolver,
)
.ok();
);
let maybe_module = maybe_module_result.ok();
dependencies = maybe_module
.as_ref()
.map(|m| Arc::new(m.dependencies.clone()))
@ -433,17 +431,21 @@ impl Document {
maybe_parsed_source = Some(parsed_source_result);
maybe_test_module_fut =
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config);
found_resolution_mode = resolution_mode;
} else {
let cli_resolver = resolver.as_cli_resolver(self.file_referrer.as_ref());
let is_cjs_resolver =
resolver.as_is_cjs_resolver(self.file_referrer.as_ref());
let npm_resolver =
resolver.create_graph_npm_resolver(self.file_referrer.as_ref());
let config_data = resolver.as_config_data(self.file_referrer.as_ref());
let jsx_import_source_config =
config_data.and_then(|d| d.maybe_jsx_import_source_config());
found_resolution_mode = is_cjs_resolver
.get_lsp_resolution_mode(&self.specifier, self.is_script);
let resolver = SingleReferrerGraphResolver {
valid_referrer: &self.specifier,
module_resolution_mode: is_cjs_resolver
.get_lsp_resolution_mode(&self.specifier, self.is_script),
module_resolution_mode: found_resolution_mode,
cli_resolver,
jsx_import_source_config: jsx_import_source_config.as_ref(),
};
@ -493,6 +495,7 @@ impl Document {
maybe_language_id: self.maybe_language_id,
maybe_test_module_fut,
media_type,
resolution_mode: found_resolution_mode,
open_data: self.open_data.as_ref().map(|d| DocumentOpenData {
lsp_version: d.lsp_version,
maybe_parsed_source,
@ -508,7 +511,6 @@ impl Document {
fn with_change(
&self,
is_cjs_resolver: &LspIsCjsResolver,
version: i32,
changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<Arc<Self>, AnyError> {
@ -530,7 +532,7 @@ impl Document {
}
let text: Arc<str> = content.into();
let media_type = self.media_type;
let (maybe_parsed_source, maybe_module) = if self
let (maybe_parsed_source, maybe_module, resolution_mode) = if self
.maybe_language_id
.as_ref()
.map(|li| li.is_diagnosable())
@ -542,11 +544,10 @@ impl Document {
self.maybe_headers.as_ref(),
media_type,
self.file_referrer.as_ref(),
is_cjs_resolver,
self.resolver.as_ref(),
)
} else {
(None, None)
(None, None, ResolutionMode::Import)
};
let maybe_module = maybe_module.and_then(Result::ok);
let dependencies = maybe_module
@ -580,6 +581,7 @@ impl Document {
maybe_navigation_tree: Mutex::new(None),
maybe_test_module_fut,
media_type,
resolution_mode,
open_data: self.open_data.is_some().then_some(DocumentOpenData {
lsp_version: version,
maybe_parsed_source,
@ -613,6 +615,7 @@ impl Document {
maybe_test_module_fut: self.maybe_test_module_fut.clone(),
media_type: self.media_type,
open_data: None,
resolution_mode: self.resolution_mode,
resolver: self.resolver.clone(),
})
}
@ -641,6 +644,7 @@ impl Document {
maybe_test_module_fut: self.maybe_test_module_fut.clone(),
media_type: self.media_type,
open_data: self.open_data.clone(),
resolution_mode: self.resolution_mode,
resolver: self.resolver.clone(),
})
}
@ -664,6 +668,10 @@ impl Document {
&self.text
}
pub fn resolution_mode(&self) -> ResolutionMode {
self.resolution_mode
}
pub fn text_info(&self) -> &SourceTextInfo {
// try to get the text info from the parsed source and if
// not then create one in the cell
@ -677,14 +685,6 @@ impl Document {
.get_or_init(|| SourceTextInfo::new(self.text.clone()))
})
}
/// If this is maybe a CJS script and maybe not an ES module.
///
/// Use `LspIsCjsResolver` to determine for sure.
pub fn is_script(&self) -> Option<bool> {
self.is_script
}
pub fn line_index(&self) -> Arc<LineIndex> {
self.line_index.clone()
}
@ -832,7 +832,6 @@ impl FileSystemDocuments {
pub fn get(
&self,
specifier: &ModuleSpecifier,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &Arc<LspResolver>,
config: &Arc<Config>,
cache: &Arc<LspCache>,
@ -856,14 +855,7 @@ impl FileSystemDocuments {
};
if dirty {
// attempt to update the file on the file system
self.refresh_document(
specifier,
is_cjs_resolver,
resolver,
config,
cache,
file_referrer,
)
self.refresh_document(specifier, resolver, config, cache, file_referrer)
} else {
old_doc
}
@ -874,7 +866,6 @@ impl FileSystemDocuments {
fn refresh_document(
&self,
specifier: &ModuleSpecifier,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &Arc<LspResolver>,
config: &Arc<Config>,
cache: &Arc<LspCache>,
@ -896,7 +887,6 @@ impl FileSystemDocuments {
None,
None,
None,
is_cjs_resolver,
resolver.clone(),
config.clone(),
cache,
@ -913,7 +903,6 @@ impl FileSystemDocuments {
None,
None,
None,
is_cjs_resolver,
resolver.clone(),
config.clone(),
cache,
@ -946,7 +935,6 @@ impl FileSystemDocuments {
None,
None,
Some(cached_file.metadata.headers),
is_cjs_resolver,
resolver.clone(),
config.clone(),
cache,
@ -987,8 +975,6 @@ pub struct Documents {
/// The DENO_DIR that the documents looks for non-file based modules.
cache: Arc<LspCache>,
config: Arc<Config>,
/// Resolver for detecting if a document is CJS or ESM.
is_cjs_resolver: Arc<LspIsCjsResolver>,
/// A resolver that takes into account currently loaded import map and JSX
/// settings.
resolver: Arc<LspResolver>,
@ -1024,7 +1010,6 @@ impl Documents {
// the cache for remote modules here in order to get the
// x-typescript-types?
None,
&self.is_cjs_resolver,
self.resolver.clone(),
self.config.clone(),
&self.cache,
@ -1059,7 +1044,7 @@ impl Documents {
))
})?;
self.dirty = true;
let doc = doc.with_change(&self.is_cjs_resolver, version, changes)?;
let doc = doc.with_change(version, changes)?;
self.open_docs.insert(doc.specifier().clone(), doc.clone());
Ok(doc)
}
@ -1191,7 +1176,6 @@ impl Documents {
if let Some(old_doc) = old_doc {
self.file_system_docs.get(
specifier,
&self.is_cjs_resolver,
&self.resolver,
&self.config,
&self.cache,
@ -1216,7 +1200,6 @@ impl Documents {
} else {
self.file_system_docs.get(
&specifier,
&self.is_cjs_resolver,
&self.resolver,
&self.config,
&self.cache,
@ -1346,7 +1329,6 @@ impl Documents {
) {
self.config = Arc::new(config.clone());
self.cache = Arc::new(cache.clone());
self.is_cjs_resolver = Arc::new(LspIsCjsResolver::new(cache));
self.resolver = resolver.clone();
node_resolver::PackageJsonThreadLocalCache::clear();
@ -1370,21 +1352,14 @@ impl Documents {
if !config.specifier_enabled(doc.specifier()) {
continue;
}
*doc = doc.with_new_config(
&self.is_cjs_resolver,
self.resolver.clone(),
self.config.clone(),
);
*doc = doc.with_new_config(self.resolver.clone(), self.config.clone());
}
for mut doc in self.file_system_docs.docs.iter_mut() {
if !config.specifier_enabled(doc.specifier()) {
continue;
}
*doc.value_mut() = doc.with_new_config(
&self.is_cjs_resolver,
self.resolver.clone(),
self.config.clone(),
);
*doc.value_mut() =
doc.with_new_config(self.resolver.clone(), self.config.clone());
}
self.open_docs = open_docs;
let mut preload_count = 0;
@ -1401,7 +1376,6 @@ impl Documents {
{
fs_docs.refresh_document(
specifier,
&self.is_cjs_resolver,
&self.resolver,
&self.config,
&self.cache,
@ -1567,8 +1541,12 @@ impl Documents {
return Some((specifier, media_type));
};
if let Some(types) = doc.maybe_types_dependency().maybe_specifier() {
let specifier_kind = self.is_cjs_resolver.get_doc_resolution_mode(&doc);
self.resolve_dependency(types, &specifier, specifier_kind, file_referrer)
self.resolve_dependency(
types,
&specifier,
doc.resolution_mode(),
file_referrer,
)
} else {
Some((doc.specifier().clone(), doc.media_type()))
}
@ -1636,19 +1614,25 @@ fn parse_and_analyze_module(
maybe_headers: Option<&HashMap<String, String>>,
media_type: MediaType,
file_referrer: Option<&ModuleSpecifier>,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver,
) -> (Option<ParsedSourceResult>, Option<ModuleResult>) {
) -> (
Option<ParsedSourceResult>,
Option<ModuleResult>,
ResolutionMode,
) {
let parsed_source_result = parse_source(specifier.clone(), text, media_type);
let module_result = analyze_module(
let (module_result, resolution_mode) = analyze_module(
specifier,
&parsed_source_result,
maybe_headers,
file_referrer,
is_cjs_resolver,
resolver,
);
(Some(parsed_source_result), Some(module_result))
(
Some(parsed_source_result),
Some(module_result),
resolution_mode,
)
}
fn parse_source(
@ -1671,44 +1655,51 @@ fn analyze_module(
parsed_source_result: &ParsedSourceResult,
maybe_headers: Option<&HashMap<String, String>>,
file_referrer: Option<&ModuleSpecifier>,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver,
) -> ModuleResult {
) -> (ModuleResult, ResolutionMode) {
match parsed_source_result {
Ok(parsed_source) => {
let npm_resolver = resolver.create_graph_npm_resolver(file_referrer);
let cli_resolver = resolver.as_cli_resolver(file_referrer);
let is_cjs_resolver = resolver.as_is_cjs_resolver(file_referrer);
let config_data = resolver.as_config_data(file_referrer);
let valid_referrer = specifier.clone();
let jsx_import_source_config =
config_data.and_then(|d| d.maybe_jsx_import_source_config());
let module_resolution_mode = is_cjs_resolver.get_lsp_resolution_mode(
&specifier,
Some(parsed_source.compute_is_script()),
);
let resolver = SingleReferrerGraphResolver {
valid_referrer: &valid_referrer,
module_resolution_mode: is_cjs_resolver.get_lsp_resolution_mode(
&specifier,
Some(parsed_source.compute_is_script()),
),
module_resolution_mode,
cli_resolver,
jsx_import_source_config: jsx_import_source_config.as_ref(),
};
Ok(deno_graph::parse_module_from_ast(
deno_graph::ParseModuleFromAstOptions {
graph_kind: deno_graph::GraphKind::TypesOnly,
specifier,
maybe_headers,
parsed_source,
// use a null file system because there's no need to bother resolving
// dynamic imports like import(`./dir/${something}`) in the LSP
file_system: &deno_graph::source::NullFileSystem,
jsr_url_provider: &CliJsrUrlProvider,
maybe_resolver: Some(&resolver),
maybe_npm_resolver: Some(&npm_resolver),
},
))
(
Ok(deno_graph::parse_module_from_ast(
deno_graph::ParseModuleFromAstOptions {
graph_kind: deno_graph::GraphKind::TypesOnly,
specifier,
maybe_headers,
parsed_source,
// use a null file system because there's no need to bother resolving
// dynamic imports like import(`./dir/${something}`) in the LSP
file_system: &deno_graph::source::NullFileSystem,
jsr_url_provider: &CliJsrUrlProvider,
maybe_resolver: Some(&resolver),
maybe_npm_resolver: Some(&npm_resolver),
},
)),
module_resolution_mode,
)
}
Err(err) => Err(deno_graph::ModuleGraphError::ModuleError(
deno_graph::ModuleError::ParseErr(specifier, err.clone()),
)),
Err(err) => (
Err(deno_graph::ModuleGraphError::ModuleError(
deno_graph::ModuleError::ParseErr(specifier, err.clone()),
)),
ResolutionMode::Import,
),
}
}

View file

@ -79,7 +79,6 @@ use super::parent_process_checker;
use super::performance::Performance;
use super::refactor;
use super::registries::ModuleRegistry;
use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver;
use super::testing;
use super::text;
@ -147,7 +146,6 @@ pub struct StateSnapshot {
pub project_version: usize,
pub assets: AssetsSnapshot,
pub config: Arc<Config>,
pub is_cjs_resolver: Arc<LspIsCjsResolver>,
pub documents: Arc<Documents>,
pub resolver: Arc<LspResolver>,
}
@ -207,7 +205,6 @@ pub struct Inner {
pub documents: Documents,
http_client_provider: Arc<HttpClientProvider>,
initial_cwd: PathBuf,
pub is_cjs_resolver: Arc<LspIsCjsResolver>,
jsr_search_api: CliJsrSearchApi,
/// Handles module registries, which allow discovery of modules
module_registry: ModuleRegistry,
@ -485,7 +482,6 @@ impl Inner {
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory")
});
let is_cjs_resolver = Arc::new(LspIsCjsResolver::new(&cache));
Self {
assets,
@ -497,7 +493,6 @@ impl Inner {
documents,
http_client_provider,
initial_cwd: initial_cwd.clone(),
is_cjs_resolver,
jsr_search_api,
project_version: 0,
task_queue: Default::default(),
@ -608,7 +603,6 @@ impl Inner {
project_version: self.project_version,
assets: self.assets.snapshot(),
config: Arc::new(self.config.clone()),
is_cjs_resolver: self.is_cjs_resolver.clone(),
documents: Arc::new(self.documents.clone()),
resolver: self.resolver.snapshot(),
})
@ -630,7 +624,6 @@ impl Inner {
}
});
self.cache = LspCache::new(global_cache_url);
self.is_cjs_resolver = Arc::new(LspIsCjsResolver::new(&self.cache));
let deno_dir = self.cache.deno_dir();
let workspace_settings = self.config.workspace_settings();
let maybe_root_path = self
@ -1638,7 +1631,7 @@ impl Inner {
.get_ts_diagnostics(&specifier, asset_or_doc.document_lsp_version());
let specifier_kind = asset_or_doc
.document()
.map(|d| self.is_cjs_resolver.get_doc_resolution_mode(d))
.map(|d| d.resolution_mode())
.unwrap_or(ResolutionMode::Import);
let mut includes_no_cache = false;
for diagnostic in &fixable_diagnostics {
@ -1862,7 +1855,7 @@ impl Inner {
maybe_asset_or_doc
.as_ref()
.and_then(|d| d.document())
.map(|d| self.is_cjs_resolver.get_doc_resolution_mode(d))
.map(|d| d.resolution_mode())
.unwrap_or(ResolutionMode::Import),
&combined_code_actions.changes,
self,
@ -1919,7 +1912,7 @@ impl Inner {
&action_data.specifier,
asset_or_doc
.document()
.map(|d| self.is_cjs_resolver.get_doc_resolution_mode(d))
.map(|d| d.resolution_mode())
.unwrap_or(ResolutionMode::Import),
&refactor_edit_info.edits,
self,
@ -2270,7 +2263,6 @@ impl Inner {
&self.jsr_search_api,
&self.npm_search_api,
&self.documents,
&self.is_cjs_resolver,
self.resolver.as_ref(),
self
.config

View file

@ -13,8 +13,8 @@ use deno_graph::GraphImport;
use deno_graph::ModuleSpecifier;
use deno_graph::Range;
use deno_npm::NpmSystemInfo;
use deno_path_util::url_from_directory_path;
use deno_path_util::url_to_file_path;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_resolver::DenoResolverOptions;
use deno_resolver::NodeAndNpmReqResolver;
@ -39,7 +39,6 @@ use std::collections::HashSet;
use std::sync::Arc;
use super::cache::LspCache;
use super::documents::Document;
use super::jsr::JsrCacheResolver;
use crate::args::create_default_npmrc;
use crate::args::CacheSetting;
@ -71,7 +70,6 @@ use crate::resolver::CliResolverOptions;
use crate::resolver::IsCjsResolver;
use crate::resolver::WorkerCliNpmGraphResolver;
use crate::tsc::into_specifier_and_media_type;
use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
@ -79,6 +77,7 @@ use crate::util::progress_bar::ProgressBarStyle;
struct LspScopeResolver {
resolver: Arc<CliResolver>,
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
is_cjs_resolver: Arc<IsCjsResolver>,
jsr_resolver: Option<Arc<JsrCacheResolver>>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
node_resolver: Option<Arc<NodeResolver>>,
@ -97,6 +96,7 @@ impl Default for LspScopeResolver {
Self {
resolver: factory.cli_resolver().clone(),
in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(),
is_cjs_resolver: factory.is_cjs_resolver().clone(),
jsr_resolver: None,
npm_resolver: None,
node_resolver: None,
@ -206,6 +206,7 @@ impl LspScopeResolver {
Self {
resolver: cli_resolver,
in_npm_pkg_checker,
is_cjs_resolver: factory.is_cjs_resolver().clone(),
jsr_resolver,
npm_pkg_req_resolver,
npm_resolver,
@ -229,6 +230,7 @@ impl LspScopeResolver {
Arc::new(Self {
resolver: factory.cli_resolver().clone(),
in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(),
is_cjs_resolver: factory.is_cjs_resolver().clone(),
jsr_resolver: self.jsr_resolver.clone(),
npm_pkg_req_resolver: factory.npm_pkg_req_resolver().cloned(),
npm_resolver: factory.npm_resolver().cloned(),
@ -346,6 +348,14 @@ impl LspResolver {
resolver.resolver.create_graph_npm_resolver()
}
pub fn as_is_cjs_resolver(
&self,
file_referrer: Option<&ModuleSpecifier>,
) -> &IsCjsResolver {
let resolver = self.get_scope_resolver(file_referrer);
resolver.is_cjs_resolver.as_ref()
}
pub fn as_config_data(
&self,
file_referrer: Option<&ModuleSpecifier>,
@ -582,6 +592,7 @@ pub struct ScopeDepInfo {
struct ResolverFactoryServices {
cli_resolver: Deferred<Arc<CliResolver>>,
in_npm_pkg_checker: Deferred<Arc<dyn InNpmPackageChecker>>,
is_cjs_resolver: Deferred<Arc<IsCjsResolver>>,
node_resolver: Deferred<Option<Arc<NodeResolver>>>,
npm_pkg_req_resolver: Deferred<Option<Arc<CliNpmReqResolver>>>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
@ -745,6 +756,23 @@ impl<'a> ResolverFactory<'a> {
})
}
pub fn is_cjs_resolver(&self) -> &Arc<IsCjsResolver> {
self.services.is_cjs_resolver.get_or_init(|| {
Arc::new(IsCjsResolver::new(
self.in_npm_pkg_checker().clone(),
self.pkg_json_resolver().clone(),
if self
.config_data
.is_some_and(|d| d.unstable.contains("detect-cjs"))
{
IsCjsResolutionMode::ImplicitTypeCommonJs
} else {
IsCjsResolutionMode::ExplicitTypeCommonJs
},
))
})
}
pub fn node_resolver(&self) -> Option<&Arc<NodeResolver>> {
self
.services
@ -804,84 +832,6 @@ impl std::fmt::Debug for RedirectResolver {
}
}
#[derive(Debug)]
pub struct LspIsCjsResolver {
inner: IsCjsResolver,
}
impl Default for LspIsCjsResolver {
fn default() -> Self {
LspIsCjsResolver::new(&Default::default())
}
}
impl LspIsCjsResolver {
pub fn new(cache: &LspCache) -> Self {
#[derive(Debug)]
struct LspInNpmPackageChecker {
global_cache_dir: ModuleSpecifier,
}
impl LspInNpmPackageChecker {
pub fn new(cache: &LspCache) -> Self {
let npm_folder_path = cache.deno_dir().npm_folder_path();
Self {
global_cache_dir: url_from_directory_path(
&canonicalize_path_maybe_not_exists(&npm_folder_path)
.unwrap_or(npm_folder_path),
)
.unwrap_or_else(|_| {
ModuleSpecifier::parse("file:///invalid/").unwrap()
}),
}
}
}
impl InNpmPackageChecker for LspInNpmPackageChecker {
fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
if specifier.scheme() != "file" {
return false;
}
if specifier
.as_str()
.starts_with(self.global_cache_dir.as_str())
{
return true;
}
specifier.as_str().contains("/node_modules/")
}
}
let fs = Arc::new(deno_fs::RealFs);
let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
));
LspIsCjsResolver {
inner: IsCjsResolver::new(
Arc::new(LspInNpmPackageChecker::new(cache)),
pkg_json_resolver,
crate::resolver::IsCjsResolverOptions {
detect_cjs: true,
is_node_main: false,
},
),
}
}
pub fn get_doc_resolution_mode(&self, document: &Document) -> ResolutionMode {
self.get_lsp_resolution_mode(document.specifier(), document.is_script())
}
pub fn get_lsp_resolution_mode(
&self,
specifier: &ModuleSpecifier,
is_script: Option<bool>,
) -> ResolutionMode {
self.inner.get_lsp_resolution_mode(specifier, is_script)
}
}
#[derive(Debug)]
pub struct SingleReferrerGraphResolver<'a> {
pub valid_referrer: &'a ModuleSpecifier,

View file

@ -4449,12 +4449,7 @@ fn op_load<'s>(
version: state.script_version(&specifier),
is_cjs: doc
.document()
.map(|d| {
state
.state_snapshot
.is_cjs_resolver
.get_doc_resolution_mode(d)
})
.map(|d| d.resolution_mode())
.unwrap_or(ResolutionMode::Import)
== ResolutionMode::Require,
})
@ -4689,10 +4684,7 @@ fn op_script_names(state: &mut OpState) -> ScriptNames {
let (types, _) = documents.resolve_dependency(
types,
specifier,
state
.state_snapshot
.is_cjs_resolver
.get_doc_resolution_mode(doc),
doc.resolution_mode(),
doc.file_referrer(),
)?;
let types_doc = documents.get_or_load(&types, doc.file_referrer())?;
@ -5576,7 +5568,6 @@ mod tests {
documents: Arc::new(documents),
assets: Default::default(),
config: Arc::new(config),
is_cjs_resolver: Default::default(),
resolver,
});
let performance = Arc::new(Performance::default());

View file

@ -42,7 +42,6 @@ use crate::util::text_encoding::from_utf8_lossy_owned;
pub type CjsTracker = deno_resolver::cjs::CjsTracker<DenoFsNodeResolverEnv>;
pub type IsCjsResolver =
deno_resolver::cjs::IsCjsResolver<DenoFsNodeResolverEnv>;
pub type IsCjsResolverOptions = deno_resolver::cjs::IsCjsResolverOptions;
pub type CliSloppyImportsResolver =
SloppyImportsResolver<SloppyImportsCachedFs>;
pub type CliDenoResolver = deno_resolver::DenoResolver<

View file

@ -554,6 +554,7 @@
"bare-node-builtins",
"byonm",
"cron",
"detect-cjs",
"ffi",
"fs",
"fmt-component",

View file

@ -777,6 +777,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
unstable_config: UnstableConfig {
legacy_flag_enabled: false,
bare_node_builtins: self.cli_options.unstable_bare_node_builtins(),
detect_cjs: self.cli_options.unstable_detect_cjs(),
sloppy_imports: self.cli_options.unstable_sloppy_imports(),
features: self.cli_options.unstable_features(),
},

View file

@ -32,6 +32,7 @@ use deno_core::ResolutionKind;
use deno_core::SourceCodeCacheInfo;
use deno_npm::npm_rc::ResolvedNpmRc;
use deno_package_json::PackageJsonDepValue;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_runtime::deno_fs;
use deno_runtime::deno_node::create_host_defined_options;
@ -87,7 +88,6 @@ use crate::npm::CreateInNpmPkgCheckerOptions;
use crate::resolver::CjsTracker;
use crate::resolver::CliDenoResolverFs;
use crate::resolver::CliNpmReqResolver;
use crate::resolver::IsCjsResolverOptions;
use crate::resolver::NpmModuleLoader;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
@ -731,9 +731,12 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
let cjs_tracker = Arc::new(CjsTracker::new(
in_npm_pkg_checker.clone(),
pkg_json_resolver.clone(),
IsCjsResolverOptions {
detect_cjs: !metadata.workspace_resolver.package_jsons.is_empty(),
is_node_main: false,
if metadata.unstable_config.detect_cjs {
IsCjsResolutionMode::ImplicitTypeCommonJs
} else if metadata.workspace_resolver.package_jsons.is_empty() {
IsCjsResolutionMode::Disabled
} else {
IsCjsResolutionMode::ExplicitTypeCommonJs
},
));
let cache_db = Caches::new(deno_dir_provider.clone());

View file

@ -26,13 +26,13 @@ impl<TEnv: NodeResolverEnv> CjsTracker<TEnv> {
pub fn new(
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>,
options: IsCjsResolverOptions,
mode: IsCjsResolutionMode,
) -> Self {
Self {
is_cjs_resolver: IsCjsResolver::new(
in_npm_pkg_checker,
pkg_json_resolver,
options,
mode,
),
known: Default::default(),
}
@ -114,10 +114,14 @@ impl<TEnv: NodeResolverEnv> CjsTracker<TEnv> {
}
}
#[derive(Debug)]
pub struct IsCjsResolverOptions {
pub detect_cjs: bool,
pub is_node_main: bool,
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsCjsResolutionMode {
/// Requires an explicit `"type": "commonjs"` in the package.json.
ExplicitTypeCommonJs,
/// Implicitly uses `"type": "commonjs"` if no `"type"` is specified.
ImplicitTypeCommonJs,
/// Does not respect `"type": "commonjs"` and always treats ambiguous files as ESM.
Disabled,
}
/// Resolves whether a module is CJS or ESM.
@ -125,19 +129,19 @@ pub struct IsCjsResolverOptions {
pub struct IsCjsResolver<TEnv: NodeResolverEnv> {
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>,
options: IsCjsResolverOptions,
mode: IsCjsResolutionMode,
}
impl<TEnv: NodeResolverEnv> IsCjsResolver<TEnv> {
pub fn new(
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>,
options: IsCjsResolverOptions,
mode: IsCjsResolutionMode,
) -> Self {
Self {
in_npm_pkg_checker,
pkg_json_resolver,
options,
mode,
}
}
@ -249,18 +253,19 @@ impl<TEnv: NodeResolverEnv> IsCjsResolver<TEnv> {
} else {
Ok(ResolutionMode::Require)
}
} else if self.options.detect_cjs || self.options.is_node_main {
} else if self.mode != IsCjsResolutionMode::Disabled {
if let Some(pkg_json) =
self.pkg_json_resolver.get_closest_package_json(specifier)?
{
let is_cjs_type = pkg_json.typ == "commonjs"
|| self.options.is_node_main && pkg_json.typ == "none";
|| self.mode == IsCjsResolutionMode::ImplicitTypeCommonJs
&& pkg_json.typ == "none";
Ok(if is_cjs_type {
ResolutionMode::Require
} else {
ResolutionMode::Import
})
} else if self.options.is_node_main {
} else if self.mode == IsCjsResolutionMode::ImplicitTypeCommonJs {
Ok(ResolutionMode::Require)
} else {
Ok(ResolutionMode::Import)

View file

@ -316,7 +316,8 @@ fn get_suggestions_for_terminal_errors(e: &JsError) -> Vec<FixSuggestion> {
FixSuggestion::hint_multiline(&[
"Rewrite this module to ESM,",
cstr!("or change the file extension to <u>.cjs</u>,"),
cstr!("or add <u>package.json</> next to the file with <i>\"type\": \"commonjs\"</> option."),
cstr!("or add <u>package.json</> next to the file with <i>\"type\": \"commonjs\"</> option,"),
cstr!("or pass <i>--unstable-detect-cjs</> flag to detect CommonJS when loading."),
]),
FixSuggestion::docs("https://docs.deno.com/go/commonjs"),
];

View file

@ -0,0 +1,4 @@
{
"args": "run --check --quiet --allow-read=. main.ts",
"output": "main.out"
}

View file

@ -0,0 +1,3 @@
export function add(a: number, b: number) {
return a + b;
}

View file

@ -0,0 +1,3 @@
{
"unstable": ["detect-cjs"]
}

View file

@ -0,0 +1 @@
3

View file

@ -0,0 +1,3 @@
import mod = require("./add.ts");
console.log(mod.add(1, 2));

View file

@ -0,0 +1,4 @@
{
"args": "run --unstable-detect-cjs --check --quiet --allow-read=. main.ts",
"output": "main.out"
}

View file

@ -0,0 +1,3 @@
export function add(a: number, b: number) {
return a + b;
}

View file

@ -0,0 +1 @@
3

View file

@ -0,0 +1,3 @@
import mod = require("./add.ts");
console.log(mod.add(1, 2));

View file

@ -0,0 +1,2 @@
{
}

View file

@ -0,0 +1,5 @@
{
"args": "run --unstable-detect-cjs --quiet --allow-read=. main.ts",
"output": "main.out",
"exitCode": 1
}

View file

@ -0,0 +1,3 @@
export function add(a: number, b: number) {
return a + b;
}

View file

@ -0,0 +1,4 @@
error: Uncaught SyntaxError: Unexpected token '='
import mod = require("./add.ts");
^
at <anonymous> (file:///[WILDLINE]/main.ts:1:8)

View file

@ -0,0 +1,3 @@
import mod = require("./add.ts");
console.log(mod.add(1, 2));

View file

@ -0,0 +1,3 @@
{
"type": "module"
}

View file

@ -7,5 +7,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option.
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.com/go/commonjs

View file

@ -7,5 +7,6 @@ module.exports = {
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option.
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.com/go/commonjs

View file

@ -7,5 +7,6 @@ const process = require("process");
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option.
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.com/go/commonjs

View file

@ -8,5 +8,6 @@ console.log(require);
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option.
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.com/go/commonjs

View file

@ -8,5 +8,6 @@ console.log(require("./add").add(1, 2));
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option.
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.com/go/commonjs

View file

@ -7,5 +7,6 @@ const { add } = require("./add");
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option.
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.com/go/commonjs