Skip to content

Commit 3d6b449

Browse files
authored
Migrate to multiplication when possible (#198)
1 parent 3f9dea5 commit 3d6b449

File tree

7 files changed

+104
-29
lines changed

7 files changed

+104
-29
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## 1.5.0
2+
3+
### Division Migrator
4+
5+
* When migrating division where the divisor is a common constant value, the
6+
migrator will now convert it to multiplication, rather than `math.div`.
7+
8+
For example: `$variable / 2` would be migrated to `$variable * 0.5`.
9+
10+
To disable this and migrate all division to `math.div`, pass
11+
`--no-multiplication`.
12+
113
## 1.4.5
214

315
* Glob syntax will no longer be resolved if a file with that literal name

lib/src/migrators/division.dart

+41-12
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,35 @@ More info: https://sass-lang.com/d/slash-div""";
3232
..addFlag('pessimistic',
3333
abbr: 'p',
3434
help: "Only migrate / expressions that are unambiguously division.",
35-
negatable: false);
35+
negatable: false)
36+
..addFlag('multiplication',
37+
help: 'Migrate / expressions with certain constant divisors to use '
38+
'multiplication instead.',
39+
defaultsTo: true);
3640

3741
bool get isPessimistic => argResults!['pessimistic'] as bool;
42+
bool get useMultiplication => argResults!['multiplication'] as bool;
3843

3944
@override
4045
Map<Uri, String> migrateFile(
4146
ImportCache importCache, Stylesheet stylesheet, Importer importer) {
4247
var visitor = _DivisionMigrationVisitor(
43-
importCache, this.isPessimistic, migrateDependencies);
48+
importCache, isPessimistic, useMultiplication, migrateDependencies);
4449
var result = visitor.run(stylesheet, importer);
4550
missingDependencies.addAll(visitor.missingDependencies);
4651
return result;
4752
}
4853
}
4954

55+
/// The set of constant divisors that should be migrated to multiplication.
56+
const _allowedDivisors = {2, 4, 5, 8, 10, 20, 40, 50, 80, 100, 1000};
57+
5058
class _DivisionMigrationVisitor extends MigrationVisitor {
5159
final bool isPessimistic;
60+
final bool useMultiplication;
5261

53-
_DivisionMigrationVisitor(
54-
ImportCache importCache, this.isPessimistic, bool migrateDependencies)
62+
_DivisionMigrationVisitor(ImportCache importCache, this.isPessimistic,
63+
this.useMultiplication, bool migrateDependencies)
5564
: super(importCache, migrateDependencies);
5665

5766
/// True when division is allowed by the context the current node is in.
@@ -260,9 +269,8 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
260269
/// Visits a `/` operation [node] and migrates it to either the `division`
261270
/// function or the `slash-list` function.
262271
///
263-
/// Returns true the `/` was migrated to either function call, and false if
264-
/// the `/` is ambiguous and a warning was emitted instead (pessimistic mode
265-
/// only).
272+
/// Returns true the `/` was migrated to either function call (indicating that
273+
/// parentheses surrounding this operation should be removed).
266274
bool _visitSlashOperation(BinaryOperationExpression node) {
267275
if ((!_isDivisionAllowed && _onlySlash(node)) ||
268276
_isDefinitelyNotNumber(node)) {
@@ -276,17 +284,17 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
276284
_visitSlashListArguments(node);
277285
}
278286
return true;
279-
} else if (_expectsNumericResult ||
280-
_isDefinitelyNumber(node) ||
281-
!isPessimistic) {
287+
}
288+
if (_expectsNumericResult || _isDefinitelyNumber(node) || !isPessimistic) {
282289
// Definitely division
290+
_withContext(() => super.visitBinaryOperationExpression(node),
291+
expectsNumericResult: true);
292+
if (_tryMultiplication(node)) return false;
283293
addPatch(patchBefore(node, "${_builtInPrefix('math')}div("));
284294
addPatch(patchAfter(node, ")"));
285295
_patchParensIfAny(node.left);
286296
_patchOperatorToComma(node);
287297
_patchParensIfAny(node.right);
288-
_withContext(() => super.visitBinaryOperationExpression(node),
289-
expectsNumericResult: true);
290298
return true;
291299
} else {
292300
emitWarning("Could not determine whether this is division", node.span);
@@ -295,6 +303,27 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
295303
}
296304
}
297305

306+
/// Given a division operation [node], patches it to use multiplication
307+
/// instead if the reciprocal of the divisor can be accurately represented as
308+
/// a decimal.
309+
///
310+
/// Returns true if patched and false otherwise.
311+
bool _tryMultiplication(BinaryOperationExpression node) {
312+
if (!useMultiplication) return false;
313+
var divisor = node.right;
314+
if (divisor is! NumberExpression) return false;
315+
if (divisor.unit != null) return false;
316+
if (!_allowedDivisors.contains(divisor.value)) return false;
317+
var operatorSpan = node.left.span
318+
.extendThroughWhitespace()
319+
.end
320+
.pointSpan()
321+
.extendIfMatches('/');
322+
addPatch(Patch(operatorSpan, '*'));
323+
addPatch(Patch(node.right.span, '${1 / divisor.value}'));
324+
return true;
325+
}
326+
298327
/// Visits the arguments of a `/` operation that is being converted into a
299328
/// call to `slash-list`, converting slashes to commas and removing
300329
/// unnecessary interpolation.

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass_migrator
2-
version: 1.4.5
2+
version: 1.5.0
33
description: A tool for running migrations on Sass files
44
author: Jennifer Thakar <[email protected]>
55
homepage: https://github.com/sass/migrator

test/cli_dart_test.dart

+7-15
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,9 @@ void main() {
3737

3838
await (await runMigrator(["division", "test-*.scss"])).shouldExit(0);
3939

40-
await d
41-
.file("test-1.scss", '@use "sass:math";\n\na {b: math.div(1, 2)}')
42-
.validate();
43-
await d
44-
.file("test-2.scss", '@use "sass:math";\n\nc {d: math.div(1, 2)}')
45-
.validate();
46-
await d
47-
.file("test-3.scss", '@use "sass:math";\n\ne {f: math.div(1, 2)}')
48-
.validate();
40+
await d.file("test-1.scss", 'a {b: (1 * 0.5)}').validate();
41+
await d.file("test-2.scss", 'c {d: (1 * 0.5)}').validate();
42+
await d.file("test-3.scss", 'e {f: (1 * 0.5)}').validate();
4943
});
5044

5145
test("allows recursive glob arguments", () async {
@@ -58,9 +52,9 @@ void main() {
5852
await (await runMigrator(["division", "**.scss"])).shouldExit(0);
5953

6054
await d.dir('dir', [
61-
d.file("test-1.scss", '@use "sass:math";\n\na {b: math.div(1, 2)}'),
62-
d.file("test-2.scss", '@use "sass:math";\n\nc {d: math.div(1, 2)}'),
63-
d.file("test-3.scss", '@use "sass:math";\n\ne {f: math.div(1, 2)}')
55+
d.file("test-1.scss", 'a {b: (1 * 0.5)}'),
56+
d.file("test-2.scss", 'c {d: (1 * 0.5)}'),
57+
d.file("test-3.scss", 'e {f: (1 * 0.5)}')
6458
]).validate();
6559
});
6660

@@ -69,9 +63,7 @@ void main() {
6963

7064
await (await runMigrator(["division", "[dir]/test.scss"])).shouldExit(0);
7165

72-
await d.dir('[dir]', [
73-
d.file("test.scss", '@use "sass:math";\n\na {b: math.div(1, 2)}')
74-
]).validate();
66+
await d.dir('[dir]', [d.file("test.scss", 'a {b: (1 * 0.5)}')]).validate();
7567
});
7668

7769
group("with --dry-run", () {

test/migrators/division/always_division.hrx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<==> arguments
2-
--pessimistic
2+
--pessimistic --no-multiplication
33

44
<==> input/entrypoint.scss
55
@function six-divided-by-three() {
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<==> input/entrypoint.scss
2+
$a: 2000px / 2;
3+
$b: $a / 4;
4+
$c: $a / 5;
5+
$d: ($a / 8);
6+
$e: ($a / 10);
7+
$f: ($a / 20);
8+
$g: $a/40;
9+
$h: ($a/50);
10+
$i: ($a + $b)/80;
11+
$j: ($a / 2)/100;
12+
$k: $a/2/1000;
13+
14+
<==> output/entrypoint.scss
15+
$a: 2000px * 0.5;
16+
$b: $a * 0.25;
17+
$c: $a * 0.2;
18+
$d: ($a * 0.125);
19+
$e: ($a * 0.1);
20+
$f: ($a * 0.05);
21+
$g: $a*0.025;
22+
$h: ($a*0.02);
23+
$i: ($a + $b)*0.0125;
24+
$j: ($a * 0.5)*0.01;
25+
$k: $a*0.5*0.001;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<==> input/entrypoint.scss
2+
$a: 2000px / 3;
3+
$b: $a / 7;
4+
$c: $a / 16;
5+
$d: $a / 25;
6+
$e: $a / 10000;
7+
$f: $a / 2px;
8+
9+
<==> output/entrypoint.scss
10+
@use "sass:math";
11+
12+
$a: math.div(2000px, 3);
13+
$b: math.div($a, 7);
14+
$c: math.div($a, 16);
15+
$d: math.div($a, 25);
16+
$e: math.div($a, 10000);
17+
$f: math.div($a, 2px);

0 commit comments

Comments
 (0)