mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-26 01:33:02 +00:00
feat(eslint): add no-jsx-text-literals rule + tests (fixes #6472)
Signed-off-by: sharvandeep <2400032427@kluniversity.in>
This commit is contained in:
parent
f8bedbd0a0
commit
2306941eda
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
'no-jsx-text-literals': require('./rules/no-jsx-text-literals'),
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* @fileoverview Disallow plain text literals in JSX to encourage use of i18n/Translate
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'disallow literal text in JSX (encourage <Translate> or i18n usage)',
|
||||
category: 'Best Practices',
|
||||
recommended: false,
|
||||
},
|
||||
schema: [], // no options
|
||||
messages: {
|
||||
noJsxText: 'Avoid using raw text in JSX — wrap translatable text with <Translate> or use i18n APIs.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
// helper: find nearest ancestor JSX element name (if any)
|
||||
function findAncestorElementName(node) {
|
||||
let p = node.parent;
|
||||
while (p) {
|
||||
if (p.type === 'JSXElement' && p.openingElement && p.openingElement.name) {
|
||||
const nameNode = p.openingElement.name;
|
||||
// handle simple identifier names only (e.g., Translate, div)
|
||||
if (nameNode.type === 'JSXIdentifier' && nameNode.name) {
|
||||
return nameNode.name;
|
||||
}
|
||||
// handle MemberExpressions like Some.Namespace.Component (rare in JSX)
|
||||
if (nameNode.type === 'JSXMemberExpression') {
|
||||
let parts = [];
|
||||
let curr = nameNode;
|
||||
while (curr) {
|
||||
if (curr.property && curr.property.name) parts.unshift(curr.property.name);
|
||||
if (curr.object && curr.object.name) { parts.unshift(curr.object.name); break; }
|
||||
curr = curr.object;
|
||||
}
|
||||
return parts.join('.');
|
||||
}
|
||||
}
|
||||
p = p.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const ignoreTags = new Set(['code', 'pre', 'script', 'style', 'Translate']);
|
||||
|
||||
return {
|
||||
JSXText(node) {
|
||||
if (!node || !node.value) {
|
||||
return;
|
||||
}
|
||||
const text = node.value.trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If inside an ignored tag (including Translate), skip
|
||||
const ancestorName = findAncestorElementName(node);
|
||||
if (ancestorName && ignoreTags.has(ancestorName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noJsxText',
|
||||
});
|
||||
},
|
||||
|
||||
Literal(node) {
|
||||
// Flag string literals used as children: e.g. { 'Hello' }
|
||||
if (typeof node.value === 'string' && node.parent && node.parent.type === 'JSXExpressionContainer') {
|
||||
const text = node.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
// If inside ignored tag (including Translate), skip
|
||||
const ancestorName = findAncestorElementName(node);
|
||||
if (ancestorName && ignoreTags.has(ancestorName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noJsxText',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "eslint-plugin-docusaurus-local",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"test": "mocha tests/*.test.js --timeout 5000"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.1",
|
||||
"mocha": "^10.8.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
const RuleTester = require('eslint').RuleTester;
|
||||
const rule = require('../lib/rules/no-jsx-text-literals');
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: { ecmaVersion: 2020, sourceType: 'module', ecmaFeatures: { jsx: true } },
|
||||
});
|
||||
|
||||
ruleTester.run('no-jsx-text-literals', rule, {
|
||||
valid: [
|
||||
// using Translate component
|
||||
{ code: "const a = <Translate>Hello</Translate>;" },
|
||||
// whitespace-only
|
||||
{ code: "const a = <div> </div>;" },
|
||||
// code/pre tags
|
||||
{ code: "const a = <pre>some code</pre>;" },
|
||||
// dynamic expression
|
||||
{ code: "const a = <div>{message}</div>;" },
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "const a = <div>Hello world</div>;",
|
||||
errors: [{ messageId: 'noJsxText' }],
|
||||
},
|
||||
{
|
||||
code: "const a = <p>{'Plain text'}</p>;",
|
||||
errors: [{ messageId: 'noJsxText' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
Loading…
Reference in New Issue