@@ -9,14 +9,73 @@ use crate::{
99 config:: LintConfig ,
1010 disable_directives:: { DisableDirectives , DisableDirectivesBuilder , RuleCommentType } ,
1111 fixer:: { Fix , FixKind , Message , PossibleFixes } ,
12- frameworks,
12+ frameworks:: { self , FrameworkOptions } ,
1313 module_record:: ModuleRecord ,
1414 options:: LintOptions ,
1515 rules:: RuleEnum ,
1616} ;
1717
1818use super :: { LintContext , plugin_name_to_prefix} ;
1919
20+ /// Stores shared information about a script block being linted.
21+ pub struct ContextSubHost < ' a > {
22+ /// Shared semantic information about the file being linted, which includes scopes, symbols
23+ /// and AST nodes. See [`Semantic`].
24+ pub ( super ) semantic : Rc < Semantic < ' a > > ,
25+ /// Cross module information.
26+ pub ( super ) module_record : Arc < ModuleRecord > ,
27+ /// Information about specific rules that should be disabled or enabled, via comment directives like
28+ /// `eslint-disable` or `eslint-disable-next-line`.
29+ pub ( super ) disable_directives : Rc < DisableDirectives < ' a > > ,
30+ // Specific framework options, for example, whether the context is inside `<script setup>` in Vue files.
31+ #[ expect( dead_code) ]
32+ pub ( super ) framework_options : FrameworkOptions ,
33+ /// The source text offset of the sub host
34+ #[ expect( dead_code) ]
35+ pub ( super ) source_text_offset : u32 ,
36+ }
37+
38+ impl < ' a > ContextSubHost < ' a > {
39+ pub fn new (
40+ semantic : Rc < Semantic < ' a > > ,
41+ module_record : Arc < ModuleRecord > ,
42+ source_text_offset : u32 ,
43+ ) -> Self {
44+ Self :: new_with_framework_options (
45+ semantic,
46+ module_record,
47+ source_text_offset,
48+ FrameworkOptions :: Default ,
49+ )
50+ }
51+
52+ /// # Panics
53+ /// If `semantic.cfg()` is `None`.
54+ pub fn new_with_framework_options (
55+ semantic : Rc < Semantic < ' a > > ,
56+ module_record : Arc < ModuleRecord > ,
57+ source_text_offset : u32 ,
58+ frameworks_options : FrameworkOptions ,
59+ ) -> Self {
60+ // We should always check for `semantic.cfg()` being `Some` since we depend on it and it is
61+ // unwrapped without any runtime checks after construction.
62+ assert ! (
63+ semantic. cfg( ) . is_some( ) ,
64+ "`LintContext` depends on `Semantic::cfg`, Build your semantic with cfg enabled(`SemanticBuilder::with_cfg`)."
65+ ) ;
66+
67+ let disable_directives =
68+ DisableDirectivesBuilder :: new ( ) . build ( semantic. source_text ( ) , semantic. comments ( ) ) ;
69+
70+ Self {
71+ semantic,
72+ module_record,
73+ source_text_offset,
74+ disable_directives : Rc :: new ( disable_directives) ,
75+ framework_options : frameworks_options,
76+ }
77+ }
78+ }
2079/// Stores shared information about a file being linted.
2180///
2281/// When linting a file, there are a number of shared resources that are
@@ -38,14 +97,11 @@ use super::{LintContext, plugin_name_to_prefix};
3897#[ must_use]
3998#[ non_exhaustive]
4099pub struct ContextHost < ' a > {
41- /// Shared semantic information about the file being linted, which includes scopes, symbols
42- /// and AST nodes. See [`Semantic`].
43- pub ( super ) semantic : Rc < Semantic < ' a > > ,
44- /// Cross module information.
45- pub ( super ) module_record : Arc < ModuleRecord > ,
46- /// Information about specific rules that should be disabled or enabled, via comment directives like
47- /// `eslint-disable` or `eslint-disable-next-line`.
48- pub ( super ) disable_directives : DisableDirectives < ' a > ,
100+ /// A file can have multiple script entries.
101+ /// Some rules (like vue) need the information of the other entries.
102+ pub ( super ) sub_hosts : Vec < ContextSubHost < ' a > > ,
103+ /// The current index which will be linted.
104+ current_sub_host_index : RefCell < usize > ,
49105 /// Diagnostics reported by the linter.
50106 ///
51107 /// Contains diagnostics for all rules across a single file.
@@ -67,32 +123,25 @@ pub struct ContextHost<'a> {
67123
68124impl < ' a > ContextHost < ' a > {
69125 /// # Panics
70- /// If `semantic.cfg() ` is `None` .
126+ /// If `sub_hosts ` is empty .
71127 pub fn new < P : AsRef < Path > > (
72128 file_path : P ,
73- semantic : Rc < Semantic < ' a > > ,
74- module_record : Arc < ModuleRecord > ,
129+ sub_hosts : Vec < ContextSubHost < ' a > > ,
75130 options : LintOptions ,
76131 config : Arc < LintConfig > ,
77132 ) -> Self {
78133 const DIAGNOSTICS_INITIAL_CAPACITY : usize = 512 ;
79134
80- // We should always check for `semantic.cfg()` being `Some` since we depend on it and it is
81- // unwrapped without any runtime checks after construction.
82135 assert ! (
83- semantic . cfg ( ) . is_some ( ) ,
84- "`LintContext` depends on `Semantic::cfg`, Build your semantic with cfg enabled(`SemanticBuilder::with_cfg`). "
136+ !sub_hosts . is_empty ( ) ,
137+ "ContextHost requires at least one ContextSubHost to be analyzed "
85138 ) ;
86139
87- let disable_directives =
88- DisableDirectivesBuilder :: new ( ) . build ( semantic. source_text ( ) , semantic. comments ( ) ) ;
89-
90140 let file_path = file_path. as_ref ( ) . to_path_buf ( ) . into_boxed_path ( ) ;
91141
92142 Self {
93- semantic,
94- module_record,
95- disable_directives,
143+ sub_hosts,
144+ current_sub_host_index : RefCell :: new ( 0 ) ,
96145 diagnostics : RefCell :: new ( Vec :: with_capacity ( DIAGNOSTICS_INITIAL_CAPACITY ) ) ,
97146 fix : options. fix ,
98147 file_path,
@@ -102,16 +151,26 @@ impl<'a> ContextHost<'a> {
102151 . sniff_for_frameworks ( )
103152 }
104153
105- /// Shared reference to the [`Semantic`] analysis of the file.
154+ /// The current [`ContextSubHost`]
155+ fn current_sub_host ( & self ) -> & ContextSubHost < ' a > {
156+ & self . sub_hosts [ * self . current_sub_host_index . borrow ( ) ]
157+ }
158+
159+ /// Shared reference to the [`Semantic`] analysis of current script block.
106160 #[ inline]
107- pub fn semantic ( & self ) -> & Semantic < ' a > {
108- & self . semantic
161+ pub fn semantic ( & self ) -> & Rc < Semantic < ' a > > {
162+ & self . current_sub_host ( ) . semantic
109163 }
110164
111- /// Shared reference to the [`ModuleRecord`] of the file .
165+ /// Shared reference to the [`ModuleRecord`] of the current script block .
112166 #[ inline]
113167 pub fn module_record ( & self ) -> & ModuleRecord {
114- & self . module_record
168+ & self . current_sub_host ( ) . module_record
169+ }
170+
171+ /// Shared reference to the [`DisableDirectives`] of the current script block.
172+ pub fn disable_directives ( & self ) -> & Rc < DisableDirectives < ' a > > {
173+ & self . current_sub_host ( ) . disable_directives
115174 }
116175
117176 /// Path to the file being linted.
@@ -127,7 +186,7 @@ impl<'a> ContextHost<'a> {
127186 /// CJS, ESM, etc.
128187 #[ inline]
129188 pub fn source_type ( & self ) -> & SourceType {
130- self . semantic . source_type ( )
189+ self . semantic ( ) . source_type ( )
131190 }
132191
133192 #[ inline]
@@ -147,14 +206,20 @@ impl<'a> ContextHost<'a> {
147206 self . diagnostics . borrow_mut ( ) . extend ( diagnostics) ;
148207 }
149208
209+ // move the context to the next sub host
210+ pub fn next_sub_host ( & self ) -> bool {
211+ * self . current_sub_host_index . borrow_mut ( ) += 1 ;
212+ self . sub_hosts . get ( * self . current_sub_host_index . borrow ( ) ) . is_some ( )
213+ }
214+
150215 /// report unused enable/disable directives, add these as Messages to diagnostics
151216 pub fn report_unused_directives ( & self , rule_severity : Severity ) {
152217 // report unused disable
153218 // relate to lint result, check after linter run finish
154- let unused_disable_comments = self . disable_directives . collect_unused_disable_comments ( ) ;
219+ let unused_disable_comments = self . disable_directives ( ) . collect_unused_disable_comments ( ) ;
155220 let message_for_disable = "Unused eslint-disable directive (no problems were reported)." ;
156221 let fix_message = "remove unused disable directive" ;
157- let source_text = self . semantic . source_text ( ) ;
222+ let source_text = self . semantic ( ) . source_text ( ) ;
158223
159224 for unused_disable_comment in unused_disable_comments {
160225 let span = unused_disable_comment. span ;
@@ -188,14 +253,14 @@ impl<'a> ContextHost<'a> {
188253 }
189254 }
190255
191- let unused_enable_comments = self . disable_directives . unused_enable_comments ( ) ;
256+ let unused_enable_comments = self . disable_directives ( ) . unused_enable_comments ( ) ;
192257 let mut unused_directive_diagnostics: Vec < ( Cow < str > , Span ) > =
193258 Vec :: with_capacity ( unused_enable_comments. len ( ) ) ;
194259 // report unused enable
195260 // not relate to lint result, check during comment directives' construction
196261 let message_for_enable =
197262 "Unused eslint-enable directive (no matching eslint-disable directives were found)." ;
198- for ( rule_name, enable_comment_span) in self . disable_directives . unused_enable_comments ( ) {
263+ for ( rule_name, enable_comment_span) in self . disable_directives ( ) . unused_enable_comments ( ) {
199264 unused_directive_diagnostics. push ( (
200265 rule_name. map_or ( Cow :: Borrowed ( message_for_enable) , |name| {
201266 Cow :: Owned ( format ! (
0 commit comments