woocommerce/plugins/woocommerce-blocks/bin/eslint-plugin-woocommerce/rules/feature-flag.js

256 lines
6.8 KiB
JavaScript
Raw Normal View History

/**
* Traverse up through the chain of parent AST nodes returning the first parent
* the predicate returns a truthy value for.
*
* @param {Object} sourceNode The AST node to search from.
* @param {Function} predicate A predicate invoked for each parent.
*
* @return {?Object } The first encountered parent node where the predicate
* returns a truthy value.
*/
function findParent( sourceNode, predicate ) {
if ( ! sourceNode.parent ) {
return;
}
if ( predicate( sourceNode.parent ) ) {
return sourceNode.parent;
}
return findParent( sourceNode.parent, predicate );
}
/**
* Tests whether the WOOCOMMERCE_BLOCKS_PHASE variable is accessed via
* `process.env.WOOCOMMERCE_BLOCKS_PHASE`.
*
* @example
* ```js
* // good
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE > 1 ) {
*
* // bad
* if ( WOOCOMMERCE_BLOCKS_PHASE > 1 ) {
* ```
*
* @param {Object} node The WOOCOMMERCE_BLOCKS_PHASE identifier node.
* @param {Object} context The eslint context object.
* @todo update this rule to match the new flags.
*/
function testIsAccessedViaProcessEnv( node, context ) {
let parent = node.parent;
if (
parent &&
parent.type === 'MemberExpression' &&
context.getSource( parent ) === 'process.env.WOOCOMMERCE_BLOCKS_PHASE'
) {
return;
}
if ( parent && parent.type === 'BinaryExpression' ) {
if ( parent.left.type === 'Identifier' ) {
parent = parent.left;
} else {
parent = parent.right;
}
}
context.report( {
node,
messageId: 'accessedViaEnv',
fix( fixer ) {
return fixer.replaceText(
parent,
'process.env.WOOCOMMERCE_BLOCKS_PHASE'
);
},
} );
}
/**
* Tests whether the WOOCOMMERCE_BLOCKS_PHASE strict binary comparison
* is strict equal only
*
* @example
* ```js
* // good
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE === 'experimental' ) {
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE === 'stable' ) {
*
* // bad
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE !== 'experimental' ) {
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE !== 'stable' ) {
* ```
*
* @param {Object} node The WOOCOMMERCE_BLOCKS_PHASE identifier node.
* @param {Object} context The eslint context object.
*/
function testBinaryExpressionOperatorIsEqual( node, context ) {
const sourceCode = context.getSourceCode();
node = node.parent.parent;
if ( node.type === 'BinaryExpression' ) {
const operatorToken = sourceCode.getFirstTokenBetween(
node.left,
node.right,
( token ) => token.value === node.operator
);
if ( operatorToken.value === '===' ) {
return;
}
context.report( {
node,
loc: operatorToken.loc,
messageId: 'equalOperator',
fix( fixer ) {
return fixer.replaceText( operatorToken, '===' );
},
} );
}
}
/**
* Tests whether the WOOCOMMERCE_BLOCKS_PHASE variable is used in a strict binary
* equality expression in a comparison with a enum of string flags, triggering a
* violation if not.
*
* @example
* ```js
* // good
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE === 'experimental' ) {
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE === 'stable' ) {
*
* // bad
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE == 'experimental' ) {
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE === 'core' ) {
* ```
*
* @param {Object} node The WOOCOMMERCE_BLOCKS_PHASE identifier node.
* @param {Object} context The eslint context object.
*/
function testIsUsedInStrictBinaryExpression( node, context ) {
const parent = findParent(
node,
( candidate ) => candidate.type === 'BinaryExpression'
);
const flags = [ 'experimental', 'stable' ];
let providedFlag;
if ( parent ) {
const comparisonNode =
node.parent.type === 'MemberExpression' ? node.parent : node;
const hasCorrectOperands =
( parent.left === comparisonNode &&
flags.includes( parent.right.value ) ) ||
( parent.right === comparisonNode &&
flags.includes( parent.left.value ) );
if (
parent.left === comparisonNode &&
typeof parent.right.value === 'string'
) {
providedFlag = parent.right;
} else {
providedFlag = parent.left;
}
if ( hasCorrectOperands ) {
return;
}
}
if ( providedFlag && providedFlag.loc ) {
context.report( {
node,
loc: providedFlag.loc,
messageId: 'whiteListedFlag',
data: {
flags: flags.join( ', ' ),
},
fix( fixer ) {
return fixer.replaceText( providedFlag, "'experimental'" );
},
} );
}
}
/**
* Tests whether the WOOCOMMERCE_BLOCKS_PHASE variable is used as the condition for an
* if statement, triggering a violation if not.
*
* @example
* ```js
* // good
* if ( process.env.WOOCOMMERCE_BLOCKS_PHASE === 'experimental' ) {
*
* // bad
* const isFeatureActive = process.env.WOOCOMMERCE_BLOCKS_PHASE === 'experimental';
* ```
*
* @param {Object} node The WOOCOMMERCE_BLOCKS_PHASE identifier node.
* @param {Object} context The eslint context object.
*/
function testIsUsedInIfOrTernary( node, context ) {
const conditionalParent = findParent( node, ( candidate ) =>
[ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type )
);
const binaryParent = findParent(
node,
( candidate ) => candidate.type === 'BinaryExpression'
);
if (
conditionalParent &&
binaryParent &&
conditionalParent.test &&
Add @woocommerce/eslint-plugin dependency (https://github.com/woocommerce/woocommerce-blocks/pull/3115) * convert eslint config to use @woocommerce/eslint-plugin - removes unnecessary dependencies - adds e2e-tests/specs to eslint ignore (they are automatically generated) - turns off rules that will be handled in subsequent pulls (to avoid a mammoth changeset for review). - NOTE: prettier config needs left in because of a bug with the existing version of `@wordpress/eslint-plugin` pulled in (fixed in https://github.com/WordPress/gutenberg/pull/25068) so I left the file for now. * prettier fixes. * remove obsolete plugin and fixes for eslint update This branch brings an update to eslint which also changes some syntax with plugins. So this commit: - fixes featuer-flag plugin syntax. - removed obsolete dependency-group plugin (which is now in the `@woocommerce/eslint-plugin` configuration. * add to-do comment * fixes for test runs - this also converts our e2e test scripts to use `wp-script test:e2e`, an advantage of this is it will load CHROMIUM on demand for the e2e test run. * fixes for test runs - this also converts our e2e test scripts to use `wp-script test:e2e`, an advantage of this is it will load CHROMIUM on demand for the e2e test run. * include prettier alias as a dependency This has to be done because prettier is installed with storybook and thus the alias setup in `@wordpress/scripts` is over-ridden by the storybook import. * another attempt at e2e-test-fix * add some debugging and temporarily just add one e2e config test for travis * more debugging * try installing full puppeteer and see if fixes * fix package-lock? * setupSettings separately from other fixture loading * add debugging of files * add another console.log (hopefully trigger travis) * split out blockPage creation to it’s own as well * fixed! remove debugging and re-enable travis configs for entire test suite * fix config and rename e2e-tests to e2e - fixes the failing product-search test - tests/e2e-tests was redundant, I changed to `tests/e2e` (this follows a file pattern change made in woocommerce core as well). * add todo for some eslint properties * remove unnecessary early function execution * revert earlier commit and remove duplicate call to createBlockPages
2020-09-07 17:31:10 +00:00
conditionalParent.test.range[ 0 ] === binaryParent.range[ 0 ] &&
conditionalParent.test.range[ 1 ] === binaryParent.range[ 1 ]
) {
return;
}
context.report( {
node,
messageId: 'noTernary',
} );
}
module.exports = {
meta: {
type: 'problem',
schema: [],
fixable: 'code',
messages: {
accessedViaEnv:
'The `WOOCOMMERCE_BLOCKS_PHASE` constant should be accessed using `process.env.WOOCOMMERCE_BLOCKS_PHASE`.',
whiteListedFlag:
'The `WOOCOMMERCE_BLOCKS_PHASE` constant should only be used in a strict equality comparison with a predefined flag of: {{ flags }}.',
equalOperator:
'The `WOOCOMMERCE_BLOCKS_PHASE` comparison should only be a strict equal `===`, if you need `!==` try switching the flag ',
noTernary:
'The `WOOCOMMERCE_BLOCKS_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.',
},
},
create( context ) {
return {
Identifier( node ) {
// Bypass any identifiers with a node name different to `WOOCOMMERCE_BLOCKS_PHASE`.
if ( node.name !== 'WOOCOMMERCE_BLOCKS_PHASE' ) {
return;
}
testIsAccessedViaProcessEnv( node, context );
testIsUsedInStrictBinaryExpression( node, context );
testBinaryExpressionOperatorIsEqual( node, context );
testIsUsedInIfOrTernary( node, context );
},
Literal( node ) {
// Bypass any identifiers with a node value different to `WOOCOMMERCE_BLOCKS_PHASE`.
if ( node.value !== 'WOOCOMMERCE_BLOCKS_PHASE' ) {
return;
}
if ( node.parent && node.parent.type === 'MemberExpression' ) {
testIsAccessedViaProcessEnv( node, context );
}
},
};
},
};