eslint-plugin-react-pug
Version:
Add supporting of pugjs with react
121 lines (98 loc) • 3.75 kB
JavaScript
/* eslint-disable no-param-reassign */
const Components = require('eslint-plugin-react/lib/util/Components')
const { isReactPugReference } = require('./eslint')
const getUsedVariablesInPug = require('./getUsedVariablesInPug')
const getTemplate = require('../util/getTemplate')
const getPropsRefs = require('./getPropsRefs')
const isSupportedComponentDeclaration = node => [
'ArrowFunctionExpression',
'FunctionDeclaration',
'FunctionExpression',
].includes(node.type)
module.exports = rule => Components.detect((context, components, utils) => {
// Once any function returns JSX it starts being treated as component, we
// extend its internals to return `true` when it contains pug
const originalUtilsIsReturningJSX = utils.isReturningJSX.bind(utils)
utils.isReturningJSX = (ASTNode, strict) => {
const nodeAndProperty = utils.getReturnPropertyAndNode(ASTNode)
const { node, property } = nodeAndProperty
if (!node) {
return false
}
const returnsPug = node[property] && isReactPugReference(node[property])
return originalUtilsIsReturningJSX(ASTNode, strict) || returnsPug
}
// We need to filter out components that are not supported by us
const originalComponentsList = components.list.bind(components)
components.list = () => {
const list = originalComponentsList()
const filteredList = {}
Object.keys(list)
.forEach((id) => {
if (isSupportedComponentDeclaration(list[id].node)) {
filteredList[id] = list[id]
}
})
return filteredList
}
function markPropsUsedInsidePug(component, variables) {
const usedPropTypes = []
variables
.forEach((variable) => {
usedPropTypes.push({
name: variable.name,
allNames: variable.allNames.slice(1),
node: {
loc: variable.loc,
},
extra: variable.extra || {},
})
})
components.set(component.node, { usedPropTypes })
}
return {
...rule(context, components, utils),
'TaggedTemplateExpression[tag.name="pug"]': function (node) {
const relatedComponentNode = utils.getParentStatelessComponent()
const relatedComponent = components.get(relatedComponentNode)
if (relatedComponent && isSupportedComponentDeclaration(relatedComponentNode)) {
const propsRefsNames = getPropsRefs(context, relatedComponent)
const template = getTemplate(node)
const usedVariables = getUsedVariablesInPug(template)
.map(variable => ({
...variable,
loc: {
start: {
...variable.loc.start,
line: node.loc.start.line + variable.loc.start.line - 1,
},
end: {
...variable.loc.end,
line: node.loc.start.line + variable.loc.end.line - 1,
},
},
}))
// Check if we use props reference with spreading, then we consider
// that we use all props
if (
usedVariables.find(item => item.extra.isSpreadElement
&& propsRefsNames.indexOf(item.name) > -1)
) {
relatedComponent.hasPropsSpreadingInPug = true
return
}
const usedProps = usedVariables
// Filter out everything not related to props
.filter(variable => propsRefsNames.indexOf(variable.allNames[0]) > -1)
// Remove duplicates
.reduce((uniq, variable) => {
if (!uniq.find(item => item.allNames.join('.') === variable.allNames.join('.'))) {
uniq.push(variable)
}
return uniq
}, [])
markPropsUsedInsidePug(relatedComponent, usedProps)
}
},
}
})