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', () => {