-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathmigrator.dart
133 lines (120 loc) · 5.02 KB
/
migrator.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2019 Google LLC
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
// The sass package's API is not necessarily stable. It is being imported with
// the Sass team's explicit knowledge and approval. See
// https://github.com/sass/dart-sass/issues/236.
import 'package:sass/sass.dart';
import 'package:sass/src/ast/sass.dart';
import 'package:sass/src/importer.dart';
import 'package:sass/src/import_cache.dart';
import 'package:args/command_runner.dart';
import 'package:glob/glob.dart';
import 'package:glob/list_local_fs.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:sass_migrator/src/util/node_modules_importer.dart';
import 'package:source_span/source_span.dart';
import 'exception.dart';
import 'io.dart';
import 'utils.dart';
/// A migrator is a command that migrates the entrypoints provided to it and
/// (optionally) their dependencies.
///
/// Migrators should provide their [name], [description], and optionally
/// [aliases].
///
/// Subclasses need to implement [migrateFile], which takes an entrypoint,
/// migrates it, and stores the results in [migrated]. If [migrateDependencies]
/// is true, they should also migrate all of that entrypoint's direct and
/// indirect dependencies.
///
/// Most migrators will want to create a subclass of [MigrationVisitor] and
/// implement [migrateFile] with `MyMigrationVisitor(this, entrypoint).run()`.
abstract class Migrator extends Command<Map<Uri, String>> {
String get invocation => super
.invocation
.replaceFirst("[arguments]", "[options] <entrypoints.scss...>");
String get usage => "${super.usage}\n\n"
"See also https://sass-lang.com/documentation/cli/migrator#$name";
/// If true, dependencies will be migrated in addition to the entrypoints.
bool get migrateDependencies => globalResults!['migrate-deps'] as bool;
/// Map of missing dependency URLs to the spans that import/use them.
///
/// Subclasses should add any missing dependencies to this when they are
/// encountered during migration. If using [MigrationVisitor], all items in
/// its `missingDependencies` property should be added to this after calling
/// `run`.
final missingDependencies = <Uri, FileSpan>{};
/// Runs this migrator on [stylesheet] (and its dependencies, if the
/// --migrate-deps flag is passed).
///
/// Files that did not require any changes, even if touched by the migrator,
/// should not be included map of results.
@protected
Map<Uri, String> migrateFile(
ImportCache importCache, Stylesheet stylesheet, Importer importer);
/// Runs this migrator.
///
/// Each entrypoint is migrated separately. If a stylesheet is migrated more
/// than once, the resulting migrated contents must be the same each time, or
/// this will error.
///
/// Entrypoints and dependencies that did not require any changes will not be
/// included in the results.
Map<Uri, String> run() {
var allMigrated = <Uri, String>{};
var importer = FilesystemImporter('.');
var importCache = ImportCache(
importers: [NodeModulesImporter()],
loadPaths: globalResults!['load-path']);
var entrypoints = [
for (var argument in argResults!.rest)
for (var entry in Glob(argument).listSync())
if (entry is File) entry.path
];
for (var entrypoint in entrypoints) {
var tuple =
importCache.import(Uri.parse(entrypoint), baseImporter: importer);
if (tuple == null) {
throw MigrationException("Could not find Sass file at '$entrypoint'.");
}
var migrated = migrateFile(importCache, tuple.item2, tuple.item1);
migrated.forEach((file, contents) {
if (allMigrated.containsKey(file) && contents != allMigrated[file]) {
throw MigrationException(
"The migrator has found multiple possible migrations for $file, "
"depending on the context in which it's loaded.");
}
allMigrated[file] = contents;
});
}
if (missingDependencies.isNotEmpty) _warnForMissingDependencies();
return allMigrated;
}
/// Prints warnings for any missing dependencies encountered during migration.
///
/// By default, this prints a short warning with one line per missing
/// dependency.
///
/// In verbose mode, this instead prints a full warning with the source span
/// for each missing dependency.
void _warnForMissingDependencies() {
if (globalResults!['verbose'] as bool) {
for (var uri in missingDependencies.keys) {
emitWarning("Could not find Sass file at '${p.prettyUri(uri)}'.",
missingDependencies[uri]);
}
} else {
var count = missingDependencies.length;
emitWarning(
"$count dependenc${count == 1 ? 'y' : 'ies'} could not be found.");
missingDependencies.forEach((url, context) {
printStderr(' ${p.prettyUri(url)} '
'@${p.prettyUri(context.sourceUrl)}:${context.start.line + 1}');
});
}
}
}