UNPKG

eslint-plugin-react-pug

Version:

Add supporting of pugjs with react

201 lines (167 loc) 6.68 kB
const { parse } = require('@babel/parser') const traverse = require('@babel/traverse').default const getVariables = require('../util/getVariables') const getTokens = require('../util/getTokens') const getCodeFromToken = require('../util/getCodeFromToken') const babelHelpers = require('../util/babel') const buildLocation = (startLine, startColumn, endLine, endColumn) => ({ start: { line: startLine, column: startColumn }, end: { line: endLine, column: endColumn }, }) const buildVariable = (name, allNames, loc, extra = {}) => ({ name, allNames, loc, extra, }) function getFullName(path) { const name = [] if (path.isIdentifier()) { name.unshift(path.node.name) } else if (path.isStringLiteral()) { name.unshift(path.node.value) } else if (path.isMemberExpression()) { if (path.node.computed) { name.unshift('__COMPUTED_PROP__') } name.unshift(...getFullName(path.get('property'))) } if (path.parentPath.isMemberExpression({ property: path.node })) { name.unshift(...getFullName(path.parentPath.get('object'))) } return name } const getUsedVariablesInPug = (template = '') => { const usedVariables = [] const variables = getVariables(template) const tokens = getTokens(template) const lines = template.split('\n') variables // Attach related token .map((variable) => { const relatedTokens = tokens .filter((token) => { const isAfterOpenBoundary = variable.loc.start.line === token.loc.start.line ? variable.loc.start.column >= token.loc.start.column - 1 : variable.loc.start.line > token.loc.start.line const isBeforeCloseBoundary = variable.loc.end.line === token.loc.end.line ? variable.loc.end.column <= token.loc.end.column - 1 : variable.loc.end.line < token.loc.end.line return isAfterOpenBoundary && isBeforeCloseBoundary }) return { ...variable, token: relatedTokens[0] } }) // Find all variables within the token .forEach((variable) => { const { token } = variable const code = getCodeFromToken(token) const isSpreadElement = code.startsWith('({...') && code.endsWith('})') const isObject = code.startsWith('{') && code.endsWith('}') const isVariableOnStartLine = token.loc.start.line === variable.loc.start.line const ast = parse(babelHelpers.normalize(code)) let codeStartsAt = 0 if (isVariableOnStartLine) { const codeFragment = code.split('\n')[0] const relatedLine = lines[variable.loc.start.line - 1] if (isSpreadElement) { // All Spread elements wrapped by ({ and }), so we remove them because // we don't have them in the template codeStartsAt = relatedLine.indexOf(codeFragment.substring(2, codeFragment.length - 2)) - 2 } else if (isObject) { // All Objects wrapped by braces, so we remove them because we don't // have them in the templace codeStartsAt = relatedLine.indexOf(codeFragment) - 1 } else { codeStartsAt = relatedLine.indexOf(codeFragment) } } traverse(ast, { Identifier(path) { const loc = buildLocation( variable.loc.start.line, codeStartsAt + path.node.loc.start.column, variable.loc.end.line, codeStartsAt + path.node.loc.start.column + path.node.name.length, ) if (path.parentPath.isMemberExpression()) { const isProperty = path.key === 'property' usedVariables.push(buildVariable( path.node.name, getFullName(path), loc, { isSpreadElement: isSpreadElement && isProperty }, )) } else if (isSpreadElement) { usedVariables.push(buildVariable( path.node.name, getFullName(path), loc, { isSpreadElement: true }, )) } else { usedVariables.push(buildVariable( path.node.name, getFullName(path), loc, { isSpreadElement: false }, )) } }, StringLiteral(path) { if (path.parentPath.isMemberExpression()) { const loc = buildLocation( variable.loc.start.line, codeStartsAt + path.node.loc.start.column, variable.loc.end.line, codeStartsAt + path.node.loc.start.column + path.node.extra.raw.length, ) const isProperty = path.key === 'property' usedVariables.push(buildVariable( path.node.value, getFullName(path), loc, { isSpreadElement: isSpreadElement && isProperty }, )) } }, }) if (token.type === 'each') { const getLastTokenInScope = (all, start) => { const startIndex = all.findIndex(item => item === start) const lastToken = all.slice(startIndex).find(item => item.type === 'outdent' && item.loc.end.column <= start.loc.start.column) return lastToken } const startToken = token const endToken = getLastTokenInScope(tokens, startToken) const bodyLines = [''].concat(lines.slice(startToken.loc.start.line, endToken.loc.end.line - 1)) const body = bodyLines.concat('\n').join('\n') const variablesInScope = getUsedVariablesInPug(body) .filter(item => item.allNames.length > 1 || item.extra.isSpreadElement === true) .map(item => ({ ...item, loc: { start: { line: variable.loc.start.line + item.loc.start.line - 1, column: item.loc.start.column, }, end: { line: variable.loc.start.line + item.loc.end.line - 1, column: item.loc.end.column, }, }, allNames: item.allNames[0] === token.val ? ['__COMPUTED_PROP__', ...item.allNames.slice(1)] : item.allNames, })) const lastAddedVariable = usedVariables[usedVariables.length - 1] variablesInScope.forEach((item) => { const allNames = item.allNames[0] === '__COMPUTED_PROP__' && item.allNames.length > 1 ? lastAddedVariable.allNames.concat(item.allNames) : item.allNames usedVariables.push({ ...item, allNames, }) }) } }) return usedVariables } module.exports = getUsedVariablesInPug