Skip to content

Commit faafb5d

Browse files
committed
fix(gantt): limit loop if excluding all dates
Add an iteration limit to `fixTaskDates` to prevent infinite loops (i.e. when `excludes` is used to exclude every possible date). I've picked 10k days, in case some users are using `dateFormat` and `excludes` to exclude entire years, since 10k days is 27 years, and anything above that starts to have noticable lag.
1 parent 9670f3d commit faafb5d

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

.changeset/common-parents-serve.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'mermaid': patch
3+
---
4+
5+
fix(gantt): add iteration limit for `excludes` field

packages/mermaid/src/diagrams/gantt/ganttDb.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,21 +245,28 @@ const checkTaskDates = function (task, dateFormat, excludes, includes) {
245245
* @param {dayjs.Dayjs} startTime - The start time.
246246
* @param {dayjs.Dayjs} endTime - The original end time (will return a different end time if it's invalid).
247247
* @param {string} dateFormat - Dayjs date format string.
248-
* @param {*} excludes
249-
* @param {*} includes
248+
* @param {string[]} excludes - Dates or days to exclude.
249+
* @param {string[]} includes - Dates to always include, even if they match the excludes.
250250
* @returns {[endTime: dayjs.Dayjs, renderEndTime: Date | null]} The new `endTime`, and the end time to render.
251251
* `renderEndTime` may be `null` if `startTime` is newer than `endTime`.
252+
* @throws {Error} If a valid end time cannot be found after 10,000 iterations.
252253
*/
253254
const fixTaskDates = function (startTime, endTime, dateFormat, excludes, includes) {
254255
let invalid = false;
255256
let renderEndTime = null;
257+
const maxEndTime = endTime.add(10000, 'd');
256258
while (startTime <= endTime) {
257259
if (!invalid) {
258260
renderEndTime = endTime.toDate();
259261
}
260262
invalid = isInvalidDate(startTime, dateFormat, excludes, includes);
261263
if (invalid) {
262264
endTime = endTime.add(1, 'd');
265+
if (endTime > maxEndTime) {
266+
throw new Error(
267+
'Failed to find a valid date that was not excluded by `excludes` after 10,000 iterations.'
268+
);
269+
}
263270
}
264271
startTime = startTime.add(1, 'd');
265272
}

packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,20 @@ describe('when using the ganttDb', function () {
282282
expect(tasks[0].task).toEqual('test1');
283283
});
284284

285+
it('should not infinite loop when excluding everything', function () {
286+
ganttDb.setDateFormat('YYYY-MM-DD');
287+
ganttDb.setExcludes('weekends,monday,tuesday,wednesday,thursday,friday');
288+
ganttDb.setWeekend('saturday');
289+
ganttDb.addSection('weekends skip test');
290+
ganttDb.addTask('test1', 'id1,2019-02-01,7d');
291+
292+
expect(() => ganttDb.getTasks()).toThrowError('Failed to find a valid date');
293+
294+
// Fridays are now allowed, so it should not throw an error
295+
ganttDb.setExcludes('weekends,monday,tuesday,wednesday,thursday');
296+
expect(() => ganttDb.getTasks()).not.toThrow();
297+
});
298+
285299
it('should maintain the order in which tasks are created', function () {
286300
ganttDb.setAccTitle('Project Execution');
287301
ganttDb.setDateFormat('YYYY-MM-DD');

0 commit comments

Comments
 (0)