Skip to content
This repository was archived by the owner on Dec 10, 2024. It is now read-only.

Commit fb59802

Browse files
authored
Merge pull request atfzl#82 from yuhsianw/frankwang/fix-no-undef-class-global-bug
Fix `no-undef-class` `:global` bug
2 parents f37dbcd + 6e1a0c2 commit fb59802

File tree

5 files changed

+131
-138
lines changed

5 files changed

+131
-138
lines changed

lib/core/traversalUtils.js

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -204,25 +204,67 @@ export const getParentSelectorClassesMap = (ast: gASTNode): classMapType => {
204204
return classesMap;
205205
};
206206

207-
/*
208-
mutates ast by removing instances of :global
207+
/**
208+
* Mutates the AST by removing `:global` instances.
209+
*
210+
* For the AST structure:
211+
* @see https://github.com/css/gonzales/blob/master/doc/AST.CSSP.en.md
209212
*/
210213
export const eliminateGlobals = (ast: gASTNode) => {
211-
ast.traverse((node, index, parent) => {
212-
if (node.type === 'ruleset') {
213-
if (
214-
fp.compose(
215-
fp.negate(fp.isEmpty),
216-
fp.find({ type: 'ident', content: 'global' }),
217-
fp.get('content'),
218-
fp.find({ type: 'pseudoClass' }),
219-
fp.get('content'),
220-
fp.find({ type: 'selector' }),
221-
fp.get('content'),
222-
)(node)
223-
) {
224-
parent.removeChild(index);
214+
// Remove all :global/:global(...) in selectors
215+
ast.traverseByType('selector', (selectorNode) => {
216+
const selectorContent = selectorNode.content;
217+
let hasGlobalWithNoArgs = false;
218+
let i = 0;
219+
let currNode = selectorContent[i];
220+
while (currNode) {
221+
if (currNode.is('pseudoClass')) {
222+
// Remove all :global/:global(...) and trailing space
223+
const identifierNode = currNode.content[0];
224+
if (identifierNode && identifierNode.content === 'global') {
225+
if (currNode.content.length === 1) hasGlobalWithNoArgs = true;
226+
selectorNode.removeChild(i);
227+
if (selectorContent[i] && selectorContent[i].is('space')) {
228+
selectorNode.removeChild(i);
229+
}
230+
} else {
231+
i++;
232+
}
233+
} else if (currNode.is('class') && hasGlobalWithNoArgs) {
234+
// Remove all class after :global and their trailing space
235+
selectorNode.removeChild(i);
236+
if (selectorContent[i] && selectorContent[i].is('space')) {
237+
selectorNode.removeChild(i);
238+
}
239+
} else {
240+
i++;
241+
}
242+
243+
currNode = selectorContent[i];
244+
}
245+
});
246+
247+
// Remove all ruleset with no selectors
248+
ast.traverseByType('ruleset', (node, index, parent) => {
249+
const rulesetContent = node.content;
250+
251+
// Remove empty selectors and trailing deliminator and space
252+
let i = 0;
253+
let currNode = rulesetContent[i];
254+
while (currNode) {
255+
if (currNode.is('selector') && currNode.content.length === 0) {
256+
node.removeChild(i);
257+
if (rulesetContent[i].is('delimiter')) node.removeChild(i);
258+
if (rulesetContent[i].is('space')) node.removeChild(i);
259+
} else {
260+
i++;
225261
}
262+
currNode = rulesetContent[i];
263+
}
264+
265+
// Remove the ruleset if no selectors
266+
if (rulesetContent.filter((node) => node.is('selector')).length === 0) {
267+
parent.removeChild(index);
226268
}
227269
});
228270
};

test/files/global1.scss

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
.foo {}
1+
.local1 {}
22

3-
.bar {
4-
:global(.baz) {}
3+
.local2 {
4+
:global(.global1) {}
55
}
66

7+
:global .global1 {}
8+
9+
:global(.global2) {}
10+
711
:global {
8-
.f {
12+
.global1 {
913

1014
}
1115
}
1216

13-
.baz {
17+
.local3 {
1418
:global {
15-
.gar {}
19+
.global2 {}
1620
}
1721
}
1822

19-
:global .as {}
23+
.local4 :global .global1 .global2 {}
2024

21-
:global(.oiu) {}
25+
.local5 :global(.global1, .global2) .local6 {}

test/files/noUndefClass2.scss

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/lib/core/traversalUtils.test.js

Lines changed: 54 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -6,132 +6,83 @@ import gonzales from '../../../lib/core/gonzales';
66
import { eliminateGlobals } from '../../../lib/core/traversalUtils';
77

88
describe('eliminateGlobals()', () => {
9-
it('should remove :global block', () => {
10-
const content = `
11-
:global {
12-
.foo {}
13-
}`;
9+
describe('resolving :global pseudo class', () => {
10+
it('should remove :global operator and the global class', () => {
11+
const content = `
12+
:global .global {}
13+
`;
1414

15-
const ast = gonzales.parse(
16-
content,
17-
{ syntax: 'scss' }
18-
);
15+
const ast = gonzales.parse(content, { syntax: 'scss' });
1916

20-
eliminateGlobals(ast);
17+
eliminateGlobals(ast);
2118

22-
expect(ast.toString()).to.be.equal('\n');
23-
});
24-
25-
it('should remove :global block, but not local', () => {
26-
const content = `
27-
.bar {}
28-
29-
:global {
30-
.foo {}
31-
}`;
32-
33-
const ast = gonzales.parse(
34-
content,
35-
{ syntax: 'scss' }
36-
);
37-
38-
eliminateGlobals(ast);
39-
40-
expect(ast.toString()).to.be.equal(
41-
`
42-
.bar {}
43-
44-
`
45-
);
46-
});
47-
48-
it('should remove nested :global block', () => {
49-
const content = `
50-
.bar {}
19+
expect(ast.toString().trim()).to.be.equal('');
20+
});
5121

52-
.baz {
53-
:global {
54-
.foo {}
55-
}
56-
}`;
22+
it('should remove :global operator and the global classes', () => {
23+
const content = `
24+
:global .global1 .global2 .global3.global4 {}
25+
`;
5726

58-
const ast = gonzales.parse(
59-
content,
60-
{ syntax: 'scss' }
61-
);
27+
const ast = gonzales.parse(content, { syntax: 'scss' });
6228

63-
eliminateGlobals(ast);
29+
eliminateGlobals(ast);
6430

65-
expect(ast.toString()).to.be.equal(
66-
`
67-
.bar {}
68-
69-
.baz {
70-
71-
}`
72-
);
73-
});
31+
expect(ast.toString().trim()).to.be.equal('');
32+
});
7433

75-
it('should remove :global selector', () => {
76-
const content = `
77-
.bar {}
34+
it('should only remove :global operator and the global classes', () => {
35+
const content = `
36+
.local1 :global .global1 :local(.local2) .global2 :local(.local3), .local4 {}
37+
`;
7838

79-
:global .baz {}`;
39+
const ast = gonzales.parse(content, { syntax: 'scss' });
8040

81-
const ast = gonzales.parse(
82-
content,
83-
{ syntax: 'scss' }
84-
);
41+
eliminateGlobals(ast);
8542

86-
eliminateGlobals(ast);
87-
88-
expect(ast.toString()).to.be.equal(
89-
`
90-
.bar {}
91-
92-
`
93-
);
43+
expect(ast.toString().trim()).to.be.equal(
44+
'.local1 :local(.local2) :local(.local3), .local4 {}'
45+
);
46+
});
9447
});
9548

96-
it('should remove :global selector with multiple classes', () => {
97-
const content = `
98-
.bar {}
49+
describe('resolving :global() pseudo class', () => {
50+
it('should remove :global() pseudo class and its argument class', () => {
51+
const content = `
52+
:global(.global1) {}
53+
`;
9954

100-
:global .baz.foo {}`;
55+
const ast = gonzales.parse(content, { syntax: 'scss' });
10156

102-
const ast = gonzales.parse(
103-
content,
104-
{ syntax: 'scss' }
105-
);
57+
eliminateGlobals(ast);
10658

107-
eliminateGlobals(ast);
59+
expect(ast.toString().trim()).to.be.equal('');
60+
});
10861

109-
expect(ast.toString()).to.be.equal(
110-
`
111-
.bar {}
62+
it('should remove :global() pseudo class and its argument classes', () => {
63+
const content = `
64+
:global(.global1) :global(.global2, .global3), :global(.global4.global5) {}
65+
`;
11266

113-
`
114-
);
115-
});
67+
const ast = gonzales.parse(content, { syntax: 'scss' });
11668

117-
it('should remove classes wrapped in :global()', () => {
118-
const content = `
119-
.bar {}
69+
eliminateGlobals(ast);
12070

121-
:global(.bar.foo) {}`;
71+
expect(ast.toString().trim()).to.be.equal('');
72+
});
12273

123-
const ast = gonzales.parse(
124-
content,
125-
{ syntax: 'scss' }
126-
);
74+
it('should only remove :global() pseudo class and its argument classes', () => {
75+
const content = `
76+
.local1 :global(.global1) .local2, .local3 :global(.global2, .global3) :local(.local4) {}
77+
`;
12778

128-
eliminateGlobals(ast);
79+
const ast = gonzales.parse(content, { syntax: 'scss' });
12980

130-
expect(ast.toString()).to.be.equal(
131-
`
132-
.bar {}
81+
eliminateGlobals(ast);
13382

134-
`
135-
);
83+
expect(ast.toString().trim()).to.be.equal(
84+
'.local1 .local2, .local3 :local(.local4) {}'
85+
);
86+
});
13687
});
13788
});

test/lib/rules/no-undef-class.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,10 @@ ruleTester.run('no-undef-class', rule, {
226226
import s from './global1.scss';
227227
228228
export default Foo = () => (
229-
<div className={s.bar}>
230-
<div className={s.baz}>
231-
<div className={s.foo}></div>
232-
</div>
229+
<div className={s.local1, s.local2, s.local3, s.local4, s.local5, s.local6}>
233230
</div>
234231
);
235-
`
232+
`,
236233
}),
237234
/*
238235
ICSS :export pseudo-selector with a correct prop name should not give error
@@ -399,14 +396,16 @@ ruleTester.run('no-undef-class', rule, {
399396
*/
400397
test({
401398
code: `
402-
import s from './noUndefClass2.scss';
399+
import s from './global1.scss';
403400
404401
export default Foo = () => (
405-
<div className={s.bold}></div>
402+
<div className={s.global1, s.global2, s.global3}></div>
406403
);
407404
`,
408405
errors: [
409-
'Class or exported property \'bold\' not found',
406+
"Class or exported property 'global1' not found",
407+
"Class or exported property 'global2' not found",
408+
"Class or exported property 'global3' not found",
410409
],
411410
}),
412411
/*

0 commit comments

Comments
 (0)