diff --git a/.editorconfig b/.editorconfig index f0c0f2bd977..804309aca9f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,7 +18,7 @@ trim_trailing_whitespace = true [*\.txt] trim_trailing_whitespace = false -[*\.{md,json,yml,xml,xml\.dist}] +[*\.{md,json,yml,yaml,xml,xml\.dist,eslintrc}] indent_style = space indent_size = 2 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3ef7b4d86ef..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -**/*{.,-}min.js -js/src/inline-links/*.js diff --git a/.eslintrc b/.eslintrc index cb27520d6a8..9f3fa887afd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,39 +1,176 @@ extends: yoast -parser: babel-eslint +parserOptions: + ecmaVersion: 2018 + sourceType: module + +settings: + react: + version: "16.8" rules: - # Custom rules: only for temporary exceptions that should be removed over time - camelcase: 1 - complexity: [1, 6] - brace-style: 1 - max-statements: 1 - 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: + no-prototype-builtins: 0 + comma-dangle: - 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 + - arrays: always-multiline + objects: always-multiline + imports: always-multiline + exports: always-multiline + functions: never overrides: - files: - - "js/tests/**/*.js" + - "packages/*/tests/**/*.js" + - "packages/*/spec/**/*.js" + # Temporary exclusion: + - "packages/components/a11y/tests/**/*.js" + env: + jest: true rules: no-restricted-imports: "off" + - files: + - "packages/js/**/*.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 + 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. -settings: - react: - version: "16.3" + # Disabled rules + # In the editor, we're using the pragma `wp.element.createElement` + react/react-in-jsx-scope: 0 + - files: + - "packages/js/tests/**/*.js" + rules: + no-restricted-imports: 0 + - files: + - "packages/schema-blocks/src/**" + env: + jest: true + rules: + new-cap: 0 + complexity: + - 2 + - 10 + "@typescript-eslint/no-parameter-properties": + - 2 + - allows: + - protected readonly + - private readonly + "@typescript-eslint/explicit-function-return-type": 0 + "@typescript-eslint/no-use-before-define": + - error + - functions: false + no-constant-condition: + - error + - checkLoops: false + max-statements: + - 2 + - 33 + no-unused-vars: 'off' + "@typescript-eslint/no-unused-vars": + - error + - varsIgnorePattern: "^createElement$" + no-dupe-class-members: 'off' + indent: 'off' + "@typescript-eslint/indent": + - 2 + - tab + - SwitchCase: 1 + space-before-function-paren: + - error + - anonymous: never + named: never + asyncArrow: always + valid-jsdoc: + - error + - requireReturn: false + requireReturnType: false + requireParamType: false + react/react-in-jsx-scope: 0 + - 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 + - files: + - "packages/configuration-wizard/**/*.js" + rules: + react/no-unused-prop-types: 1 + react/jsx-no-bind: 1 + react/no-access-state-in-setstate: 1 + require-jsdoc: 1 + - files: + - "packages/replacement-variable-editor/**/*.js" + rules: + react/jsx-no-bind: 1 + react/require-default-props: 1 + - files: + - "packages/search-metadata-previews/**/*.js" + rules: + react/jsx-no-bind: 1 + react/no-unused-state: 1 + react/no-access-state-in-setstate: 1 + react/require-default-props: 1 + react/default-props-match-prop-types: 1 + - files: + - "packages/yoastseo/**/*.js" + rules: + complexity: 1 + # Longer grace period for Yoast config. + no-shadow: [ 1, { "builtinGlobals": false, "hoist": "all", "allow": [ ] } ] + require-jsdoc: [ 1, { "require": { "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true, "FunctionExpression": true } } ] + no-useless-escape: 1 + - files: + - "packages/analysis-report/**/*.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 + - files: + - "packages/yoast-components/**/*.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 + env: + jest: true diff --git a/package.json b/package.json index 24df41fef45..62ab4490774 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,7 @@ }, "workspaces": { "packages": [ - "packages/*", - "apps/*" + "packages/*" ], "nohoist": [ "**/grunt-*", @@ -45,8 +44,11 @@ "unlink-monorepo": "node config/yarn/unlink_monorepo.js", "bump-monorepo-packages": "node config/yarn/bump_monorepo_packages.js" }, + "dependencies": {}, "devDependencies": { "@slack/webhook": "^5.0.2", + "@typescript-eslint/eslint-plugin": "^2.15.0", + "@typescript-eslint/parser": "^2.15.0", "@wordpress/babel-plugin-makepot": "^1.0.0", "@wordpress/babel-preset-default": "^1.1.3", "@wordpress/dependency-extraction-webpack-plugin": "^2.8.0", @@ -75,9 +77,10 @@ "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.5.0", "enzyme-to-json": "^3.3.3", - "eslint": "^5.15.3", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.12.4", + "eslint": "^6.8.0", + "eslint-config-yoast": "file:packages/eslint", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.22.0", "grunt-git": "^1.0.14", "grunt-po2json": "^0.3.0", "grunt-prompt": "^1.3.3", @@ -92,12 +95,12 @@ "shusher": "^0.1.1", "svg-react-loader": "^0.4.6", "tmp": "^0.1.0", + "typescript": "^3.7.4", "webpack": "^4.20.2", "webpack-bundle-analyzer": "^3.0.2", "webpack-cli": "^3.1.1", "webpack-dev-server": "^3.1.14" }, - "dependencies": {}, "browserslist": [ "extends @yoast/browserslist-config" ], diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 16775b532d6..491171ff937 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -17,7 +17,7 @@ }, "scripts": { "test": "jest", - "lint": "eslint .", + "lint": "eslint . --max-warnings=0", "prepublishOnly": "rm -rf dist && cp -R src dist && cp package.json dist/package.json && json -I -f dist/package.json -e \\\"this.main='index.js'\\\"" }, "bugs": { diff --git a/packages/components/package.json b/packages/components/package.json index 2972aad3fbc..6d6742fd950 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -13,7 +13,7 @@ "license": "GPL-3.0", "scripts": { "test": "jest", - "lint": "eslint . --max-warnings=96", + "lint": "eslint . --max-warnings=93", "prepublishOnly": "rm -rf dist && cp -R src dist && cp package.json dist/package.json && json -I -f dist/package.json -e \"this.main='index.js'\" && cp .babelrc dist/.babelrc" }, "jest": { diff --git a/packages/components/src/radiobutton/RadioButtonGroup.js b/packages/components/src/radiobutton/RadioButtonGroup.js index 97e5d80c7d8..f2669117363 100644 --- a/packages/components/src/radiobutton/RadioButtonGroup.js +++ b/packages/components/src/radiobutton/RadioButtonGroup.js @@ -108,7 +108,7 @@ const VerticalRadioButtons = ( { options, onChange, groupName, id, selected } ) id={ `${ id }_${ option.value }` } { ...option } /> - , + ) } ); diff --git a/packages/components/tests/AlertTest.js b/packages/components/tests/AlertTest.js index e2257d48539..2ff7f255d62 100644 --- a/packages/components/tests/AlertTest.js +++ b/packages/components/tests/AlertTest.js @@ -37,7 +37,7 @@ describe( "Alert", () => { const component = renderer.create( { content } - , + ); const tree = component.toJSON(); @@ -70,7 +70,7 @@ describe( "Alert", () => { const component = renderer.create( Dismissable alert. - , + ); const tree = component.toJSON(); diff --git a/packages/components/tests/CheckboxTest.js b/packages/components/tests/CheckboxTest.js index 7e130a3d6fc..c606c287cb6 100644 --- a/packages/components/tests/CheckboxTest.js +++ b/packages/components/tests/CheckboxTest.js @@ -6,7 +6,7 @@ import Checkbox from "../src/checkbox/Checkbox"; describe( "Checkbox", () => { it( "matches the snapshot", () => { const component = renderer.create( - , + ); const tree = component.toJSON(); @@ -15,7 +15,7 @@ describe( "Checkbox", () => { it( "matches the snapshot when an array is provided as a label", () => { const component = renderer.create( - , + ); const tree = component.toJSON(); @@ -34,7 +34,7 @@ describe( "Checkbox", () => { id="testCallback" onChange={ onChange } label="testCallbackLabel" - />, + /> ); const tree = component.toTree(); diff --git a/packages/components/tests/RadioButtonGroupTest.js b/packages/components/tests/RadioButtonGroupTest.js index 7e20a331325..18ed8ccc2b8 100644 --- a/packages/components/tests/RadioButtonGroupTest.js +++ b/packages/components/tests/RadioButtonGroupTest.js @@ -22,7 +22,7 @@ describe( "RadioButtonGroup", () => { checked: true, }, ] } - />, + /> ); const result = renderer.getRenderOutput(); diff --git a/packages/configuration-wizard/package.json b/packages/configuration-wizard/package.json index c88added4e9..afef12b113a 100644 --- a/packages/configuration-wizard/package.json +++ b/packages/configuration-wizard/package.json @@ -13,7 +13,7 @@ "private": false, "scripts": { "test": "jest", - "lint": "eslint . --max-warnings=11", + "lint": "eslint . --max-warnings=8", "prepublishOnly": "rm -rf dist && cp -R src dist && cp package.json dist/package.json && json -I -f dist/package.json -e \"this.main='index.js'\" && mkdir dist/src && mkdir dist/src/helpers && cp src/helpers/_colors.scss dist/src/helpers/_colors.scss && cp .babelrc dist/.babelrc" }, "dependencies": { diff --git a/packages/feature-flag/package.json b/packages/feature-flag/package.json index 53f4088df75..1ed9b0ce827 100644 --- a/packages/feature-flag/package.json +++ b/packages/feature-flag/package.json @@ -12,7 +12,7 @@ "private": false, "scripts": { "test": "jest --coverage", - "lint": "eslint ./src ./tests --max-warnings=5", + "lint": "eslint ./src ./tests --max-warnings=0", "prepublishOnly": "rm -rf dist && cp -R src dist && cp package.json dist/package.json && json -I -f dist/package.json -e \"this.main='index.js'\"" }, "devDependencies": { diff --git a/packages/helpers/package.json b/packages/helpers/package.json index edf4b351795..3145a26672d 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -10,7 +10,7 @@ }, "scripts": { "test": "jest", - "lint": "eslint .", + "lint": "eslint . --max-warnings=0", "prepublishOnly": "rm -rf dist && cp -R src dist && cp package.json dist/package.json && json -I -f dist/package.json -e \"this.main='index.js'\" && cp .babelrc dist/.babelrc" }, "author": "Yoast", diff --git a/packages/helpers/src/makeOutboundLink.js b/packages/helpers/src/makeOutboundLink.js index d433f3c1c12..42306909741 100644 --- a/packages/helpers/src/makeOutboundLink.js +++ b/packages/helpers/src/makeOutboundLink.js @@ -62,7 +62,7 @@ export const makeOutboundLink = ( Component = "a" ) => { { target: "_blank", rel: isYoastLink ? this.props.rel : "noopener", - }, + } ); // Use React.createElement instead of JSX because it can accept a string as a component parameter. return React.createElement( @@ -72,7 +72,7 @@ export const makeOutboundLink = ( Component = "a" ) => { React.createElement( A11yNotice, null, - __( "(Opens in a new browser tab)", "yoast-components" ), + __( "(Opens in a new browser tab)", "yoast-components" ) ) ); } diff --git a/packages/helpers/src/social-preview-image-validation/facebookValidation.js b/packages/helpers/src/social-preview-image-validation/facebookValidation.js index 56d0c985018..0a936fe9356 100644 --- a/packages/helpers/src/social-preview-image-validation/facebookValidation.js +++ b/packages/helpers/src/social-preview-image-validation/facebookValidation.js @@ -23,7 +23,7 @@ export const validateSize = ( image ) => { "Your image dimensions are not suitable. The minimum dimensions are %dx%d pixels.", "yoast-components" ), - MIN_WIDTH, MIN_HEIGHT, + MIN_WIDTH, MIN_HEIGHT ); return ( width < MIN_WIDTH || height < MIN_HEIGHT ) ? warningMessage : true; diff --git a/packages/helpers/src/social-preview-image-validation/twitterValidation.js b/packages/helpers/src/social-preview-image-validation/twitterValidation.js index 8b94ac5d223..37f64400719 100644 --- a/packages/helpers/src/social-preview-image-validation/twitterValidation.js +++ b/packages/helpers/src/social-preview-image-validation/twitterValidation.js @@ -59,7 +59,7 @@ export const validateType = ( image ) => { "You have uploaded a %s. Please note that, if it’s an animated %s, only the first frame will be used.", "yoast-components" ), - "GIF", "GIF", + "GIF", "GIF" ); const warningMessage = sprintf( @@ -68,7 +68,7 @@ export const validateType = ( image ) => { "The format of the uploaded image is not supported. The supported formats are: %s, %s, %s and %s.", "yoast-components" ), - "JPG", "PNG", "WEBP", "GIF", + "JPG", "PNG", "WEBP", "GIF" ); if ( validTypes.includes( type ) ) { @@ -97,7 +97,7 @@ export const validatesBytes = ( image ) => { "The file size of the uploaded image is too large for %s. File size must be less than %s.", "yoast-components" ), - "Twitter", "5MB", + "Twitter", "5MB" ); if ( bytes >= 5 ) { diff --git a/packages/js/package.json b/packages/js/package.json index 7042cdf8046..fde761e515a 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,10 @@ { "name": "@yoast/wordpress-seo", "version": "1.0.0", + "scripts": { + "test": "jest", + "lint": "eslint . --max-warnings=159" + }, "jest": { "setupTestFrameworkScriptFile": "/tests/setupTests.js", "transformIgnorePatterns": [ @@ -67,15 +71,15 @@ "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.5.0", "enzyme-to-json": "^3.3.3", - "eslint": "^5.15.3", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.12.4", "jest": "^23.3.0", "jest-styled-components": "^6.3.4" }, "peerDependencies": { "react": "16.8.6", - "react-dom": "16.8.6" + "react-dom": "16.8.6", + "babel-cli": ">=6.26.0", + "babel-core": ">=6.26.0", + "babel-jest": ">=18.0.0" }, "browserslist": [ "extends @yoast/browserslist-config" diff --git a/packages/js/src/analysis/PostDataCollector.js b/packages/js/src/analysis/PostDataCollector.js index ebd23b7436c..c81483b4544 100644 --- a/packages/js/src/analysis/PostDataCollector.js +++ b/packages/js/src/analysis/PostDataCollector.js @@ -1,4 +1,4 @@ -/* global jQuery, wpseoScriptData */ +/* global wpseoScriptData */ /* External dependencies */ import { get } from "lodash-es"; diff --git a/packages/js/src/analysis/TermDataCollector.js b/packages/js/src/analysis/TermDataCollector.js index d26c12f6938..0a000c49520 100644 --- a/packages/js/src/analysis/TermDataCollector.js +++ b/packages/js/src/analysis/TermDataCollector.js @@ -1,4 +1,4 @@ -/* global jQuery, wpseoScriptData */ +/* global wpseoScriptData */ /* External dependencies */ import { get } from "lodash-es"; diff --git a/packages/js/src/analysis/classicEditorData.js b/packages/js/src/analysis/classicEditorData.js index ceeb92bd1fa..3c31f06da9d 100644 --- a/packages/js/src/analysis/classicEditorData.js +++ b/packages/js/src/analysis/classicEditorData.js @@ -1,4 +1,4 @@ -/* global wp jQuery */ +/* global wp */ /* External dependencies */ import analysis from "yoastseo"; diff --git a/packages/js/src/analysis/plugins/shortcode-plugin.js b/packages/js/src/analysis/plugins/shortcode-plugin.js index eee4cfe9937..f20cd9d1e51 100644 --- a/packages/js/src/analysis/plugins/shortcode-plugin.js +++ b/packages/js/src/analysis/plugins/shortcode-plugin.js @@ -2,8 +2,6 @@ /* global wpseoScriptData */ /* global ajaxurl */ /* global _ */ -/* global JSON */ -/* global console */ const shortcodeNameMatcher = "[^<>&/\\[\\]\x00-\x20=]+?"; const shortcodeAttributesMatcher = "( [^\\]]+?)?"; diff --git a/packages/js/src/analysis/usedKeywords.js b/packages/js/src/analysis/usedKeywords.js index 91239b18d2a..b4bd48ad3ba 100644 --- a/packages/js/src/analysis/usedKeywords.js +++ b/packages/js/src/analysis/usedKeywords.js @@ -1,4 +1,4 @@ -/* global jQuery, ajaxurl */ +/* global ajaxurl */ import { has, debounce, diff --git a/packages/js/src/api-client.js b/packages/js/src/api-client.js index 36ad39f1e54..cb012b45efb 100644 --- a/packages/js/src/api-client.js +++ b/packages/js/src/api-client.js @@ -1,4 +1,4 @@ -/* global jQuery, wpApiSettings */ +/* global wpApiSettings */ ( function( $, wpApiSettings ) { window.wpseoApi = { diff --git a/packages/js/src/bulk-editor.js b/packages/js/src/bulk-editor.js index 508523364e2..edfa4a19baa 100644 --- a/packages/js/src/bulk-editor.js +++ b/packages/js/src/bulk-editor.js @@ -1,5 +1,4 @@ /* global ajaxurl */ -/* global JSON */ /* global wpseoBulkEditorNonce */ /* jshint -W097 */ /* eslint-disable camelcase */ diff --git a/packages/js/src/components/AdvancedSettings.js b/packages/js/src/components/AdvancedSettings.js index a81b6dc6e05..5f38303b917 100644 --- a/packages/js/src/components/AdvancedSettings.js +++ b/packages/js/src/components/AdvancedSettings.js @@ -32,7 +32,7 @@ const getNoIndexOptions = ( editorContext ) => { /* Translators: %s translates to "yes" or "no", %s translates to the Post Label in plural form */ __( "%s (current default for %s)", "wordpress-seo" ), noIndex, - editorContext.postTypeNamePlural, + editorContext.postTypeNamePlural ), value: "0", }, @@ -46,7 +46,7 @@ const getNoIndexOptions = ( editorContext ) => { /* Translators: %s translates to the "yes" or "no" ,%s translates to the Post Label in plural form */ __( "%s (current default for %s)", "wordpress-seo" ), noIndex, - editorContext.postTypeNamePlural, + editorContext.postTypeNamePlural ), value: "default", }, @@ -75,7 +75,7 @@ const MetaRobotsNoIndex = ( { noIndex, onNoIndexChange, editorContext, isPrivate "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", + "wordpress-seo" ) } } @@ -84,7 +84,7 @@ const MetaRobotsNoIndex = ( { noIndex, onNoIndexChange, editorContext, isPrivate sprintf( /* Translators: %s translates to the Post Label in singular form */ __( "Allow search engines to show this %s in search results?", "wordpress-seo" ), - editorContext.postTypeNameSingular, + editorContext.postTypeNameSingular ) } onChange={ onNoIndexChange } id={ join( [ "yoast-meta-robots-noindex", location ] ) } @@ -125,7 +125,7 @@ const MetaRobotsNoFollow = ( { noFollow, onNoFollowChange, postTypeName } ) => { label={ sprintf( /* Translators: %s translates to the Post Label in singular form */ __( "Should search engines follow links on this %s", "wordpress-seo" ), - postTypeName, + postTypeName ) } groupName={ id } onChange={ onNoFollowChange } diff --git a/packages/js/src/components/PrimaryTaxonomyFilter.js b/packages/js/src/components/PrimaryTaxonomyFilter.js index 086232dd21b..0c647ed6a28 100644 --- a/packages/js/src/components/PrimaryTaxonomyFilter.js +++ b/packages/js/src/components/PrimaryTaxonomyFilter.js @@ -1,4 +1,3 @@ -/* global window */ /* External dependencies */ import PropTypes from "prop-types"; import { Component, Fragment } from "@wordpress/element"; diff --git a/packages/js/src/components/SchemaSettings.js b/packages/js/src/components/SchemaSettings.js index b926bc06be4..6c9915493c0 100644 --- a/packages/js/src/components/SchemaSettings.js +++ b/packages/js/src/components/SchemaSettings.js @@ -87,7 +87,7 @@ class SchemaSettings extends Component { "You can change these settings for individual %1$s.", "wordpress-seo" ), - this.props.postTypeName, + this.props.postTypeName ) } /> { this.shouldShowAlert() && @@ -98,9 +98,9 @@ class SchemaSettings extends Component { "Upon saving, these settings will apply to all of your %1$s." + " %1$s that are manually configured will be left untouched.", this.props.articleType ? 2 : 1, - "wordpress-seo", + "wordpress-seo" ), - this.props.postTypeName, + this.props.postTypeName ) } }