Skip to content

Commit 08d68a5

Browse files
kapobajzaKapobajzacamc314
authored
feat(linter/react): implement jsx-no-literals rule (#23145)
Related to #1022 ### AI usage disclosure I used Claude Code to help document the code, navigate the codebase to find the exact AST kinds I needed, work through the `elementOverrides` resolution logic, diagnose a few failing test cases, and generate this PR description. All code has been reviewed and tested by me. ### What this does Adds the `react/jsx-no-literals` rule ([eslint-plugin-react equivalent](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md)), which flags unwrapped string literals used as JSX children or string-valued props. Tracked under #1022. - **Category:** `restriction` (opt-in; only meaningful for codebases enforcing i18n) - **Fix:** `none` (no safe mechanical transform exists) ### Implemented options | Option | Notes | | --- | --- | | `noStrings` | Flags string/template literals inside `{...}` containers and props, including string concatenation (`{'foo' + bar}`). | | `allowedStrings` | Whitelist, compared on trimmed values. | | `ignoreProps` | Skips attribute checks. | | `noAttributeStrings` | Flags string-literal attribute values. | | `restrictedAttributes` | Flags string literals only on the named attributes. | | `elementOverrides` | Per-element option overrides, including `allowElement` and `applyToNestedElements`. Override keys resolve through import aliases (`import { T as U }`), `require` destructuring, member-expression tags (`<T.U>`), and the bare-property fallback (`React.Fragment` matches a `Fragment` override). | ### Notes - JSX fragments (`<>…</>`) are handled in addition to elements. - Diagnostics are tailored per situation (literal text child, string literal in JSX, restricted attribute) rather than a single generic message. - The rule config is boxed so `RuleEnum` stays pointer-sized (`size_asserts` passes). ### Known limitations - HTML-entity handling for `allowedStrings` (`&nbsp;`, `&mdash;`) compares against `JSXText.value`; some entity edge cases may need `JSXText.raw`. --------- Co-authored-by: Kapobajza <[email protected]> Co-authored-by: Cameron Clark <[email protected]>
1 parent 9a2788b commit 08d68a5

8 files changed

Lines changed: 2696 additions & 1 deletion

File tree

apps/oxlint/src-js/package/config.generated.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,7 @@ export interface DummyRuleMap {
11721172
"react/jsx-no-comment-textnodes"?: RuleNoConfig;
11731173
"react/jsx-no-constructed-context-values"?: RuleNoConfig;
11741174
"react/jsx-no-duplicate-props"?: RuleNoConfig;
1175+
"react/jsx-no-literals"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, JsxNoLiteralsConfig];
11751176
"react/jsx-no-script-url"?: DummyRule;
11761177
"react/jsx-no-target-blank"?: DummyRule;
11771178
"react/jsx-no-undef"?: RuleNoConfig;
@@ -4077,6 +4078,70 @@ export interface JsxMaxDepthConfig {
40774078
*/
40784079
max?: number;
40794080
}
4081+
/**
4082+
* The options shared between the top-level config and each `elementOverrides` entry.
4083+
*/
4084+
export interface JsxNoLiteralsConfig {
4085+
/**
4086+
* An array of unique string values that would otherwise warn, but will be ignored.
4087+
*/
4088+
allowedStrings?: string[];
4089+
/**
4090+
* 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.
4091+
*/
4092+
elementOverrides?: {
4093+
[k: string]: ElementOverrideOptions;
4094+
};
4095+
/**
4096+
* (default: false) - When true the rule ignores literals used in props, wrapped or unwrapped.
4097+
*/
4098+
ignoreProps?: boolean;
4099+
/**
4100+
* (default: false) - Enforces no string literals used in attributes when set to true.
4101+
*/
4102+
noAttributeStrings?: boolean;
4103+
/**
4104+
* (default: false) - Enforces no string literals used as children, wrapped or unwrapped.
4105+
*/
4106+
noStrings?: boolean;
4107+
/**
4108+
* 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.
4109+
*/
4110+
restrictedAttributes?: string[];
4111+
}
4112+
/**
4113+
* One entry in `elementOverrides`: the base options plus override-only fields.
4114+
*/
4115+
export interface ElementOverrideOptions {
4116+
/**
4117+
* (default: false) - When true the rule will allow the specified element to have string literals as children, wrapped or unwrapped without warning.
4118+
*/
4119+
allowElement?: boolean;
4120+
/**
4121+
* An array of unique string values that would otherwise warn, but will be ignored.
4122+
*/
4123+
allowedStrings?: string[];
4124+
/**
4125+
* (default: true) - When false the rule will not apply the current options set to nested elements. This is useful when you want to apply the rule to a specific element, but not to its children.
4126+
*/
4127+
applyToNestedElements?: boolean;
4128+
/**
4129+
* (default: false) - When true the rule ignores literals used in props, wrapped or unwrapped.
4130+
*/
4131+
ignoreProps?: boolean;
4132+
/**
4133+
* (default: false) - Enforces no string literals used in attributes when set to true.
4134+
*/
4135+
noAttributeStrings?: boolean;
4136+
/**
4137+
* (default: false) - Enforces no string literals used as children, wrapped or unwrapped.
4138+
*/
4139+
noStrings?: boolean;
4140+
/**
4141+
* 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.
4142+
*/
4143+
restrictedAttributes?: string[];
4144+
}
40804145
export interface JsxNoUselessFragment {
40814146
/**
40824147
* Allow fragments with a single expression child.

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_linter/src/generated/rules_enum.rs

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_linter/src/rules.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ pub(crate) mod react {
428428
pub mod jsx_no_comment_textnodes;
429429
pub mod jsx_no_constructed_context_values;
430430
pub mod jsx_no_duplicate_props;
431+
pub mod jsx_no_literals;
431432
pub mod jsx_no_script_url;
432433
pub mod jsx_no_target_blank;
433434
pub mod jsx_no_undef;

0 commit comments

Comments
 (0)