Skip to content

Commit 76e48bd

Browse files
Detect dependency loops in module migrator
1 parent dce67db commit 76e48bd

File tree

5 files changed

+88
-1
lines changed

5 files changed

+88
-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

+27
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,29 @@ 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}', span);
222+
}
223+
_dependencies.add(source, importedPath);
224+
}
225+
202226
/// Constructs a new module migration visitor.
203227
///
204228
/// [importCache] must be the same one used by [references].
@@ -1224,6 +1248,9 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
12241248
var url = declaration.sourceUrl;
12251249
if (url == currentUrl) return null;
12261250

1251+
// Trace dependencies for loop detection.
1252+
_checkDependency(currentUrl, url, declaration.member.span);
1253+
12271254
// If we can load [declaration] from a library entrypoint URL, do so. Choose
12281255
// the shortest one if there are multiple options.
12291256
var libraryUrls = references.libraries[declaration];

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,24 @@
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+
,
20+
1 | $b: blue;
21+
| ^^^^^^^^
22+
'
23+
_ejemplo.scss 1:1 root stylesheet
24+
Migration failed!

0 commit comments

Comments
 (0)