Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/jsx-no-literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The supported options are:
- `allowedStrings` - An array of unique string values that would otherwise warn, but will be ignored.
- `ignoreProps` (default: `false`) - When `true` the rule ignores literals used in props, wrapped or unwrapped.
- `noAttributeStrings` (default: `false`) - Enforces no string literals used in attributes when set to `true`.
- `restrictedAttributes` - An array of unique attribute names where string literals should be restricted. Only the specified attributes will be checked for string literals when this option is used. **Note**: When `noAttributeStrings` is `true`, this option is ignored at the root level.
- `elementOverrides` - An object where the keys are the element names and the values are objects with the same options as above. This allows you to specify different options for different elements.

### `elementOverrides`
Expand Down
32 changes: 32 additions & 0 deletions lib/rules/jsx-no-literals.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const messages = {
noStringsInJSXInElement: 'Strings not allowed in JSX files: "{{text}}" in {{element}}',
literalNotInJSXExpression: 'Missing JSX expression container around literal string: "{{text}}"',
literalNotInJSXExpressionInElement: 'Missing JSX expression container around literal string: "{{text}}" in {{element}}',
restrictedAttributeString: 'Restricted attribute string: "{{text}}" in {{attribute}}',
restrictedAttributeStringInElement: 'Restricted attribute string: "{{text}}" in {{attribute}} of {{element}}',
};

/** @type {Exclude<RuleModule['meta']['schema'], unknown[] | false>['properties']} */
Expand All @@ -71,6 +73,13 @@ const commonPropertiesSchema = {
noAttributeStrings: {
type: 'boolean',
},
restrictedAttributes: {
type: 'array',
uniqueItems: true,
items: {
type: 'string',
},
},
};

// eslint-disable-next-line valid-jsdoc
Expand All @@ -88,6 +97,9 @@ function normalizeElementConfig(config) {
: new Set(),
ignoreProps: !!config.ignoreProps,
noAttributeStrings: !!config.noAttributeStrings,
restrictedAttributes: config.restrictedAttributes
? new Set(map(iterFrom(config.restrictedAttributes), trimIfString))
: new Set(),
};
}

Expand Down Expand Up @@ -478,6 +490,26 @@ module.exports = {

if (isLiteralString || isStringLiteral) {
const resolvedConfig = getOverrideConfig(node) || config;
const restrictedAttributes = resolvedConfig.restrictedAttributes;

if (restrictedAttributes.size > 0 && node.name && node.name.type === 'JSXIdentifier') {
const attributeName = node.name.name;

if (!restrictedAttributes.has(attributeName)) {
return; // Skip reporting this attribute if it's not in the restricted list
}

const messageId = resolvedConfig.type === 'override' ? 'restrictedAttributeStringInElement' : 'restrictedAttributeString';
report(context, messages[messageId], messageId, {
node,
data: {
text: node.value.value,
attribute: attributeName,
element: resolvedConfig.type === 'override' && 'name' in resolvedConfig ? resolvedConfig.name : undefined,
},
});
return;
}

if (
resolvedConfig.noStrings
Expand Down
164 changes: 164 additions & 0 deletions tests/lib/rules/jsx-no-literals.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,18 @@ ruleTester.run('jsx-no-literals', rule, {
`,
options: [{ noStrings: true, allowedStrings: ['&mdash;', '—'] }],
},
{
code: `
<img src="image.jpg" alt="text" />
`,
options: [{ restrictedAttributes: ['className', 'id'] }],
},
{
code: `
<div className="test" id="foo" />
`,
options: [{ restrictedAttributes: [] }],
},
{
code: `
<T>foo</T>
Expand Down Expand Up @@ -476,6 +488,45 @@ ruleTester.run('jsx-no-literals', rule, {
`,
options: [{ elementOverrides: { div: { allowElement: true } } }],
},
{
code: `
<div>
<Input type="text" />
<Button className="primary" />
<Image src="photo.jpg" />
</div>
`,
options: [{
elementOverrides: {
Input: { restrictedAttributes: ['placeholder'] },
Button: { restrictedAttributes: ['type'] },
},
}],
},
{
code: `
<div title="container">
<Button className="btn" />
</div>
`,
options: [{
restrictedAttributes: ['className'],
elementOverrides: {
Button: { restrictedAttributes: ['disabled'] },
},
}],
},
{
code: `
<Button className="btn" />
`,
options: [{
noAttributeStrings: true,
elementOverrides: {
Button: { restrictedAttributes: ['type'] },
},
}],
},
]),

invalid: parsers.all([
Expand Down Expand Up @@ -845,6 +896,49 @@ ruleTester.run('jsx-no-literals', rule, {
},
],
},
{
code: `
<div className="test" />
`,
options: [{ restrictedAttributes: ['className'] }],
errors: [{
messageId: 'restrictedAttributeString',
data: { text: 'test', attribute: 'className' },
}],
},
{
code: `
<div className="test" id="foo" title="bar" />
`,
options: [{ restrictedAttributes: ['className', 'id'] }],
errors: [
{ messageId: 'restrictedAttributeString', data: { text: 'test', attribute: 'className' } },
{ messageId: 'restrictedAttributeString', data: { text: 'foo', attribute: 'id' } },
],
},
{
code: `
<div src="image.jpg" />
`,
options: [{
noAttributeStrings: true,
restrictedAttributes: ['className'],
}],
errors: [{ messageId: 'noStringsInAttributes', data: { text: '"image.jpg"' } }],
},
{
code: `
<div title="text">test</div>
`,
options: [{
restrictedAttributes: ['title'],
noStrings: true,
}],
errors: [
{ messageId: 'restrictedAttributeString', data: { text: 'text', attribute: 'title' } },
{ messageId: 'noStringsInJSX', data: { text: 'test' } },
],
},
{
code: `
<div>
Expand Down Expand Up @@ -1169,5 +1263,75 @@ ruleTester.run('jsx-no-literals', rule, {
options: [{ elementOverrides: { div: { allowElement: true } } }],
errors: [{ messageId: 'literalNotInJSXExpression', data: { text: 'foo' } }],
},
{
code: `
<div>
<div type="text" />
<Button type="submit" />
</div>
`,
options: [{
elementOverrides: {
Button: { restrictedAttributes: ['type'] },
},
}],
errors: [
{ messageId: 'restrictedAttributeStringInElement', data: { text: 'submit', attribute: 'type', element: 'Button' } },
],
},
{
code: `
<div>
<Input placeholder="Enter text" type="password" />
<Button type="submit" disabled="true" />
</div>
`,
options: [{
elementOverrides: {
Input: { restrictedAttributes: ['placeholder'] },
Button: { restrictedAttributes: ['disabled'] },
},
}],
errors: [
{ messageId: 'restrictedAttributeStringInElement', data: { text: 'Enter text', attribute: 'placeholder', element: 'Input' } },
{ messageId: 'restrictedAttributeStringInElement', data: { text: 'true', attribute: 'disabled', element: 'Button' } },
],
},
{
code: `
<div>
<div className="wrapper" id="main" />
<Button className="btn" id="submit-btn" />
</div>
`,
options: [{
restrictedAttributes: ['className'],
elementOverrides: {
Button: { restrictedAttributes: ['id'] },
},
}],
errors: [
{ messageId: 'restrictedAttributeString', data: { text: 'wrapper', attribute: 'className' } },
{ messageId: 'restrictedAttributeStringInElement', data: { text: 'submit-btn', attribute: 'id', element: 'Button' } },
],
},
{
code: `
<div>
<div foo1="bar1" />
<T foo2="bar2" />
</div>
`,
options: [{
noAttributeStrings: true,
elementOverrides: {
T: { restrictedAttributes: ['foo2'] },
},
}],
errors: [
{ messageId: 'noStringsInAttributes', data: { text: '"bar1"' } },
{ messageId: 'restrictedAttributeStringInElement', data: { text: 'bar2', attribute: 'foo2', element: 'T' } },
],
},
]),
});
2 changes: 2 additions & 0 deletions types/rules/jsx-no-literals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ type RawElementConfig = {
allowedStrings?: string[];
ignoreProps?: boolean;
noAttributeStrings?: boolean;
restrictedAttributes?: string[];
};

type RawOverrideConfig = {
Expand All @@ -25,6 +26,7 @@ interface ElementConfigProperties {
allowedStrings: Set<string>;
ignoreProps: boolean;
noAttributeStrings: boolean;
restrictedAttributes: Set<string>;
}

interface OverrideConfigProperties {
Expand Down
Loading