Skip to content

Commit 2c7e436

Browse files
authored
Scheduler: Add Eslint rules according to codestyle (#30853)
1 parent a066e84 commit 2c7e436

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+621
-249
lines changed

packages/devextreme/eslint.config.mjs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import importPlugin from 'eslint-plugin-import';
1212
import globals from 'globals';
1313
import simpleImportSort from 'eslint-plugin-simple-import-sort';
1414
import { changeRulesToStylistic } from 'eslint-migration-utils';
15+
import unicorn from 'eslint-plugin-unicorn';
16+
import customRules from './eslint_plugins/index.js';
1517

1618
const __filename = fileURLToPath(import.meta.url);
1719
const __dirname = path.dirname(__filename);
@@ -47,6 +49,8 @@ export default [
4749
plugins: {
4850
'no-only-tests': noOnlyTests,
4951
i18n: i18N,
52+
unicorn,
53+
'devextreme-custom': customRules,
5054
},
5155
settings: {
5256
'import/resolver': {
@@ -520,6 +524,51 @@ export default [
520524
'@typescript-eslint/prefer-for-of': 'warn',
521525
},
522526
},
527+
// Rules for scheduler
528+
{
529+
files: ['js/__internal/scheduler/**/*.ts?(x)'],
530+
languageOptions: {
531+
parser: tsParser,
532+
ecmaVersion: 5,
533+
sourceType: 'script',
534+
parserOptions: {
535+
project: './tsconfig.json',
536+
tsconfigRootDir: `${__dirname}/js/__internal`,
537+
},
538+
},
539+
rules: {
540+
'no-implicit-coercion': ['error'],
541+
'no-extra-boolean-cast': 'error',
542+
'unicorn/filename-case': ['error', { case: 'snakeCase' }],
543+
'@typescript-eslint/naming-convention': [
544+
'error',
545+
{
546+
selector: ['variable', 'function', 'parameter'],
547+
format: null,
548+
leadingUnderscore: 'forbid',
549+
// allow only a single underscore identifier `_` to bypass this rule
550+
filter: {
551+
regex: '^_$',
552+
match: false,
553+
},
554+
},
555+
{
556+
selector: 'memberLike',
557+
format: null,
558+
leadingUnderscore: 'allow',
559+
},
560+
],
561+
'devextreme-custom/no-deferred': 'error',
562+
'devextreme-custom/prefer-switch-true': ['error', { minBranches: 3 }],
563+
},
564+
},
565+
// Allow Deferred in m_* scheduler files only
566+
{
567+
files: ['js/__internal/scheduler/**/m_*.ts?(x)'],
568+
rules: {
569+
'devextreme-custom/no-deferred': 'off',
570+
},
571+
},
523572
// Rules for grid controls
524573
{
525574
files: [
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const preferSwitchTrue = require('./prefer_switch_true');
2+
const noDeferred = require('./no_deferred');
3+
4+
module.exports = {
5+
rules: {
6+
'prefer-switch-true': preferSwitchTrue,
7+
'no-deferred': noDeferred,
8+
},
9+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module.exports = {
2+
meta: {
3+
type: 'problem',
4+
docs: {
5+
description: 'Disallow Deferred in favor of native Promise',
6+
recommended: false,
7+
},
8+
messages: {
9+
noDeferred: 'Use native Promise instead of Deferred.',
10+
},
11+
schema: [],
12+
},
13+
create(context) {
14+
return {
15+
ImportDeclaration(node) {
16+
const source = node.source && node.source.value;
17+
if(typeof source !== 'string') return;
18+
const specs = node.specifiers || [];
19+
for(let i = 0; i < specs.length; i += 1) {
20+
const spec = specs[i];
21+
if(spec.imported && spec.imported.name === 'Deferred') {
22+
context.report({ node: spec, messageId: 'noDeferred' });
23+
}
24+
}
25+
},
26+
Identifier(node) {
27+
if(node.name === 'Deferred') {
28+
context.report({ node, messageId: 'noDeferred' });
29+
}
30+
},
31+
NewExpression(node) {
32+
// eslint-disable-next-line spellcheck/spell-checker
33+
if(node.callee && node.callee.name === 'Deferred') {
34+
context.report({ node, messageId: 'noDeferred' });
35+
}
36+
},
37+
MemberExpression(node) {
38+
// e.g., $.Deferred()
39+
if(node.property && node.property.type === 'Identifier' && node.property.name === 'Deferred') {
40+
context.report({ node, messageId: 'noDeferred' });
41+
}
42+
},
43+
};
44+
},
45+
};
46+
47+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
module.exports = {
2+
meta: {
3+
type: 'suggestion',
4+
docs: {
5+
description: 'Prefer switch(true) over long if/else chains',
6+
recommended: false,
7+
},
8+
schema: [
9+
{
10+
type: 'object',
11+
properties: {
12+
minBranches: { type: 'integer', minimum: 2 },
13+
},
14+
additionalProperties: false,
15+
},
16+
],
17+
messages: {
18+
preferSwitchTrue: 'Prefer switch(true) over this if/else chain.',
19+
},
20+
},
21+
create(context) {
22+
const options = context.options && context.options[0] || {};
23+
const minBranches = Math.max(2, options.minBranches || 3); // total conditional branches (if + else-if's)
24+
25+
function countChainBranches(ifNode) {
26+
let branches = 1; // start with initial if
27+
let current = ifNode.alternate;
28+
while(current && current.type === 'IfStatement') {
29+
branches += 1;
30+
current = current.alternate;
31+
}
32+
return branches;
33+
}
34+
35+
return {
36+
IfStatement(node) {
37+
// Only evaluate at the head of the chain (parent isn't an else-if)
38+
const parent = node.parent;
39+
const isElseIf = parent && parent.type === 'IfStatement' && parent.alternate === node;
40+
if(isElseIf) return;
41+
42+
const branches = countChainBranches(node);
43+
if(branches >= minBranches) {
44+
context.report({ node, messageId: 'preferSwitchTrue' });
45+
}
46+
},
47+
};
48+
},
49+
};
50+
51+

packages/devextreme/js/__internal/scheduler/__mock__/resourceManager.mock.ts renamed to packages/devextreme/js/__internal/scheduler/__mock__/resource_manager.mock.ts

File renamed without changes.

packages/devextreme/js/__internal/scheduler/__tests__/workspace.base.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
describe, expect, it, jest,
33
} from '@jest/globals';
44

5-
import { getResourceManagerMock } from '../__mock__/resourceManager.mock';
5+
import { getResourceManagerMock } from '../__mock__/resource_manager.mock';
66
import SchedulerTimelineDay from '../workspaces/m_timeline_day';
77
import SchedulerTimelineMonth from '../workspaces/m_timeline_month';
88
import SchedulerTimelineWeek from '../workspaces/m_timeline_week';

packages/devextreme/js/__internal/scheduler/appointment_popup/m_form.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ const getStylingModeFunc = (): string | undefined => (isFluent(current()) ? 'fil
4949
const getStartDateWithStartHour = (startDate, startDayHour) => new Date(new Date(startDate).setHours(startDayHour));
5050

5151
const validateAppointmentFormDate = (editor, value, previousValue) => {
52-
const isCurrentDateCorrect = value === null || !!value;
53-
const isPreviousDateCorrect = previousValue === null || !!previousValue;
52+
const isCurrentDateCorrect = value === null || Boolean(value);
53+
const isPreviousDateCorrect = previousValue === null || Boolean(previousValue);
5454
if (!isCurrentDateCorrect && isPreviousDateCorrect) {
5555
editor.option('value', previousValue);
5656
}

packages/devextreme/js/__internal/scheduler/appointment_popup/m_popup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export class AppointmentPopup {
161161
return {
162162
...rawAppointment,
163163
...rawAppointmentGroupValues,
164-
repeat: !!appointment.recurrenceRule,
164+
repeat: Boolean(appointment.recurrenceRule),
165165
};
166166
}
167167

@@ -251,7 +251,7 @@ export class AppointmentPopup {
251251
const clonedAdapter = adapter
252252
.clone()
253253
.calculateDates(this.scheduler.getTimeZoneCalculator(), 'fromAppointment');
254-
const shouldClearRecurrenceRule = !repeat && !!clonedAdapter.recurrenceRule;
254+
const shouldClearRecurrenceRule = !repeat && Boolean(clonedAdapter.recurrenceRule);
255255

256256
this._addMissingDSTTime(adapter, clonedAdapter);
257257

packages/devextreme/js/__internal/scheduler/appointments/appointment/m_appointment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,11 @@ export class Appointment extends DOMComponent<AppointmentProperties> {
260260
}
261261

262262
_renderAllDayClass() {
263-
(this.$element() as any).toggleClass(ALL_DAY_APPOINTMENT_CLASS, !!this.option('allDay'));
263+
(this.$element() as any).toggleClass(ALL_DAY_APPOINTMENT_CLASS, Boolean(this.option('allDay')));
264264
}
265265

266266
_renderDragSourceClass() {
267-
(this.$element() as any).toggleClass(APPOINTMENT_DRAG_SOURCE_CLASS, !!this.option('isDragSource'));
267+
(this.$element() as any).toggleClass(APPOINTMENT_DRAG_SOURCE_CLASS, Boolean(this.option('isDragSource')));
268268
}
269269

270270
_renderRecurrenceClass() {

packages/devextreme/js/__internal/scheduler/appointments/m_appointment_layout.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export const createAgendaAppointmentLayout = (formatText, config) => {
5050
.addClass('dx-scheduler-agenda-appointment-right-layout')
5151
.appendTo(result);
5252

53-
// eslint-disable-next-line no-unused-vars
5453
const marker = $('<div>')
5554
.addClass(APPOINTMENT_CONTENT_CLASSES.AGENDA_MARKER)
5655
.appendTo(leftLayoutContainer);

0 commit comments

Comments
 (0)