Skip to content

Commit be6274a

Browse files
committed
feat(JsSource): find tokens used as object keys
1 parent 2817bcb commit be6274a

File tree

7 files changed

+84
-45
lines changed

7 files changed

+84
-45
lines changed

lib/sources/html-source.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ class HtmlSource extends ParsedSource {
77
return 'html'
88
}
99

10-
_findWholeSelectorInTokens(selector) {
11-
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
12-
}
13-
14-
contains(selector) {
15-
return Boolean(this._tokens.has(selector) ||
16-
this._findWholeSelectorInTokens(selector))
17-
}
18-
1910
static get joiner() {
2011
return '\n<!-- joined by nukecss -->\n'
2112
}

lib/sources/js-source.js

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,43 @@
1+
const _ = require('lodash')
12
const esprima = require('esprima')
3+
const traverse = require('ast-traverse')
24
const ParsedSource = require('./parsed-source')
35

46
class JsSource extends ParsedSource {
57
get type() {
68
return 'js'
79
}
810

9-
_findWholeSelectorInTokens(selector) {
10-
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
11-
}
12-
13-
_findSelectorPartsInTokens(selector) {
14-
if (selector.length === 0) {
15-
return true
16-
}
17-
18-
for (let i = 1; i <= selector.length; i++) {
19-
const part = selector.substr(0, i)
20-
const rest = selector.substr(i)
21-
if (this._tokens.has(part) && this._findSelectorPartsInTokens(rest)) {
22-
return true
23-
}
24-
}
25-
26-
return false
27-
}
28-
29-
contains(selector) {
30-
return Boolean(this._tokens.has(selector) ||
31-
this._findWholeSelectorInTokens(selector) ||
32-
this._findSelectorPartsInTokens(selector))
33-
}
34-
3511
static get joiner() {
3612
return ';\n/* joined by nukecss */\n'
3713
}
3814

3915
static parse(text) {
40-
const jsStrings = esprima.tokenize(text)
41-
.filter(token => token.type === 'String')
42-
.map(token => token.value.substr(1, token.value.length - 2).toLowerCase())
43-
return new Set(jsStrings)
16+
const ancestry = []
17+
const tokens = new Set()
18+
traverse(esprima.parse(text), {
19+
pre(node) {
20+
ancestry.push(node)
21+
if (node.type === 'Literal' && typeof node.value === 'string') {
22+
tokens.add(node.value)
23+
} else if (node.type === 'AssignmentExpression') {
24+
const identifierName = _.get(node, 'left.property.name')
25+
if (identifierName) {
26+
tokens.add(identifierName)
27+
}
28+
} else if (node.type === 'ObjectExpression') {
29+
node.properties.forEach(property => {
30+
if (property.key.type === 'Identifier') {
31+
tokens.add(property.key.name)
32+
}
33+
})
34+
}
35+
},
36+
post() {
37+
ancestry.pop()
38+
},
39+
})
40+
return tokens
4441
}
4542
}
4643

lib/sources/parsed-source.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,39 @@ class ParsedSource {
66
constructor(text, tokens, opts = {}) {
77
this._text = text
88
this._options = opts
9-
this._tokens = tokens
10-
this._tokensArray = Array.from(tokens)
9+
this._tokensArray = Array.from(tokens).map(t => t.toLowerCase())
10+
this._tokens = new Set(this._tokensArray)
1111
}
1212

1313
get type() {
1414
throw new Error('unimplemented')
1515
}
1616

17-
contains() {
18-
throw new Error('unimplemented')
17+
_findWholeSelectorInTokens(selector) {
18+
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
19+
}
20+
21+
_findSelectorPartsInTokens(selector) {
22+
if (selector.length === 0) {
23+
return true
24+
}
25+
26+
for (let i = 1; i <= selector.length; i++) {
27+
const part = selector.substr(0, i)
28+
const rest = selector.substr(i)
29+
if (this._tokens.has(part) && this._findSelectorPartsInTokens(rest)) {
30+
return true
31+
}
32+
}
33+
34+
return false
35+
}
36+
37+
contains(selector) {
38+
selector = selector.toLowerCase()
39+
return Boolean(this._tokens.has(selector) ||
40+
this._findWholeSelectorInTokens(selector) ||
41+
this._findSelectorPartsInTokens(selector))
1942
}
2043

2144
join(that) {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
}
4848
},
4949
"dependencies": {
50+
"ast-traverse": "^0.1.1",
5051
"debug": "^2.6.1",
5152
"esprima": "^3.1.3",
5253
"glob": "^7.1.1",

test/nuke.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('nuke.js', () => {
9191
expect(nuked).to.not.contain('jsignored')
9292
})
9393

94-
it.skip('should remove unused rules mentioned in textnodes', () => {
94+
it('should remove unused rules mentioned in textnodes', () => {
9595
expect(nuked).to.not.contain('html-ignored')
9696
})
9797

test/sources/js-source.test.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,16 @@ describe('sources/js-source.js', () => {
3333
context('when script is simple', () => {
3434
const script = `
3535
const myVar = 'the-class'
36-
const otherVar = 'the-other-class'
36+
const otherVar = 'the-OtHer-cLass'
3737
const html = '<div class="inner-class">Content</div>'
3838
const dynamicVar = ['fa', 'icon'].join('-')
39+
const classes = {
40+
inVisIble: true,
41+
blocK__Element: true,
42+
}
43+
44+
classes.aDditIonal_class = false
45+
const no_find = window.should_not_be_found(classes.also_not_found)
3946
`
4047

4148
const source = JsSource.from(script)
@@ -56,11 +63,27 @@ describe('sources/js-source.js', () => {
5663
expect(source).to.contain('fa-icon')
5764
})
5865

66+
it('should find tokens as object keys', () => {
67+
expect(source).to.contain('invisible')
68+
expect(source).to.contain('block__element')
69+
})
70+
71+
it('should find tokens as object key assignment', () => {
72+
expect(source).to.contain('additional_class')
73+
})
74+
5975
it('should not find tokens as identifiers', () => {
6076
expect(source).to.not.contain('const')
6177
expect(source).to.not.contain('myVar')
6278
expect(source).to.not.contain('otherVar')
6379
expect(source).to.not.contain('dynamicVar')
80+
expect(source).to.not.contain('window')
81+
expect(source).to.not.contain('no_find')
82+
})
83+
84+
it('should not find tokens as object key access', () => {
85+
expect(source).to.not.contain('should_not_be_found')
86+
expect(source).to.not.contain('also_not_found')
6487
})
6588
})
6689

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ assertion-error@^1.0.1:
172172
version "1.0.2"
173173
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
174174

175+
ast-traverse@^0.1.1:
176+
version "0.1.1"
177+
resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6"
178+
175179
async@1.x, async@^1.4.0:
176180
version "1.5.2"
177181
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"

0 commit comments

Comments
 (0)