Skip to content

Commit dd3bc85

Browse files
authored
Improvements to the division migrator (#41)
* Improvements to the division migrator Resolves #32 and resolves #33. * Refactor special color function handle into fn * Fix remaining comments * Add comment and changelog entry
1 parent d39d6a3 commit dd3bc85

File tree

7 files changed

+143
-25
lines changed

7 files changed

+143
-25
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 1.0.0-alpha.3
2+
3+
* Division migrator: Treat most slash operations within argument lists as
4+
division.
5+
* Division migrator: Only migrate slash operations to slash-list calls in a
6+
non-plain-CSS context.
7+
18
## 1.0.0-alpha.2
29

310
* Internal changes only.

lib/src/migrators/division.dart

+89-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// https://opensource.org/licenses/MIT.
66

77
import 'package:args/args.dart';
8+
import 'package:sass/sass.dart';
89

910
// The sass package's API is not necessarily stable. It is being imported with
1011
// the Sass team's explicit knowledge and approval. See
@@ -49,6 +50,13 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
4950
/// True when the current node is expected to evaluate to a number.
5051
var _expectsNumericResult = false;
5152

53+
/// Allows division within this argument invocation.
54+
@override
55+
void visitArgumentInvocation(ArgumentInvocation invocation) {
56+
_withContext(() => super.visitArgumentInvocation(invocation),
57+
isDivisionAllowed: true);
58+
}
59+
5260
/// If this is a division operation, migrates it.
5361
///
5462
/// If this is any other operator, allows division within its left and right
@@ -65,6 +73,15 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
6573
}
6674
}
6775

76+
/// Allows division within a function call's arguments, with special handling
77+
/// for new-syntax color functions.
78+
@override
79+
void visitFunctionExpression(FunctionExpression node) {
80+
visitInterpolation(node.name);
81+
if (_tryColorFunction(node)) return;
82+
visitArgumentInvocation(node.arguments);
83+
}
84+
6885
/// Disallows division within this list.
6986
@override
7087
void visitListExpression(ListExpression node) {
@@ -105,6 +122,48 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
105122
isDivisionAllowed: true);
106123
}
107124

125+
/// Migrates [node] and returns true if it is a new-syntax color function or
126+
/// returns false if it is any other function.
127+
bool _tryColorFunction(FunctionExpression node) {
128+
if (!["rgb", "rgba", "hsl", "hsla"].contains(node.name.asPlain)) {
129+
return false;
130+
}
131+
132+
ListExpression channels;
133+
if (node.arguments.positional.length == 1 &&
134+
node.arguments.named.isEmpty &&
135+
node.arguments.positional.first is ListExpression) {
136+
channels = node.arguments.positional.first;
137+
} else if (node.arguments.positional.isEmpty &&
138+
node.arguments.named.containsKey(r'$channels') &&
139+
node.arguments.named.length == 1 &&
140+
node.arguments.named[r'$channels'] is ListExpression) {
141+
channels = node.arguments.named[r'$channels'];
142+
}
143+
if (channels == null ||
144+
channels.hasBrackets ||
145+
channels.separator != ListSeparator.space ||
146+
channels.contents.length != 3 ||
147+
channels.contents.last is! BinaryOperationExpression) {
148+
return false;
149+
}
150+
151+
var last = channels.contents.last as BinaryOperationExpression;
152+
if (last.left is! NumberExpression || last.right is! NumberExpression) {
153+
// Handles cases like `rgb(10 20 30/2 / 0.5)`, since converting `30/2` to
154+
// `divide(30, 20)` would cause `/ 0.5` to be interpreted as division.
155+
_patchSpacesToCommas(channels);
156+
_patchOperatorToComma(last);
157+
}
158+
_withContext(() {
159+
channels.contents[0].accept(this);
160+
channels.contents[1].accept(this);
161+
last.left.accept(this);
162+
}, isDivisionAllowed: true);
163+
last.right.accept(this);
164+
return true;
165+
}
166+
108167
/// Visits a `/` operation [node] and migrates it to either the `division`
109168
/// function or the `slash-list` function.
110169
///
@@ -115,9 +174,14 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
115174
if ((!_isDivisionAllowed && _onlySlash(node)) ||
116175
_isDefinitelyNotNumber(node)) {
117176
// Definitely not division
118-
addPatch(patchBefore(node, "slash-list("));
119-
addPatch(patchAfter(node, ")"));
120-
_visitSlashListArguments(node);
177+
if (_isDivisionAllowed || _containsInterpolation(node)) {
178+
// We only want to convert a non-division slash operation to a
179+
// slash-list call when it's in a non-plain-CSS context to avoid
180+
// unnecessary function calls within plain CSS.
181+
addPatch(patchBefore(node, "slash-list("));
182+
addPatch(patchAfter(node, ")"));
183+
_visitSlashListArguments(node);
184+
}
121185
return true;
122186
} else if (_expectsNumericResult ||
123187
_isDefinitelyNumber(node) ||
@@ -126,7 +190,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
126190
addPatch(patchBefore(node, "divide("));
127191
addPatch(patchAfter(node, ")"));
128192
_patchParensIfAny(node.left);
129-
_patchSlashToComma(node);
193+
_patchOperatorToComma(node);
130194
_patchParensIfAny(node.right);
131195
_withContext(() => super.visitBinaryOperationExpression(node),
132196
expectsNumericResult: true);
@@ -145,7 +209,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
145209
if (node is BinaryOperationExpression &&
146210
node.operator == BinaryOperator.dividedBy) {
147211
_visitSlashListArguments(node.left);
148-
_patchSlashToComma(node);
212+
_patchOperatorToComma(node);
149213
_visitSlashListArguments(node.right);
150214
} else if (node is StringExpression &&
151215
node.text.contents.length == 1 &&
@@ -218,8 +282,27 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
218282
node is StringExpression;
219283
}
220284

285+
/// Returns true if [node] contains an interpolation.
286+
bool _containsInterpolation(Expression node) {
287+
if (node is ParenthesizedExpression) return _containsInterpolation(node);
288+
if (node is BinaryOperationExpression) {
289+
return _containsInterpolation(node.left) ||
290+
_containsInterpolation(node.right);
291+
}
292+
return node is StringExpression && node.text.asPlain == null;
293+
}
294+
295+
/// Converts a space-separated list [node] to a comma-separated list.
296+
void _patchSpacesToCommas(ListExpression node) {
297+
for (var i = 0; i < node.contents.length - 1; i++) {
298+
var start = node.contents[i].span.end;
299+
var end = node.contents[i + 1].span.start;
300+
addPatch(Patch(start.file.span(start.offset, end.offset), ", "));
301+
}
302+
}
303+
221304
/// Adds a patch replacing the operator of [node] with ", ".
222-
void _patchSlashToComma(BinaryOperationExpression node) {
305+
void _patchOperatorToComma(BinaryOperationExpression node) {
223306
var start = node.left.span.end;
224307
var end = node.right.span.start;
225308
addPatch(Patch(start.file.span(start.offset, end.offset), ", "));

pubspec.yaml

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

77
environment:
88
sdk: '>=2.2.0 <3.0.0'

test/migrators/division/always_division.hrx

+20
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
@return 6px / 3px;
77
}
88

9+
@function identity($x) {
10+
@return $x;
11+
}
12+
913
a {
14+
// Arithmetic on number literals
1015
b: (4px + 2px) / 3px;
1116
c: 6px/3px + 1;
1217
d: (6px / 3px);
@@ -24,14 +29,24 @@ a {
2429
l: 3 / $x > 1;
2530
m: 3 / $x <= 1;
2631
n: 3 / $x >= 1;
32+
33+
// Function calls
34+
o: identity(6px / 3px);
35+
p: rgba(10, 20, 30/2, 0.5);
36+
q: rgb(10 20 30/2 / 0.5);
2737
}
2838

2939
<==> output/entrypoint.scss
3040
@function six-divided-by-three() {
3141
@return divide(6px, 3px);
3242
}
3343

44+
@function identity($x) {
45+
@return $x;
46+
}
47+
3448
a {
49+
// Arithmetic on number literals
3550
b: divide(4px + 2px, 3px);
3651
c: divide(6px, 3px) + 1;
3752
d: divide(6px, 3px);
@@ -49,4 +64,9 @@ a {
4964
l: divide(3, $x) > 1;
5065
m: divide(3, $x) <= 1;
5166
n: divide(3, $x) >= 1;
67+
68+
// Function calls
69+
o: identity(divide(6px, 3px));
70+
p: rgba(10, 20, divide(30, 2), 0.5);
71+
q: rgb(10, 20, divide(30, 2), 0.5);
5272
}

test/migrators/division/maybe_division.hrx

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ a {
55
d: 3 / $x == 4;
66
e: fn() / 3;
77
f: 3 / $x;
8+
g: fn(3 / $x);
89
}
910

1011
<==> output/entrypoint.scss
@@ -14,4 +15,5 @@ a {
1415
d: divide(3, $x) == 4;
1516
e: divide(fn(), 3);
1617
f: divide(3, $x);
18+
g: fn(divide(3, $x));
1719
}

test/migrators/division/never_division.hrx

+6-17
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,11 @@
22
:foo(#{6/3}) {
33
b: 6px / 3px;
44
c: 6px / 3px / 2;
5-
d: #{4px + 2px} / 3px;
6-
e: bold 6px/3px sans-serif;
7-
f: (bold 6px/3px sans-serif);
8-
g: (six / three);
9-
h: 6 / three;
10-
i: #{$x} / #{6px / 3px} / #{2};
5+
d: bold 6px/3px sans-serif;
6+
e: (bold 6px/3px sans-serif);
7+
f: 6 / three;
8+
g: rgb(10 20 30 / 0.5);
119
}
1210

13-
<==> output/entrypoint.scss
14-
:foo(#{slash-list(6, 3)}) {
15-
b: slash-list(6px, 3px);
16-
c: slash-list(6px, 3px, 2);
17-
d: slash-list(4px + 2px, 3px);
18-
e: bold slash-list(6px, 3px) sans-serif;
19-
f: (bold slash-list(6px, 3px) sans-serif);
20-
g: slash-list(six, three);
21-
h: slash-list(6, three);
22-
i: slash-list($x, slash-list(6px, 3px), 2);
23-
}
11+
<==> log.txt
12+
Nothing to migrate!
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<==> input/entrypoint.scss
2+
a {
3+
b: #{4px + 2px} / 3px;
4+
c: (six / three);
5+
$d: 6 / three;
6+
e: #{$x} / #{6px / 3px} / #{2};
7+
$f: #{$x} / #{6px / 3px} / #{2};
8+
}
9+
10+
<==> output/entrypoint.scss
11+
a {
12+
b: slash-list(4px + 2px, 3px);
13+
c: slash-list(six, three);
14+
$d: slash-list(6, three);
15+
e: slash-list($x, 6px / 3px, 2);
16+
$f: slash-list($x, divide(6px, 3px), 2);
17+
}

0 commit comments

Comments
 (0)