Skip to content
Permalink
trunk
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
/**
* External dependencies
*/
const glob = require( 'glob' ).sync;
const { join } = require( 'path' );
/**
* Internal dependencies
*/
const { version } = require( './package' );
/**
* Regular expression string matching a SemVer string with equal major/minor to
* the current package version. Used in identifying deprecations.
*
* @type {string}
*/
const majorMinorRegExp =
version.replace( /\.\d+$/, '' ).replace( /[\\^$.*+?()[\]{}|]/g, '\\$&' ) +
'(\\.\\d+)?';
/**
* The list of patterns matching files used only for development purposes.
*
* @type {string[]}
*/
const developmentFiles = [
'**/benchmark/**/*.js',
'**/@(__mocks__|__tests__|test)/**/*.[tj]s?(x)',
'**/@(storybook|stories)/**/*.[tj]s?(x)',
'packages/babel-preset-default/bin/**/*.js',
];
// All files from packages that have types provided with TypeScript.
const typedFiles = glob( 'packages/*/package.json' )
.filter( ( fileName ) => require( join( __dirname, fileName ) ).types )
.map( ( fileName ) => fileName.replace( 'package.json', '**/*.js' ) );
const restrictedImports = [
{
name: 'framer-motion',
message:
'Please use the Framer Motion API through `@wordpress/components` instead.',
},
{
name: 'lodash',
importNames: [
'camelCase',
'capitalize',
'castArray',
'chunk',
'clamp',
'clone',
'cloneDeep',
'compact',
'concat',
'countBy',
'debounce',
'deburr',
'defaults',
'defaultTo',
'delay',
'difference',
'differenceWith',
'dropRight',
'each',
'escape',
'escapeRegExp',
'every',
'extend',
'filter',
'find',
'findIndex',
'findKey',
'findLast',
'first',
'flatMap',
'flatten',
'flattenDeep',
'flow',
'flowRight',
'forEach',
'fromPairs',
'groupBy',
'has',
'identity',
'includes',
'invoke',
'isArray',
'isBoolean',
'isEmpty',
'isEqual',
'isFinite',
'isFunction',
'isMatch',
'isNil',
'isNumber',
'isObject',
'isObjectLike',
'isPlainObject',
'isString',
'isUndefined',
'keyBy',
'keys',
'last',
'lowerCase',
'map',
'mapKeys',
'mapValues',
'maxBy',
'memoize',
'merge',
'mergeWith',
'negate',
'noop',
'nth',
'omit',
'omitBy',
'once',
'orderby',
'overEvery',
'partial',
'partialRight',
'pick',
'pickBy',
'random',
'reduce',
'reject',
'repeat',
'reverse',
'setWith',
'size',
'snakeCase',
'some',
'sortBy',
'startCase',
'startsWith',
'stubFalse',
'stubTrue',
'sum',
'sumBy',
'take',
'throttle',
'times',
'toString',
'trim',
'truncate',
'unescape',
'unionBy',
'uniq',
'uniqBy',
'uniqueId',
'uniqWith',
'upperFirst',
'values',
'without',
'words',
'xor',
'zip',
],
message:
'This Lodash method is not recommended. Please use native functionality instead. If using `memoize`, please use `memize` instead.',
},
{
name: 'reakit',
message:
'Please use Reakit API through `@wordpress/components` instead.',
},
{
name: 'redux',
importNames: [ 'combineReducers' ],
message: 'Please use `combineReducers` from `@wordpress/data` instead.',
},
{
name: 'puppeteer-testing-library',
message: '`puppeteer-testing-library` is still experimental.',
},
{
name: '@emotion/css',
message:
'Please use `@emotion/react` and `@emotion/styled` in order to maintain iframe support. As a replacement for the `cx` function, please use the `useCx` hook defined in `@wordpress/components` instead.',
},
{
name: '@wordpress/edit-post',
message:
"edit-post is a WordPress top level package that shouldn't be imported into other packages",
},
{
name: '@wordpress/edit-site',
message:
"edit-site is a WordPress top level package that shouldn't be imported into other packages",
},
{
name: '@wordpress/edit-widgets',
message:
"edit-widgets is a WordPress top level package that shouldn't be imported into other packages",
},
];
module.exports = {
root: true,
extends: [
'plugin:@wordpress/eslint-plugin/recommended',
'plugin:eslint-comments/recommended',
],
globals: {
wp: 'off',
},
settings: {
jsdoc: {
mode: 'typescript',
},
'import/internal-regex': null,
'import/resolver': require.resolve( './tools/eslint/import-resolver' ),
},
rules: {
'jest/expect-expect': 'off',
'@wordpress/dependency-group': 'error',
'@wordpress/is-gutenberg-plugin': 'error',
'@wordpress/react-no-unsafe-timeout': 'error',
'@wordpress/i18n-text-domain': [
'error',
{
allowedTextDomain: 'default',
},
],
'@wordpress/no-unsafe-wp-apis': 'off',
'@wordpress/data-no-store-string-literals': 'error',
'import/default': 'error',
'import/named': 'error',
'no-restricted-imports': [
'error',
{
paths: restrictedImports,
},
],
'@typescript-eslint/no-restricted-imports': [
'error',
{
paths: [
{
name: 'react',
message:
'Please use React API through `@wordpress/element` instead.',
allowTypeImports: true,
},
],
},
],
'no-restricted-syntax': [
'error',
// NOTE: We can't include the forward slash in our regex or
// we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern)
// here. That's why we use \\u002F in the regexes below.
{
selector:
'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]',
message:
'Path access on WordPress dependencies is not allowed.',
},
{
selector:
'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' +
majorMinorRegExp +
'/]',
message:
'Deprecated functions must be removed before releasing this version.',
},
{
selector:
'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]',
message:
'This method is deprecated. You should use the more explicit API methods available.',
},
{
selector:
'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]',
message: 'Prefer page.waitForSelector instead.',
},
{
selector: 'JSXAttribute[name.name="id"][value.type="Literal"]',
message:
'Do not use string literals for IDs; use withInstanceId instead.',
},
{
// Discourage the usage of `Math.random()` as it's a code smell
// for UUID generation, for which we already have a higher-order
// component: `withInstanceId`.
selector:
'CallExpression[callee.object.name="Math"][callee.property.name="random"]',
message:
'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)',
},
{
selector:
'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)',
message:
'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.',
},
{
selector:
'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]',
message:
'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.',
},
],
},
overrides: [
{
files: [
'**/*.@(android|ios|native).js',
'packages/react-native-*/**/*.js',
...developmentFiles,
],
rules: {
'import/default': 'off',
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 'off',
'import/named': 'off',
'@wordpress/data-no-store-string-literals': 'off',
},
},
{
files: [ 'packages/react-native-*/**/*.js' ],
settings: {
'import/ignore': [ 'react-native' ], // Workaround for https://github.com/facebook/react-native/issues/28549.
},
},
{
files: [ 'packages/**/*.js' ],
excludedFiles: [
'packages/block-library/src/*/save.js',
...developmentFiles,
],
rules: {
'react/forbid-elements': [
'error',
{
forbid: [
[ 'circle', 'Circle' ],
[ 'g', 'G' ],
[ 'path', 'Path' ],
[ 'polygon', 'Polygon' ],
[ 'rect', 'Rect' ],
[ 'svg', 'SVG' ],
].map( ( [ element, componentName ] ) => {
return {
element,
message: `use cross-platform <${ componentName } /> component instead.`,
};
} ),
},
],
},
},
{
files: [
// Components package.
'packages/components/src/**/*.[tj]s?(x)',
// Navigation block.
'packages/block-library/src/navigation/**/*.[tj]s?(x)',
],
excludedFiles: [ ...developmentFiles ],
rules: {
'react-hooks/exhaustive-deps': 'error',
},
},
{
files: [ 'packages/jest*/**/*.js', '**/test/**/*.js' ],
excludedFiles: [ 'test/e2e/**/*.js' ],
extends: [ 'plugin:@wordpress/eslint-plugin/test-unit' ],
},
{
files: [ '**/test/**/*.[tj]s?(x)' ],
excludedFiles: [
'**/*.@(android|ios|native).[tj]s?(x)',
'packages/react-native-*/**/*.[tj]s?(x)',
'test/native/**/*.[tj]s?(x)',
'test/e2e/**/*.[tj]s?(x)',
'test/storybook-playwright/**/*.[tj]s?(x)',
],
extends: [
'plugin:jest-dom/recommended',
'plugin:testing-library/react',
'plugin:jest/recommended',
],
},
{
files: [ 'packages/e2e-test*/**/*.js' ],
excludedFiles: [ 'packages/e2e-test-utils-playwright/**/*.js' ],
extends: [ 'plugin:@wordpress/eslint-plugin/test-e2e' ],
rules: {
'jest/expect-expect': 'off',
},
},
{
files: [
'test/e2e/**/*.[tj]s',
'packages/e2e-test-utils-playwright/**/*.[tj]s',
],
extends: [
'plugin:eslint-plugin-playwright/playwright-test',
'plugin:@typescript-eslint/base',
],
parserOptions: {
tsconfigRootDir: __dirname,
project: [
'./test/e2e/tsconfig.json',
'./packages/e2e-test-utils-playwright/tsconfig.json',
],
},
rules: {
'@wordpress/no-global-active-element': 'off',
'@wordpress/no-global-get-selection': 'off',
'playwright/no-page-pause': 'error',
'no-restricted-syntax': [
'error',
{
selector: 'CallExpression[callee.property.name="$"]',
message:
'`$` is discouraged, please use `locator` instead',
},
{
selector: 'CallExpression[callee.property.name="$$"]',
message:
'`$$` is discouraged, please use `locator` instead',
},
{
selector:
'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]',
message: 'Prefer page.locator instead.',
},
],
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
},
},
{
files: [ 'bin/**/*.js', 'bin/**/*.mjs', 'packages/env/**' ],
rules: {
'no-console': 'off',
},
},
{
files: typedFiles,
rules: {
'jsdoc/no-undefined-types': 'off',
'jsdoc/valid-types': 'off',
},
},
{
files: [
'**/@(storybook|stories)/*',
'packages/components/src/**/*.tsx',
],
rules: {
// Useful to add story descriptions via JSDoc without specifying params,
// or in TypeScript files where params are likely already documented outside of the JSDoc.
'jsdoc/require-param': 'off',
},
},
{
files: [ 'packages/components/src/**' ],
excludedFiles: [ 'packages/components/src/**/@(test|stories)/**' ],
plugins: [ 'ssr-friendly' ],
extends: [ 'plugin:ssr-friendly/recommended' ],
},
{
files: [ 'packages/block-editor/**' ],
rules: {
'no-restricted-imports': [
'error',
{
paths: [
...restrictedImports,
{
name: '@wordpress/api-fetch',
message:
"block-editor is a generic package that doesn't depend on a server or WordPress backend. To provide WordPress integration, consider passing settings to the BlockEditorProvider components.",
},
{
name: '@wordpress/core-data',
message:
"block-editor is a generic package that doesn't depend on a server or WordPress backend. To provide WordPress integration, consider passing settings to the BlockEditorProvider components.",
},
],
},
],
},
},
],
};