You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.5 KiB
134 lines
3.5 KiB
/** |
|
* @fileoverview Disallow multiple spaces between inline JSX props |
|
* @author Adrian Moennich |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const docsUrl = require('../util/docsUrl'); |
|
const report = require('../util/report'); |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Rule Definition |
|
// ------------------------------------------------------------------------------ |
|
|
|
const messages = { |
|
noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”', |
|
onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”', |
|
}; |
|
|
|
module.exports = { |
|
meta: { |
|
docs: { |
|
description: 'Disallow multiple spaces between inline JSX props', |
|
category: 'Stylistic Issues', |
|
recommended: false, |
|
url: docsUrl('jsx-props-no-multi-spaces'), |
|
}, |
|
fixable: 'code', |
|
|
|
messages, |
|
|
|
schema: [], |
|
}, |
|
|
|
create(context) { |
|
const sourceCode = context.getSourceCode(); |
|
|
|
function getPropName(propNode) { |
|
switch (propNode.type) { |
|
case 'JSXSpreadAttribute': |
|
return context.getSourceCode().getText(propNode.argument); |
|
case 'JSXIdentifier': |
|
return propNode.name; |
|
case 'JSXMemberExpression': |
|
return `${getPropName(propNode.object)}.${propNode.property.name}`; |
|
default: |
|
return propNode.name |
|
? propNode.name.name |
|
: `${context.getSourceCode().getText(propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser |
|
} |
|
} |
|
|
|
// First and second must be adjacent nodes |
|
function hasEmptyLines(first, second) { |
|
const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : []; |
|
const nodes = [].concat(first, comments, second); |
|
|
|
for (let i = 1; i < nodes.length; i += 1) { |
|
const prev = nodes[i - 1]; |
|
const curr = nodes[i]; |
|
if (curr.loc.start.line - prev.loc.end.line >= 2) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function checkSpacing(prev, node) { |
|
if (hasEmptyLines(prev, node)) { |
|
report(context, messages.noLineGap, 'noLineGap', { |
|
node, |
|
data: { |
|
prop1: getPropName(prev), |
|
prop2: getPropName(node), |
|
}, |
|
}); |
|
} |
|
|
|
if (prev.loc.end.line !== node.loc.end.line) { |
|
return; |
|
} |
|
|
|
const between = context.getSourceCode().text.slice(prev.range[1], node.range[0]); |
|
|
|
if (between !== ' ') { |
|
report(context, messages.onlyOneSpace, 'onlyOneSpace', { |
|
node, |
|
data: { |
|
prop1: getPropName(prev), |
|
prop2: getPropName(node), |
|
}, |
|
fix(fixer) { |
|
return fixer.replaceTextRange([prev.range[1], node.range[0]], ' '); |
|
}, |
|
}); |
|
} |
|
} |
|
|
|
function containsGenericType(node) { |
|
const containsTypeParams = typeof node.typeParameters !== 'undefined'; |
|
return containsTypeParams && node.typeParameters.type === 'TSTypeParameterInstantiation'; |
|
} |
|
|
|
function getGenericNode(node) { |
|
const name = node.name; |
|
if (containsGenericType(node)) { |
|
const type = node.typeParameters; |
|
|
|
return Object.assign( |
|
{}, |
|
node, |
|
{ |
|
range: [ |
|
name.range[0], |
|
type.range[1], |
|
], |
|
} |
|
); |
|
} |
|
|
|
return name; |
|
} |
|
|
|
return { |
|
JSXOpeningElement(node) { |
|
node.attributes.reduce((prev, prop) => { |
|
checkSpacing(prev, prop); |
|
return prop; |
|
}, getGenericNode(node)); |
|
}, |
|
}; |
|
}, |
|
};
|
|
|