eslint-plugin-criteo
Version:
An opinionated collection of ESLint shared configs and rules used by Criteo.
99 lines (85 loc) • 3.7 kB
JavaScript
/**
* @fileoverview ngx-component-display
* @author Xavier Dupessey
*/
;
const moreInfo = `More info: https://github.com/criteo/eslint-plugin-criteo#ngx-component-display`;
export default {
meta: {
type: 'suggestion',
docs: {
description: `Define components' display mode to avoid them being rendered as inline (default value if unspecified)`,
recommended: 'error',
},
messages: {
missingProperty: `Missing '{{ propertyName }}' property in component '{{ name }}'. Please define '@HostBinding('class.cds-display-block') readonly {{ propertyName }} = true;'. Valid values: cds-display-block, cds-display-inline, cds-display-inline-block. With the default config, this rule is disabled for components whose names end with 'DialogComponent' or 'ModalComponent'. ${moreInfo}`,
missingReadonly: `'{{ propertyName }}' property should be 'readonly' in component '{{ name }}'. ${moreInfo}`,
invalidValue: `{{ propertyName }} value should be 'true' in component '{{ name }}'. ${moreInfo}`,
missingDecorator: `'{{ propertyName }}' property should be decorated with '@HostBinding()' in component '{{ name }}'. ${moreInfo}`,
invalidDecoratorValue: `Invalid '@HostBinding' value on '{{ propertyName }}' property in component '{{ name }}'. Valid values: cds-display-block, cds-display-inline, cds-display-inline-block. ${moreInfo}`,
},
schema: [
{
type: 'object',
properties: {
ignore: { type: 'string' },
propertyName: { type: 'string' },
},
additionalProperties: false,
},
],
},
create(context) {
const options = context.options[0] || {};
const ignore = new RegExp(options.ignore || '^.*(?:Dialog|Modal)Component$');
const propertyName = options.propertyName || 'cdsDisplay';
return {
ClassDeclaration: function (node) {
// The rule is only relevant on components
if (!(node.decorators || []).some((decorator) => decorator.expression.callee.name === 'Component')) {
return;
}
// Skip if component's name is explicitly allowed
if (ignore.test(node.id.name)) {
return;
}
// Try to get the display property
const displayNode = node.body.body
.filter((n) => n.type === 'ClassProperty' || n.type === 'PropertyDefinition')
.find((n) => n.key.name === propertyName);
if (!displayNode) {
return context.report(buildReport(node, 'missingProperty', propertyName));
}
// Check it is readonly
if (!displayNode.readonly) {
return context.report(buildReport(node, 'missingReadonly', propertyName));
}
// Check value is true
if (displayNode.value.value !== true) {
return context.report(buildReport(node, 'invalidValue', propertyName));
}
// Try to get the HostBinding decorator
const decorator = (displayNode.decorators || []).find(
(decorator) => decorator.expression.callee.name === 'HostBinding',
);
if (!decorator) {
return context.report(buildReport(node, 'missingDecorator', propertyName));
}
// Check decorator value is valid
if (
decorator.expression.arguments.length !== 1 ||
!decorator.expression.arguments[0].value.startsWith('class.cds-display-')
) {
return context.report(buildReport(node, 'invalidDecoratorValue', propertyName));
}
},
};
},
};
function buildReport(node, messageId, propertyName) {
return {
data: { name: node.id.name, propertyName },
messageId,
node,
};
}