Skip to content

Commit 8592cc3

Browse files
Detect dependency loops in module migrator
1 parent dce67db commit 8592cc3

File tree

6 files changed

+120
-1
lines changed

6 files changed

+120
-1
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 2.0.4
2+
* Detect dependency loops in module migrator fix.
3+
14
## 2.0.3
25

36
### Module Migrator

lib/src/migrators/module.dart

+31
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import '../migration_visitor.dart';
1515
import '../migrator.dart';
1616
import '../patch.dart';
1717
import '../utils.dart';
18+
import '../util/dependency_graph.dart';
1819
import '../util/member_declaration.dart';
1920
import '../util/node_modules_importer.dart';
2021

@@ -199,6 +200,33 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
199200
/// The values of the --forward flag.
200201
final Set<ForwardType> forwards;
201202

203+
/// Dependencies where keys represent source URIs and values represent imported URIs.
204+
final DependencyGraph _dependencies = DependencyGraph();
205+
206+
/// Checks for dependency loops between source and imported paths.
207+
///
208+
/// This method verifies whether importing a path introduces a circular dependency
209+
/// by checking if the imported path is already mapped as a dependency of the source path.
210+
///
211+
/// Throws a [MigrationException] if a dependency loop is detected.
212+
///
213+
/// The [source] parameter is the path where the dependency is checked.
214+
/// The [importedPath] parameter is the path being imported.
215+
void _checkDependency(Uri source, Uri importedPath, FileSpan span) {
216+
if (_dependencies.hasDependency(importedPath, source)) {
217+
// Throw an error indicating a potential loop.
218+
var (sourceUrl, _) = _absoluteUrlToDependency(source);
219+
var (importedPathUrl, _) = _absoluteUrlToDependency(importedPath);
220+
throw MigrationSourceSpanException(
221+
'Dependency loop detected: ${sourceUrl} -> ${importedPathUrl}.\n'
222+
'To resolve this issue, consider either of the following:\n'
223+
'1. Remove the import statement that causes the dependency loop.\n'
224+
'2. Declare the variables used in the other stylesheet within the same stylesheet.\n',
225+
span);
226+
}
227+
_dependencies.add(source, importedPath);
228+
}
229+
202230
/// Constructs a new module migration visitor.
203231
///
204232
/// [importCache] must be the same one used by [references].
@@ -848,6 +876,7 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
848876
withClause.isNotEmpty ||
849877
references.anyMemberReferenced(canonicalUrl, currentUrl)) {
850878
_usedUrls.add(canonicalUrl);
879+
_checkDependency(currentUrl, canonicalUrl, context);
851880
rules.add('@use $quotedUrl$asClause$withClause');
852881
}
853882
if (normalForwardRules != null) rules.addAll(normalForwardRules);
@@ -1223,6 +1252,8 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
12231252
String? _namespaceForDeclaration(MemberDeclaration declaration) {
12241253
var url = declaration.sourceUrl;
12251254
if (url == currentUrl) return null;
1255+
// Trace dependencies for loop detection.
1256+
_checkDependency(currentUrl, url, declaration.member.span);
12261257

12271258
// If we can load [declaration] from a library entrypoint URL, do so. Choose
12281259
// the shortest one if there are multiple options.

lib/src/util/dependency_graph.dart

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file or at
5+
// https://opensource.org/licenses/MIT.
6+
7+
/// A graph data structure to manage dependencies between URIs.
8+
///
9+
/// This class allows adding, removing, and querying dependencies
10+
/// between source URIs and their imported paths.
11+
class DependencyGraph {
12+
final Map<Uri, Set<Uri>> _graph = {};
13+
14+
/// Adds a dependency relationship between source and importedPath.
15+
void add(Uri source, Uri importedPath) {
16+
_graph.putIfAbsent(source, () => {}).add(importedPath);
17+
}
18+
19+
/// Removes a dependency relationship between source and importedPath.
20+
void remove(Uri source, Uri importedPath) {
21+
_graph[source]?.remove(importedPath);
22+
}
23+
24+
/// Finds all dependencies of a given source.
25+
Set<Uri>? find(Uri source) {
26+
return _graph[source];
27+
}
28+
29+
/// Checks if a specific dependency exists.
30+
bool hasDependency(Uri source, Uri importedPath) {
31+
return _graph.containsKey(source) && _graph[source]!.contains(importedPath);
32+
}
33+
}

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass_migrator
2-
version: 2.0.3
2+
version: 2.0.4
33
description: A tool for running migrations on Sass files
44
homepage: https://github.com/sass/migrator
55

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<==> arguments
2+
--migrate-deps
3+
4+
<==> input/entrypoint.scss
5+
$a: red;
6+
@import "ejemplo";
7+
a {
8+
background: $b;
9+
}
10+
11+
<==> input/_ejemplo.scss
12+
$b: blue;
13+
a {
14+
color: $a;
15+
}
16+
17+
<==> error.txt
18+
Error: Dependency loop detected: entrypoint -> ejemplo.
19+
To resolve this issue, consider either of the following:
20+
1. Remove the import statement that causes the dependency loop.
21+
2. Declare the variables used in the other stylesheet within the same stylesheet.
22+
23+
,
24+
2 | @import "ejemplo";
25+
| ^^^^^^^^^
26+
'
27+
entrypoint.scss 2:9 root stylesheet
28+
Migration failed!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<==> arguments
2+
--migrate-deps
3+
4+
<==> input/entrypoint.scss
5+
$var1: 2;
6+
@import "library";
7+
8+
<==> input/_library.scss
9+
a {
10+
margin: $var1;
11+
}
12+
13+
<==> error.txt
14+
Error: Dependency loop detected: entrypoint -> library.
15+
To resolve this issue, consider either of the following:
16+
1. Remove the import statement that causes the dependency loop.
17+
2. Declare the variables used in the other stylesheet within the same stylesheet.
18+
19+
,
20+
2 | @import "library";
21+
| ^^^^^^^^^
22+
'
23+
entrypoint.scss 2:9 root stylesheet
24+
Migration failed!

0 commit comments

Comments
 (0)