Add coexistence checks to all enqueue methods to prevent loading both React and Grid.js assets simultaneously. Changes: - ReactAdmin.php: Only enqueue React assets when ?react=1 - Init.php: Skip Grid.js when React active on admin pages - Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0 - Customer.php, Product.php, License.php: Add coexistence checks Now the toggle between Classic and React versions works correctly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
250 lines
6.4 KiB
JavaScript
250 lines
6.4 KiB
JavaScript
/**
|
|
* Converts store name to variable name.
|
|
* Removes dashes and uppercases the characters after dashes and appends `Store` at the end.
|
|
*
|
|
* @param {string} storeName
|
|
* @return {string} store name as variable name
|
|
*/
|
|
function storeNameToVariableNames( storeName ) {
|
|
return (
|
|
storeName
|
|
.split( '-' )
|
|
.map( ( value, index ) =>
|
|
index === 0
|
|
? value.toLowerCase()
|
|
: value[ 0 ].toUpperCase() + value.slice( 1 ).toLowerCase()
|
|
)
|
|
.join( '' ) + 'Store'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns last element of an array.
|
|
*
|
|
* @param {Array} array
|
|
* @return {*} last element of the array
|
|
*/
|
|
function arrayLast( array ) {
|
|
return array[ array.length - 1 ];
|
|
}
|
|
|
|
function getReferences( context, specifiers ) {
|
|
const variables = specifiers.reduce(
|
|
( acc, specifier ) =>
|
|
acc.concat( context.getDeclaredVariables( specifier ) ),
|
|
[]
|
|
);
|
|
const references = variables.reduce(
|
|
( acc, variable ) => acc.concat( variable.references ),
|
|
[]
|
|
);
|
|
return references;
|
|
}
|
|
|
|
function collectAllNodesFromCallbackFunctions( context, node ) {
|
|
const functionSpecifiers = node.specifiers.filter(
|
|
( specifier ) =>
|
|
specifier.imported &&
|
|
[
|
|
'createRegistrySelector',
|
|
'useSelect',
|
|
'withSelect',
|
|
'withDispatch',
|
|
].includes( specifier.imported.name )
|
|
);
|
|
const functionReferences = getReferences( context, functionSpecifiers );
|
|
|
|
const functionArgumentVariables = functionReferences.reduce(
|
|
( acc, { identifier: { parent } } ) =>
|
|
parent && parent.arguments && parent.arguments.length > 0
|
|
? acc.concat(
|
|
context.getDeclaredVariables( parent.arguments[ 0 ] )
|
|
)
|
|
: acc,
|
|
[]
|
|
);
|
|
const functionArgumentReferences = functionArgumentVariables.reduce(
|
|
( acc, variable ) => acc.concat( variable.references ),
|
|
[]
|
|
);
|
|
const possibleCallExpressionNodes = functionArgumentReferences
|
|
.filter( ( reference ) => reference.identifier.parent )
|
|
.map( ( reference ) => reference.identifier.parent );
|
|
|
|
return possibleCallExpressionNodes;
|
|
}
|
|
|
|
function collectAllNodesFromDirectFunctionCalls( context, node ) {
|
|
const specifiers = node.specifiers.filter(
|
|
( specifier ) =>
|
|
specifier.imported &&
|
|
[
|
|
'useDispatch',
|
|
'dispatch',
|
|
'useSelect',
|
|
'select',
|
|
'resolveSelect',
|
|
].includes( specifier.imported.name )
|
|
);
|
|
const references = getReferences( context, specifiers );
|
|
const possibleCallExpressionNodes = references
|
|
.filter( ( reference ) => reference.identifier.parent )
|
|
.map( ( reference ) => reference.identifier.parent );
|
|
|
|
return possibleCallExpressionNodes;
|
|
}
|
|
|
|
function collectAllNodesFromObjectPropertyFunctionCalls( context, node ) {
|
|
const specifiers = node.specifiers.filter(
|
|
( specifier ) =>
|
|
specifier.imported &&
|
|
[ 'controls' ].includes( specifier.imported.name )
|
|
);
|
|
const references = getReferences( context, specifiers );
|
|
const referencesWithPropertyCalls = references.filter(
|
|
( reference ) =>
|
|
reference.identifier.parent.property &&
|
|
[ 'select', 'resolveSelect', 'dispatch' ].includes(
|
|
reference.identifier.parent.property.name
|
|
)
|
|
);
|
|
const possibleCallExpressionNodes = referencesWithPropertyCalls
|
|
.filter(
|
|
( reference ) =>
|
|
reference.identifier.parent &&
|
|
reference.identifier.parent.parent
|
|
)
|
|
.map( ( reference ) => reference.identifier.parent.parent );
|
|
|
|
return possibleCallExpressionNodes;
|
|
}
|
|
|
|
function getSuggest( context, callNode ) {
|
|
return [
|
|
{
|
|
desc: 'Replace literal with store definition. Import store if necessary.',
|
|
fix: ( fixer ) => getFixes( fixer, context, callNode ),
|
|
},
|
|
];
|
|
}
|
|
|
|
function getFixes( fixer, context, callNode ) {
|
|
const storeName = callNode.arguments[ 0 ].value;
|
|
const storeDefinitions = {
|
|
core: {
|
|
import: '@wordpress/core-data',
|
|
variable: 'coreStore',
|
|
},
|
|
};
|
|
let storeDefinition = storeDefinitions[ storeName ];
|
|
if ( ! storeDefinition && storeName.startsWith( 'core/' ) ) {
|
|
const storeNameWithoutCore = storeName.substring( 5 );
|
|
storeDefinition = {
|
|
import: `@wordpress/${ storeNameWithoutCore }`,
|
|
variable: storeNameToVariableNames( storeNameWithoutCore ),
|
|
};
|
|
}
|
|
if ( ! storeDefinition ) {
|
|
return null;
|
|
}
|
|
const { variable: variableName, import: importName } = storeDefinition;
|
|
|
|
const fixes = [
|
|
fixer.replaceText( callNode.arguments[ 0 ], variableName ),
|
|
];
|
|
|
|
const imports = context
|
|
.getAncestors()[ 0 ]
|
|
.body.filter( ( node ) => node.type === 'ImportDeclaration' );
|
|
const packageImports = imports.filter(
|
|
( node ) => node.source.value === importName
|
|
);
|
|
const packageImport =
|
|
packageImports.length > 0 ? packageImports[ 0 ] : null;
|
|
if ( packageImport ) {
|
|
const alreadyHasStore = packageImport.specifiers.some(
|
|
( specifier ) => specifier.imported.name === 'store'
|
|
);
|
|
if ( ! alreadyHasStore ) {
|
|
const lastSpecifier = arrayLast( packageImport.specifiers );
|
|
fixes.push(
|
|
fixer.insertTextAfter(
|
|
lastSpecifier,
|
|
`,store as ${ variableName }`
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
const wpImports = imports.filter( ( node ) =>
|
|
node.source.value.startsWith( '@wordpress/' )
|
|
);
|
|
const lastImport =
|
|
wpImports.length > 0
|
|
? arrayLast( wpImports )
|
|
: arrayLast( imports );
|
|
|
|
fixes.push(
|
|
fixer.insertTextAfter(
|
|
lastImport,
|
|
`\nimport { store as ${ variableName } } from '${ importName }';`
|
|
)
|
|
);
|
|
}
|
|
|
|
return fixes;
|
|
}
|
|
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
hasSuggestions: true,
|
|
schema: [],
|
|
messages: {
|
|
doNotUseStringLiteral: `Do not use string literals ( '{{ argument }}' ) for accessing @wordpress/data stores. Pass the store definition instead`,
|
|
},
|
|
},
|
|
create( context ) {
|
|
return {
|
|
ImportDeclaration( node ) {
|
|
if ( node.source.value !== '@wordpress/data' ) {
|
|
return;
|
|
}
|
|
|
|
const callbackFunctionNodes =
|
|
collectAllNodesFromCallbackFunctions( context, node );
|
|
const directNodes = collectAllNodesFromDirectFunctionCalls(
|
|
context,
|
|
node
|
|
);
|
|
const objectPropertyCallNodes =
|
|
collectAllNodesFromObjectPropertyFunctionCalls(
|
|
context,
|
|
node
|
|
);
|
|
|
|
const allNodes = [
|
|
...callbackFunctionNodes,
|
|
...directNodes,
|
|
...objectPropertyCallNodes,
|
|
];
|
|
allNodes
|
|
.filter(
|
|
( callNode ) =>
|
|
callNode &&
|
|
callNode.type === 'CallExpression' &&
|
|
callNode.arguments.length > 0 &&
|
|
callNode.arguments[ 0 ].type === 'Literal'
|
|
)
|
|
.forEach( ( callNode ) => {
|
|
context.report( {
|
|
node: callNode.parent,
|
|
messageId: 'doNotUseStringLiteral',
|
|
data: { argument: callNode.arguments[ 0 ].value },
|
|
suggest: getSuggest( context, callNode ),
|
|
} );
|
|
} );
|
|
},
|
|
};
|
|
},
|
|
};
|