diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c9422e73d00..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -artifact/ -apps/ -packages/ -js/dist/ -vendor/ -vendor_prefixed/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index bdb48ee2129..00000000000 --- a/.eslintrc +++ /dev/null @@ -1,55 +0,0 @@ -extends: yoast -root: true -parserOptions: - ecmaVersion: 2018 - sourceType: module - -settings: - react: - version: "18.2" - -ignorePatterns: - - "packages/*/build/*" - -rules: - no-prototype-builtins: 0 - comma-dangle: - - error - - arrays: always-multiline - objects: always-multiline - imports: always-multiline - exports: always-multiline - functions: never - -overrides: - - files: - - "packages/*/tests/**/*.js" - - "packages/*/spec/**/*.js" - # Temporary exclusion: - - "packages/components/a11y/tests/**/*.js" - env: - jest: true - rules: - no-restricted-imports: "off" - - files: - - "packages/components/**/*.js" - rules: - react/jsx-no-bind: 1 - react/require-default-props: 1 - react/default-props-match-prop-types: 1 - react/no-unused-prop-types: 1 - require-jsdoc: 1 - import/no-unresolved: [ "error", { ignore: [ "^@yoast/(helpers|style-guide)" ] } ] - - files: - - "packages/social-metadata-forms/**/*.js" - rules: - import/no-unresolved: [ "error", { ignore: [ "^@yoast/(components|helpers|replacement-variable-editor|style-guide)" ] } ] - - files: - - "packages/social-metadata-previews/**/*.js" - rules: - import/no-unresolved: [ "error", { ignore: [ "^@yoast/(components|replacement-variable-editor|social-metadata-forms|style-guide)" ] } ] - - files: - - "packages/**/tests/**/*Test.js" - rules: - no-console: 0 - react/jsx-no-bind: 0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bf330b703a4..d36b3d292f4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,9 +27,9 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml index 52f6e58fc81..f6b4f78efb9 100644 --- a/.github/workflows/cs.yml +++ b/.github/workflows/cs.yml @@ -11,7 +11,7 @@ on: - 'composer.lock' - '.phpcs.xml.dist' - 'phpcs.xml.dist' - - '.github/workflows/cs.yml' + - '.github/workflows/**' pull_request: paths: - '**.php' # Includes config/*.php files. @@ -19,7 +19,7 @@ on: - 'composer.lock' - '.phpcs.xml.dist' - 'phpcs.xml.dist' - - '.github/workflows/cs.yml' + - '.github/workflows/**' # Allow manually triggering the workflow. workflow_dispatch: @@ -30,6 +30,10 @@ concurrency: cancel-in-progress: true jobs: + actionlint: + name: 'Lint GH Action workflows' + uses: Yoast/.github/.github/workflows/reusable-actionlint.yml@main + checkcs: name: 'Check code style' runs-on: ubuntu-latest @@ -44,11 +48,11 @@ jobs: BASE_REF: ${{ github.base_ref }} run: | if [ "${{ github.event_name }}" == "pull_request" ]; then - echo "NAME=$BASE_REF" >> $GITHUB_OUTPUT - echo "REF=origin/$BASE_REF" >> $GITHUB_OUTPUT + echo "NAME=$BASE_REF" >> "$GITHUB_OUTPUT" + echo "REF=origin/$BASE_REF" >> "$GITHUB_OUTPUT" else - echo 'NAME=trunk' >> $GITHUB_OUTPUT - echo "REF=origin/trunk" >> $GITHUB_OUTPUT + echo 'NAME=trunk' >> "$GITHUB_OUTPUT" + echo "REF=origin/trunk" >> "$GITHUB_OUTPUT" fi - name: Fetch base branch @@ -83,7 +87,7 @@ jobs: set +e composer check-cs-thresholds exitcode="$?" - echo "EXITCODE=$exitcode" >> $GITHUB_OUTPUT + echo "EXITCODE=$exitcode" >> "$GITHUB_OUTPUT" exit "$exitcode" # Check the codestyle only of the files which were changed in the current branch. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cbbda0aaf0f..82eb305927d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -148,7 +148,7 @@ jobs: SHA: ${{ github.sha }} run: | shortsha=$(echo "$SHA" | cut -b 1-6) - echo "SHORTSHA=$shortsha" >> $GITHUB_OUTPUT + echo "SHORTSHA=$shortsha" >> "$GITHUB_OUTPUT" - name: "Set variables: target branch, commit title" id: set_vars @@ -156,14 +156,14 @@ jobs: REF_NAME: ${{ github.ref_name }} run: | if [[ "${{ github.event_name }}" == 'push' && "${{ github.ref_type }}" == 'branch' && "$REF_NAME" != "${{ env.DIST_DEFAULT_BRANCH }}" ]]; then - echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT - echo "TITLE=Syncing branch $REF_NAME (sha: ${{ steps.set_sha.outputs.SHORTSHA }})" >> $GITHUB_OUTPUT + echo "BRANCH=$REF_NAME" >> "$GITHUB_OUTPUT" + echo "TITLE=Syncing branch $REF_NAME (sha: ${{ steps.set_sha.outputs.SHORTSHA }})" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event_name }}" == 'workflow_dispatch' && "$REF_NAME" != "${{ env.DIST_DEFAULT_BRANCH }}" ]]; then - echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT - echo "TITLE=Manual deploy for $REF_NAME (sha: ${{ steps.set_sha.outputs.SHORTSHA }})" >> $GITHUB_OUTPUT + echo "BRANCH=$REF_NAME" >> "$GITHUB_OUTPUT" + echo "TITLE=Manual deploy for $REF_NAME (sha: ${{ steps.set_sha.outputs.SHORTSHA }})" >> "$GITHUB_OUTPUT" else # = Pushed tag. - echo "BRANCH=${{ env.DIST_DEFAULT_BRANCH }}" >> $GITHUB_OUTPUT - echo "TITLE=Release $REF_NAME" >> $GITHUB_OUTPUT + echo "BRANCH=${{ env.DIST_DEFAULT_BRANCH }}" >> "$GITHUB_OUTPUT" + echo "TITLE=Release $REF_NAME" >> "$GITHUB_OUTPUT" fi - name: Checkout Yoast Dist repo @@ -257,7 +257,7 @@ jobs: if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} env: REF_NAME: ${{ github.ref_name }} - run: git tag "$REF_NAME" $(git rev-parse HEAD) + run: git tag "$REF_NAME" "$(git rev-parse HEAD)" - name: Push to target branch run: git push -u origin ${{ steps.set_vars.outputs.BRANCH }} --tags -v diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index db98686d717..55bf543c56f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -73,6 +73,8 @@ jobs: ini-values: zend.assertions=1, error_reporting=-1, display_errors=On coverage: none tools: cs2pr + env: + update: true - name: Lint against parse errors run: composer lint -- --checkstyle | cs2pr diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c7632c565bc..d135f79a416 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,6 +90,8 @@ jobs: php-version: ${{ matrix.php_version }} ini-values: zend.assertions=1, error_reporting=-1, display_errors=On coverage: ${{ matrix.coverage == true && 'xdebug' || 'none' }} + env: + update: true # The PHP platform requirement would prevent updating the test utilities to the appropriate versions. # As long as the `composer update` is run selectively to only update the test utils, removing this is fine. @@ -220,6 +222,8 @@ jobs: php-version: ${{ matrix.php_version }} ini-values: zend.assertions=1, error_reporting=-1, display_errors=On coverage: ${{ matrix.coverage == true && 'xdebug' || 'none' }} + env: + update: true # The PHP platform requirement would prevent updating the test utilities to the appropriate versions. # As long as the `composer update` is run selectively to only update the test utils, removing this is fine. diff --git a/apps/content-analysis-api/routes/research.js b/apps/content-analysis-api/routes/research.js index c861e42c78d..eb252c65c88 100644 --- a/apps/content-analysis-api/routes/research.js +++ b/apps/content-analysis-api/routes/research.js @@ -1,4 +1,6 @@ const { Paper } = require( "yoastseo" ); +const { build } = require( "yoastseo/build/parse/build" ); +const { LanguageProcessor } = require( "yoastseo/build/parse/language" ); const { getResearcher } = require( "../helpers/get-researcher" ); module.exports = function( app ) { @@ -45,10 +47,11 @@ module.exports = function( app ) { request.body.text || "", request.body || {} ); + paper.setTree( build( paper, new LanguageProcessor( researcher ), paper._attributes && paper._attributes.shortcodes ) ); researcher.setPaper( paper ); const sentenceLengths = researcher.getResearch( "countSentencesFromText" ); - const responseBody = sentenceLengths.map( sentence => ( { sentence: sentence.sentence, length: sentence.sentenceLength } ) ); + const responseBody = sentenceLengths.map( sentence => ( { sentence: sentence.sentence.text, length: sentence.sentenceLength } ) ); response.json( responseBody ); } ); @@ -59,10 +62,11 @@ module.exports = function( app ) { request.body.text || "", request.body || {} ); + paper.setTree( build( paper, new LanguageProcessor( researcher ), paper._attributes && paper._attributes.shortcodes ) ); researcher.setPaper( paper ); const paragraphLengths = researcher.getResearch( "getParagraphLength" ); - const responseBody = paragraphLengths.map( paragraph => ( { paragraph: paragraph.text, length: paragraph.countLength } ) ); + const responseBody = paragraphLengths.map( paragraph => ( { length: paragraph.paragraphLength } ) ); response.json( responseBody ); } ); } diff --git a/config/grunt/custom-tasks/sync-gutenberg-version.js b/config/grunt/custom-tasks/sync-gutenberg-version.js index 12baf89e104..0fb020f6c66 100644 --- a/config/grunt/custom-tasks/sync-gutenberg-version.js +++ b/config/grunt/custom-tasks/sync-gutenberg-version.js @@ -10,7 +10,7 @@ module.exports = function( grunt ) { * * @returns {void} */ - function setVersion( file, pattern, version ) { + function setVersion( file, pattern, version ) { const contents = grunt.file.read( file ).replace( pattern, version diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..e1e0c1b50c5 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,40 @@ +import globals from "globals"; +import yoastConfig from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: [ "js/dist", "packages", "apps", "artifact", "vendor", "vendor_prefixed", ".yarn" ] }, + ...yoastConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.browser, + }, + }, + rules: { + // Deviate from the Yoast config to prohibit dangling commas in functions. + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + + // Deviate from the Yoast config to allow for not using the error that is caught. + "no-unused-vars": [ "error", { caughtErrors: "none" } ], + }, + }, + { + files: [ "*.config.js", "config/**", "Gruntfile.js" ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/package.json b/package.json index 365ef67097d..4ca35eb78e7 100644 --- a/package.json +++ b/package.json @@ -55,11 +55,9 @@ "core-js": "^2.6.12", "cross-env": "^7.0.3", "dotenv": "^8.2.0", - "eslint": "^8.57.0", - "eslint-config-yoast": "^6.0.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-react": "^7.34.1", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.13.0", "grunt-git": "^1.0.14", "grunt-prompt": "^1.3.3", "grunt-shell": "^3.0.1", @@ -77,7 +75,7 @@ "webpack-bundle-analyzer": "^4.9.1" }, "yoast": { - "pluginVersion": "24.2-RC1" + "pluginVersion": "24.3-RC2" }, "version": "0.0.0" } diff --git a/packages/analysis-report/.eslintrc.js b/packages/analysis-report/.eslintrc.js deleted file mode 100644 index bf921b075c6..00000000000 --- a/packages/analysis-report/.eslintrc.js +++ /dev/null @@ -1,66 +0,0 @@ -module.exports = { - root: true, - "extends": [ - "yoast", - ], - settings: { - react: { - version: "detect", - }, - }, - parserOptions: { - ecmaVersion: 2020, - sourceType: "module", - }, - ignorePatterns: [ - "/build/", - ], - rules: { - "no-prototype-builtins": 0, - "comma-dangle": [ - "error", - { - arrays: "always-multiline", - objects: "always-multiline", - imports: "always-multiline", - exports: "always-multiline", - functions: "never", - }, - ], - "import/no-unresolved": [ - "error", - { - ignore: [ "^@yoast/(components|helpers|style-guide)" ], - }, - ], - }, - overrides: [ - { - files: [ "tests/**/*.js" ], - env: { - jest: true, - }, - rules: { - "no-restricted-imports": 0, - }, - }, - { - files: [ "**/*.js" ], - rules: { - complexity: [ 1, 6 ], - // A wrapping label is not necessary when there already is an htmlFor attribute. - "jsx-a11y/label-has-for": [ "error", { required: "id" } ], - "require-jsdoc": 1, - "react/button-has-type": 1, - "react/default-props-match-prop-types": 1, - "react/no-unused-prop-types": 1, - "react/no-access-state-in-setstate": 1, - "react/no-unused-state": 1, - "react/jsx-no-bind": 1, - "react/jsx-no-target-blank": 1, - "react/require-default-props": 1, - "react/forbid-foreign-prop-types": 1, - }, - }, - ], -}; diff --git a/packages/analysis-report/eslint.config.mjs b/packages/analysis-report/eslint.config.mjs new file mode 100644 index 00000000000..5123bd756cb --- /dev/null +++ b/packages/analysis-report/eslint.config.mjs @@ -0,0 +1,55 @@ +import globals from "globals"; +import yoastConfig, { reactConfig } from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: [ "build" ] }, + ...yoastConfig, + ...reactConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.browser, + }, + }, + rules: { + // Account for webpack externals and potentially unbuilt packages in the monorepo setup. + "import/no-unresolved": [ + "error", + { + ignore: [ + // Ignore @yoast packages from this workspace, or we have to build the code before linting. + // Because `main` in `package.json` points to the `build/index.js`, which is not present before building. + // As we are dealing with our source, not the actual NPM download, due to the monorepo setup. + "^@yoast/(ui-library|style-guide|components|helpers|search-metadata-previews|social-metadata-forms|replacement-variable-editor|analysis-report|feature-flag|related-keyphrase-suggestions)$", + "yoastseo", + ], + }, + ], + + // Deviate from the Yoast config to prohibit dangling commas in functions. + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + + // Deviate from the Yoast config to allow existing violations. New occurrences are still disallowed. + "react/jsx-no-bind": "warn", + }, + }, + { + files: [ "*.config.js", "tools/jest/**" ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/packages/analysis-report/package.json b/packages/analysis-report/package.json index bbbae94eb83..941d2bf611e 100644 --- a/packages/analysis-report/package.json +++ b/packages/analysis-report/package.json @@ -36,11 +36,9 @@ "@yoast/babel-preset": "^1.1.1", "@yoast/jest-preset": "^1.0.1", "babel-plugin-styled-components": "^2.0.6", - "eslint": "^8.57.0", - "eslint-config-yoast": "^6.0.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-react": "^7.34.1", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.13.0", "jest": "^27.5.1", "jest-styled-components": "^7.0.8", "react-test-renderer": "^18.2.0" diff --git a/packages/analysis-report/src/AnalysisResult.js b/packages/analysis-report/src/AnalysisResult.js index 63af6fdf71b..567889aeba9 100644 --- a/packages/analysis-report/src/AnalysisResult.js +++ b/packages/analysis-report/src/AnalysisResult.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import React, { useCallback, useEffect, useState } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; diff --git a/packages/analysis-report/tests/__mocks__/@wordpress/i18n.js b/packages/analysis-report/tests/__mocks__/@wordpress/i18n.js index 0794fa12ab4..6935667003a 100644 --- a/packages/analysis-report/tests/__mocks__/@wordpress/i18n.js +++ b/packages/analysis-report/tests/__mocks__/@wordpress/i18n.js @@ -1,4 +1,4 @@ -/* eslint-disable require-jsdoc */ +/* eslint-disable jsdoc/require-jsdoc */ import { sprintf, setLocaleData } from "@wordpress/i18n"; diff --git a/packages/browserslist-config/eslint.config.mjs b/packages/browserslist-config/eslint.config.mjs new file mode 100644 index 00000000000..1a81839d556 --- /dev/null +++ b/packages/browserslist-config/eslint.config.mjs @@ -0,0 +1,36 @@ +import globals from "globals"; +import yoastConfig from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + ...yoastConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.node, + }, + }, + rules: { + // Deviate from the Yoast config to prohibit dangling commas in functions. + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + }, + }, + { + files: [ "*.config.*" ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index a5ac3f5646f..58523f2676d 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -27,6 +27,9 @@ }, "devDependencies": { "browserslist": "^4.7.3", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.13.0", "jest": "^29.7.0" } } diff --git a/packages/components/eslint.config.mjs b/packages/components/eslint.config.mjs new file mode 100644 index 00000000000..e71e4079381 --- /dev/null +++ b/packages/components/eslint.config.mjs @@ -0,0 +1,55 @@ +import globals from "globals"; +import yoastConfig, { reactConfig } from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: [ "build" ] }, + ...yoastConfig, + ...reactConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.browser, + }, + }, + rules: { + // Account for webpack externals and potentially unbuilt packages in the monorepo setup. + "import/no-unresolved": [ + "error", + { + ignore: [ + // Ignore @yoast packages from this workspace, or we have to build the code before linting. + // Because `main` in `package.json` points to the `build/index.js`, which is not present before building. + // As we are dealing with our source, not the actual NPM download, due to the monorepo setup. + "^@yoast/(ui-library|style-guide|components|helpers|search-metadata-previews|social-metadata-forms|replacement-variable-editor|analysis-report|feature-flag|related-keyphrase-suggestions)$", + "yoastseo", + ], + }, + ], + + // Deviate from the Yoast config to prohibit dangling commas in functions. + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + + // Deviate from the Yoast config to allow existing violations. New occurrences are still disallowed. + "react/jsx-no-bind": "warn", + }, + }, + { + files: [ "*.config.*", "jest/**" ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/packages/components/package.json b/packages/components/package.json index fce94dd6529..5f29e9bf6b7 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -21,7 +21,7 @@ "build:css": "postcss src/**/*.css --base src --dir build", "clean": "rm -rf build", "test": "jest", - "lint": "eslint . --max-warnings=0" + "lint": "eslint . --max-warnings=20" }, "dependencies": { "@wordpress/a11y": "^1.1.3", @@ -48,6 +48,9 @@ "babel-plugin-styled-components": "^2.0.6", "browserslist": "^4.7.3", "cssnano": "^6.0.2", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.13.0", "jest": "^27.5.1", "jest-styled-components": "^7.0.3", "postcss": "^8.4.32", diff --git a/packages/components/postcss.config.js b/packages/components/postcss.config.js index d65ca6d44db..f117f86070d 100644 --- a/packages/components/postcss.config.js +++ b/packages/components/postcss.config.js @@ -1,4 +1,3 @@ -/* eslint-disable global-require */ module.exports = { map: process.env.NODE_ENV === "production" ? false : { inline: false, diff --git a/packages/components/src/AIFixesButton.js b/packages/components/src/AIFixesButton.js index b7ec83fd0df..5955585f914 100644 --- a/packages/components/src/AIFixesButton.js +++ b/packages/components/src/AIFixesButton.js @@ -32,8 +32,8 @@ const AIFixesButtonBase = styled( IconButtonBase )` : `0 1px 0 ${ rgba( props.unpressedBoxShadowColor, 0.7 ) }` }; &:hover { background-image: ${ props => props.pressed - ? gradientEffect.pressedStateBackground - : gradientEffect.hoverStateBackground } !important; + ? gradientEffect.pressedStateBackground + : gradientEffect.hoverStateBackground } !important; } `; diff --git a/packages/components/src/Logo.js b/packages/components/src/Logo.js index 8570394f222..bd052556340 100644 --- a/packages/components/src/Logo.js +++ b/packages/components/src/Logo.js @@ -8,7 +8,6 @@ import React from "react"; * @returns {ReactElement} The svg icon. */ const Logo = ( props ) => ( - /* eslint-disable max-len */ @@ -61,7 +60,6 @@ const Logo = ( props ) => ( - /* eslint-enable max-len */ ); export default Logo; diff --git a/packages/components/src/SparklesIcon.js b/packages/components/src/SparklesIcon.js index 9347e78a5fa..0a61ec812b3 100644 --- a/packages/components/src/SparklesIcon.js +++ b/packages/components/src/SparklesIcon.js @@ -13,7 +13,6 @@ export const SparklesIcon = ( { pressed = false, className = "" } ) => { return , diff --git a/packages/components/src/Toggle.js b/packages/components/src/Toggle.js index 3feb1f49f9c..c121c2629da 100644 --- a/packages/components/src/Toggle.js +++ b/packages/components/src/Toggle.js @@ -123,7 +123,6 @@ class Toggle extends React.Component { render() { return ( - { /* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */ } { this.props.labelText && { return interpolateComponents( { mixedString: keywordsResearchLinkTranslation, components: { - // eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-no-target-blank + // eslint-disable-next-line jsx-a11y/anchor-has-content a: , }, } ); @@ -46,14 +46,12 @@ const getKeywordResearchArticleLink = ( url ) => { const getExplanation = keywords => { if ( keywords.length === 0 ) { return __( - // eslint-disable-next-line max-len "Once you add a bit more copy, we'll give you a list of words that occur the most in the content. These give an indication of what your content focuses on.", "wordpress-seo" ); } return __( - // eslint-disable-next-line max-len "The following words occur the most in the content. These give an indication of what your content focuses on. If the words differ a lot from your topic, you might want to rewrite your content accordingly. ", "wordpress-seo" ); diff --git a/packages/components/src/button/Icon.js b/packages/components/src/button/Icon.js index e5cb83c8c74..f9ea800eb04 100644 --- a/packages/components/src/button/Icon.js +++ b/packages/components/src/button/Icon.js @@ -4,7 +4,6 @@ import "./buttons.css"; import { __ } from "@wordpress/i18n"; const closeIcon = ; diff --git a/packages/components/src/help-icon/HelpIcon.js b/packages/components/src/help-icon/HelpIcon.js index 4f3131aee7c..0f3bb681b71 100644 --- a/packages/components/src/help-icon/HelpIcon.js +++ b/packages/components/src/help-icon/HelpIcon.js @@ -41,7 +41,6 @@ const HelpIcon = ( { linkTo, linkText } ) => ( focusable="false" > diff --git a/packages/components/src/image-select/ImageSelectButtons.js b/packages/components/src/image-select/ImageSelectButtons.js index b0f0dc4aca8..11053f5a43a 100644 --- a/packages/components/src/image-select/ImageSelectButtons.js +++ b/packages/components/src/image-select/ImageSelectButtons.js @@ -19,7 +19,7 @@ const ImageSelectButtons = ( props ) => { replaceImageButtonId, removeImageButtonId, isDisabled, - } = props; + } = props; const removeImage = useCallback( ( event ) => { event.target.previousElementSibling.focus(); diff --git a/packages/components/src/select/Select.js b/packages/components/src/select/Select.js index a8bfa1809c0..8a1d820a223 100644 --- a/packages/components/src/select/Select.js +++ b/packages/components/src/select/Select.js @@ -43,7 +43,6 @@ Option.propTypes = { value: PropTypes.string.isRequired, }; -/* eslint-disable jsx-a11y/no-onchange*/ /** * Function to map options to a react-select compatible array. * diff --git a/packages/components/src/toggle/Toggle.js b/packages/components/src/toggle/Toggle.js index f312d05c56a..7ae1d9e144e 100644 --- a/packages/components/src/toggle/Toggle.js +++ b/packages/components/src/toggle/Toggle.js @@ -1,4 +1,3 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ import React from "react"; import PropTypes from "prop-types"; import FieldGroup from "../field-group/FieldGroup"; diff --git a/packages/components/tests/__mocks__/@wordpress/i18n.js b/packages/components/tests/__mocks__/@wordpress/i18n.js index faa8653a52a..ac9438a37e0 100644 --- a/packages/components/tests/__mocks__/@wordpress/i18n.js +++ b/packages/components/tests/__mocks__/@wordpress/i18n.js @@ -1,4 +1,4 @@ -/* eslint-disable require-jsdoc */ +/* eslint-disable jsdoc/require-jsdoc */ import { sprintf, setLocaleData } from "@wordpress/i18n"; diff --git a/packages/components/tests/inputs/DurationInputTest.js b/packages/components/tests/inputs/DurationInputTest.js index a764bd1f9ec..0e9bbb2eb9b 100644 --- a/packages/components/tests/inputs/DurationInputTest.js +++ b/packages/components/tests/inputs/DurationInputTest.js @@ -1,7 +1,7 @@ import React from "react"; import ReactShallowRenderer from "react-test-renderer/shallow"; import DurationInput from "../../src/inputs/DurationInput"; - +const noop = () => {}; describe( "DurationInput", () => { const renderer = new ReactShallowRenderer(); it( "should render with only required props", () => { @@ -9,7 +9,7 @@ describe( "DurationInput", () => { label="Duration" duration={ 3661 } id="very-nice-id" - onChange={ () => console.log( "test" ) } + onChange={ noop } /> ); const result = renderer.getRenderOutput(); @@ -23,7 +23,7 @@ describe( "DurationInput", () => { label="This is my label" id="very-nice-id" duration={ 3661 } - onChange={ () => console.log( "test" ) } + onChange={ noop } /> ); diff --git a/packages/eslint/default.mjs b/packages/eslint/default.mjs new file mode 100644 index 00000000000..a2726ca3cc4 --- /dev/null +++ b/packages/eslint/default.mjs @@ -0,0 +1,262 @@ +import globals from "globals"; +import js from "@eslint/js"; +import reactPlugin from "eslint-plugin-react"; +import jsxA11YPlugin from "eslint-plugin-jsx-a11y"; +import jsdocPlugin from "eslint-plugin-jsdoc"; +import nodePlugin from "eslint-plugin-n"; +import importPlugin from "eslint-plugin-import"; +import stylisticPlugin from "@stylistic/eslint-plugin"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + js.configs.recommended, + importPlugin.flatConfigs.recommended, + { + name: "yoast/recommended", + plugins: { + jsdoc: jsdocPlugin, + stylistic: stylisticPlugin, + }, + // https://eslint.org/docs/rules/ + rules: { + // Native ESLint rules + // - ESLint "Possible problems - These rules relate to possible logic errors in code:" + // (https://eslint.org/docs/latest/rules/#possible-problems). + "no-inner-declarations": [ "error", "functions" ], + "no-self-compare": "error", + "no-use-before-define": "error", + + // - ESLint "Suggestions - These rules suggest alternate ways of doing things:" (https://eslint.org/docs/latest/rules/#suggestions). + "accessor-pairs": "error", + camelcase: "error", + complexity: [ "warn", 10 ], + "consistent-this": "error", + curly: "error", + "dot-notation": "error", + eqeqeq: "error", + "guard-for-in": "error", + "max-depth": "error", + "max-nested-callbacks": "error", + "max-statements": [ "warn", 30 ], + "new-cap": "error", + "no-alert": "error", + "no-array-constructor": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-console": [ "warn", { allow: [ "warn", "error", "trace" ] } ], + "no-div-regex": "error", + "no-else-return": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-implied-eval": "error", + "no-inline-comments": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-loop-func": "error", + "no-multi-str": "error", + "no-negated-condition": "error", + "no-nested-ternary": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-new": "error", + "no-object-constructor": "error", + "no-octal-escape": "error", + "no-proto": "error", + "no-restricted-syntax": [ + "error", + { + selector: "CallExpression[callee.name=/^(__)$/][arguments.length!=2]", + message: "A textdomain needs to be provided for translation calls.", + }, { + selector: "CallExpression[callee.name=/^(_x)$/][arguments.length!=3]", + message: "A textdomain needs to be provided for translation calls.", + }, { + selector: "CallExpression[callee.name=/^(_n)$/][arguments.length!=4]", + message: "A textdomain needs to be provided for translation calls.", + }, { + selector: "CallExpression[callee.name=/^(_nx)$/][arguments.length!=5]", + message: "A textdomain needs to be provided for translation calls.", + }, + ], + "no-return-assign": "error", + "no-script-url": "error", + "no-shadow": [ "error", { builtinGlobals: false, hoist: "all", allow: [] } ], + "no-undef-init": "error", + "no-undefined": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "error", + "no-useless-call": "error", + "no-useless-concat": "error", + "no-void": "error", + "no-warning-comments": [ "error", { terms: [ "todo" ], location: "anywhere" } ], + "prefer-const": "error", + radix: "error", + strict: "error", + + // Plugin: Stylistic rules (https://eslint.style/packages/default). + "stylistic/array-bracket-spacing": [ "error", "always" ], + "stylistic/block-spacing": "error", + "stylistic/brace-style": [ "error", "1tbs" ], + "stylistic/comma-dangle": [ "error", "always-multiline" ], + "stylistic/comma-spacing": "error", + "stylistic/comma-style": "error", + "stylistic/computed-property-spacing": [ "error", "always" ], + "stylistic/eol-last": "error", + "stylistic/func-call-spacing": "error", + "stylistic/indent": [ "error", "tab", { SwitchCase: 1 } ], + "stylistic/jsx-quotes": "error", + "stylistic/key-spacing": "error", + "stylistic/keyword-spacing": "error", + "stylistic/linebreak-style": "error", + "stylistic/max-len": [ + "error", + { + code: 150, + tabWidth: 4, + ignoreStrings: true, + }, + ], + "stylistic/new-parens": "error", + "stylistic/no-extra-semi": "error", + "stylistic/no-mixed-spaces-and-tabs": [ "error", "smart-tabs" ], + "stylistic/no-multiple-empty-lines": "error", + "stylistic/no-trailing-spaces": "error", + "stylistic/no-whitespace-before-property": "error", + "stylistic/object-curly-spacing": [ "error", "always" ], + "stylistic/operator-linebreak": "error", + "stylistic/padded-blocks": [ "error", "never" ], + "stylistic/quote-props": [ "error", "as-needed", { keywords: true } ], + "stylistic/quotes": [ "error", "double", "avoid-escape" ], + "stylistic/semi-spacing": "error", + "stylistic/semi": "error", + "stylistic/space-before-blocks": "error", + "stylistic/space-before-function-paren": [ "error", "never" ], + "stylistic/space-in-parens": [ "error", "always", { exceptions: [ "empty" ] } ], + "stylistic/space-infix-ops": "error", + "stylistic/space-unary-ops": [ "error", { words: false, nonwords: false, overrides: { "!": true } } ], + "stylistic/spaced-comment": [ "error", "always" ], + "stylistic/wrap-iife": "error", + + // Plugin: JSDoc rules (https://github.com/gajus/eslint-plugin-jsdoc) + "jsdoc/require-jsdoc": [ "error", { + require: { + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: false, + FunctionExpression: false, + }, + // The fixer only adds empty blocks, which are easy to forget to fill in. + enableFixer: false, + exemptEmptyFunctions: true, + } ], + "jsdoc/require-returns": "error", + + // Plugin: Import rules (https://github.com/import-js/eslint-plugin-import) + "import/no-extraneous-dependencies": "error", + "import/no-unresolved": "error", + }, + settings: { + jsdoc: { + tagNamePreference: { + "return": "returns", + constant: "const", + }, + }, + }, + }, + { + files: [ + "**/*.{spec,test,tests}.{js,jsx,mjs,cjs,ts,tsx}", + "{spec,test,tests}/**/*.{js,jsx,mjs,cjs,ts,tsx}", + ], + languageOptions: { + globals: { + ...globals.jest, + ...globals.node, + }, + }, + rules: { + // Some tests assert that a constructor throws an error, which is a valid use case. + "no-new": "off", + }, + }, + { + files: [ "jest.config.{js,ts,cjs}" ], + languageOptions: { + globals: { + ...globals.commonjs, + }, + }, + }, +]; + +/** @type {import('eslint').Linter.Config[]} */ +export const reactConfig = [ + reactPlugin.configs.flat.recommended, + jsxA11YPlugin.flatConfigs.recommended, + { + name: "yoast/react", + settings: { + react: { + version: "detect", + }, + }, + rules: { + // Plugin: jsx-a11y rules (https://github.com/jsx-eslint/eslint-plugin-jsx-a11y). + // Deprecated in v6.1.0 in favor of label-has-associated-control but we still want to require only for/id and not nesting. + "jsx-a11y/label-has-for": [ "error", { required: "id" } ], + + // Plugin: React rules (https://github.com/jsx-eslint/eslint-plugin-react) + "react/boolean-prop-naming": "error", + "react/button-has-type": 0, + "react/default-props-match-prop-types": "error", + "react/forbid-foreign-prop-types": "error", + "react/jsx-boolean-value": [ "error", "always" ], + "react/jsx-closing-bracket-location": [ "error", "line-aligned" ], + "react/jsx-curly-spacing": [ "error", { when: "always", children: true } ], + "react/jsx-equals-spacing": "error", + "react/jsx-first-prop-new-line": [ "error", "multiline" ], + "react/jsx-indent-props": [ "error", "tab" ], + "react/jsx-indent": [ "error", "tab" ], + "react/jsx-max-depth": [ "error", { max: 8 } ], + "react/jsx-max-props-per-line": [ "error", { maximum: 6 } ], + "react/jsx-no-bind": "error", + "react/jsx-pascal-case": "error", + "react/jsx-tag-spacing": [ "error", { beforeClosing: "never" } ], + "react/no-access-state-in-setstate": "error", + "react/no-redundant-should-component-update": "error", + "react/no-render-return-value": "error", + "react/no-this-in-sfc": "error", + "react/no-typos": "error", + "react/no-unused-prop-types": "error", + "react/no-unused-state": "error", + "react/prefer-es6-class": "error", + "react/require-default-props": [ "error", { ignoreFunctionalComponents: true } ], + "react/self-closing-comp": "error", + "react/void-dom-elements-no-children": "error", + }, + }, +]; + +/** @type {import('eslint').Linter.Config[]} */ +export const nodeConfig = [ + { + name: "yoast/node", + plugins: { + node: nodePlugin, + }, + rules: { + // Plugin: Node rules (https://github.com/eslint-community/eslint-plugin-n) + "node/callback-return": "error", + "node/global-require": "error", + "node/handle-callback-err": "error", + "node/no-mixed-requires": "error", + "node/no-path-concat": "error", + "node/no-process-exit": "error", + }, + }, +]; diff --git a/packages/eslint/default.yml b/packages/eslint/default.yml deleted file mode 100644 index 7507f0f1d82..00000000000 --- a/packages/eslint/default.yml +++ /dev/null @@ -1,268 +0,0 @@ -extends: - - "eslint:recommended" - - "plugin:react/recommended" - - "plugin:jsx-a11y/recommended" - - "plugin:import/recommended" - -parserOptions: - ecmaVersion: 2017 - ecmaFeatures: - jsx: true - sourceType: "module" - -env: - amd: true - browser: true - es6: true - jquery: true - node: true - -plugins: - - react - - "jsx-a11y" - -# http://eslint.org/docs/rules/ -rules: - # Possible Errors - comma-dangle: [2, "always-multiline"] - no-cond-assign: 2 - - # Warning to allow webpack hot reloading to not fail on no-console. - no-console: [ 1, { allow: ["warn", "error", "trace"] } ] - no-constant-condition: 2 - no-control-regex: 2 - no-debugger: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-empty: 2 - no-empty-character-class: 2 - no-ex-assign: 2 - no-extra-boolean-cast: 2 - no-extra-parens: 0 - no-extra-semi: 2 - no-func-assign: 2 - no-inner-declarations: [2, functions] - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-negated-in-lhs: 2 - no-obj-calls: 2 - no-regex-spaces: 2 - no-sparse-arrays: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - use-isnan: 2 - valid-jsdoc: [ 2, { - "prefer": { - "return": "returns" - } - }] - valid-typeof: 2 - - # Best Practices - accessor-pairs: 2 - block-scoped-var: 0 - complexity: [2, 10] - consistent-return: 0 - curly: 2 - default-case: 0 - dot-location: 0 - dot-notation: 2 - eqeqeq: 2 - guard-for-in: 2 - no-alert: 2 - no-caller: 2 - no-case-declarations: 2 - no-div-regex: 2 - no-else-return: 2 - no-empty-pattern: 2 - no-eq-null: 2 - no-eval: 2 - no-extend-native: 2 - no-extra-bind: 2 - no-fallthrough: 2 - no-floating-decimal: 0 - no-implicit-coercion: 0 - no-implied-eval: 2 - no-invalid-this: 0 - no-iterator: 2 - no-labels: 2 - no-lone-blocks: 2 - no-loop-func: 2 - no-magic-number: 0 - no-multi-spaces: 0 - no-multi-str: 2 - no-native-reassign: 2 - no-new-func: 2 - no-new-wrappers: 2 - no-new: 2 - no-octal-escape: 2 - no-octal: 2 - no-proto: 2 - no-redeclare: 2 - no-return-assign: 2 - no-script-url: 2 - no-self-compare: 2 - no-sequences: 0 - no-throw-literal: 0 - no-unused-expressions: 2 - no-useless-call: 2 - no-useless-concat: 2 - no-useless-escape: 2 - no-void: 2 - no-warning-comments: [ 2, { "terms": [ "todo" ], "location": "anywhere" } ] - no-with: 2 - radix: 2 - vars-on-top: 0 - wrap-iife: 2 - yoda: 0 - - # Strict - strict: 2 - - # Variables - init-declarations: 0 - no-catch-shadow: 2 - no-delete-var: 2 - no-label-var: 2 - no-shadow-restricted-names: 2 - no-shadow: [ 2, { "builtinGlobals": false, "hoist": "all", "allow": [] } ] - no-undef-init: 2 - no-undef: 2 - no-undefined: 2 - no-unused-vars: 2 - no-use-before-define: 2 - prefer-const: 2 - - # Node.js and CommonJS - callback-return: 2 - global-require: 2 - handle-callback-err: 2 - no-mixed-requires: 2 - no-new-require: 0 - no-path-concat: 2 - no-process-exit: 2 - no-restricted-modules: 0 - no-sync: 0 - - # Stylistic Issues - array-bracket-spacing: [2, always] - block-spacing: [2, always] - brace-style: [2, 1tbs] - camelcase: 2 - comma-spacing: 2 - comma-style: 2 - computed-property-spacing: [2, always] - consistent-this: [2, "that"] - eol-last: 2 - func-names: 0 - func-style: 0 - id-length: 0 - id-match: 0 - indent: [2, tab, {"SwitchCase": 1}] - jsx-quotes: [2, "prefer-double"] - key-spacing: [2, {"beforeColon": false, "afterColon": true}] - keyword-spacing: [2, {"before": true, "after": true}] - linebreak-style: [2, unix] - lines-around-comment: 0 - max-depth: 2 - max-len: [2, 150, 4] - max-nested-callbacks: 2 - max-params: 0 - max-statements: [2, 30] - new-cap: 2 - new-parens: 2 - newline-after-var: 0 - newline-per-chained-call: 0 - no-array-constructor: 2 - no-bitwise: 2 - no-continue: 0 - no-inline-comments: 2 - no-lonely-if: 0 - no-mixed-spaces-and-tabs: [2, "smart-tabs"] - no-multiple-empty-lines: 2 - no-negated-condition: 2 - no-nested-ternary: 2 - no-new-object: 2 - no-plusplus: 0 - no-restricted-syntax: [ - 'error', - { - selector: 'CallExpression[callee.name=/^(__)$/][arguments.length!=2]', - message: 'A textdomain needs to be provided for translation calls.', - }, - { - selector: 'CallExpression[callee.name=/^(_x)$/][arguments.length!=3]', - message: 'A textdomain needs to be provided for translation calls.', - }, - { - selector: 'CallExpression[callee.name=/^(_n)$/][arguments.length!=4]', - message: 'A textdomain needs to be provided for translation calls.', - }, - { - selector: 'CallExpression[callee.name=/^(_nx)$/][arguments.length!=5]', - message: 'A textdomain needs to be provided for translation calls.', - } - ] - no-spaced-func: 2 - no-ternary: 0 - no-trailing-spaces: 2 - no-underscore-dangle: 0 - no-unneeded-ternary: 2 - no-whitespace-before-property: 2 - object-curly-spacing: [2, "always"] - one-var: 0 - operator-assignment: 0 - operator-linebreak: 2 - padded-blocks: [2, "never"] - quote-props: [2, "as-needed", {"keywords": true}] - quotes: [2, double, avoid-escape] - require-jsdoc: [2, {"require": {"MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true, "FunctionExpression": true}}] - semi-spacing: 2 - semi: [2, always] - sort-vars: 0 - space-before-blocks: 2 - space-before-function-paren: [2, "never"] - space-in-parens: [2, "always", {"exceptions": ["empty"]}] - space-infix-ops: 2 - space-unary-ops: [2, {"words": false, "nonwords": false, "overrides": { "!": true }}] - spaced-comment: [2, "always"] - wrap-regex: 0 - - # Accessibility rules. https://github.com/evcohen/eslint-plugin-jsx-a11y - # Deprecated in v6.1.0 in favor of label-has-associated-control but we still want to require only for/id and not nesting. - jsx-a11y/label-has-for: [2, { "required": "id" } ] - - # React plugins settings (https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules) - react/jsx-tag-spacing: [2, {"beforeClosing": "never"}] - react/jsx-pascal-case: 2 - react/jsx-no-bind: 2 - react/jsx-max-props-per-line: [2, {"maximum": 6}] # A high maximum to make sure it is always a smell when it occurs. - react/jsx-max-depth: [2, {"max": 8}] # A high maximum to make sure it is always a smell when it occurs. - react/jsx-indent-props: [2, "tab"] - react/jsx-indent: [2, "tab"] - react/jsx-first-prop-new-line: [2, "multiline"] - react/jsx-equals-spacing: 2 - react/jsx-curly-spacing: [2, {"when": "always", "children": true}] - react/jsx-closing-bracket-location: [2, "line-aligned"] - react/jsx-boolean-value: [2, "always"] - - react/void-dom-elements-no-children: 2 - react/self-closing-comp: 2 - react/require-default-props: [2, { "ignoreFunctionalComponents": true }] - react/prefer-es6-class: 2 - react/no-unused-state: 2 - react/no-unused-prop-types: 2 - react/no-this-in-sfc: 2 - react/no-typos: 2 - react/no-render-return-value: 2 - react/no-redundant-should-component-update: 2 - react/no-access-state-in-setstate: 2 - react/forbid-foreign-prop-types: 2 - react/default-props-match-prop-types: 2 - react/button-has-type: 0 # See https://github.com/yannickcr/eslint-plugin-react/issues/1555 - react/boolean-prop-naming: 2 - - # Import rules (https://github.com/import-js/eslint-plugin-import) - import/no-extraneous-dependencies: 2 - import/no-unresolved: 2 diff --git a/packages/eslint/eslint.config.mjs b/packages/eslint/eslint.config.mjs new file mode 100644 index 00000000000..3ecede8f7b8 --- /dev/null +++ b/packages/eslint/eslint.config.mjs @@ -0,0 +1,4 @@ +import yoastConfig from "./default.mjs"; +export default [ + ...yoastConfig, +]; diff --git a/packages/eslint/index.js b/packages/eslint/index.js deleted file mode 100644 index c11d5a4dc66..00000000000 --- a/packages/eslint/index.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @fileoverview Converts YAML file into JSON. - * @author Nicholas C. Zakas - */ - - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -var fs = require( "fs" ), - path = require( "path" ), - yaml = require( "js-yaml" ); - -// ------------------------------------------------------------------------------ -// Bootstrapping -// ------------------------------------------------------------------------------ - -var filePath = path.resolve( __dirname, "./default.yml" ), - config; - -try { - config = yaml.safeLoad( fs.readFileSync( filePath, "utf8" ) ) || {}; -} catch ( e ) { - console.error( "Error reading YAML file: " + filePath ); - e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; - throw e; -} - - -// ------------------------------------------------------------------------------ -// Public Interface -// ------------------------------------------------------------------------------ - -module.exports = config; diff --git a/packages/eslint/package.json b/packages/eslint/package.json index ccb8cbd12cf..081c73ac2da 100644 --- a/packages/eslint/package.json +++ b/packages/eslint/package.json @@ -1,34 +1,44 @@ { "name": "eslint-config-yoast", - "version": "6.0.0", + "version": "7.0.0", "private": false, "description": "ESLint configuration for Yoast projects", "keywords": [ "eslint", "eslintconfig" ], - "main": "index.js", + "main": "default.mjs", "files": [ - "index.js", - "default.yml" + "default.mjs" ], + "scripts": { + "lint": "eslint . --max-warnings=0" + }, "repository": { "type": "git", - "url": "https://github.com/Yoast/javascript.git", + "url": "https://github.com/Yoast/wordpress-seo.git", "directory": "packages/eslint" }, "author": "Team Yoast ", "license": "GPL-3.0", "bugs": { - "url": "https://github.com/Yoast/eslint/issues" + "url": "https://github.com/Yoast/wordpress-seo/issues" }, - "homepage": "https://github.com/Yoast/eslint#readme", + "homepage": "https://github.com/Yoast/wordpress-seo/tree/main/packages/eslint", "dependencies": { - "js-yaml": "^3.6.0" + "@eslint/js": "^9.14.0", + "@stylistic/eslint-plugin": "^2.11.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsdoc": "^50.5.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-n": "^17.13.1", + "eslint-plugin-react": "^7.35.0", + "globals": "^15.12.0" }, "peerDependencies": { - "eslint-plugin-import": ">=2.27.5", - "eslint-plugin-jsx-a11y": ">=6.1.1", - "eslint-plugin-react": ">=7.11.1" + "eslint": "^8.57 || >= 9" + }, + "devDependencies": { + "eslint": "^9.16.0" } } diff --git a/packages/feature-flag/eslint.config.mjs b/packages/feature-flag/eslint.config.mjs new file mode 100644 index 00000000000..4c25ad04ab2 --- /dev/null +++ b/packages/feature-flag/eslint.config.mjs @@ -0,0 +1,37 @@ +import globals from "globals"; +import yoastConfig from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: [ "build" ] }, + ...yoastConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.browser, + }, + }, + rules: { + // Deviate from the Yoast config to prohibit dangling commas in functions. + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + }, + }, + { + files: [ "*.config.*" ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/packages/feature-flag/jest.config.js b/packages/feature-flag/jest.config.js index 4e2ec903c50..1de1f0143ed 100644 --- a/packages/feature-flag/jest.config.js +++ b/packages/feature-flag/jest.config.js @@ -15,7 +15,7 @@ const config = { "clover", "text-summary", ], - "testEnvironment": "jsdom", + testEnvironment: "jsdom", }; module.exports = config; diff --git a/packages/feature-flag/package.json b/packages/feature-flag/package.json index 28ebd4f7541..ca78998f7d1 100644 --- a/packages/feature-flag/package.json +++ b/packages/feature-flag/package.json @@ -18,7 +18,7 @@ "build:js": "babel src --out-dir build", "clean": "rm -rf build", "test": "jest", - "lint": "eslint ./src ./tests --max-warnings=0" + "lint": "eslint . --max-warnings=0" }, "devDependencies": { "@babel/cli": "^7.17.10", @@ -26,7 +26,10 @@ "@babel/plugin-transform-react-jsx": "^7.17.3", "@babel/preset-env": "^7.16.11", "@yoast/browserslist-config": "^1.2.3", - "babel-plugin-styled-components": "^2.0.6" + "babel-plugin-styled-components": "^2.0.6", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.13.0" }, "publishConfig": { "access": "public" diff --git a/packages/helpers/eslint.config.mjs b/packages/helpers/eslint.config.mjs new file mode 100644 index 00000000000..b5d9f81d493 --- /dev/null +++ b/packages/helpers/eslint.config.mjs @@ -0,0 +1,30 @@ +import globals from "globals"; +import yoastConfig, { reactConfig } from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: [ "build" ] }, + ...yoastConfig, + ...reactConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.browser, + ...globals.jquery, + }, + }, + rules: { + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + }, + }, +]; diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 0e349084362..705b885b216 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -40,6 +40,9 @@ "@babel/preset-env": "^7.16.11", "@yoast/browserslist-config": "^1.2.3", "babel-plugin-styled-components": "^2.0.6", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.12.0", "jest-styled-components": "^7.0.3", "react-test-renderer": "^18.2.0" }, diff --git a/packages/helpers/src/ajaxHelper.js b/packages/helpers/src/ajaxHelper.js index 7d2768ca5a9..9812920ee3b 100644 --- a/packages/helpers/src/ajaxHelper.js +++ b/packages/helpers/src/ajaxHelper.js @@ -71,6 +71,11 @@ const sendJQueryRequest = ( url, requestParams ) => { const parseHeaders = ( type, config ) => { if ( type === "jquery" ) { Object.assign( config, { + /** + * Adds configured headers to the request. + * + * @param {XMLHttpRequest} xhr The XMLHttpRequest object. + */ beforeSend: ( xhr ) => { jQuery.each( config.headers, ( headerName, headerValue ) => { xhr.setRequestHeader( headerName, headerValue ); @@ -96,7 +101,7 @@ const parseHeaders = ( type, config ) => { */ const overwriteObjectWithDefaultValues = ( target, defaults ) => { for ( const key in defaults ) { - if ( defaults.hasOwnProperty( key ) ) { + if ( Object.hasOwn( defaults, key ) ) { if ( typeof target[ key ] === "undefined" || target[ key ] === "" ) { target[ key ] = defaults[ key ]; } diff --git a/packages/helpers/tests/__mocks__/@wordpress/i18n.js b/packages/helpers/tests/__mocks__/@wordpress/i18n.js index faa8653a52a..ac9438a37e0 100644 --- a/packages/helpers/tests/__mocks__/@wordpress/i18n.js +++ b/packages/helpers/tests/__mocks__/@wordpress/i18n.js @@ -1,4 +1,4 @@ -/* eslint-disable require-jsdoc */ +/* eslint-disable jsdoc/require-jsdoc */ import { sprintf, setLocaleData } from "@wordpress/i18n"; diff --git a/packages/helpers/tests/strings/stripTagsFromHtmlStringTest.js b/packages/helpers/tests/strings/stripTagsFromHtmlStringTest.js index 80a9155745f..a50cc6a062a 100644 --- a/packages/helpers/tests/strings/stripTagsFromHtmlStringTest.js +++ b/packages/helpers/tests/strings/stripTagsFromHtmlStringTest.js @@ -24,7 +24,6 @@ describe( "stripTagsFromHtmlString", () => { ], [ "removes all the tags in a full HTML document", - // eslint-disable-next-line max-len "title

header

", "headeronetwothree", ], @@ -41,7 +40,6 @@ describe( "stripTagsFromHtmlString", () => { ], [ "removes all but the allowed tags", - // eslint-disable-next-line max-len "", "onetwothree", [ "a" ], @@ -53,7 +51,6 @@ describe( "stripTagsFromHtmlString", () => { ], [ "removes all but the allowed tags, keeping allowed attributes", - // eslint-disable-next-line max-len "", "onetwothree", [ "a" ], diff --git a/packages/js/.eslintrc.js b/packages/js/.eslintrc.js deleted file mode 100644 index ed4fe8ecd26..00000000000 --- a/packages/js/.eslintrc.js +++ /dev/null @@ -1,114 +0,0 @@ -module.exports = { - root: true, - "extends": [ - "yoast", - ], - settings: { - react: { - version: "detect", - }, - }, - parser: "@babel/eslint-parser", - parserOptions: { - ecmaVersion: 2019, - sourceType: "module", - }, - plugins: [ - "@babel", - ], - rules: { - "no-prototype-builtins": 0, - "comma-dangle": [ - "error", - { - arrays: "always-multiline", - objects: "always-multiline", - imports: "always-multiline", - exports: "always-multiline", - functions: "never", - }, - ], - "import/no-unresolved": [ - "error", - { - ignore: [ - // This is our internal externals used within `packages/js`, not actually a package. - "^@yoast/externals/(components|contexts|redux)$", - // Ignore UI library and schema-blocks, or we have to build the code before linting. - // Because `main` in `package.json` points to the `build/index.js` (in the UI library), which is not present before building. - // As we are dealing with our source, not the actual NPM download, due to the monorepo setup. - "^@yoast/(ui-library|schema-blocks|style-guide|components|helpers|search-metadata-previews|social-metadata-forms|replacement-variable-editor|analysis-report|feature-flag|related-keyphrase-suggestions)$", - "^@wordpress/(annotations|api|edit-post|sanitize)$", - "^jquery$", - "yoastseo", - ], - }, - ], - }, - overrides: [ - { - files: [ "**/*.js" ], - rules: { - // Custom rules: only for temporary exceptions that should be removed over time - camelcase: 1, - complexity: [ 1, 6 ], - "brace-style": 1, - "max-statements": 1, - "max-len": [ - "error", - { - code: 150, - ignoreStrings: true, - ignoreTemplateLiterals: true, - ignorePattern: "[\t]*\n", - }, - ], - "no-shadow": 1, - "require-jsdoc": 1, - "react/jsx-no-bind": 1, - "react/jsx-no-target-blank": 1, - "react/no-access-state-in-setstate": 1, - "react/no-deprecated": 1, - "react/no-unused-prop-types": 1, - "react/prop-types": 1, - "react/require-default-props": 1, - "no-restricted-imports": [ - "error", - { - name: "react", - message: "Please use @wordpress/element instead. No need to import just for JSX.", - - }, - { - name: "react-dom", - message: "Please use @wordpress/element instead.", - }, - - ], - - // Disabled rules - // In the editor, we're using the pragma `wp.element.createElement` - "react/react-in-jsx-scope": 0, - }, - - }, - { - files: [ "tests/**/*.js" ], - env: { - jest: true, - }, - rules: { - "no-restricted-imports": 0, - "no-undefined": 0, - "react/display-name": 0, - }, - }, - // Ignore Proptypes in the dashboard. - { - files: [ "src/dashboard/**/*.js" ], - rules: { - "react/prop-types": 0, - }, - }, - ], -}; diff --git a/packages/js/eslint.config.mjs b/packages/js/eslint.config.mjs new file mode 100644 index 00000000000..8ccb8b61c53 --- /dev/null +++ b/packages/js/eslint.config.mjs @@ -0,0 +1,120 @@ +import globals from "globals"; +import yoastConfig, { reactConfig } from "eslint-config-yoast"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + ...yoastConfig, + ...reactConfig, + { + languageOptions: { + ecmaVersion: "latest", + globals: { + ...globals.browser, + ...globals.jquery, + // Webpack maps `global` to the window. + global: false, + }, + }, + rules: { + // Account for webpack externals and potentially unbuilt packages in the monorepo setup. + "import/no-unresolved": [ + "error", + { + ignore: [ + // This is our internal externals used within `packages/js`, not actually a package. + "^@yoast/externals/(components|contexts|redux)$", + // Externals provided by @wordpress/dependency-extraction-webpack-plugin + "^@wordpress/(annotations|api|edit-post|sanitize)$", + "^jquery$", + // Ignore @yoast packages from this workspace, or we have to build the code before linting. + // Because `main` in `package.json` points to the `build/index.js`, which is not present before building. + // As we are dealing with our source, not the actual NPM download, due to the monorepo setup. + "^@yoast/(ui-library|style-guide|components|helpers|search-metadata-previews|social-metadata-forms|replacement-variable-editor|analysis-report|feature-flag|related-keyphrase-suggestions)$", + "yoastseo", + ], + }, + ], + "no-restricted-imports": [ + "error", + { + name: "react", + message: "Please use @wordpress/element instead. No need to import just for JSX.", + + }, + { + name: "react-dom", + message: "Please use @wordpress/element instead.", + }, + ], + // Disabled rules + // In the editor, we're using the pragma `wp.element.createElement` + "react/react-in-jsx-scope": "off", + + // Deviate from the Yoast config to prohibit dangling commas in functions. + "stylistic/comma-dangle": [ + "error", + { + functions: "never", + arrays: "always-multiline", + objects: "always-multiline", + imports: "always-multiline", + exports: "always-multiline", + }, + ], + + // Deviate from the Yoast config to allow for not using the error that is caught. + "no-unused-vars": [ "error", { caughtErrors: "none" } ], + + // Deviate from the Yoast config to allow longer template literals. + "stylistic/max-len": [ + "error", + { + code: 150, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignorePattern: "[\t]*\n", + }, + ], + + // Deviate from the Yoast config to allow existing violations. New occurrences are still disallowed. + complexity: [ "warn", 6 ], + "no-shadow": "warn", + "jsdoc/require-jsdoc": "warn", + "react/jsx-no-bind": "warn", + "react/no-access-state-in-setstate": "warn", + "react/no-unused-prop-types": "warn", + "react/prop-types": "warn", + "react/require-default-props": "warn", + "no-prototype-builtins": "warn", + }, + }, + { + files: [ "src/externals/**" ], + languageOptions: { + globals: { + ...globals.commonjs, + }, + }, + }, + { + files: [ "tests/**" ], + rules: { + "no-undefined": "off", + }, + }, + // Ignore Proptypes in the dashboard. + { + files: [ "src/dashboard/**" ], + rules: { + "react/prop-types": "off", + }, + }, + { + files: [ "*.config.*" ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/packages/js/package.json b/packages/js/package.json index 267309cb351..5f6d121ce15 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "cd ../.. && wp-scripts build --config config/webpack/webpack.config.js", "test": "jest", - "lint": "eslint src tests --max-warnings=59" + "lint": "eslint . --max-warnings=64" }, "dependencies": { "@draft-js-plugins/mention": "^5.0.0", @@ -73,8 +73,6 @@ }, "devDependencies": { "@babel/core": "^7.17.9", - "@babel/eslint-parser": "^7.17.0", - "@babel/eslint-plugin": "^7.17.7", "@jest/globals": "^27.5.1", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", @@ -85,11 +83,9 @@ "@yoast/jest-preset": "^1.0.0", "babel-plugin-styled-components": "^2.0.6", "case-sensitive-paths-webpack-plugin": "^2.1.2", - "eslint": "^8.57.0", - "eslint-config-yoast": "^6.0.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-react": "^7.34.1", + "eslint": "^9.16.0", + "eslint-config-yoast": "^7.0.0", + "globals": "^15.13.0", "jest": "^27.5.1", "jest-styled-components": "^7.0.3", "raf": "^3.4.1", diff --git a/packages/js/src/academy/app.js b/packages/js/src/academy/app.js index 6cf0be445a0..33176ea7cb1 100644 --- a/packages/js/src/academy/app.js +++ b/packages/js/src/academy/app.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { ExternalLinkIcon, LockOpenIcon } from "@heroicons/react/outline"; import { ArrowSmRightIcon } from "@heroicons/react/solid"; import { useMemo } from "@wordpress/element"; diff --git a/packages/js/src/ai-assessment-fixes/components/modal-content.js b/packages/js/src/ai-assessment-fixes/components/modal-content.js index 3d8cc872936..b3b31088116 100644 --- a/packages/js/src/ai-assessment-fixes/components/modal-content.js +++ b/packages/js/src/ai-assessment-fixes/components/modal-content.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { useDispatch, useSelect } from "@wordpress/data"; import { useMemo } from "@wordpress/element"; import { AiFixAssessmentsUpsell } from "../../shared-admin/components"; diff --git a/packages/js/src/analysis-worker.js b/packages/js/src/analysis-worker.js index e7f01942714..ec919058a96 100644 --- a/packages/js/src/analysis-worker.js +++ b/packages/js/src/analysis-worker.js @@ -42,7 +42,7 @@ function loadDependencies( dependencies ) { if ( dependency === "lodash" ) { // eslint-disable-next-line no-undef - self.lodash = _.noConflict(); + self.lodash = _.noConflict(); } } } diff --git a/packages/js/src/block-editor.js b/packages/js/src/block-editor.js index 90426be4f27..fdf31bda9e8 100644 --- a/packages/js/src/block-editor.js +++ b/packages/js/src/block-editor.js @@ -1,8 +1,6 @@ -/* eslint-disable no-unused-vars */ import initBlockEditorIntegration from "./initializers/block-editor-integration"; import BlockEditorData from "./analysis/blockEditorData"; window.yoast = window.yoast || {}; window.yoast.initEditorIntegration = initBlockEditorIntegration; window.yoast.EditorData = BlockEditorData; -/* eslint-enable no-unused-vars */ diff --git a/packages/js/src/bulk-editor.js b/packages/js/src/bulk-editor.js index 52b6ec76de4..881a6570130 100644 --- a/packages/js/src/bulk-editor.js +++ b/packages/js/src/bulk-editor.js @@ -70,7 +70,6 @@ import jQuery from "jquery"; var data = { action: saveAllMethod, - // eslint-disable-next-line _ajax_nonce: wpseoBulkEditorNonce, }; @@ -170,7 +169,7 @@ import jQuery from "jquery"; return instance; }; - // eslint-disable-next-line + window.bulk_editor = bulkEditor; window.bulkEditor = bulkEditor; diff --git a/packages/js/src/classic-editor.js b/packages/js/src/classic-editor.js index 43d6fcf5619..7915ea68d4b 100644 --- a/packages/js/src/classic-editor.js +++ b/packages/js/src/classic-editor.js @@ -1,8 +1,6 @@ -/* eslint-disable no-unused-vars */ import initClassicEditorIntegration from "./initializers/classic-editor-integration"; import ClassicEditorData from "./analysis/classicEditorData"; window.yoast = window.yoast || {}; window.yoast.initEditorIntegration = initClassicEditorIntegration; window.yoast.EditorData = ClassicEditorData; -/* eslint-enable no-unused-vars */ diff --git a/packages/js/src/components/AdvancedSettings.js b/packages/js/src/components/AdvancedSettings.js index 89ecfd4547c..4bd1da9384c 100644 --- a/packages/js/src/components/AdvancedSettings.js +++ b/packages/js/src/components/AdvancedSettings.js @@ -73,7 +73,6 @@ const MetaRobotsNoIndex = ( { noIndex, onNoIndexChange, editorContext, isPrivate isPrivateBlog && { __( - // eslint-disable-next-line max-len "Even though you can set the meta robots setting here, the entire site is set to noindex in the sitewide privacy settings, so these settings won't have an effect.", "wordpress-seo" ) } diff --git a/packages/js/src/components/IntlProvider.js b/packages/js/src/components/IntlProvider.js index 86c10dabc70..34b700c54c8 100644 --- a/packages/js/src/components/IntlProvider.js +++ b/packages/js/src/components/IntlProvider.js @@ -17,8 +17,10 @@ class IntlProvider extends Component { if ( typeof window.Intl === "undefined" ) { return (
- { /* eslint-disable-next-line max-len */ } -

Yoast SEO detected that you are using a browser that does not support all the features we require. Please try using a different browser.

+

+ Yoast SEO detected that you are using a browser that does not support + all the features we require. Please try using a different browser. +

); } diff --git a/packages/js/src/components/PluginIcon.js b/packages/js/src/components/PluginIcon.js index 60f1d85ff49..1a61413f682 100644 --- a/packages/js/src/components/PluginIcon.js +++ b/packages/js/src/components/PluginIcon.js @@ -24,7 +24,6 @@ const PluginIconSVG = styled.svg` * @returns {wp.Element} The component. */ const PluginIcon = function( props ) { - /* eslint-disable max-len */ return ; - /* eslint-enable max-len */ }; PluginIcon.propTypes = { diff --git a/packages/js/src/components/SidebarButton.js b/packages/js/src/components/SidebarButton.js index ec5bc0415d0..20589bf1bc1 100644 --- a/packages/js/src/components/SidebarButton.js +++ b/packages/js/src/components/SidebarButton.js @@ -22,10 +22,10 @@ const SidebarButton = ( props ) => { style={ { fill: `${ props.prefixIcon && props.prefixIcon.color || "" }` } } > { - + /> } ) } diff --git a/packages/js/src/components/WincherPerformanceReport.js b/packages/js/src/components/WincherPerformanceReport.js index a5e1e5b7afa..21a787d8631 100644 --- a/packages/js/src/components/WincherPerformanceReport.js +++ b/packages/js/src/components/WincherPerformanceReport.js @@ -192,7 +192,6 @@ const WincherNetworkErrorAlert = () => { { sprintf( __( - // eslint-disable-next-line max-len "Network Error: Unable to connect to the server. Please check your internet connection and try again later.", "wordpress-seo" ) @@ -219,7 +218,6 @@ const WincherConnectSuccessAlert = ( props ) => { sprintf( /* translators: %1$s and %2$s: Expands to "Wincher". */ __( - // eslint-disable-next-line max-len "You have successfully connected with %1$s. Your %2$s account does not contain any keyphrases for this website yet. You can track keyphrases by using the \"Track SEO Performance\" button in the post editor.", "wordpress-seo" ), @@ -237,7 +235,6 @@ const WincherConnectSuccessAlert = ( props ) => { sprintf( /* translators: %s: Expands to "Wincher". */ __( - // eslint-disable-next-line max-len "You have successfully connected with %s.", "wordpress-seo" ), @@ -354,15 +351,7 @@ const TableExplanation = ( { isLoggedIn } ) => { ) } , - wincherLink: - { - sprintf( - /* translators: %s : Expands to "Wincher". */ - __( "%s", "wordpress-seo" ), - "Wincher" - ) - } - , + wincherLink: Wincher, }, } ) } diff --git a/packages/js/src/components/WincherPostPublish.js b/packages/js/src/components/WincherPostPublish.js index a7463aea3ad..55b0888f629 100644 --- a/packages/js/src/components/WincherPostPublish.js +++ b/packages/js/src/components/WincherPostPublish.js @@ -39,7 +39,6 @@ export default function WincherPostPublish( props ) { { hasTrackedKeyphrases &&

{ __( - // eslint-disable-next-line max-len "Tracking has already been enabled for one or more keyphrases of this page. Clicking the button below will enable tracking for all of its keyphrases.", "wordpress-seo" ) } diff --git a/packages/js/src/components/WincherTableRow.js b/packages/js/src/components/WincherTableRow.js index a8b6001468f..45723316ef0 100644 --- a/packages/js/src/components/WincherTableRow.js +++ b/packages/js/src/components/WincherTableRow.js @@ -283,7 +283,7 @@ export function getPositionalDataByState( props ) { { formatLastUpdated( rowData.updated_at ) } - + ); } diff --git a/packages/js/src/components/contentAnalysis/InclusiveLanguageAnalysis.js b/packages/js/src/components/contentAnalysis/InclusiveLanguageAnalysis.js index f1ce7c612b5..64cb2bd2aa1 100644 --- a/packages/js/src/components/contentAnalysis/InclusiveLanguageAnalysis.js +++ b/packages/js/src/components/contentAnalysis/InclusiveLanguageAnalysis.js @@ -120,7 +120,6 @@ const InclusiveLanguageAnalysis = ( props ) => { */ function renderMultilingualPluginDetectedNotice() { const notice = __( - // eslint-disable-next-line max-len "We noticed that you are using a multilingual plugin. Please be aware that this analysis feedback is intended only for texts written in English.", "wordpress-seo" ); @@ -233,7 +232,6 @@ const InclusiveLanguageAnalysis = ( props ) => { InclusiveLanguageAnalysis.propTypes = { results: PropTypes.array, // `marksButtonStatus` is used, but not recognized by ESLint. - // eslint-disable-next-line react/no-unused-prop-types marksButtonStatus: PropTypes.oneOf( [ "enabled", "disabled", "hidden" ] ).isRequired, overallScore: PropTypes.number, shouldUpsellHighlighting: PropTypes.bool, diff --git a/packages/js/src/components/contentAnalysis/mapResults.js b/packages/js/src/components/contentAnalysis/mapResults.js index a9ef408c0e9..82b2a940dc4 100644 --- a/packages/js/src/components/contentAnalysis/mapResults.js +++ b/packages/js/src/components/contentAnalysis/mapResults.js @@ -94,7 +94,7 @@ function processResult( mappedResult, mappedResults ) { * * @returns {Object} The icon and color for the score. */ -export function getIconForScore( score ) { // eslint-disable-line complexity +export function getIconForScore( score ) { switch ( score ) { case "loading": return { icon: "loading-spinner", color: colors.$color_green_medium_light }; diff --git a/packages/js/src/components/modals/WincherAutoTrackingEnabledAlert.js b/packages/js/src/components/modals/WincherAutoTrackingEnabledAlert.js index 4171c7c61f4..9c89069bfa5 100644 --- a/packages/js/src/components/modals/WincherAutoTrackingEnabledAlert.js +++ b/packages/js/src/components/modals/WincherAutoTrackingEnabledAlert.js @@ -16,7 +16,6 @@ const WincherAutoTrackingEnabledAlert = () => { sprintf( /* translators: %s: Expands to "Wincher". */ __( - // eslint-disable-next-line max-len "Automatic tracking of keyphrases is enabled. Your keyphrase(s) will automatically be tracked by %s when you publish your post.", "wordpress-seo" ), diff --git a/packages/js/src/components/modals/WincherCurrentlyTrackingAlert.js b/packages/js/src/components/modals/WincherCurrentlyTrackingAlert.js index 9b7c1638976..538ccfaf524 100644 --- a/packages/js/src/components/modals/WincherCurrentlyTrackingAlert.js +++ b/packages/js/src/components/modals/WincherCurrentlyTrackingAlert.js @@ -16,7 +16,6 @@ const WincherCurrentlyTrackingAlert = () => { sprintf( /* translators: %s: Expands to "Wincher". */ __( - // eslint-disable-next-line max-len "%s is currently tracking the ranking position(s) of your page. This may take a few minutes. Please wait or check back later.", "wordpress-seo" ), diff --git a/packages/js/src/components/modals/WincherLimitReached.js b/packages/js/src/components/modals/WincherLimitReached.js index 604c24dc62f..889aa77ed14 100644 --- a/packages/js/src/components/modals/WincherLimitReached.js +++ b/packages/js/src/components/modals/WincherLimitReached.js @@ -22,7 +22,6 @@ const WincherLimitReached = ( props ) => { const message = sprintf( /* translators: %d expands to the amount of allowed keyphrases on a free account, %s expands to a link to Wincher plans. */ __( - // eslint-disable-next-line max-len "You've reached the maximum amount of %d keyphrases you can add to your Wincher account. If you wish to add more keyphrases, please %s.", "wordpress-seo" ), diff --git a/packages/js/src/components/modals/WincherNoPermalinkAlert.js b/packages/js/src/components/modals/WincherNoPermalinkAlert.js index 8cf6a9dea89..952eff5e106 100644 --- a/packages/js/src/components/modals/WincherNoPermalinkAlert.js +++ b/packages/js/src/components/modals/WincherNoPermalinkAlert.js @@ -13,7 +13,6 @@ const WincherNoPermalinkAlert = () => { return ( { __( - // eslint-disable-next-line max-len "Before you can track your SEO performance make sure to set either the post’s title and save it as a draft or manually set the post’s slug.", "wordpress-seo" ) } diff --git a/packages/js/src/components/modals/WincherNoTrackedKeyphrasesAlert.js b/packages/js/src/components/modals/WincherNoTrackedKeyphrasesAlert.js index fcbdd1844c5..911b1ad8ab2 100644 --- a/packages/js/src/components/modals/WincherNoTrackedKeyphrasesAlert.js +++ b/packages/js/src/components/modals/WincherNoTrackedKeyphrasesAlert.js @@ -19,7 +19,6 @@ const WincherNoTrackedKeyphrasesAlert = ( props ) => { sprintf( /* translators: %s: Expands to "Wincher". */ __( - // eslint-disable-next-line max-len "Your %s account does not contain any keyphrases for this website yet. You can track keyphrases by using the \"Track SEO Performance\" button in the post editor.", "wordpress-seo" ), diff --git a/packages/js/src/containers/PluginIcon.js b/packages/js/src/containers/PluginIcon.js index 8117191b32c..706b7914f1b 100644 --- a/packages/js/src/containers/PluginIcon.js +++ b/packages/js/src/containers/PluginIcon.js @@ -11,7 +11,6 @@ export default compose( [ const readabilityScoreIndicator = getIndicatorForScore( data.getReadabilityResults().overallScore ); const { isKeywordAnalysisActive, isContentAnalysisActive } = data.getPreferences(); - /* eslint-disable-next-line no-unused-vars */ let readabilityScoreColor; switch ( readabilityScoreIndicator.className ) { case "good": @@ -26,7 +25,6 @@ export default compose( [ break; } - /* eslint-disable-next-line no-unused-vars */ let seoScoreColor; switch ( seoScoreIndicator.className ) { case "good": diff --git a/packages/js/src/containers/SEMrushRelatedKeyphrases.js b/packages/js/src/containers/SEMrushRelatedKeyphrases.js index 843e2f5218c..9bec2e5c1cf 100644 --- a/packages/js/src/containers/SEMrushRelatedKeyphrases.js +++ b/packages/js/src/containers/SEMrushRelatedKeyphrases.js @@ -42,7 +42,7 @@ export default compose( [ const { setSEMrushChangeCountry, setSEMrushNewRequest, - } = dispatch( "yoast-seo/editor" ); + } = dispatch( "yoast-seo/editor" ); return { setCountry: ( countryCode ) => { setSEMrushChangeCountry( countryCode ); diff --git a/packages/js/src/dashboard/scores/components/content-type-filter.js b/packages/js/src/dashboard/scores/components/content-type-filter.js index 5e03d00a39c..f21a5e8f82f 100644 --- a/packages/js/src/dashboard/scores/components/content-type-filter.js +++ b/packages/js/src/dashboard/scores/components/content-type-filter.js @@ -53,7 +53,7 @@ export const ContentTypeFilter = ( { idSuffix, contentTypes, selected, onChange onQueryChange={ handleQueryChange } > { filtered.map( ( { name, label } ) => { - const decodedLabel = decodeString( label ); + const decodedLabel = decodeString( label ); return { decodedLabel } ; diff --git a/packages/js/src/decorator/helpers/getAnnotationsHelpers.js b/packages/js/src/decorator/helpers/getAnnotationsHelpers.js index 65701117fdc..64dad6fcaf4 100644 --- a/packages/js/src/decorator/helpers/getAnnotationsHelpers.js +++ b/packages/js/src/decorator/helpers/getAnnotationsHelpers.js @@ -140,9 +140,9 @@ const getAnnotationsFromBlockAttributes = ( annotatableAttributesFromBlock, mark const secondSectionHtml = annotatableAttribute[ `json${ secondSectionKeyName }` ]; const { marksForFirstSection, marksForSecondSection } = getMarksForYoastBlock( marks, annotatableAttribute ); - // eslint-disable-next-line max-len + // eslint-disable-next-line stylistic/max-len const firstSectionAnnotations = createAnnotations( firstSectionHtml, firstSectionIdentifier, attributeWithAnnotationSupport, block, marksForFirstSection ); - // eslint-disable-next-line max-len + // eslint-disable-next-line stylistic/max-len const secondSectionAnnotations = createAnnotations( secondSectionHtml, secondSectionIdentifier, attributeWithAnnotationSupport, block, marksForSecondSection ); return firstSectionAnnotations.concat( secondSectionAnnotations ); diff --git a/packages/js/src/elementor/containers/ElementorFill.js b/packages/js/src/elementor/containers/ElementorFill.js index b4ad80b9605..73f5cf3ec72 100644 --- a/packages/js/src/elementor/containers/ElementorFill.js +++ b/packages/js/src/elementor/containers/ElementorFill.js @@ -2,7 +2,6 @@ import { compose } from "@wordpress/compose"; import { withDispatch, withSelect } from "@wordpress/data"; import ElementorFill from "../components/fills/ElementorFill"; -/* eslint-disable complexity */ export default compose( [ withSelect( select => { const { @@ -23,4 +22,3 @@ export default compose( [ }; } ), ] )( ElementorFill ); -/* eslint-enable complexity */ diff --git a/packages/js/src/elementor/helpers/update-save-as-draft-warning.js b/packages/js/src/elementor/helpers/update-save-as-draft-warning.js index cf67340f77d..4c6f6ee0dc9 100644 --- a/packages/js/src/elementor/helpers/update-save-as-draft-warning.js +++ b/packages/js/src/elementor/helpers/update-save-as-draft-warning.js @@ -16,7 +16,6 @@ export const updateSaveAsDraftWarning = ( hasUnsavedSeoChanges ) => { message = sprintf( /* translators: %1$s translates to the Post Label in singular form */ __( - // eslint-disable-next-line max-len "Unfortunately we cannot save changes to your SEO settings while you are working on a draft of an already-published %1$s. If you want to save your SEO changes, make sure to click 'Update', or wait to make your SEO changes until you are ready to update the %1$s.", "wordpress-seo" ), diff --git a/packages/js/src/filter-explanation.js b/packages/js/src/filter-explanation.js index 75d468f4959..991c7aa276b 100644 --- a/packages/js/src/filter-explanation.js +++ b/packages/js/src/filter-explanation.js @@ -1,8 +1,6 @@ /* global yoastFilterExplanation */ -/* eslint-disable max-len */ ( function( $ ) { $( "#posts-filter .tablenav.top" ).after( `

${ yoastFilterExplanation.text }

` ); }( jQuery ) ); -/* eslint-enable max-len */ diff --git a/packages/js/src/first-time-configuration/first-time-configuration-steps.js b/packages/js/src/first-time-configuration/first-time-configuration-steps.js index 89c631cd9a9..3e5c0feb2a3 100644 --- a/packages/js/src/first-time-configuration/first-time-configuration-steps.js +++ b/packages/js/src/first-time-configuration/first-time-configuration-steps.js @@ -150,8 +150,6 @@ function calculateInitialState( windowObject, isStepFinished ) { }; } -/* eslint-enable max-len, react/prop-types */ - /* eslint-disable max-statements */ /** * The first time configuration. @@ -401,7 +399,6 @@ export default function FirstTimeConfigurationSteps() { setShowEditButton( stepperFinishedOnce && ! isStepBeingEdited ); }, [ stepperFinishedOnce, isStepBeingEdited ] ); - /* eslint-disable max-len */ useEffect( () => { /** * Prevents the submission of the form upon pressing enter. @@ -586,7 +583,6 @@ export default function FirstTimeConfigurationSteps() { ); } -/* eslint-enable max-len */ /* eslint-enable complexity */ /* eslint-enable react/jsx-no-bind */ /* eslint-enable max-statements */ diff --git a/packages/js/src/first-time-configuration/tailwind-components/base/combo-box.js b/packages/js/src/first-time-configuration/tailwind-components/base/combo-box.js index 7367e353c7f..09770dccc5d 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/base/combo-box.js +++ b/packages/js/src/first-time-configuration/tailwind-components/base/combo-box.js @@ -125,7 +125,7 @@ export default function YoastComboBox( { id, value, label, onChange, onQueryChan ; } } - ) ) } + ) ) } ) } diff --git a/packages/js/src/first-time-configuration/tailwind-components/base/radio-group.js b/packages/js/src/first-time-configuration/tailwind-components/base/radio-group.js index 1a40681278e..4ceada138b5 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/base/radio-group.js +++ b/packages/js/src/first-time-configuration/tailwind-components/base/radio-group.js @@ -1,4 +1,3 @@ -/* eslint-disable no-undefined */ import classNames from "classnames"; import PropTypes from "prop-types"; import { useCallback } from "@wordpress/element"; diff --git a/packages/js/src/first-time-configuration/tailwind-components/step-circle.js b/packages/js/src/first-time-configuration/tailwind-components/step-circle.js index ce3db96c403..f4d05e6b210 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/step-circle.js +++ b/packages/js/src/first-time-configuration/tailwind-components/step-circle.js @@ -3,7 +3,6 @@ import { useState, useEffect } from "@wordpress/element"; import PropTypes from "prop-types"; import { stepperTimingClasses } from "../stepper-helper"; import { useStepperContext } from "./stepper"; -/* eslint-disable complexity */ const { slideDuration, delayUntilStepFaded } = stepperTimingClasses; const commonCircleClasses = `yst-transition-opacity ${ slideDuration } yst-absolute yst-inset-0 yst-border-2 yst-flex yst-items-center yst-justify-center yst-rounded-full`; diff --git a/packages/js/src/first-time-configuration/tailwind-components/step-header.js b/packages/js/src/first-time-configuration/tailwind-components/step-header.js index 70b9ed81c36..57204222e8d 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/step-header.js +++ b/packages/js/src/first-time-configuration/tailwind-components/step-header.js @@ -4,8 +4,6 @@ import { useEffect, useState } from "@wordpress/element"; import { stepperTimings } from "../stepper-helper"; import { useStepperContext } from "./stepper"; -/* eslint-disable complexity */ - /** * Gets the classnames for the step name. * @@ -22,7 +20,6 @@ function getNameClassNames( isFinished, isActiveStep, isLastStep ) { return isFinished ? "yst-text-slate-900" : "yst-text-slate-600"; } -/* eslint-disable complexity */ /** * The Step header component. * @@ -86,5 +83,3 @@ StepHeader.defaultProps = { description: "", children: [], }; - -/* eslint-enable complexity */ diff --git a/packages/js/src/first-time-configuration/tailwind-components/stepper.js b/packages/js/src/first-time-configuration/tailwind-components/stepper.js index 6a2d7c9196f..f687da7fb4f 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/stepper.js +++ b/packages/js/src/first-time-configuration/tailwind-components/stepper.js @@ -7,7 +7,6 @@ import { stepperTimings, stepperTimingClasses } from "../stepper-helper"; import StepHeader from "./step-header"; import { FadeInAlert } from "../tailwind-components/base/alert"; -/* eslint-disable complexity */ const { slideDuration, delayBeforeOpening, @@ -27,9 +26,9 @@ const StepperContext = createContext(); export function useStepperContext() { const context = useContext( StepperContext ); if ( ! context ) { - throw new Error( + throw new Error( "Stepper compound components cannot be rendered outside the Stepper component" - ); + ); } return context; } @@ -274,4 +273,3 @@ Step.Error = StepError; Step.Header = StepHeader; Step.GoButton = GoButton; Step.EditButton = EditButton; -/* eslint-enable complexity */ diff --git a/packages/js/src/first-time-configuration/tailwind-components/steps/finish/finish-step.js b/packages/js/src/first-time-configuration/tailwind-components/steps/finish/finish-step.js index 1781f994176..ce5a8ca9861 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/steps/finish/finish-step.js +++ b/packages/js/src/first-time-configuration/tailwind-components/steps/finish/finish-step.js @@ -34,7 +34,7 @@ export default function FinishStep() { __( "Great work! Thanks to the details you've provided, %1$s has enhanced your site for search engines, giving them a clearer picture of what your site is all about.", "wordpress-seo" ), "Yoast" ) - } + }

{ __( "If your goal is to increase your rankings, you need to work on your SEO regularly. That can be overwhelming, so let's tackle it one step at a time!", "wordpress-seo" ) } diff --git a/packages/js/src/first-time-configuration/tailwind-components/steps/indexation/indexation.js b/packages/js/src/first-time-configuration/tailwind-components/steps/indexation/indexation.js index ad76028a1eb..677880bea6f 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/steps/indexation/indexation.js +++ b/packages/js/src/first-time-configuration/tailwind-components/steps/indexation/indexation.js @@ -392,7 +392,7 @@ class Indexation extends Component { * * @returns {WPElement} The rendered component. */ - render() { + render() { if ( this.settings.disabled ) { return this.renderDisabledTool(); } diff --git a/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-input-section.js b/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-input-section.js index 19896197bd9..9627319f595 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-input-section.js +++ b/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-input-section.js @@ -5,7 +5,6 @@ import PropTypes from "prop-types"; import SocialFieldArray from "./social-field-array"; import SocialInput from "./social-input"; -/* eslint-disable complexity */ /** * A wrapper that combines all the SocialInputs. Intended for use in the first time configuration. * @@ -54,7 +53,6 @@ export default function SocialInputSectionContainer( { socialProfiles, errorFiel /> ); } -/* eslint-enable complexity */ SocialInputSectionContainer.propTypes = { socialProfiles: PropTypes.object.isRequired, diff --git a/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-profiles-step.js b/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-profiles-step.js index 07c43e0da58..884fa686def 100644 --- a/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-profiles-step.js +++ b/packages/js/src/first-time-configuration/tailwind-components/steps/social-profiles/social-profiles-step.js @@ -4,7 +4,6 @@ import PropTypes from "prop-types"; import SocialInputSection from "./social-input-section"; import Alert from "../../base/alert"; -/* eslint-disable max-len, react/prop-types */ /** * Social profiles step component * @@ -83,7 +82,9 @@ export default function SocialProfilesStep( { state, dispatch, setErrorFields } { /* No person has been selected in step 2 */ } { - // translators: please note that "Site representation" here refers to the name of a step in the first-time configuration, so this occurrence needs to be translated in the same manner as that step's heading. + /* translators: please note that "Site representation" here refers to the name of a step in the first-time configuration, + * so this occurrence needs to be translated in the same manner as that step's heading. + */ __( "Please select a user in the Site representation step.", "wordpress-seo" diff --git a/packages/js/src/general/app.js b/packages/js/src/general/app.js index 832ca60b497..bf7be7dfaa4 100644 --- a/packages/js/src/general/app.js +++ b/packages/js/src/general/app.js @@ -1,5 +1,3 @@ -/* eslint-disable complexity */ - import { Transition } from "@headlessui/react"; import { AdjustmentsIcon, BellIcon, ChartPieIcon } from "@heroicons/react/outline"; import { useDispatch, useSelect } from "@wordpress/data"; @@ -140,7 +138,7 @@ const App = () => { > { notice.content } - ) + ) } } } diff --git a/packages/js/src/general/components/problems.js b/packages/js/src/general/components/problems.js index 74ed0a3739d..4a1badb2d73 100644 --- a/packages/js/src/general/components/problems.js +++ b/packages/js/src/general/components/problems.js @@ -38,9 +38,9 @@ export const Problems = () => {

{ problemsList.length > 0 - ? __( "We have detected the following issues that affect the SEO of your site.", "wordpress-seo" ) - : __( "Good job! We could detect no serious SEO problems.", "wordpress-seo" ) - } + ? __( "We have detected the following issues that affect the SEO of your site.", "wordpress-seo" ) + : __( "Good job! We could detect no serious SEO problems.", "wordpress-seo" ) + }

diff --git a/packages/js/src/help-scout-beacon.js b/packages/js/src/help-scout-beacon.js index f71408c2add..1510ead4136 100644 --- a/packages/js/src/help-scout-beacon.js +++ b/packages/js/src/help-scout-beacon.js @@ -175,7 +175,6 @@ function loadHelpScoutConsent( beaconId, sessionData = null ) { return ( - { /* eslint-disable-next-line max-len */ } @@ -229,14 +228,12 @@ function loadHelpScoutConsent( beaconId, sessionData = null ) { */ function onClick() { const askConsentText = __( - // eslint-disable-next-line max-len "When you click OK we will open our HelpScout beacon where you can find answers to your questions. This beacon will load our support data and also potentially set cookies.", "wordpress-seo" ); // eslint-disable-next-line no-alert if ( window.confirm( askConsentText ) ) { - // eslint-disable-next-line callback-return loadHelpScout( beaconId, sessionData ); // eslint-disable-next-line new-cap window.Beacon( "open" ); diff --git a/packages/js/src/initializers/admin.js b/packages/js/src/initializers/admin.js index 81776fc11ca..51a8bb10f5a 100644 --- a/packages/js/src/initializers/admin.js +++ b/packages/js/src/initializers/admin.js @@ -1,5 +1,4 @@ /* global ajaxurl */ - import { __ } from "@wordpress/i18n"; import { debounce } from "lodash"; @@ -77,7 +76,7 @@ export default function initAdmin( jQuery ) { * * @returns {void} */ - function setInitialActiveTab() { + function setInitialActiveTab() { var activeTabId = window.location.hash.replace( "#top#", "" ); /* In some cases, the second # gets replace by %23, which makes the tab * switching not work unless we do this. */ @@ -108,7 +107,7 @@ export default function initAdmin( jQuery ) { jQuery( window ).on( "hashchange", function() { setInitialActiveTab(); wpseoSetTabHash(); - } ); + } ); /** * Hides or shows the Author without posts toggle. @@ -216,10 +215,8 @@ export default function initAdmin( jQuery ) { window.setWPOption = setWPOption; window.wpseoCopyHomeMeta = wpseoCopyHomeMeta; - // eslint-disable-next-line window.wpseoSetTabHash = wpseoSetTabHash; - // eslint-disable-next-line jQuery( document ).ready( function() { /** * When the hash changes, get the base url from the action and then add the current hash. diff --git a/packages/js/src/inline-links/inline.js b/packages/js/src/inline-links/inline.js index 3da86476ef1..46888772a3f 100644 --- a/packages/js/src/inline-links/inline.js +++ b/packages/js/src/inline-links/inline.js @@ -7,7 +7,7 @@ import PropTypes from "prop-types"; /** * WordPress dependencies */ -import { useMemo, useState, useCallback } from "@wordpress/element"; +import { useMemo, useState } from "@wordpress/element"; import { __, sprintf } from "@wordpress/i18n"; import { withSpokenMessages, Popover } from "@wordpress/components"; import { prependHTTP } from "@wordpress/url"; @@ -190,7 +190,12 @@ function InlineLinkUI( { } }; - const onChangeLink = useCallback( ( nextValue ) =>{ + /** + * Handles the change of the link. + * @param {Object} nextValue The next link URL. + * @returns {void} + */ + const onChangeLink = ( nextValue ) => { /* * Merge with values from state, both for the purpose of assigning the next state value, and for use in constructing the new link format if * the link is ready to be applied. @@ -256,7 +261,7 @@ function InlineLinkUI( { } actionCompleteMessage( newUrl ); - }, [] ); + }; const NoFollowHelpLink = { // eslint-disable-line complexity ) }