eslint-plugin-sitecore-jss
Version:
An ESLint plugin to enforce correct usage of Sitecore JSS components
110 lines • 6.14 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const type_checking_1 = __importDefault(require("./utils/type-checking"));
const { getType, isRichTextField } = type_checking_1.default;
exports.default = {
defaultOptions: [],
meta: {
type: "problem",
docs: {
description: "Ensure `<RichText>` is used for `RichTextField` instead of raw JSX elements.",
recommended: "recommended",
},
messages: {
useRichTextComponent: "Use `<RichText field={{fieldName}} tag='{{tagName}}' />` instead of `{props.fields.body.value}` inside `<{{tagName}}>`. `RichTextField` should always use `<RichText />`.",
avoidNestedPTags: "RichTextField in `<p>` tags creates nested `<p>` elements which is invalid HTML. Use `<RichText field={{fieldName}} />` or change the wrapper to `<div>`.",
avoidRichTextPTag: 'Using `<RichText>` with `tag="p"` creates nested `<p>` elements since Sitecore automatically adds `<p>` tags. Remove the `tag` attribute or use `tag="div"`.',
},
fixable: "code",
schema: [],
},
create(context) {
const parserServices = utils_1.ESLintUtils.getParserServices(context);
if (!parserServices || !parserServices.program)
return {};
const checker = parserServices.program.getTypeChecker();
return {
JSXOpeningElement(node) {
if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier)
return;
const tagName = node.name.name;
// Check for RichText component with tag="p"
if (tagName === "RichText") {
const tagAttribute = node.attributes.find((attr) => attr.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
attr.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
attr.name.name === "tag");
if ((tagAttribute === null || tagAttribute === void 0 ? void 0 : tagAttribute.value) &&
tagAttribute.value.type === utils_1.AST_NODE_TYPES.Literal &&
tagAttribute.value.value === "p") {
context.report({
node,
messageId: "avoidRichTextPTag",
fix(fixer) {
// Auto-fix: remove the tag="p" attribute
return fixer.removeRange([
tagAttribute.range[0] - 1, // Include the space before the attribute
tagAttribute.range[1],
]);
},
});
}
return; // Don't process RichText components further
}
const parentElement = node.parent;
if (!(parentElement === null || parentElement === void 0 ? void 0 : parentElement.children))
return;
parentElement.children.forEach((child) => {
if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
child.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(child.expression.object);
if (!tsNode)
return;
const type = getType(tsNode, checker);
if (isRichTextField(type)) {
let fieldName = child.expression;
while (fieldName.type === utils_1.AST_NODE_TYPES.MemberExpression) {
if (fieldName.property.type === utils_1.AST_NODE_TYPES.Identifier &&
fieldName.property.name === "value") {
fieldName = fieldName.object;
}
else {
break;
}
}
const fieldNameText = context.getSourceCode().getText(fieldName);
// Special case: if RichTextField is inside a <p> tag, warn about nested <p> tags
if (tagName === "p") {
context.report({
node,
messageId: "avoidNestedPTags",
data: { fieldName: fieldNameText },
fix(fixer) {
// Auto-fix: use <RichText> without specifying tag (defaults to div)
// or explicitly use div to avoid nested p tags
return fixer.replaceText(parentElement, `<RichText field={${fieldNameText}} />`);
},
});
}
else {
// Original behavior for other tags
context.report({
node,
messageId: "useRichTextComponent",
data: { tagName, fieldName: fieldNameText },
fix(fixer) {
return fixer.replaceText(parentElement, `<RichText field={${fieldNameText}} tag="${tagName}" />`);
},
});
}
}
}
});
},
};
},
};
//# sourceMappingURL=enforce-richtext-component.js.map