Skip to content

Commit

Permalink
feat(JsSource): find tokens used as object keys
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce committed Mar 7, 2017
1 parent 2817bcb commit be6274a
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 45 deletions.
9 changes: 0 additions & 9 deletions lib/sources/html-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@ class HtmlSource extends ParsedSource {
return 'html'
}

_findWholeSelectorInTokens(selector) {
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
}

contains(selector) {
return Boolean(this._tokens.has(selector) ||
this._findWholeSelectorInTokens(selector))
}

static get joiner() {
return '\n<!-- joined by nukecss -->\n'
}
Expand Down
57 changes: 27 additions & 30 deletions lib/sources/js-source.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,43 @@
const _ = require('lodash')
const esprima = require('esprima')
const traverse = require('ast-traverse')
const ParsedSource = require('./parsed-source')

class JsSource extends ParsedSource {
get type() {
return 'js'
}

_findWholeSelectorInTokens(selector) {
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
}

_findSelectorPartsInTokens(selector) {
if (selector.length === 0) {
return true
}

for (let i = 1; i <= selector.length; i++) {
const part = selector.substr(0, i)
const rest = selector.substr(i)
if (this._tokens.has(part) && this._findSelectorPartsInTokens(rest)) {
return true
}
}

return false
}

contains(selector) {
return Boolean(this._tokens.has(selector) ||
this._findWholeSelectorInTokens(selector) ||
this._findSelectorPartsInTokens(selector))
}

static get joiner() {
return ';\n/* joined by nukecss */\n'
}

static parse(text) {
const jsStrings = esprima.tokenize(text)
.filter(token => token.type === 'String')
.map(token => token.value.substr(1, token.value.length - 2).toLowerCase())
return new Set(jsStrings)
const ancestry = []
const tokens = new Set()
traverse(esprima.parse(text), {
pre(node) {
ancestry.push(node)
if (node.type === 'Literal' && typeof node.value === 'string') {
tokens.add(node.value)
} else if (node.type === 'AssignmentExpression') {
const identifierName = _.get(node, 'left.property.name')
if (identifierName) {
tokens.add(identifierName)
}
} else if (node.type === 'ObjectExpression') {
node.properties.forEach(property => {
if (property.key.type === 'Identifier') {
tokens.add(property.key.name)
}
})
}
},
post() {
ancestry.pop()
},
})
return tokens
}
}

Expand Down
31 changes: 27 additions & 4 deletions lib/sources/parsed-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,39 @@ class ParsedSource {
constructor(text, tokens, opts = {}) {
this._text = text
this._options = opts
this._tokens = tokens
this._tokensArray = Array.from(tokens)
this._tokensArray = Array.from(tokens).map(t => t.toLowerCase())
this._tokens = new Set(this._tokensArray)
}

get type() {
throw new Error('unimplemented')
}

contains() {
throw new Error('unimplemented')
_findWholeSelectorInTokens(selector) {
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
}

_findSelectorPartsInTokens(selector) {
if (selector.length === 0) {
return true
}

for (let i = 1; i <= selector.length; i++) {
const part = selector.substr(0, i)
const rest = selector.substr(i)
if (this._tokens.has(part) && this._findSelectorPartsInTokens(rest)) {
return true
}
}

return false
}

contains(selector) {
selector = selector.toLowerCase()
return Boolean(this._tokens.has(selector) ||
this._findWholeSelectorInTokens(selector) ||
this._findSelectorPartsInTokens(selector))
}

join(that) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
}
},
"dependencies": {
"ast-traverse": "^0.1.1",
"debug": "^2.6.1",
"esprima": "^3.1.3",
"glob": "^7.1.1",
Expand Down
2 changes: 1 addition & 1 deletion test/nuke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('nuke.js', () => {
expect(nuked).to.not.contain('jsignored')
})

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

Expand Down
25 changes: 24 additions & 1 deletion test/sources/js-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,16 @@ describe('sources/js-source.js', () => {
context('when script is simple', () => {
const script = `
const myVar = 'the-class'
const otherVar = 'the-other-class'
const otherVar = 'the-OtHer-cLass'
const html = '<div class="inner-class">Content</div>'
const dynamicVar = ['fa', 'icon'].join('-')
const classes = {
inVisIble: true,
blocK__Element: true,
}
classes.aDditIonal_class = false
const no_find = window.should_not_be_found(classes.also_not_found)
`

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

it('should find tokens as object keys', () => {
expect(source).to.contain('invisible')
expect(source).to.contain('block__element')
})

it('should find tokens as object key assignment', () => {
expect(source).to.contain('additional_class')
})

it('should not find tokens as identifiers', () => {
expect(source).to.not.contain('const')
expect(source).to.not.contain('myVar')
expect(source).to.not.contain('otherVar')
expect(source).to.not.contain('dynamicVar')
expect(source).to.not.contain('window')
expect(source).to.not.contain('no_find')
})

it('should not find tokens as object key access', () => {
expect(source).to.not.contain('should_not_be_found')
expect(source).to.not.contain('also_not_found')
})
})

Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ assertion-error@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"

ast-traverse@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6"

[email protected], async@^1.4.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
Expand Down

0 comments on commit be6274a

Please sign in to comment.