1use std::sync::Arc;
4
5use dashmap::DashMap;
6use deno_media_type::MediaType;
7use lib_node_resolver::env::NodeResolverEnv;
8use lib_node_resolver::errors::ClosestPkgJsonError;
9use lib_node_resolver::InNpmPackageChecker;
10use lib_node_resolver::NodeModuleKind;
11use lib_node_resolver::PackageJsonResolver;
12use url::Url;
13
14#[derive(Debug)]
20pub struct CjsTracker<TEnv: NodeResolverEnv> {
21 is_cjs_resolver: IsCjsResolver<TEnv>,
22 known: DashMap<Url, NodeModuleKind>,
23}
24
25impl<TEnv: NodeResolverEnv> CjsTracker<TEnv> {
26 pub fn new(
27 in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
28 pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>,
29 options: IsCjsResolverOptions,
30 ) -> Self {
31 Self {
32 is_cjs_resolver: IsCjsResolver::new(
33 in_npm_pkg_checker,
34 pkg_json_resolver,
35 options,
36 ),
37 known: Default::default(),
38 }
39 }
40
41 pub fn is_maybe_cjs(
45 &self,
46 specifier: &Url,
47 media_type: MediaType,
48 ) -> Result<bool, ClosestPkgJsonError> {
49 self.treat_as_cjs_with_is_script(specifier, media_type, None)
50 }
51
52 pub fn is_cjs_with_known_is_script(
58 &self,
59 specifier: &Url,
60 media_type: MediaType,
61 is_script: bool,
62 ) -> Result<bool, ClosestPkgJsonError> {
63 self.treat_as_cjs_with_is_script(specifier, media_type, Some(is_script))
64 }
65
66 fn treat_as_cjs_with_is_script(
67 &self,
68 specifier: &Url,
69 media_type: MediaType,
70 is_script: Option<bool>,
71 ) -> Result<bool, ClosestPkgJsonError> {
72 let kind = match self
73 .get_known_kind_with_is_script(specifier, media_type, is_script)
74 {
75 Some(kind) => kind,
76 None => self.is_cjs_resolver.check_based_on_pkg_json(specifier)?,
77 };
78 Ok(kind == NodeModuleKind::Cjs)
79 }
80
81 pub fn get_referrer_kind(&self, specifier: &Url) -> NodeModuleKind {
86 if specifier.scheme() != "file" {
87 return NodeModuleKind::Esm;
88 }
89 self
90 .get_known_kind(specifier, MediaType::from_specifier(specifier))
91 .unwrap_or(NodeModuleKind::Esm)
92 }
93
94 fn get_known_kind(
95 &self,
96 specifier: &Url,
97 media_type: MediaType,
98 ) -> Option<NodeModuleKind> {
99 self.get_known_kind_with_is_script(specifier, media_type, None)
100 }
101
102 fn get_known_kind_with_is_script(
103 &self,
104 specifier: &Url,
105 media_type: MediaType,
106 is_script: Option<bool>,
107 ) -> Option<NodeModuleKind> {
108 self.is_cjs_resolver.get_known_kind_with_is_script(
109 specifier,
110 media_type,
111 is_script,
112 &self.known,
113 )
114 }
115}
116
117#[derive(Debug)]
118pub struct IsCjsResolverOptions {
119 pub detect_cjs: bool,
120 pub is_node_main: bool,
121}
122
123#[derive(Debug)]
125pub struct IsCjsResolver<TEnv: NodeResolverEnv> {
126 in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
127 pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>,
128 options: IsCjsResolverOptions,
129}
130
131impl<TEnv: NodeResolverEnv> IsCjsResolver<TEnv> {
132 pub fn new(
133 in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
134 pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>,
135 options: IsCjsResolverOptions,
136 ) -> Self {
137 Self {
138 in_npm_pkg_checker,
139 pkg_json_resolver,
140 options,
141 }
142 }
143
144 pub fn get_lsp_referrer_kind(
146 &self,
147 specifier: &Url,
148 is_script: Option<bool>,
149 ) -> NodeModuleKind {
150 if specifier.scheme() != "file" {
151 return NodeModuleKind::Esm;
152 }
153 match MediaType::from_specifier(specifier) {
154 MediaType::Mts | MediaType::Mjs | MediaType::Dmts => NodeModuleKind::Esm,
155 MediaType::Cjs | MediaType::Cts | MediaType::Dcts => NodeModuleKind::Cjs,
156 MediaType::Dts => {
157 self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm)
160 }
161 MediaType::Wasm |
162 MediaType::Json => NodeModuleKind::Esm,
163 MediaType::JavaScript
164 | MediaType::Jsx
165 | MediaType::TypeScript
166 | MediaType::Tsx
167 | MediaType::Css
169 | MediaType::SourceMap
170 | MediaType::Unknown => {
171 match is_script {
172 Some(true) => self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm),
173 Some(false) | None => NodeModuleKind::Esm,
174 }
175 }
176 }
177 }
178
179 fn get_known_kind_with_is_script(
180 &self,
181 specifier: &Url,
182 media_type: MediaType,
183 is_script: Option<bool>,
184 known_cache: &DashMap<Url, NodeModuleKind>,
185 ) -> Option<NodeModuleKind> {
186 if specifier.scheme() != "file" {
187 return Some(NodeModuleKind::Esm);
188 }
189
190 match media_type {
191 MediaType::Mts | MediaType::Mjs | MediaType::Dmts => Some(NodeModuleKind::Esm),
192 MediaType::Cjs | MediaType::Cts | MediaType::Dcts => Some(NodeModuleKind::Cjs),
193 MediaType::Dts => {
194 if let Some(value) = known_cache.get(specifier).map(|v| *v) {
197 Some(value)
198 } else {
199 let value = self.check_based_on_pkg_json(specifier).ok();
200 if let Some(value) = value {
201 known_cache.insert(specifier.clone(), value);
202 }
203 Some(value.unwrap_or(NodeModuleKind::Esm))
204 }
205 }
206 MediaType::Wasm |
207 MediaType::Json => Some(NodeModuleKind::Esm),
208 MediaType::JavaScript
209 | MediaType::Jsx
210 | MediaType::TypeScript
211 | MediaType::Tsx
212 | MediaType::Css
214 | MediaType::SourceMap
215 | MediaType::Unknown => {
216 if let Some(value) = known_cache.get(specifier).map(|v| *v) {
217 if value == NodeModuleKind::Cjs && is_script == Some(false) {
218 known_cache.insert(specifier.clone(), NodeModuleKind::Esm);
220 Some(NodeModuleKind::Esm)
221 } else {
222 Some(value)
223 }
224 } else if is_script == Some(false) {
225 known_cache.insert(specifier.clone(), NodeModuleKind::Esm);
227 Some(NodeModuleKind::Esm)
228 } else {
229 None
230 }
231 }
232 }
233 }
234
235 fn check_based_on_pkg_json(
236 &self,
237 specifier: &Url,
238 ) -> Result<NodeModuleKind, ClosestPkgJsonError> {
239 if self.in_npm_pkg_checker.in_npm_package(specifier) {
240 if let Some(pkg_json) =
241 self.pkg_json_resolver.get_closest_package_json(specifier)?
242 {
243 let is_file_location_cjs = pkg_json.typ != "module";
244 Ok(if is_file_location_cjs {
245 NodeModuleKind::Cjs
246 } else {
247 NodeModuleKind::Esm
248 })
249 } else {
250 Ok(NodeModuleKind::Cjs)
251 }
252 } else if self.options.detect_cjs || self.options.is_node_main {
253 if let Some(pkg_json) =
254 self.pkg_json_resolver.get_closest_package_json(specifier)?
255 {
256 let is_cjs_type = pkg_json.typ == "commonjs"
257 || self.options.is_node_main && pkg_json.typ == "none";
258 Ok(if is_cjs_type {
259 NodeModuleKind::Cjs
260 } else {
261 NodeModuleKind::Esm
262 })
263 } else if self.options.is_node_main {
264 Ok(NodeModuleKind::Cjs)
265 } else {
266 Ok(NodeModuleKind::Esm)
267 }
268 } else {
269 Ok(NodeModuleKind::Esm)
270 }
271 }
272}