128 lines
3.1 KiB
JavaScript
128 lines
3.1 KiB
JavaScript
|
/**
|
||
|
* @fileoverview Prevent adjacent inline elements not separated by whitespace.
|
||
|
* @author Sean Hayes
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
const docsUrl = require('../util/docsUrl');
|
||
|
const isCreateElement = require('../util/isCreateElement');
|
||
|
const report = require('../util/report');
|
||
|
const astUtil = require('../util/ast');
|
||
|
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// Helpers
|
||
|
// ------------------------------------------------------------------------------
|
||
|
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
|
||
|
const inlineNames = [
|
||
|
'a',
|
||
|
'b',
|
||
|
'big',
|
||
|
'i',
|
||
|
'small',
|
||
|
'tt',
|
||
|
'abbr',
|
||
|
'acronym',
|
||
|
'cite',
|
||
|
'code',
|
||
|
'dfn',
|
||
|
'em',
|
||
|
'kbd',
|
||
|
'strong',
|
||
|
'samp',
|
||
|
'time',
|
||
|
'var',
|
||
|
'bdo',
|
||
|
'br',
|
||
|
'img',
|
||
|
'map',
|
||
|
'object',
|
||
|
'q',
|
||
|
'script',
|
||
|
'span',
|
||
|
'sub',
|
||
|
'sup',
|
||
|
'button',
|
||
|
'input',
|
||
|
'label',
|
||
|
'select',
|
||
|
'textarea',
|
||
|
];
|
||
|
// Note: raw will be transformed into \u00a0.
|
||
|
const whitespaceRegex = /(?:^\s|\s$)/;
|
||
|
|
||
|
function isInline(node) {
|
||
|
if (node.type === 'Literal') {
|
||
|
// Regular whitespace will be removed.
|
||
|
const value = node.value;
|
||
|
// To properly separate inline elements, each end of the literal will need
|
||
|
// whitespace.
|
||
|
return !whitespaceRegex.test(value);
|
||
|
}
|
||
|
if (node.type === 'JSXElement' && inlineNames.indexOf(node.openingElement.name.name) > -1) {
|
||
|
return true;
|
||
|
}
|
||
|
if (astUtil.isCallExpression(node) && inlineNames.indexOf(node.arguments[0].value) > -1) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// Rule Definition
|
||
|
// ------------------------------------------------------------------------------
|
||
|
|
||
|
const messages = {
|
||
|
inlineElement: 'Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.',
|
||
|
};
|
||
|
|
||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||
|
module.exports = {
|
||
|
meta: {
|
||
|
docs: {
|
||
|
description: 'Disallow adjacent inline elements not separated by whitespace.',
|
||
|
category: 'Best Practices',
|
||
|
recommended: false,
|
||
|
url: docsUrl('no-adjacent-inline-elements'),
|
||
|
},
|
||
|
schema: [],
|
||
|
|
||
|
messages,
|
||
|
},
|
||
|
create(context) {
|
||
|
function validate(node, children) {
|
||
|
let currentIsInline = false;
|
||
|
let previousIsInline = false;
|
||
|
if (!children) {
|
||
|
return;
|
||
|
}
|
||
|
for (let i = 0; i < children.length; i++) {
|
||
|
currentIsInline = isInline(children[i]);
|
||
|
if (previousIsInline && currentIsInline) {
|
||
|
report(context, messages.inlineElement, 'inlineElement', {
|
||
|
node,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
previousIsInline = currentIsInline;
|
||
|
}
|
||
|
}
|
||
|
return {
|
||
|
JSXElement(node) {
|
||
|
validate(node, node.children);
|
||
|
},
|
||
|
CallExpression(node) {
|
||
|
if (!isCreateElement(context, node)) {
|
||
|
return;
|
||
|
}
|
||
|
if (node.arguments.length < 2 || !node.arguments[2]) {
|
||
|
return;
|
||
|
}
|
||
|
const children = 'elements' in node.arguments[2] ? node.arguments[2].elements : undefined;
|
||
|
validate(node, children);
|
||
|
},
|
||
|
};
|
||
|
},
|
||
|
};
|