11// Copyright 2018-2024 the Deno authors. MIT license.
22
3+ use std:: collections:: HashMap ;
34use 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 ;
1510use serde:: Deserialize ;
1611use serde:: Serialize ;
17- use serde:: Serializer ;
1812
19- use crate :: ast:: DENO_TYPES_RE ;
2013use crate :: graph:: Position ;
14+ use crate :: graph:: PositionRange ;
2115use 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" ) ]
124110pub struct StaticDependencyDescriptor {
@@ -213,6 +199,7 @@ pub enum TypeScriptTypesResolutionMode {
213199}
214200
215201impl 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+
284275pub 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
381366impl < ' 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) ]
392480mod 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