Skip to content

Instantly share code, notes, and snippets.

@azirbel
Created November 29, 2021 04:46
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save azirbel/51518d919de979197a7c5c25c54a56d6 to your computer and use it in GitHub Desktop.
Save azirbel/51518d919de979197a7c5c25c54a56d6 to your computer and use it in GitHub Desktop.
eslint rule to avoid mixing conditionals and raw JSX text
/*
Handy tools & articles for developing eslint rules:
https://astexplorer.net/
https://www.webiny.com/blog/create-custom-eslint-rules-in-2-minutes-e3d41cb6a9a0
https://blog.maximeheckel.com/posts/how-to-build-first-eslint-rule/
Developing:
(make changes)
yarn add --dev file:./eslint && yarn eslint .
*/
module.exports = {
rules: {
'no-conditional-literals-in-jsx': {
meta: {
docs: {
description:
'Browser auto-translation will break if pieces of text nodes are be rendered conditionally.',
},
schema: [],
messages: {
unexpected:
'Conditional expression is a sibling of raw text and must be wrapped in <div> or <span>',
},
},
create: function(context) {
return {
// Imagine evaluating <div>text {conditional && 'string'}</div>
JSXExpressionContainer(node) {
// We start at the expression {conditional && 'string'}
if (node.expression.type !== 'LogicalExpression') return
// "text" is one of the siblingTextNodes.
const siblingTextNodes = (node.parent.children || []).filter(n => {
// In normal code these are 'Literal', but in test code they are 'JSXText'
const isText = n.type === 'Literal' || n.type === 'JSXText'
// Skip empty text nodes, like " \n " -- these may be JSX artifacts
return isText && !!n.value.trim()
})
// If we were evaluting
// <div>{property} {conditional && 'string'}</div>
// Then {property} would be one of the siblingExpressionNodes
const siblingExpressionNodes = (node.parent.children || []).filter(
n =>
n.type === 'JSXExpressionContainer' &&
(n.expression.type === 'Identifier' ||
n.expression.type === 'MemberExpression')
)
// Operands of {conditional && 'string'} -- the conditional and the
// literal. We want to make sure we have a text literal, otherwise we'd
// trigger this rule on the (safe) {conditional && <div>string</div>}.
const expressionOperandTypes = [
node.expression.left.type,
node.expression.right.type,
]
if (
siblingTextNodes.concat(siblingExpressionNodes).length > 0 &&
expressionOperandTypes.includes('Literal')
) {
context.report({ node, messageId: 'unexpected' })
}
},
}
},
},
},
}
const rules = require('./rules').rules
const RuleTester = require('eslint').RuleTester
const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
})
const errors = [{ messageId: 'unexpected' }]
const noConditionalLiterals = rules['no-conditional-literals-in-jsx']
ruleTester.run('no-conditional-literals-in-jsx', noConditionalLiterals, {
valid: [
{
code: `<div>{conditional && 'string'}</div>`,
errors,
},
{
code: `<div>{conditional || 'string'}</div>`,
errors,
},
{
// The error happens when DOM elements are added or removed; swapping
// is ok
code: `<div>{conditional ? 'a' : 'b'}</div>`,
errors,
},
{
// As long as conditionally-rendered stuff is wrapped in div or span, it's fine
code: `<div>text {conditional && <div>wrapped is ok</div>}</div>`,
errors,
},
{
// Logic within an attribute doesn't affect the DOM
code: `<Avatar alt={conditional && 'string'} />`,
errors,
},
{
// JSX auto-adds whitespace when there are newlines. Make sure they don't trigger
code: `<div>
{conditional && 'string'}
</div>`,
errors,
},
],
invalid: [
{
code: `<div>text {conditional && 'string'}</div>`,
errors,
},
{
code: `<div>text {conditional || 'string'}</div>`,
errors,
},
{
code: `<div>{conditional && 'string'} text</div>`,
errors,
},
{
code: `<div>{conditional || 'string'} text</div>`,
errors,
},
{
// More complicated logic
code: `<div>text {(conditional1 && conditional2) || 'string'}</div>`,
errors,
},
{
// This results in 2 text nodes with no JSX containers -- dangerous
code: `<div>{property} {conditional && 'string'}</div>`,
errors,
},
{
// This results in 2 text nodes with no JSX containers -- dangerous
code: `<div>{object.property} {conditional && 'string'}</div>`,
errors,
},
],
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment