Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eslint/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
'no-get-block-parents': require( './rules/no-get-block-parents' ),
'no-use-dispatch': require( './rules/no-use-dispatch' ),
'no-import-create-root': require( './rules/no-import-create-root' ),
'no-function-default-props': require( './rules/no-function-default-props' ),
},
configs: {
recommended: {
Expand All @@ -23,6 +24,7 @@ module.exports = {
'stackable/no-get-block-parents': 'error',
'stackable/no-use-dispatch': 'error',
'stackable/no-import-create-root': 'error',
'stackable/no-function-default-props': 'error',
},
},
},
Expand Down
49 changes: 49 additions & 0 deletions eslint/rules/__test__/no-function-default-props.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Internal dependencies
*/
import { RuleTester } from 'eslint'
import rule from '../no-function-default-props'

const ruleTester = new RuleTester( {
parser: require.resolve( 'babel-eslint' ),
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
} )

ruleTester.run( 'no-function-default-props', rule, {
valid: [
`class Button {}
Button.defaultProps = { label: '' }`,
`class Button {
static defaultProps = { label: '' }
}`,
'const value = WrappedComponent.defaultProps || {}',
`const Button = forwardRef( props => props.label )
Button.defaultProps = { label: '' }`,
],
invalid: [
{
code: `const LinkControl = props => null
LinkControl.defaultProps = { screens: [ 'desktop' ] }`,
errors: [ { messageId: 'noFunctionDefaultProps' } ],
},
{
code: `function LinkControl( props ) {
return props.label
}
LinkControl.defaultProps = { label: '' }`,
errors: [ { messageId: 'noFunctionDefaultProps' } ],
},
{
code: `const LinkControl = function( props ) {
return props.label
}
LinkControl.defaultProps = { label: '' }`,
errors: [ { messageId: 'noFunctionDefaultProps' } ],
},
],
} )
79 changes: 79 additions & 0 deletions eslint/rules/no-function-default-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* React 19 ignores `defaultProps` on function components. The component then
* reads `undefined` and can crash, which is what `props.tabs.includes` did
* in the inspector tabs.
*
* Class components may still set `defaultProps`. React 19 still applies those.
*
* @see https://github.com/gambitph/Stackable/pull/3767
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow defaultProps on function components',
recommended: true,
url: 'https://github.com/gambitph/Stackable/pull/3767',
},
messages: {
noFunctionDefaultProps: 'Do not set defaultProps on a function component. React 19 ignores them, so the defaults never apply. Pass the defaults as function arguments, or use getPropsWithDefaults().',
},
schema: [],
},
create: context => {
const isDefaultPropsMember = node => {
if ( ! node || node.type !== 'MemberExpression' || node.computed ) {
return false
}
const property = node.property
return ( property.type === 'Identifier' && property.name === 'defaultProps' ) ||
( property.type === 'Literal' && property.value === 'defaultProps' )
}

const findVariable = name => {
let scope = context.getScope()
while ( scope ) {
const variable = scope.set && scope.set.get( name )
if ( variable ) {
return variable
}
scope = scope.upper
}
return null
}

// True only when the name is a function or an arrow function.
// Classes, imports, and higher-order wrappers are left alone.
const isFunctionComponent = name => {
const variable = findVariable( name )
if ( ! variable ) {
return false
}
return variable.defs.some( def => {
if ( def.type === 'FunctionName' ) {
return true
}
if ( def.type !== 'Variable' || ! def.node || ! def.node.init ) {
return false
}
const initType = def.node.init.type
return initType === 'ArrowFunctionExpression' || initType === 'FunctionExpression'
} )
}

return {
AssignmentExpression: node => {
if ( ! isDefaultPropsMember( node.left ) || node.left.object.type !== 'Identifier' ) {
return
}
if ( ! isFunctionComponent( node.left.object.name ) ) {
return
}
context.report( {
node,
messageId: 'noFunctionDefaultProps',
} )
},
}
},
}
11 changes: 7 additions & 4 deletions src/__block-container-template/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import { BlockCssCompiler } from '~stackable/components'
*/
import { memo } from '@wordpress/element'
import { applyFilters } from '@wordpress/hooks'
import { getPropsWithDefaults } from '../util/props'

const alignmentOptions = {
editorSelectorCallback: getAttribute => `.stk--block-align-${ getAttribute( 'uniqueId' ) } > .block-editor-inner-blocks > .block-editor-block-list__layout`,
}

const BlockStyles = memo( props => {
const BlockStyles = memo( incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_STYLES_DEFAULT_PROPS )
const columnArrangement = useBlockAttributesContext( attributes => attributes.columnArrangementMobile || attributes.columnArrangementTablet )
const numColumns = ( columnArrangement || '' ).split( ',' ).length

Expand All @@ -45,11 +47,12 @@ const BlockStyles = memo( props => {
)
} )

BlockStyles.defaultProps = {
const BLOCK_STYLES_DEFAULT_PROPS = {
version: '',
}

BlockStyles.Content = props => {
BlockStyles.Content = incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_STYLES_CONTENT_DEFAULT_PROPS )
if ( props.attributes.generatedCss ) {
return <style>{ props.attributes.generatedCss }</style>
}
Expand All @@ -72,7 +75,7 @@ BlockStyles.Content = props => {
)
}

BlockStyles.Content.defaultProps = {
const BLOCK_STYLES_CONTENT_DEFAULT_PROPS = {
version: '',
attributes: {},
}
Expand Down
11 changes: 7 additions & 4 deletions src/__block-template/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
* WordPress dependencies
*/
import { memo } from '@wordpress/element'
import { getPropsWithDefaults } from '../util/props'

const typographyOptions = {
selector: '.stk-block-text__text',
Expand Down Expand Up @@ -48,7 +49,8 @@ const Styles = () => {
)
}

export const BlockStyles = memo( props => {
export const BlockStyles = memo( incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_STYLES_DEFAULT_PROPS )
return (
<>
<Alignment.Style { ...props } />
Expand All @@ -62,11 +64,12 @@ export const BlockStyles = memo( props => {
)
} )

BlockStyles.defaultProps = {
const BLOCK_STYLES_DEFAULT_PROPS = {
version: '',
}

BlockStyles.Content = props => {
BlockStyles.Content = incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_STYLES_CONTENT_DEFAULT_PROPS )
if ( props.attributes.generatedCss ) {
return <style>{ props.attributes.generatedCss }</style>
}
Expand All @@ -85,7 +88,7 @@ BlockStyles.Content = props => {
)
}

BlockStyles.Content.defaultProps = {
const BLOCK_STYLES_CONTENT_DEFAULT_PROPS = {
version: '',
attributes: {},
}
Expand Down
6 changes: 4 additions & 2 deletions src/block-components/alignment/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { memo } from '@wordpress/element'
import { sprintf, __ } from '@wordpress/i18n'
import { addFilter } from '@wordpress/hooks'
import { useSelect } from '@wordpress/data'
import { getPropsWithDefaults } from '../../util/props'

const ALIGN_OPTIONS = [
{
Expand All @@ -53,7 +54,8 @@ const ALIGN_OPTIONS = [

const ALIGN_OPTIONS_NO_JUSTIFY = ALIGN_OPTIONS.filter( option => option.align !== 'justify' )

export const Edit = memo( props => {
export const Edit = memo( incomingProps => {
const props = getPropsWithDefaults( incomingProps, EDIT_DEFAULT_PROPS )
const {
contentAlign,
columnJustify,
Expand Down Expand Up @@ -382,7 +384,7 @@ if ( userAgent && userAgent.indexOf( 'Firefox' ) !== -1 ) {
} )
}

Edit.defaultProps = {
const EDIT_DEFAULT_PROPS = {
hasColumnJustify: false,
hasRowAlignment: false,
hasColumnAlignment: false,
Expand Down
6 changes: 4 additions & 2 deletions src/block-components/block-div/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ import {
*/
import { __ } from '@wordpress/i18n'
import { memo } from '@wordpress/element'
import { getPropsWithDefaults } from '../../util/props'

export const Edit = memo( props => {
export const Edit = memo( incomingProps => {
const props = getPropsWithDefaults( incomingProps, EDIT_DEFAULT_PROPS )
const {
hasSizeSpacing,
initialOpen,
Expand Down Expand Up @@ -126,7 +128,7 @@ export const Edit = memo( props => {
)
} )

Edit.defaultProps = {
const EDIT_DEFAULT_PROPS = {
hasSizeSpacing: true,
initialOpen: false,
sizeControlLayoutProps: {},
Expand Down
11 changes: 7 additions & 4 deletions src/block-components/block-div/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import classnames from 'classnames/dedupe'
import { Div } from '~stackable/components'
import { useVariationPicker } from '~stackable/hooks'
import { getUniqueBlockClass, useQueryLoopInstanceId } from '~stackable/util'
import { getPropsWithDefaults } from '../../util/props'
import { memo } from '@wordpress/element'

import { useBlockProps } from '@wordpress/block-editor'
Expand All @@ -22,7 +23,8 @@ export {
deprecateBlockBackgroundColorOpacity, deprecateBlockShadowColor, deprecateBlockHeight,
} from './deprecated'

export const BlockDiv = memo( props => {
export const BlockDiv = memo( incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_DIV_DEFAULT_PROPS )
const {
className,
applyCustomAttributes,
Expand Down Expand Up @@ -103,7 +105,7 @@ export const BlockDiv = memo( props => {
</Div>
} )

BlockDiv.defaultProps = {
const BLOCK_DIV_DEFAULT_PROPS = {
className: '',
applyCustomAttributes: true,
renderHtmlTag: true, // If true, this renders the HTML Tag based from the block attributes.
Expand All @@ -113,7 +115,8 @@ BlockDiv.defaultProps = {
blockTag: 'div',
}

BlockDiv.Content = props => {
BlockDiv.Content = incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_DIV_CONTENT_DEFAULT_PROPS )
const {
className,
attributes,
Expand Down Expand Up @@ -169,7 +172,7 @@ BlockDiv.Content = props => {
/>
}

BlockDiv.Content.defaultProps = {
const BLOCK_DIV_CONTENT_DEFAULT_PROPS = {
version: VERSION,
className: '',
attributes: {},
Expand Down
6 changes: 4 additions & 2 deletions src/block-components/block-link/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import {
*/
import { __ } from '@wordpress/i18n'
import { LinkControls } from '../helpers/link'
import { getPropsWithDefaults } from '../../util/props'

export const Edit = props => {
export const Edit = incomingProps => {
const props = getPropsWithDefaults( incomingProps, EDIT_DEFAULT_PROPS )
const {
hasLink,
hasTitle,
Expand Down Expand Up @@ -41,7 +43,7 @@ export const Edit = props => {
)
}

Edit.defaultProps = {
const EDIT_DEFAULT_PROPS = {
hasLink: true,
hasTitle: true,
hasLightbox: true,
Expand Down
6 changes: 4 additions & 2 deletions src/block-components/block-link/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { Edit } from './edit'
* External dependencies
*/
import { Link } from '~stackable/components'
import { getPropsWithDefaults } from '../../util/props'

export const BlockLink = () => {
return null
}

BlockLink.Content = props => {
BlockLink.Content = incomingProps => {
const props = getPropsWithDefaults( incomingProps, BLOCK_LINK_CONTENT_DEFAULT_PROPS )
const {
href, attributes, isHidden,
} = props
Expand All @@ -35,7 +37,7 @@ BlockLink.Content = props => {
)
}

BlockLink.Content.defaultProps = {
const BLOCK_LINK_CONTENT_DEFAULT_PROPS = {
className: '',
attributes: {},
href: '',
Expand Down
6 changes: 4 additions & 2 deletions src/block-components/block-style/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import {
*/
import { __ } from '@wordpress/i18n'
import { memo } from '@wordpress/element'
import { getPropsWithDefaults } from '../../util/props'

export const Edit = memo( props => {
export const Edit = memo( incomingProps => {
const props = getPropsWithDefaults( incomingProps, EDIT_DEFAULT_PROPS )
return (
<InspectorStyleControls>
<PanelAdvancedSettings
Expand All @@ -34,7 +36,7 @@ export const Edit = memo( props => {
)
} )

Edit.defaultProps = {
const EDIT_DEFAULT_PROPS = {
initialOpen: true,
styles: [],
}
Loading
Loading