diff --git a/CHANGELOG.md b/CHANGELOG.md
index de18616929..90bc4e80e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,12 +15,16 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`jsx-key`]: avoid a crash from optional chaining from [#3320][] ([#3327][] @ljharb)
* [`jsx-key`]: avoid a crash on a non-array node.body from [#3320][] ([#3328][] @ljharb)
* [`display-name`]: fix false positive for assignment of function returning null ([#3331][] @apbarrero)
+* [`display-name`]: fix identifying `_` as a capital letter ([#3335][] @apbarrero)
### Changed
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)
* [Tests] [`jsx-indent`], [`jsx-one-expression-per-line`]: add passing test cases ([#3314][] @ROSSROSALES)
* [Refactor] `boolean-prop-naming`, `jsx-indent`: avoid assigning to arguments ([#3316][] @caroline223)
+* [Docs] `sort-comp`: add class component examples ([#3339][] @maurer2)
+[#3339]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3339
+[#3335]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3335
[#3331]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3331
[#3328]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3328
[#3327]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3327
diff --git a/docs/rules/sort-comp.md b/docs/rules/sort-comp.md
index 9eef7c4bfb..974d6d14b9 100644
--- a/docs/rules/sort-comp.md
+++ b/docs/rules/sort-comp.md
@@ -24,6 +24,15 @@ var Hello = createReactClass({
});
```
+```jsx
+class Hello extends React.Component {
+ render() {
+ return
Hello
;
+ }
+ static displayName = 'Hello';
+}
+```
+
Examples of **correct** code for this rule:
```jsx
@@ -35,6 +44,15 @@ var Hello = createReactClass({
});
```
+```jsx
+class Hello extends React.Component {
+ static displayName = 'Hello';
+ render() {
+ return Hello
;
+ }
+}
+```
+
## Rule Options
This rule can take one argument to customize the components organisation.
@@ -128,6 +146,16 @@ var Hello = createReactClass({
});
```
+```jsx
+class Hello extends React.Component {
+ render() {
+ return Hello
;
+ }
+ onClick = this.onClick.bind(this);
+ onClick() {}
+}
+```
+
Examples of **correct** code for this rule, with the above configuration:
```jsx
@@ -139,6 +167,16 @@ var Hello = createReactClass({
});
```
+```jsx
+class Hello extends React.Component {
+ onClick = this.onClick.bind(this);
+ onClick() {}
+ render() {
+ return Hello
;
+ }
+}
+```
+
If you want to split your `render` method into smaller ones and keep them just before render:
```js
@@ -170,6 +208,17 @@ var Hello = createReactClass({
});
```
+```jsx
+class Hello extends React.Component {
+ renderButton = () => {}
+ onClick = this.onClick.bind(this);
+ onClick() {}
+ render() {
+ return Hello
;
+ }
+}
+```
+
Examples of **correct** code for this rule, with the above configuration:
```jsx
@@ -182,6 +231,17 @@ var Hello = createReactClass({
});
```
+```jsx
+class Hello extends React.Component {
+ onClick = this.onClick.bind(this);
+ onClick() {}
+ renderButton = () => {}
+ render() {
+ return Hello
;
+ }
+}
+```
+
If you want to flow annotations to be at the top:
```js
diff --git a/lib/util/isFirstLetterCapitalized.js b/lib/util/isFirstLetterCapitalized.js
index f487489b65..819edc8860 100644
--- a/lib/util/isFirstLetterCapitalized.js
+++ b/lib/util/isFirstLetterCapitalized.js
@@ -6,7 +6,7 @@
* @returns {Boolean} True if first letter is capitalized.
*/
function isFirstLetterCapitalized(word) {
- if (!word) {
+ if (!word || word.charAt(0) === '_') {
return false;
}
const firstLetter = word.charAt(0);
diff --git a/tests/helpers/parsers.js b/tests/helpers/parsers.js
index b0e0a7af9c..de7dcbc8e6 100644
--- a/tests/helpers/parsers.js
+++ b/tests/helpers/parsers.js
@@ -2,6 +2,7 @@
const path = require('path');
const semver = require('semver');
+const entries = require('object.entries');
const version = require('eslint/package.json').version;
const flatMap = require('array.prototype.flatmap');
const tsParserVersion = require('@typescript-eslint/parser/package.json').version;
@@ -10,6 +11,25 @@ const disableNewTS = semver.satisfies(tsParserVersion, '>= 4.1') // this rule is
? (x) => Object.assign({}, x, { features: [].concat(x.features, 'no-ts-new') })
: (x) => x;
+function minEcmaVersion(features, parserOptions) {
+ const minEcmaVersionForFeatures = {
+ 'class fields': 2022,
+ 'optional chaining': 2020,
+ };
+ const result = Math.max.apply(
+ Math,
+ [].concat(
+ (parserOptions && parserOptions.ecmaVersion) || [],
+ flatMap(entries(minEcmaVersionForFeatures), (entry) => {
+ const f = entry[0];
+ const y = entry[1];
+ return features.has(f) ? y : [];
+ })
+ ).map((y) => (y > 5 && y < 2015 ? y + 2009 : y)) // normalize editions to years
+ );
+ return Number.isFinite(result) ? result : undefined;
+}
+
const NODE_MODULES = '../../node_modules';
const parsers = {
@@ -58,7 +78,7 @@ const parsers = {
const features = new Set([].concat(test.features || []));
delete test.features;
- const es = features.has('class fields') ? 2022 : (features.has('optional chaining') ? 2020 : (test.parserOptions && test.parserOptions.ecmaVersion) || undefined); // eslint-disable-line no-nested-ternary
+ const es = minEcmaVersion(features, test.parserOptions);
function addComment(testObject, parser) {
const extras = [].concat(
@@ -133,10 +153,8 @@ const parsers = {
return [].concat(
skipBase ? [] : addComment(
- Object.assign({}, test, typeof es !== 'undefined' && {
- parserOptions: Object.assign({}, test.parserOptions, {
- ecmaVersion: Math.max((test.parserOptions && test.parserOptions.ecmaVersion) || 0, es),
- }),
+ Object.assign({}, test, typeof es === 'number' && {
+ parserOptions: Object.assign({}, test.parserOptions, { ecmaVersion: es }),
}),
'default'
),
diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js
index 39bdb59cb5..ba4b9181fe 100644
--- a/tests/lib/rules/display-name.js
+++ b/tests/lib/rules/display-name.js
@@ -597,6 +597,24 @@ ruleTester.run('display-name', rule, {
return f(a);
};`,
},
+ {
+ // issue #3334
+ code: `
+ obj._property = (a) => {
+ if (a == null) return null;
+ return f(a);
+ };
+ `,
+ },
+ {
+ // issue #3334
+ code: `
+ _variable = (a) => {
+ if (a == null) return null;
+ return f(a);
+ };
+ `,
+ },
{
// issue #3303
code: `
diff --git a/tests/util/isFirstLetterCapitalized.js b/tests/util/isFirstLetterCapitalized.js
index 5ccbfea68e..85bdffd1bc 100644
--- a/tests/util/isFirstLetterCapitalized.js
+++ b/tests/util/isFirstLetterCapitalized.js
@@ -14,6 +14,8 @@ describe('isFirstLetterCapitalized', () => {
it('should return false for uncapitalized string', () => {
assert.equal(isFirstLetterCapitalized('isCapitalized'), false);
assert.equal(isFirstLetterCapitalized('lowercase'), false);
+ assert.equal(isFirstLetterCapitalized('_startsWithUnderscore'), false);
+ assert.equal(isFirstLetterCapitalized('_StartsWithUnderscore'), false);
});
it('should return true for capitalized string', () => {