diff --git a/src/parsers/index.js b/src/parsers/index.js index 19a750b..36d847c 100644 --- a/src/parsers/index.js +++ b/src/parsers/index.js @@ -4,6 +4,9 @@ const traverse = require('babel-traverse').default const isStyled = require('../utils/styled').isStyled const isHelper = require('../utils/styled').isHelper const isStyledImport = require('../utils/styled').isStyledImport +const hasAttrsCall = require('../utils/styled').hasAttrsCall +const getAttrsObject = require('../utils/styled').getAttrsObject +const isExtendCall = require('../utils/styled').isExtendCall const wrapSelector = require('../utils/general').wrapSelector const wrapKeyframes = require('../utils/general').wrapKeyframes @@ -39,8 +42,12 @@ const processStyledComponentsFile = ast => { return } const helper = isHelper(node, importedNames) - if (!helper && !isStyled(node, importedNames.default)) return - const content = getTTLContent(node) + const processedNode = Object.assign({}, node) + if (hasAttrsCall(node)) { + processedNode.tag = getAttrsObject(node) + } + if (!helper && !isStyled(processedNode, importedNames.default) && !isExtendCall(node)) return + const content = getTTLContent(processedNode) const fixedContent = fixIndentation(content).text const wrapperFn = helper === 'keyframes' ? wrapKeyframes : wrapSelector const wrappedContent = wrapperFn(fixedContent) @@ -50,7 +57,7 @@ const processStyledComponentsFile = ast => { extractedCSS.push(stylelintCommentsAdded) sourceMap = Object.assign( sourceMap, - getSourceMap(extractedCSS.join('\n'), wrappedContent, node.loc.start.line) + getSourceMap(extractedCSS.join('\n'), wrappedContent, processedNode.loc.start.line) ) /** * All queued comments have been added to the file so we don't need to, and actually shouldn't diff --git a/src/utils/styled.js b/src/utils/styled.js index e253f5b..b33b58f 100644 --- a/src/utils/styled.js +++ b/src/utils/styled.js @@ -33,6 +33,20 @@ const isStyledCall = (node, styledVariableName) => // And that the function name matches the imported name node.tag.callee.name === styledVariableName +/** + * Check if it has a .attrs postfix which we in that case handle specially + */ +const hasAttrsCall = node => + // Check that it's a function call + node.tag && + node.tag.callee && + // Check that the last member of the call is attrs + node.tag.callee.property && + node.tag.callee.property.name === 'attrs' + +// We don't need the checks here as they were checked in hasAttrsCall +const getAttrsObject = node => node.tag.callee.object + /** * Check if something is a styled component call */ @@ -40,6 +54,13 @@ const isStyled = (node, styledVariableName) => isTaggedTemplateLiteral(node) && (isStyledCall(node, styledVariableName) || isStyledShorthand(node, styledVariableName)) +/** + * Check if it is a .extend call and we pretty reasonable assume that any TTL that ends + * in a .extend must be a styled components call as there is no way to check if it was + * called on a Styled Component + */ +const isExtendCall = node => node.tag && node.tag.property && node.tag.property.name === 'extend' + /** * Check if something is a call to one of our helper methods * @@ -63,3 +84,6 @@ exports.isStyledShorthand = isStyledShorthand exports.isStyledCall = isStyledCall exports.isStyled = isStyled exports.isHelper = isHelper +exports.hasAttrsCall = hasAttrsCall +exports.getAttrsObject = getAttrsObject +exports.isExtendCall = isExtendCall diff --git a/test/fixtures/simple/identify-styled.js b/test/fixtures/simple/identify-styled.js new file mode 100644 index 0000000..6f12f4e --- /dev/null +++ b/test/fixtures/simple/identify-styled.js @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +const Button1 = styled.div` + bad-selector { + color: red; + } +`; + +const Button2 = styled(Button1)` + bad-selector { + color: red; + } +`; + +const Image1 = styled.img.attrs({ src: 'url' })` + bad-selector { + color: red; + } +`; + +const Image2 = styled(Image1).attrs({ src: 'newUrl' })` + bad-selector { + color: red; + } +`; + +const Image3 = Image2.extend` + bad-selector2 { + color: blue; + } +`; diff --git a/test/simple.test.js b/test/simple.test.js index 85b047f..b7862b1 100644 --- a/test/simple.test.js +++ b/test/simple.test.js @@ -11,7 +11,8 @@ const rules = { except: ['first-nested'], ignore: ['after-comment'] } - ] + ], + 'selector-type-no-unknown': true } describe('simple', () => { @@ -195,4 +196,44 @@ describe('simple', () => { expect(data.results[0].warnings.length).toEqual(8) }) }) + + describe('identify styled', () => { + beforeAll(() => { + fixture = path.join(__dirname, './fixtures/simple/identify-styled.js') + }) + + it('should have one result', () => { + expect(data.results.length).toEqual(1) + }) + + it('should use the right file', () => { + expect(data.results[0].source).toEqual(fixture) + }) + + it('should have errored', () => { + expect(data.errored).toEqual(true) + }) + + it('should have 4 warnings', () => { + expect(data.results[0].warnings.length).toEqual(5) + }) + + it('should have correct warnings', () => { + const warnings = data.results[0].warnings + expect(warnings[0].line).toBe(4) + expect(warnings[0].rule).toBe('selector-type-no-unknown') + + expect(warnings[1].line).toBe(10) + expect(warnings[1].rule).toBe('selector-type-no-unknown') + + expect(warnings[2].line).toBe(16) + expect(warnings[2].rule).toBe('selector-type-no-unknown') + + expect(warnings[3].line).toBe(22) + expect(warnings[3].rule).toBe('selector-type-no-unknown') + + expect(warnings[4].line).toBe(28) + expect(warnings[4].rule).toBe('selector-type-no-unknown') + }) + }) })