Skip to content

Commit 5d3f6ea

Browse files
authored
fix(issue:4267) support starting-style at-rule (#4333)
* fix(issue:4267) support starting-style at-rule * Add support for the starting-style at-rule. * fix(issue:4267) improve comment and call handling * Improve at-rule comment and call handling and add more starting-style at-rule tests. * refactor(issue:4267) refactor starting-style code * Refactor new code introduced to broaded at-rule support for starting-syle at-rule and other at-rules. * chore(issue:4267) cleanup at-rule enhancement * Cleanup at-rule enhancement code for starting-style at-rule support. * fix: pin Playwright to exact version * Pin Playwright to exact version to try to resolve CI issues.
1 parent ddef3eb commit 5d3f6ea

File tree

8 files changed

+269
-19
lines changed

8 files changed

+269
-19
lines changed

packages/less/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"less-plugin-clean-css": "^1.6.0",
8585
"minimist": "^1.2.0",
8686
"mocha": "^6.2.1",
87-
"playwright": "~1.50.1",
87+
"playwright": "1.50.1",
8888
"mocha-teamcity-reporter": "^3.0.0",
8989
"nock": "^11.8.2",
9090
"npm-run-all": "^4.1.5",

packages/less/src/less/parser/parser.js

+3
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,9 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
21022102
hasUnknown = true;
21032103
isRooted = false;
21042104
break;
2105+
case '@starting-style':
2106+
isRooted = false;
2107+
break;
21052108
default:
21062109
hasUnknown = true;
21072110
break;

packages/less/src/less/tree/atrule.js

+119-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Node from './node';
22
import Selector from './selector';
33
import Ruleset from './ruleset';
44
import Anonymous from './anonymous';
5+
import NestableAtRulePrototype from './nested-at-rule';
56

67
const AtRule = function(
78
name,
@@ -14,19 +15,45 @@ const AtRule = function(
1415
visibilityInfo
1516
) {
1617
let i;
18+
var selectors = (new Selector([], null, null, this._index, this._fileInfo)).createEmptySelectors();
1719

1820
this.name = name;
1921
this.value = (value instanceof Node) ? value : (value ? new Anonymous(value) : value);
2022
if (rules) {
2123
if (Array.isArray(rules)) {
22-
this.rules = rules;
24+
const allDeclarations = this.declarationsBlock(rules);
25+
26+
let allRulesetDeclarations = true;
27+
rules.forEach(rule => {
28+
if (rule.type === 'Ruleset' && rule.rules) allRulesetDeclarations = allRulesetDeclarations && this.declarationsBlock(rule.rules, true);
29+
});
30+
31+
if (allDeclarations && !isRooted) {
32+
this.simpleBlock = true;
33+
this.declarations = rules;
34+
} else if (allRulesetDeclarations && rules.length === 1 && !isRooted && !value) {
35+
this.simpleBlock = true;
36+
this.declarations = rules[0].rules ? rules[0].rules : rules;
37+
} else {
38+
this.rules = rules;
39+
}
2340
} else {
24-
this.rules = [rules];
25-
this.rules[0].selectors = (new Selector([], null, null, index, currentFileInfo)).createEmptySelectors();
41+
const allDeclarations = this.declarationsBlock(rules.rules);
42+
43+
if (allDeclarations && !isRooted && !value) {
44+
this.simpleBlock = true;
45+
this.declarations = rules.rules;
46+
} else {
47+
this.rules = [rules];
48+
this.rules[0].selectors = (new Selector([], null, null, index, currentFileInfo)).createEmptySelectors();
49+
}
2650
}
27-
for (i = 0; i < this.rules.length; i++) {
28-
this.rules[i].allowImports = true;
51+
if (!this.simpleBlock) {
52+
for (i = 0; i < this.rules.length; i++) {
53+
this.rules[i].allowImports = true;
54+
}
2955
}
56+
this.setParent(selectors, this);
3057
this.setParent(this.rules, this);
3158
}
3259
this._index = index;
@@ -39,10 +66,24 @@ const AtRule = function(
3966

4067
AtRule.prototype = Object.assign(new Node(), {
4168
type: 'AtRule',
69+
70+
...NestableAtRulePrototype,
71+
72+
declarationsBlock(rules, mergeable = false) {
73+
if (!mergeable) {
74+
return rules.filter(function (node) { return (node.type === 'Declaration' || node.type === 'Comment') && !node.merge}).length === rules.length;
75+
} else {
76+
return rules.filter(function (node) { return (node.type === 'Declaration' || node.type === 'Comment'); }).length === rules.length;
77+
}
78+
},
79+
4280
accept(visitor) {
43-
const value = this.value, rules = this.rules;
81+
const value = this.value, rules = this.rules, declarations = this.declarations;
82+
4483
if (rules) {
4584
this.rules = visitor.visitArray(rules);
85+
} else if (declarations) {
86+
this.declarations = visitor.visitArray(declarations);
4687
}
4788
if (value) {
4889
this.value = visitor.visit(value);
@@ -58,22 +99,24 @@ AtRule.prototype = Object.assign(new Node(), {
5899
},
59100

60101
genCSS(context, output) {
61-
const value = this.value, rules = this.rules;
102+
const value = this.value, rules = this.rules || this.declarations;
62103
output.add(this.name, this.fileInfo(), this.getIndex());
63104
if (value) {
64105
output.add(' ');
65106
value.genCSS(context, output);
66107
}
67-
if (rules) {
108+
if (this.simpleBlock) {
109+
this.outputRuleset(context, output, this.declarations);
110+
} else if (rules) {
68111
this.outputRuleset(context, output, rules);
69112
} else {
70113
output.add(';');
71114
}
72115
},
73116

74117
eval(context) {
75-
let mediaPathBackup, mediaBlocksBackup, value = this.value, rules = this.rules;
76-
118+
let mediaPathBackup, mediaBlocksBackup, value = this.value, rules = this.rules || this.declarations;
119+
77120
// media stored inside other atrule should not bubble over it
78121
// backpup media bubbling information
79122
mediaPathBackup = context.mediaPath;
@@ -85,17 +128,78 @@ AtRule.prototype = Object.assign(new Node(), {
85128
if (value) {
86129
value = value.eval(context);
87130
}
131+
88132
if (rules) {
89-
// assuming that there is only one rule at this point - that is how parser constructs the rule
90-
rules = [rules[0].eval(context)];
91-
rules[0].root = true;
133+
rules = this.evalRoot(context, rules);
134+
}
135+
if (Array.isArray(rules) && rules[0].rules && Array.isArray(rules[0].rules) && rules[0].rules.length) {
136+
const allMergeableDeclarations = this.declarationsBlock(rules[0].rules, true);
137+
if (allMergeableDeclarations && !this.isRooted && !value) {
138+
var mergeRules = context.pluginManager.less.visitors.ToCSSVisitor.prototype._mergeRules;
139+
mergeRules(rules[0].rules);
140+
rules = rules[0].rules;
141+
rules.forEach(rule => rule.merge = false);
142+
}
143+
}
144+
if (this.simpleBlock && rules) {
145+
rules[0].functionRegistry = context.frames[0].functionRegistry.inherit();
146+
rules= rules.map(function (rule) { return rule.eval(context); });
92147
}
148+
93149
// restore media bubbling information
94150
context.mediaPath = mediaPathBackup;
95151
context.mediaBlocks = mediaBlocksBackup;
152+
return new AtRule(this.name, value, rules, this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
153+
},
96154

97-
return new AtRule(this.name, value, rules,
98-
this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
155+
evalRoot(context, rules) {
156+
let ampersandCount = 0;
157+
let noAmpersandCount = 0;
158+
let noAmpersands = true;
159+
let allAmpersands = false;
160+
161+
if (!this.simpleBlock) {
162+
rules = [rules[0].eval(context)];
163+
}
164+
165+
let precedingSelectors = [];
166+
if (context.frames.length > 0) {
167+
for (let index = 0; index < context.frames.length; index++) {
168+
const frame = context.frames[index];
169+
if (
170+
frame.type === 'Ruleset' &&
171+
frame.rules &&
172+
frame.rules.length > 0
173+
) {
174+
if (frame && !frame.root && frame.selectors && frame.selectors.length > 0) {
175+
precedingSelectors = precedingSelectors.concat(frame.selectors);
176+
}
177+
}
178+
if (precedingSelectors.length > 0) {
179+
let value = '';
180+
const output = { add: function (s) { value += s; } };
181+
for (let i = 0; i < precedingSelectors.length; i++) {
182+
precedingSelectors[i].genCSS(context, output);
183+
}
184+
if (/^&+$/.test(value.replace(/\s+/g, ''))) {
185+
noAmpersands = false;
186+
noAmpersandCount++;
187+
} else {
188+
allAmpersands = false;
189+
ampersandCount++;
190+
}
191+
}
192+
}
193+
}
194+
195+
const mixedAmpersands = ampersandCount > 0 && noAmpersandCount > 0 && !allAmpersands && !noAmpersands;
196+
if (
197+
(this.isRooted && ampersandCount > 0 && noAmpersandCount === 0 && !allAmpersands && noAmpersands)
198+
|| !mixedAmpersands
199+
) {
200+
rules[0].root = true;
201+
}
202+
return rules;
99203
},
100204

101205
variable(name) {

packages/less/src/less/visitors/import-visitor.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,17 @@ ImportVisitor.prototype = {
166166
}
167167
},
168168
visitAtRule: function (atRuleNode, visitArgs) {
169-
this.context.frames.unshift(atRuleNode);
169+
if (atRuleNode.value) {
170+
this.context.frames.unshift(atRuleNode);
171+
} else if (atRuleNode.declarations && atRuleNode.declarations.length) {
172+
if (atRuleNode.isRooted) {
173+
this.context.frames.unshift(atRuleNode);
174+
} else {
175+
this.context.frames.unshift(atRuleNode.declarations[0]);
176+
}
177+
} else if (atRuleNode.rules && atRuleNode.rules.length) {
178+
this.context.frames.unshift(atRuleNode);
179+
}
170180
},
171181
visitAtRuleOut: function (atRuleNode) {
172182
this.context.frames.shift();

packages/less/src/less/visitors/join-selector-visitor.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ class JoinSelectorVisitor {
5252

5353
visitAtRule(atRuleNode, visitArgs) {
5454
const context = this.contexts[this.contexts.length - 1];
55-
if (atRuleNode.rules && atRuleNode.rules.length) {
55+
56+
if (atRuleNode.declarations && atRuleNode.declarations.length) {
57+
atRuleNode.declarations[0].root = (context.length === 0 || context[0].multiMedia);
58+
}
59+
else if (atRuleNode.rules && atRuleNode.rules.length) {
5660
atRuleNode.rules[0].root = (atRuleNode.isRooted || context.length === 0 || null);
5761
}
5862
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#nav {
2+
transition: background-color 3.5s;
3+
background-color: gray;
4+
}
5+
[popover]:popover-open {
6+
opacity: 1;
7+
transform: scaleX(1);
8+
@starting-style {
9+
opacity: 0;
10+
transform: scaleX(0);
11+
}
12+
}
13+
#target {
14+
transition: background-color 1.5s;
15+
background-color: green;
16+
}
17+
@starting-style {
18+
#target {
19+
background-color: transparent;
20+
}
21+
}
22+
#source {
23+
transition: background-color 2.5s;
24+
background-color: red;
25+
}
26+
source:first {
27+
opacity: 1;
28+
transform: scaleX(1);
29+
@starting-style {
30+
opacity: 0;
31+
transform: scaleX(0);
32+
}
33+
}
34+
#footer {
35+
color: yellow;
36+
}
37+
@starting-style {
38+
#footer {
39+
background-color: transparent;
40+
}
41+
}
42+
nav > [popover]:popover-open {
43+
opacity: 1;
44+
transform: scaleX(1);
45+
@starting-style {
46+
padding: 10px 8px 6px 4px;
47+
}
48+
}
49+
aside > [popover]:popover-open {
50+
opacity: 1;
51+
transform: scaleX(1);
52+
@starting-style {
53+
padding: 10px 20px 30px 40px;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#nav {
2+
transition: background-color 3.5s;
3+
background-color: gray;
4+
}
5+
6+
[popover]:popover-open {
7+
opacity: 1;
8+
transform: scaleX(1);
9+
10+
@starting-style {
11+
opacity: 0;
12+
transform: scaleX(0);
13+
}
14+
}
15+
16+
#target {
17+
transition: background-color 1.5s;
18+
background-color: green;
19+
}
20+
21+
@starting-style {
22+
#target {
23+
background-color: transparent;
24+
}
25+
}
26+
27+
#source {
28+
transition: background-color 2.5s;
29+
background-color: red;
30+
}
31+
32+
source:first {
33+
opacity: 1;
34+
transform: scaleX(1);
35+
36+
@starting-style {
37+
opacity: 0;
38+
transform: scaleX(0);
39+
}
40+
}
41+
42+
#footer {
43+
color: yellow;
44+
}
45+
46+
@starting-style {
47+
#footer {
48+
background-color: transparent;
49+
}
50+
}
51+
52+
nav > [popover]:popover-open {
53+
opacity: 1;
54+
transform: scaleX(1);
55+
56+
@starting-style {
57+
padding+_: 10px;
58+
padding+_: 8px;
59+
padding+_: 6px;
60+
padding+_: 4px;
61+
}
62+
}
63+
64+
aside > [popover]:popover-open {
65+
opacity: 1;
66+
transform: scaleX(1);
67+
68+
@starting-style {
69+
// vector math
70+
each(1 2 3 4, {
71+
padding+_: (@value * 10px);
72+
});
73+
}
74+
}

pnpm-lock.yaml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)