@@ -35,16 +35,32 @@ class AsyncEnvironment {
35
35
Map <String , Module > get modules => UnmodifiableMapView (_modules);
36
36
final Map <String , Module > _modules;
37
37
38
+ /// A map from module namespaces to the nodes whose spans indicate where those
39
+ /// modules were originally loaded.
40
+ final Map <String , AstNode > _namespaceNodes;
41
+
38
42
/// The namespaceless modules used in the current scope.
39
43
///
40
44
/// This is `null` if there are no namespaceless modules.
41
45
Set <Module > _globalModules;
42
46
47
+ /// A map from modules in [_globalModules] to the nodes whose spans
48
+ /// indicate where those modules were originally loaded.
49
+ ///
50
+ /// This is `null` if there are no namespaceless modules.
51
+ Map <Module , AstNode > _globalModuleNodes;
52
+
43
53
/// The modules forwarded by this module.
44
54
///
45
55
/// This is `null` if there are no forwarded modules.
46
56
List <Module > _forwardedModules;
47
57
58
+ /// A map from modules in [_forwardedModules] to the nodes whose spans
59
+ /// indicate where those modules were originally forwarded.
60
+ ///
61
+ /// This is `null` if there are no forwarded modules.
62
+ Map <Module , AstNode > _forwardedModuleNodes;
63
+
48
64
/// Modules forwarded by nested imports at each lexical scope level *beneath
49
65
/// the global scope*.
50
66
///
@@ -136,8 +152,11 @@ class AsyncEnvironment {
136
152
/// If [sourceMap] is `true` , this tracks variables' source locations
137
153
AsyncEnvironment ({bool sourceMap = false })
138
154
: _modules = {},
155
+ _namespaceNodes = {},
139
156
_globalModules = null ,
157
+ _globalModuleNodes = null ,
140
158
_forwardedModules = null ,
159
+ _forwardedModuleNodes = null ,
141
160
_nestedForwardedModules = null ,
142
161
_allModules = [],
143
162
_variables = [{}],
@@ -150,8 +169,11 @@ class AsyncEnvironment {
150
169
151
170
AsyncEnvironment ._(
152
171
this ._modules,
172
+ this ._namespaceNodes,
153
173
this ._globalModules,
174
+ this ._globalModuleNodes,
154
175
this ._forwardedModules,
176
+ this ._forwardedModuleNodes,
155
177
this ._nestedForwardedModules,
156
178
this ._allModules,
157
179
this ._variables,
@@ -174,8 +196,11 @@ class AsyncEnvironment {
174
196
/// when the closure was created will be reflected.
175
197
AsyncEnvironment closure () => AsyncEnvironment ._(
176
198
_modules,
199
+ _namespaceNodes,
177
200
_globalModules,
201
+ _globalModuleNodes,
178
202
_forwardedModules,
203
+ _forwardedModuleNodes,
179
204
_nestedForwardedModules,
180
205
_allModules,
181
206
_variables.toList (),
@@ -190,6 +215,9 @@ class AsyncEnvironment {
190
215
/// functions, and mixins, but not its modules.
191
216
AsyncEnvironment global () => AsyncEnvironment ._(
192
217
{},
218
+ {},
219
+ null ,
220
+ null ,
193
221
null ,
194
222
null ,
195
223
null ,
@@ -202,16 +230,20 @@ class AsyncEnvironment {
202
230
203
231
/// Adds [module] to the set of modules visible in this environment.
204
232
///
233
+ /// [nodeWithSpan] 's span is used to report any errors with the module.
234
+ ///
205
235
/// If [namespace] is passed, the module is made available under that
206
236
/// namespace.
207
237
///
208
- /// Throws a [SassScriptException ] if there's already a module with the given
238
+ /// Throws a [SassException ] if there's already a module with the given
209
239
/// [namespace] , or if [namespace] is `null` and [module] defines a variable
210
240
/// with the same name as a variable defined in this environment.
211
- void addModule (Module module, {String namespace}) {
241
+ void addModule (Module module, AstNode nodeWithSpan, {String namespace}) {
212
242
if (namespace == null ) {
213
243
_globalModules ?? = {};
244
+ _globalModuleNodes ?? = {};
214
245
_globalModules.add (module);
246
+ _globalModuleNodes[module] = nodeWithSpan;
215
247
_allModules.add (module);
216
248
217
249
for (var name in _variables.first.keys) {
@@ -223,11 +255,14 @@ class AsyncEnvironment {
223
255
}
224
256
} else {
225
257
if (_modules.containsKey (namespace)) {
226
- throw SassScriptException (
227
- "There's already a module with namespace \" $namespace \" ." );
258
+ throw MultiSpanSassScriptException (
259
+ "There's already a module with namespace \" $namespace \" ." ,
260
+ "new @use" ,
261
+ {_namespaceNodes[namespace].span: "original @use" });
228
262
}
229
263
230
264
_modules[namespace] = module;
265
+ _namespaceNodes[namespace] = nodeWithSpan;
231
266
_allModules.add (module);
232
267
}
233
268
}
@@ -236,12 +271,15 @@ class AsyncEnvironment {
236
271
/// defined in this module, according to the modifications defined by [rule] .
237
272
void forwardModule (Module module, ForwardRule rule) {
238
273
_forwardedModules ?? = [];
274
+ _forwardedModuleNodes ?? = {};
239
275
240
276
var view = ForwardedModuleView (module, rule);
241
277
for (var other in _forwardedModules) {
242
- _assertNoConflicts (view.variables, other.variables, "variable" , other);
243
- _assertNoConflicts (view.functions, other.functions, "function" , other);
244
- _assertNoConflicts (view.mixins, other.mixins, "mixin" , other);
278
+ _assertNoConflicts (
279
+ view.variables, other.variables, "variable" , other, rule);
280
+ _assertNoConflicts (
281
+ view.functions, other.functions, "function" , other, rule);
282
+ _assertNoConflicts (view.mixins, other.mixins, "mixin" , other, rule);
245
283
}
246
284
247
285
// Add the original module to [_allModules] (rather than the
@@ -250,14 +288,19 @@ class AsyncEnvironment {
250
288
// CSS, not for the members they expose.
251
289
_allModules.add (module);
252
290
_forwardedModules.add (view);
291
+ _forwardedModuleNodes[view] = rule;
253
292
}
254
293
255
294
/// Throws a [SassScriptException] if [newMembers] has any keys that overlap
256
295
/// with [oldMembers] .
257
296
///
258
- /// The [type] and [oldModule] is used for error reporting.
259
- void _assertNoConflicts (Map <String , Object > newMembers,
260
- Map <String , Object > oldMembers, String type, Module oldModule) {
297
+ /// The [type] , [other] , [newModuleNodeWithSpan] are used for error reporting.
298
+ void _assertNoConflicts (
299
+ Map <String , Object > newMembers,
300
+ Map <String , Object > oldMembers,
301
+ String type,
302
+ Module other,
303
+ AstNode newModuleNodeWithSpan) {
261
304
Map <String , Object > smaller;
262
305
Map <String , Object > larger;
263
306
if (newMembers.length < oldMembers.length) {
@@ -271,9 +314,10 @@ class AsyncEnvironment {
271
314
for (var name in smaller.keys) {
272
315
if (larger.containsKey (name)) {
273
316
if (type == "variable" ) name = "\$ $name " ;
274
- throw SassScriptException (
275
- 'Module ${p .prettyUri (oldModule .url )} and the new module both '
276
- 'forward a $type named $name .' );
317
+ throw MultiSpanSassScriptException (
318
+ 'Two forwarded modules both define a $type named $name .' ,
319
+ "new @forward" ,
320
+ {_forwardedModuleNodes[other].span: "original @forward" });
277
321
}
278
322
}
279
323
}
@@ -288,7 +332,9 @@ class AsyncEnvironment {
288
332
if (forwarded == null ) return ;
289
333
290
334
_globalModules ?? = {};
335
+ _globalModuleNodes ?? = {};
291
336
_forwardedModules ?? = [];
337
+ _forwardedModuleNodes ?? = {};
292
338
293
339
var forwardedVariableNames =
294
340
forwarded.expand ((module) => module.variables.keys).toSet ();
@@ -308,6 +354,7 @@ class AsyncEnvironment {
308
354
if (shadowed != null ) {
309
355
_globalModules.remove (module);
310
356
_globalModules.add (shadowed);
357
+ _globalModuleNodes[shadowed] = _globalModuleNodes.remove (module);
311
358
}
312
359
}
313
360
for (var i = 0 ; i < _forwardedModules.length; i++ ) {
@@ -316,11 +363,17 @@ class AsyncEnvironment {
316
363
variables: forwardedVariableNames,
317
364
mixins: forwardedMixinNames,
318
365
functions: forwardedFunctionNames);
319
- if (shadowed != null ) _forwardedModules[i] = shadowed;
366
+ if (shadowed != null ) {
367
+ _forwardedModules[i] = shadowed;
368
+ _forwardedModuleNodes[shadowed] =
369
+ _forwardedModuleNodes.remove (module);
370
+ }
320
371
}
321
372
322
373
_globalModules.addAll (forwarded);
374
+ _globalModuleNodes.addAll (module._environment._forwardedModuleNodes);
323
375
_forwardedModules.addAll (forwarded);
376
+ _forwardedModuleNodes.addAll (module._environment._forwardedModuleNodes);
324
377
} else {
325
378
_nestedForwardedModules ?? =
326
379
List .generate (_variables.length - 1 , (_) => []);
@@ -795,11 +848,12 @@ class AsyncEnvironment {
795
848
if (valueInModule == null ) continue ;
796
849
797
850
if (value != null ) {
798
- throw SassScriptException (
799
- 'This $type is available from multiple global modules:\n ' +
800
- bulletedList (_globalModules
801
- .where ((module) => callback (module) != null )
802
- .map ((module) => p.prettyUri (module.url))));
851
+ throw MultiSpanSassScriptException (
852
+ 'This $type is available from multiple global modules.' ,
853
+ '$type use' , {
854
+ for (var entry in _globalModuleNodes.entries)
855
+ if (callback (entry.key) != null ) entry.value.span: 'includes $type '
856
+ });
803
857
}
804
858
805
859
value = valueInModule;
0 commit comments