Skip to content

Commit 7495a08

Browse files
authored
refactor: support not depending on deno_ast/swc (#588)
1 parent 05e0972 commit 7495a08

File tree

20 files changed

+1522
-334
lines changed

20 files changed

+1522
-334
lines changed

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ all-features = true
2727
[features]
2828
default = ["fast_check", "ecosystem_test"]
2929
fast_check = ["symbols", "deno_ast/transpiling", "twox-hash"]
30-
symbols = ["deno_ast/transforms", "deno_ast/visit", "deno_ast/utils"]
30+
symbols = ["swc", "deno_ast/transforms", "deno_ast/utils"]
31+
swc = ["deno_ast", "deno_ast/visit"]
3132
ecosystem_test = []
3233

3334
[[test]]
@@ -44,7 +45,8 @@ harness = false
4445
async-trait = "0.1.68"
4546
capacity_builder = "0.5.0"
4647
data-url = "0.3.0"
47-
deno_ast = { version = "0.48.0", features = ["dep_analysis", "emit"] }
48+
# this is optional in order to support using parsers other than swc
49+
deno_ast = { version = "0.48.0", features = ["emit"], optional = true }
4850
deno_media_type = { version = "0.2.8", features = ["decoding", "data_url", "module_specifier"] }
4951
deno_unsync.workspace = true
5052
deno_path_util = "0.4.0"

lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ crate-type = ["cdylib"]
1515

1616
[dependencies]
1717
console_error_panic_hook = "0.1.7"
18-
deno_graph = { path = "../", default-features = false }
18+
deno_graph = { path = "../", features = ["swc"], default-features = false }
1919
futures = "0.3.17"
2020
js-sys = "0.3.68"
2121
serde = { version = "1.0.130", features = ["derive", "rc"] }

src/analyzer.rs renamed to src/analysis.rs

Lines changed: 190 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,19 @@
11
// Copyright 2018-2024 the Deno authors. MIT license.
22

3+
use std::collections::HashMap;
34
use std::sync::Arc;
45

5-
use deno_ast::dep::DynamicDependencyKind;
6-
use deno_ast::dep::ImportAttributes;
7-
use deno_ast::dep::StaticDependencyKind;
8-
use deno_ast::MediaType;
9-
use deno_ast::ModuleSpecifier;
10-
use deno_ast::ParseDiagnostic;
11-
use deno_ast::SourceRange;
12-
use deno_ast::SourceTextInfo;
13-
use regex::Match;
14-
use serde::ser::SerializeTuple;
6+
use deno_error::JsErrorBox;
7+
use deno_media_type::MediaType;
8+
use once_cell::sync::Lazy;
9+
use regex::Regex;
1510
use serde::Deserialize;
1611
use serde::Serialize;
17-
use serde::Serializer;
1812

19-
use crate::ast::DENO_TYPES_RE;
2013
use crate::graph::Position;
14+
use crate::graph::PositionRange;
2115
use crate::source::ResolutionMode;
22-
use crate::DefaultModuleAnalyzer;
23-
24-
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Hash)]
25-
pub struct PositionRange {
26-
#[serde(default = "Position::zeroed")]
27-
pub start: Position,
28-
#[serde(default = "Position::zeroed")]
29-
pub end: Position,
30-
}
31-
32-
impl PositionRange {
33-
pub fn zeroed() -> Self {
34-
Self {
35-
start: Position::zeroed(),
36-
end: Position::zeroed(),
37-
}
38-
}
39-
40-
/// Determines if a given position is within the range.
41-
pub fn includes(&self, position: Position) -> bool {
42-
(position >= self.start) && (position <= self.end)
43-
}
44-
45-
pub fn from_source_range(
46-
range: SourceRange,
47-
text_info: &SourceTextInfo,
48-
) -> Self {
49-
Self {
50-
start: Position::from_source_pos(range.start, text_info),
51-
end: Position::from_source_pos(range.end, text_info),
52-
}
53-
}
54-
55-
pub fn as_source_range(&self, text_info: &SourceTextInfo) -> SourceRange {
56-
SourceRange::new(
57-
self.start.as_source_pos(text_info),
58-
self.end.as_source_pos(text_info),
59-
)
60-
}
61-
}
62-
63-
// Custom serialization to serialize to an array. Interestingly we
64-
// don't need to implement custom deserialization logic that does
65-
// the same thing, and serde_json will handle it fine.
66-
impl Serialize for PositionRange {
67-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68-
where
69-
S: Serializer,
70-
{
71-
struct PositionSerializer<'a>(&'a Position);
72-
73-
impl Serialize for PositionSerializer<'_> {
74-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75-
where
76-
S: Serializer,
77-
{
78-
let mut seq = serializer.serialize_tuple(2)?;
79-
seq.serialize_element(&self.0.line)?;
80-
seq.serialize_element(&self.0.character)?;
81-
seq.end()
82-
}
83-
}
84-
85-
let mut seq = serializer.serialize_tuple(2)?;
86-
seq.serialize_element(&PositionSerializer(&self.start))?;
87-
seq.serialize_element(&PositionSerializer(&self.end))?;
88-
seq.end()
89-
}
90-
}
16+
use crate::ModuleSpecifier;
9117

9218
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
9319
#[serde(rename_all = "camelCase", tag = "type")]
@@ -119,6 +45,66 @@ impl DependencyDescriptor {
11945
}
12046
}
12147

48+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
49+
#[serde(rename_all = "camelCase")]
50+
#[serde(untagged)]
51+
pub enum ImportAttribute {
52+
/// The value of this attribute could not be statically analyzed.
53+
Unknown,
54+
/// The value of this attribute is a statically analyzed string.
55+
Known(String),
56+
}
57+
58+
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
59+
#[serde(rename_all = "camelCase")]
60+
pub enum ImportAttributes {
61+
/// There was no import attributes object literal.
62+
#[default]
63+
None,
64+
/// The set of attribute keys could not be statically analyzed.
65+
Unknown,
66+
/// The set of attribute keys is statically analyzed, though each respective
67+
/// value may or may not not be for dynamic imports.
68+
Known(HashMap<String, ImportAttribute>),
69+
}
70+
71+
impl ImportAttributes {
72+
pub fn is_none(&self) -> bool {
73+
matches!(self, ImportAttributes::None)
74+
}
75+
76+
pub fn get(&self, key: &str) -> Option<&String> {
77+
match self {
78+
ImportAttributes::Known(map) => match map.get(key) {
79+
Some(ImportAttribute::Known(value)) => Some(value),
80+
_ => None,
81+
},
82+
_ => None,
83+
}
84+
}
85+
}
86+
87+
#[derive(
88+
Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize,
89+
)]
90+
#[serde(rename_all = "camelCase")]
91+
pub enum DynamicDependencyKind {
92+
#[default]
93+
Import,
94+
Require,
95+
}
96+
97+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
98+
#[serde(rename_all = "camelCase")]
99+
pub enum StaticDependencyKind {
100+
Import,
101+
ImportType,
102+
ImportEquals,
103+
Export,
104+
ExportType,
105+
ExportEquals,
106+
}
107+
122108
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
123109
#[serde(rename_all = "camelCase")]
124110
pub struct StaticDependencyDescriptor {
@@ -213,6 +199,7 @@ pub enum TypeScriptTypesResolutionMode {
213199
}
214200

215201
impl TypeScriptTypesResolutionMode {
202+
#[allow(clippy::should_implement_trait)]
216203
pub fn from_str(text: &str) -> Option<Self> {
217204
match text {
218205
"import" => Some(Self::Import),
@@ -281,6 +268,10 @@ pub struct ModuleInfo {
281268
pub jsdoc_imports: Vec<JsDocImportInfo>,
282269
}
283270

271+
fn is_false(v: &bool) -> bool {
272+
!v
273+
}
274+
284275
pub fn module_graph_1_to_2(module_info: &mut serde_json::Value) {
285276
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
286277
#[serde(rename_all = "camelCase")]
@@ -295,7 +286,7 @@ pub fn module_graph_1_to_2(module_info: &mut serde_json::Value) {
295286
) -> Option<SpecifierWithRange> {
296287
fn comment_position_to_position_range(
297288
mut comment_start: Position,
298-
m: &Match,
289+
range: std::ops::Range<usize>,
299290
) -> PositionRange {
300291
// the comment text starts after the double slash or slash star, so add 2
301292
comment_start.character += 2;
@@ -304,30 +295,24 @@ pub fn module_graph_1_to_2(module_info: &mut serde_json::Value) {
304295
// Does -1 and +1 to include the quotes
305296
start: Position {
306297
line: comment_start.line,
307-
character: comment_start.character + m.start() - 1,
298+
character: comment_start.character + range.start - 1,
308299
},
309300
end: Position {
310301
line: comment_start.line,
311-
character: comment_start.character + m.end() + 1,
302+
character: comment_start.character + range.end + 1,
312303
},
313304
}
314305
}
315306

316307
let comment = leading_comments.last()?;
317-
let captures = DENO_TYPES_RE.captures(&comment.text)?;
318-
if let Some(m) = captures.get(1) {
319-
Some(SpecifierWithRange {
320-
text: m.as_str().to_string(),
321-
range: comment_position_to_position_range(comment.range.start, &m),
322-
})
323-
} else if let Some(m) = captures.get(2) {
324-
Some(SpecifierWithRange {
325-
text: m.as_str().to_string(),
326-
range: comment_position_to_position_range(comment.range.start, &m),
327-
})
328-
} else {
329-
unreachable!("Unexpected captures from deno types regex")
330-
}
308+
let deno_types = find_deno_types(&comment.text)?;
309+
Some(SpecifierWithRange {
310+
text: deno_types.text.to_string(),
311+
range: comment_position_to_position_range(
312+
comment.range.start,
313+
deno_types.range,
314+
),
315+
})
331316
}
332317

333318
// To support older module graphs, we need to convert the module graph 1
@@ -375,24 +360,126 @@ pub trait ModuleAnalyzer {
375360
specifier: &ModuleSpecifier,
376361
source: Arc<str>,
377362
media_type: MediaType,
378-
) -> Result<ModuleInfo, ParseDiagnostic>;
363+
) -> Result<ModuleInfo, JsErrorBox>;
379364
}
380365

381366
impl<'a> Default for &'a dyn ModuleAnalyzer {
382367
fn default() -> &'a dyn ModuleAnalyzer {
383-
&DefaultModuleAnalyzer
368+
#[cfg(feature = "swc")]
369+
{
370+
&crate::ast::DefaultModuleAnalyzer
371+
}
372+
#[cfg(not(feature = "swc"))]
373+
{
374+
panic!(
375+
"Provide a module analyzer or turn on the 'swc' cargo feature of deno_graph."
376+
);
377+
}
384378
}
385379
}
386380

387-
fn is_false(v: &bool) -> bool {
388-
!v
381+
pub struct DenoTypesPragmaMatch<'a> {
382+
pub text: &'a str,
383+
pub range: std::ops::Range<usize>,
384+
pub is_quoteless: bool,
385+
}
386+
387+
/// Matches a `/// <reference ... />` comment reference.
388+
pub fn is_comment_triple_slash_reference(comment_text: &str) -> bool {
389+
static TRIPLE_SLASH_REFERENCE_RE: Lazy<Regex> =
390+
Lazy::new(|| Regex::new(r"(?i)^/\s*<reference\s.*?/>").unwrap());
391+
TRIPLE_SLASH_REFERENCE_RE.is_match(comment_text)
392+
}
393+
394+
/// Matches a path reference, which adds a dependency to a module
395+
pub fn find_path_reference(text: &str) -> Option<regex::Match> {
396+
static PATH_REFERENCE_RE: Lazy<Regex> =
397+
Lazy::new(|| Regex::new(r#"(?i)\spath\s*=\s*["']([^"']*)["']"#).unwrap());
398+
PATH_REFERENCE_RE
399+
.captures(text)
400+
.and_then(|captures| captures.get(1))
401+
}
402+
403+
/// Matches a types reference, which for JavaScript files indicates the
404+
/// location of types to use when type checking a program that includes it as
405+
/// a dependency.
406+
pub fn find_types_reference(text: &str) -> Option<regex::Match> {
407+
static TYPES_REFERENCE_RE: Lazy<Regex> =
408+
Lazy::new(|| Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap());
409+
TYPES_REFERENCE_RE.captures(text).and_then(|c| c.get(1))
410+
}
411+
412+
/// Ex. `resolution-mode="require"` in `/// <reference types="pkg" resolution-mode="require" />`
413+
pub fn find_resolution_mode(text: &str) -> Option<regex::Match> {
414+
static RESOLUTION_MODE_RE: Lazy<Regex> = Lazy::new(|| {
415+
Regex::new(r#"(?i)\sresolution-mode\s*=\s*["']([^"']*)["']"#).unwrap()
416+
});
417+
RESOLUTION_MODE_RE.captures(text).and_then(|m| m.get(1))
418+
}
419+
420+
/// Matches the `@jsxImportSource` pragma.
421+
pub fn find_jsx_import_source(text: &str) -> Option<regex::Match> {
422+
static JSX_IMPORT_SOURCE_RE: Lazy<Regex> =
423+
Lazy::new(|| Regex::new(r"(?i)^[\s*]*@jsxImportSource\s+(\S+)").unwrap());
424+
JSX_IMPORT_SOURCE_RE.captures(text).and_then(|c| c.get(1))
425+
}
426+
427+
/// Matches the `@jsxImportSourceTypes` pragma.
428+
pub fn find_jsx_import_source_types(text: &str) -> Option<regex::Match> {
429+
static JSX_IMPORT_SOURCE_TYPES_RE: Lazy<Regex> = Lazy::new(|| {
430+
Regex::new(r"(?i)^[\s*]*@jsxImportSourceTypes\s+(\S+)").unwrap()
431+
});
432+
JSX_IMPORT_SOURCE_TYPES_RE
433+
.captures(text)
434+
.and_then(|c| c.get(1))
435+
}
436+
437+
/// Matches the `@ts-self-types` pragma.
438+
pub fn find_ts_self_types(text: &str) -> Option<regex::Match> {
439+
static TS_SELF_TYPES_RE: Lazy<Regex> = Lazy::new(|| {
440+
Regex::new(r#"(?i)^\s*@ts-self-types\s*=\s*["']([^"']+)["']"#).unwrap()
441+
});
442+
TS_SELF_TYPES_RE.captures(text).and_then(|c| c.get(1))
443+
}
444+
445+
/// Matches the `@ts-types` pragma.
446+
pub fn find_ts_types(text: &str) -> Option<regex::Match> {
447+
static TS_TYPES_RE: Lazy<Regex> = Lazy::new(|| {
448+
Regex::new(r#"(?i)^\s*@ts-types\s*=\s*["']([^"']+)["']"#).unwrap()
449+
});
450+
TS_TYPES_RE.captures(text).and_then(|c| c.get(1))
451+
}
452+
453+
pub fn find_deno_types(text: &str) -> Option<DenoTypesPragmaMatch> {
454+
/// Matches the `@deno-types` pragma.
455+
static DENO_TYPES_RE: Lazy<Regex> = Lazy::new(|| {
456+
Regex::new(r#"(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"#)
457+
.unwrap()
458+
});
459+
460+
let captures = DENO_TYPES_RE.captures(text)?;
461+
462+
if let Some(m) = captures.get(1) {
463+
Some(DenoTypesPragmaMatch {
464+
text: m.as_str(),
465+
range: m.range(),
466+
is_quoteless: false,
467+
})
468+
} else if let Some(m) = captures.get(2) {
469+
Some(DenoTypesPragmaMatch {
470+
text: m.as_str(),
471+
range: m.range(),
472+
is_quoteless: true,
473+
})
474+
} else {
475+
unreachable!("Unexpected captures from deno types regex")
476+
}
389477
}
390478

391479
#[cfg(test)]
392480
mod test {
393481
use std::collections::HashMap;
394482

395-
use deno_ast::dep::ImportAttribute;
396483
use pretty_assertions::assert_eq;
397484
use serde::de::DeserializeOwned;
398485
use serde_json::json;

0 commit comments

Comments
 (0)