Skip to content

Commit 9635a52

Browse files
authored
Merge pull request #890 from sass/import-forward-twice
Fix some @import edge cases
2 parents 3ec2f3f + 731bd44 commit 9635a52

File tree

9 files changed

+311
-74
lines changed

9 files changed

+311
-74
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 1.23.8
2+
3+
* **Potentially breaking bug fix:** Members loaded through a nested `@import`
4+
are no longer ever accessible outside that nested context.
5+
6+
* Don't throw an error when importing two modules that both forward members with
7+
the same name. The latter name now takes precedence over the former, as per
8+
the specification.
9+
110
## 1.23.7
211

312
* No user-visible changes.

lib/src/ast/sass/statement/if_rule.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import 'package:source_span/source_span.dart';
66

77
import '../../../visitor/interface/statement.dart';
88
import '../expression.dart';
9+
import '../import/dynamic.dart';
910
import '../statement.dart';
1011
import 'function_rule.dart';
12+
import 'import_rule.dart';
1113
import 'mixin_rule.dart';
1214
import 'variable_declaration.dart';
1315

@@ -69,7 +71,9 @@ class IfClause {
6971
: hasDeclarations = children.any((child) =>
7072
child is VariableDeclaration ||
7173
child is FunctionRule ||
72-
child is MixinRule);
74+
child is MixinRule ||
75+
(child is ImportRule &&
76+
child.imports.any((import) => import is DynamicImport)));
7377

7478
String toString() =>
7579
(expression == null ? "@else" : "@if $expression") +

lib/src/ast/sass/statement/parent.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5+
import '../import/dynamic.dart';
56
import '../statement.dart';
67
import 'function_rule.dart';
8+
import 'import_rule.dart';
79
import 'mixin_rule.dart';
810
import 'variable_declaration.dart';
911

@@ -12,13 +14,16 @@ abstract class ParentStatement implements Statement {
1214
/// The child statements of this statement.
1315
final List<Statement> children;
1416

15-
/// Whether any of [children] is a variable, function, or mixin declaration.
17+
/// Whether any of [children] is a variable, function, or mixin declaration,
18+
/// or a dynamic import rule.
1619
final bool hasDeclarations;
1720

1821
ParentStatement(this.children)
1922
: hasDeclarations = children?.any((child) =>
2023
child is VariableDeclaration ||
2124
child is FunctionRule ||
22-
child is MixinRule) ??
25+
child is MixinRule ||
26+
(child is ImportRule &&
27+
child.imports.any((import) => import is DynamicImport))) ??
2328
false;
2429
}

lib/src/async_environment.dart

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'exception.dart';
1717
import 'extend/extender.dart';
1818
import 'module.dart';
1919
import 'module/forwarded_view.dart';
20+
import 'module/shadowed_view.dart';
2021
import 'util/merged_map_view.dart';
2122
import 'util/public_member_map_view.dart';
2223
import 'utils.dart';
@@ -42,6 +43,13 @@ class AsyncEnvironment {
4243
/// This is `null` if there are no forwarded modules.
4344
List<Module> _forwardedModules;
4445

46+
/// Modules forwarded by nested imports at each lexical scope level *beneath
47+
/// the global scope*.
48+
///
49+
/// This is `null` until it's needed, since most environments won't ever use
50+
/// this.
51+
List<List<Module>> _nestedForwardedModules;
52+
4553
/// Modules from [_modules], [_globalModules], and [_forwardedModules], in the
4654
/// order in which they were `@use`d.
4755
final List<Module> _allModules;
@@ -128,6 +136,7 @@ class AsyncEnvironment {
128136
: _modules = {},
129137
_globalModules = null,
130138
_forwardedModules = null,
139+
_nestedForwardedModules = null,
131140
_allModules = [],
132141
_variables = [{}],
133142
_variableNodes = sourceMap ? [{}] : null,
@@ -141,6 +150,7 @@ class AsyncEnvironment {
141150
this._modules,
142151
this._globalModules,
143152
this._forwardedModules,
153+
this._nestedForwardedModules,
144154
this._allModules,
145155
this._variables,
146156
this._variableNodes,
@@ -164,6 +174,7 @@ class AsyncEnvironment {
164174
_modules,
165175
_globalModules,
166176
_forwardedModules,
177+
_nestedForwardedModules,
167178
_allModules,
168179
_variables.toList(),
169180
_variableNodes?.toList(),
@@ -179,6 +190,7 @@ class AsyncEnvironment {
179190
{},
180191
null,
181192
null,
193+
null,
182194
[],
183195
_variables.toList(),
184196
_variableNodes?.toList(),
@@ -270,30 +282,63 @@ class AsyncEnvironment {
270282
/// This is called when [module] is `@import`ed.
271283
void importForwards(Module module) {
272284
if (module is _EnvironmentModule) {
273-
for (var forwarded
274-
in module._environment._forwardedModules ?? const <Module>[]) {
275-
_globalModules ??= {};
276-
_globalModules.add(forwarded);
277-
278-
// Remove existing definitions that the forwarded members are now
279-
// shadowing.
280-
for (var variable in forwarded.variables.keys) {
281-
var index =
282-
_variableIndices.remove(variable) ?? _variableIndex(variable);
283-
if (index != null) {
284-
_variables[index].remove(variable);
285-
if (_variableNodes != null) _variableNodes[index].remove(variable);
285+
var forwarded = module._environment._forwardedModules;
286+
if (forwarded == null) return;
287+
288+
_globalModules ??= {};
289+
_forwardedModules ??= [];
290+
291+
var forwardedVariableNames =
292+
forwarded.expand((module) => module.variables.keys).toSet();
293+
var forwardedFunctionNames =
294+
forwarded.expand((module) => module.functions.keys).toSet();
295+
var forwardedMixinNames =
296+
forwarded.expand((module) => module.mixins.keys).toSet();
297+
298+
if (atRoot) {
299+
// Hide members from modules that have already been imported or
300+
// forwarded that would otherwise conflict with the @imported members.
301+
for (var module in _globalModules.toList()) {
302+
var shadowed = ShadowedModuleView.ifNecessary(module,
303+
variables: forwardedVariableNames,
304+
mixins: forwardedMixinNames,
305+
functions: forwardedFunctionNames);
306+
if (shadowed != null) {
307+
_globalModules.remove(module);
308+
_globalModules.add(shadowed);
286309
}
287310
}
288-
for (var function in forwarded.functions.keys) {
289-
var index =
290-
_functionIndices.remove(function) ?? _functionIndex(function);
291-
if (index != null) _functions[index].remove(function);
292-
}
293-
for (var mixin in forwarded.mixins.keys) {
294-
var index = _mixinIndices.remove(mixin) ?? _mixinIndex(mixin);
295-
if (index != null) _mixins[index].remove(mixin);
311+
for (var i = 0; i < _forwardedModules.length; i++) {
312+
var module = _forwardedModules[i];
313+
var shadowed = ShadowedModuleView.ifNecessary(module,
314+
variables: forwardedVariableNames,
315+
mixins: forwardedMixinNames,
316+
functions: forwardedFunctionNames);
317+
if (shadowed != null) _forwardedModules[i] = shadowed;
296318
}
319+
320+
_globalModules.addAll(forwarded);
321+
_forwardedModules.addAll(forwarded);
322+
} else {
323+
_nestedForwardedModules ??=
324+
List.generate(_variables.length - 1, (_) => []);
325+
_nestedForwardedModules.last.addAll(forwarded);
326+
}
327+
328+
// Remove existing member definitions that are now shadowed by the
329+
// forwarded modules.
330+
for (var variable in forwardedVariableNames) {
331+
_variableIndices.remove(variable);
332+
_variables.last.remove(variable);
333+
if (_variableNodes != null) _variableNodes.last.remove(variable);
334+
}
335+
for (var function in forwardedFunctionNames) {
336+
_functionIndices.remove(function);
337+
_functions.last.remove(function);
338+
}
339+
for (var mixin in forwardedMixinNames) {
340+
_mixinIndices.remove(mixin);
341+
_mixins.last.remove(mixin);
297342
}
298343
}
299344
}
@@ -467,6 +512,19 @@ class AsyncEnvironment {
467512
return;
468513
}
469514

515+
if (_nestedForwardedModules != null &&
516+
!_variableIndices.containsKey(name) &&
517+
_variableIndex(name) == null) {
518+
for (var modules in _nestedForwardedModules.reversed) {
519+
for (var module in modules.reversed) {
520+
if (module.variables.containsKey(name)) {
521+
module.setVariable(name, value, nodeWithSpan);
522+
return;
523+
}
524+
}
525+
}
526+
}
527+
470528
var index = _lastVariableName == name
471529
? _lastVariableIndex
472530
: _variableIndices.putIfAbsent(
@@ -652,6 +710,7 @@ class AsyncEnvironment {
652710
_variableNodes?.add({});
653711
_functions.add({});
654712
_mixins.add({});
713+
_nestedForwardedModules?.add([]);
655714
try {
656715
return await callback();
657716
} finally {
@@ -667,14 +726,18 @@ class AsyncEnvironment {
667726
for (var name in _mixins.removeLast().keys) {
668727
_mixinIndices.remove(name);
669728
}
729+
_nestedForwardedModules?.removeLast();
670730
}
671731
}
672732

673733
/// Returns a module that represents the top-level members defined in [this],
674734
/// that contains [css] as its CSS tree, which can be extended using
675735
/// [extender].
676-
Module toModule(CssStylesheet css, Extender extender) =>
677-
_EnvironmentModule(this, css, extender, forwarded: _forwardedModules);
736+
Module toModule(CssStylesheet css, Extender extender) {
737+
assert(atRoot);
738+
return _EnvironmentModule(this, css, extender,
739+
forwarded: _forwardedModules);
740+
}
678741

679742
/// Returns the module with the given [namespace], or throws a
680743
/// [SassScriptException] if none exists.
@@ -687,14 +750,24 @@ class AsyncEnvironment {
687750
}
688751

689752
/// Returns the result of [callback] if it returns non-`null` for exactly one
690-
/// module in [_globalModules].
753+
/// module in [_globalModules] *or* for any module in
754+
/// [_nestedForwardedModules].
691755
///
692756
/// Returns `null` if [callback] returns `null` for all modules. Throws an
693757
/// error if [callback] returns non-`null` for more than one module.
694758
///
695759
/// The [type] should be the singular name of the value type being returned.
696760
/// It's used to format an appropriate error message.
697761
T _fromOneModule<T>(String type, T callback(Module module)) {
762+
if (_nestedForwardedModules != null) {
763+
for (var modules in _nestedForwardedModules.reversed) {
764+
for (var module in modules.reversed) {
765+
var value = callback(module);
766+
if (value != null) return value;
767+
}
768+
}
769+
}
770+
698771
if (_globalModules == null) return null;
699772

700773
T value;

0 commit comments

Comments
 (0)