Skip to content

Commit 5e10db0

Browse files
committed
Remove assert usage for better compability. Add and update test cases to code that was missing proper test cases
1 parent 9ddf705 commit 5e10db0

8 files changed

+413
-237
lines changed

src/CronExpression.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from 'assert';
21
import { DateTime } from 'luxon';
32

43
import { CronDate, DateMathOp, TimeUnit } from './CronDate';
@@ -117,7 +116,9 @@ export class CronExpression {
117116

118117
// The first character represents the weekday
119118
const weekday = parseInt(expression.toString().charAt(0), 10) % 7;
120-
assert(!Number.isNaN(weekday), `Invalid last weekday of the month expression: ${expression}`);
119+
if (Number.isNaN(weekday)) {
120+
throw new Error(`Invalid last weekday of the month expression: ${expression}`);
121+
}
121122

122123
// Check if the current date matches the last specified weekday of the month
123124
return currentDate.getDay() === weekday && currentDate.isLastWeekdayOfMonth();
@@ -239,9 +240,7 @@ export class CronExpression {
239240
*/
240241
includesDate(date: Date | CronDate): boolean {
241242
const { second, minute, hour, dayOfMonth, month, dayOfWeek } = this.#fields;
242-
const dtStr = date.toISOString();
243-
assert(dtStr != null, 'Invalid date');
244-
const dt = DateTime.fromISO(dtStr, { zone: this.#tz });
243+
const dt = DateTime.fromISO(date.toISOString()!, { zone: this.#tz });
245244
return (
246245
dayOfMonth.values.includes(<DayOfMonthRange>dt.day) &&
247246
dayOfWeek.values.includes(<DayOfWeekRange>dt.weekday) &&

src/CronExpressionParser.ts

+50-43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import assert from 'assert';
2-
31
import { CronFieldCollection } from './CronFieldCollection';
42
import { CronDate } from './CronDate';
53
import { CronExpression, CronExpressionOptions } from './CronExpression';
@@ -15,7 +13,7 @@ import {
1513
DayOfWeekRange,
1614
HourRange,
1715
MonthRange,
18-
ParseRageResponse,
16+
ParseRangeResponse,
1917
SixtyRange,
2018
} from './fields';
2119

@@ -99,10 +97,9 @@ export class CronExpressionParser {
9997

10098
expression = PredefinedExpressions[expression as keyof typeof PredefinedExpressions] || expression;
10199
const rawFields = CronExpressionParser.#getRawFields(expression, strict);
102-
assert(
103-
rawFields.dayOfMonth === '*' || rawFields.dayOfWeek === '*' || !strict,
104-
'Cannot use both dayOfMonth and dayOfWeek together in strict mode!',
105-
);
100+
if (!(rawFields.dayOfMonth === '*' || rawFields.dayOfWeek === '*' || !strict)) {
101+
throw new Error('Cannot use both dayOfMonth and dayOfWeek together in strict mode!');
102+
}
106103

107104
const second = CronExpressionParser.#parseField(
108105
CronUnit.Second,
@@ -151,11 +148,17 @@ export class CronExpressionParser {
151148
* @returns {RawCronFields} The raw fields.
152149
*/
153150
static #getRawFields(expression: string, strict: boolean): RawCronFields {
154-
assert(!strict || expression.length, 'Invalid cron expression');
151+
if (!(!strict || expression.length)) {
152+
throw new Error('Invalid cron expression');
153+
}
155154
expression = expression || '0 * * * * *';
156155
const atoms = expression.trim().split(/\s+/);
157-
assert(!strict || atoms.length === 6, 'Invalid cron expression, expected 6 fields');
158-
assert(atoms.length <= 6, 'Invalid cron expression, too many fields');
156+
if (!(!strict || atoms.length === 6)) {
157+
throw new Error('Invalid cron expression, expected 6 fields');
158+
}
159+
if (atoms.length > 6) {
160+
throw new Error('Invalid cron expression, too many fields');
161+
}
159162
const defaults = ['*', '*', '*', '*', '*', '0'];
160163
if (atoms.length < defaults.length) {
161164
atoms.unshift(...defaults.slice(atoms.length));
@@ -178,13 +181,17 @@ export class CronExpressionParser {
178181
value = value.replace(/[a-z]{3}/gi, (match) => {
179182
match = match.toLowerCase();
180183
const replacer = Months[match as keyof typeof Months] || DayOfWeek[match as keyof typeof DayOfWeek];
181-
assert(replacer != null, `Validation error, cannot resolve alias "${match}"`);
184+
if (!replacer) {
185+
throw new Error(`Validation error, cannot resolve alias "${match}"`);
186+
}
182187
return replacer.toString();
183188
});
184189
}
185190

186191
// Check for valid characters
187-
assert(constraints.validChars.test(value), `Invalid characters, got value: ${value}`);
192+
if (!constraints.validChars.test(value)) {
193+
throw new Error(`Invalid characters, got value: ${value}`);
194+
}
188195

189196
// Replace '*' and '?'
190197
value = value.replace(/[*?]/g, constraints.min + '-' + constraints.max);
@@ -200,35 +207,30 @@ export class CronExpressionParser {
200207
*/
201208
static #parseSequence(field: CronUnit, val: string, constraints: CronConstraints): (number | string)[] {
202209
const stack: (number | string)[] = [];
203-
204210
function handleResult(result: number | string | (number | string)[], constraints: CronConstraints) {
205211
if (Array.isArray(result)) {
206-
result.forEach((value) => {
207-
if (!CronExpressionParser.#isValidConstraintChar(constraints, value)) {
208-
const v = parseInt(value.toString(), 10);
209-
const isValid = v >= constraints.min && v <= constraints.max;
210-
assert(
211-
isValid,
212-
`Constraint error, got value ${value} expected range ${constraints.min}-${constraints.max}`,
213-
);
214-
stack.push(value);
215-
}
216-
});
212+
stack.push(...result);
217213
} else {
218214
if (CronExpressionParser.#isValidConstraintChar(constraints, result)) {
219215
stack.push(result);
220216
} else {
221217
const v = parseInt(result.toString(), 10);
222218
const isValid = v >= constraints.min && v <= constraints.max;
223-
assert(isValid, `Constraint error, got value ${result} expected range ${constraints.min}-${constraints.max}`);
219+
if (!isValid) {
220+
throw new Error(
221+
`Constraint error, got value ${result} expected range ${constraints.min}-${constraints.max}`,
222+
);
223+
}
224224
stack.push(field === CronUnit.DayOfWeek ? v % 7 : result);
225225
}
226226
}
227227
}
228228

229229
const atoms = val.split(',');
230230
atoms.forEach((atom) => {
231-
assert(atom.length > 0, 'Invalid list value format');
231+
if (!(atom.length > 0)) {
232+
throw new Error('Invalid list value format');
233+
}
232234
handleResult(CronExpressionParser.#parseRepeat(field, atom, constraints), constraints);
233235
});
234236
return stack;
@@ -242,9 +244,11 @@ export class CronExpressionParser {
242244
* @private
243245
* @returns {(number | string)[]} The parsed repeat.
244246
*/
245-
static #parseRepeat(field: CronUnit, val: string, constraints: CronConstraints): ParseRageResponse {
247+
static #parseRepeat(field: CronUnit, val: string, constraints: CronConstraints): ParseRangeResponse {
246248
const atoms = val.split('/');
247-
assert(atoms.length <= 2, `Invalid repeat: ${val}`);
249+
if (atoms.length > 2) {
250+
throw new Error(`Invalid repeat: ${val}`);
251+
}
248252
if (atoms.length === 2) {
249253
if (!isNaN(parseInt(atoms[0], 10))) {
250254
atoms[0] = `${atoms[0]}-${constraints.max}`;
@@ -266,8 +270,12 @@ export class CronExpressionParser {
266270
*/
267271
static #validateRange(min: number, max: number, constraints: CronConstraints): void {
268272
const isValid = !isNaN(min) && !isNaN(max) && min >= constraints.min && max <= constraints.max;
269-
assert(isValid, `Constraint error, got range ${min}-${max} expected range ${constraints.min}-${constraints.max}`);
270-
assert(min <= max, `Invalid range: ${min}-${max}, min(${min}) > max(${max})`);
273+
if (!isValid) {
274+
throw new Error(`Constraint error, got range ${min}-${max} expected range ${constraints.min}-${constraints.max}`);
275+
}
276+
if (min > max) {
277+
throw new Error(`Invalid range: ${min}-${max}, min(${min}) > max(${max})`);
278+
}
271279
}
272280

273281
/**
@@ -278,10 +286,9 @@ export class CronExpressionParser {
278286
* @throws {Error} Throws an error if the repeat interval is invalid.
279287
*/
280288
static #validateRepeatInterval(repeatInterval: number): void {
281-
assert(
282-
!isNaN(repeatInterval) && repeatInterval > 0,
283-
`Constraint error, cannot repeat at every ${repeatInterval} time.`,
284-
);
289+
if (!(!isNaN(repeatInterval) && repeatInterval > 0)) {
290+
throw new Error(`Constraint error, cannot repeat at every ${repeatInterval} time.`);
291+
}
285292
}
286293

287294
/**
@@ -320,7 +327,7 @@ export class CronExpressionParser {
320327
val: string,
321328
repeatInterval: number,
322329
constraints: CronConstraints,
323-
): ParseRageResponse {
330+
): ParseRangeResponse {
324331
const atoms: string[] = val.split('-');
325332
if (atoms.length <= 1) {
326333
return isNaN(+val) ? val : +val;
@@ -346,14 +353,14 @@ export class CronExpressionParser {
346353
}
347354
const nthValue = +atoms[atoms.length - 1];
348355
const matches = val.match(/([,-/])/);
349-
assert(
350-
matches === null,
351-
`Constraint error, invalid dayOfWeek \`#\` and \`${matches?.[0]}\` special characters are incompatible`,
352-
);
353-
assert(
354-
atoms.length <= 2 && !isNaN(nthValue) && nthValue >= 1 && nthValue <= 5,
355-
'Constraint error, invalid dayOfWeek occurrence number (#)',
356-
);
356+
if (matches !== null) {
357+
throw new Error(
358+
`Constraint error, invalid dayOfWeek \`#\` and \`${matches?.[0]}\` special characters are incompatible`,
359+
);
360+
}
361+
if (!(atoms.length <= 2 && !isNaN(nthValue) && nthValue >= 1 && nthValue <= 5)) {
362+
throw new Error('Constraint error, invalid dayOfWeek occurrence number (#)');
363+
}
357364
return { dayOfWeek: atoms[0], nthDayOfWeek: nthValue };
358365
}
359366

src/CronFieldCollection.ts

+37-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from 'assert';
21
import {
32
CronSecond,
43
CronMinute,
@@ -140,18 +139,29 @@ export class CronFieldCollection {
140139
* console.log(cronFields.dayOfWeek.values); // [1, 2, 3, 4, 5]
141140
*/
142141
constructor({ second, minute, hour, dayOfMonth, month, dayOfWeek }: CronFields) {
143-
assert(second, 'Validation error, Field second is missing');
144-
assert(minute, 'Validation error, Field minute is missing');
145-
assert(hour, 'Validation error, Field hour is missing');
146-
assert(dayOfMonth, 'Validation error, Field dayOfMonth is missing');
147-
assert(month, 'Validation error, Field month is missing');
148-
assert(dayOfWeek, 'Validation error, Field dayOfWeek is missing');
142+
if (!second) {
143+
throw new Error('Validation error, Field second is missing');
144+
}
145+
if (!minute) {
146+
throw new Error('Validation error, Field minute is missing');
147+
}
148+
if (!hour) {
149+
throw new Error('Validation error, Field hour is missing');
150+
}
151+
if (!dayOfMonth) {
152+
throw new Error('Validation error, Field dayOfMonth is missing');
153+
}
154+
if (!month) {
155+
throw new Error('Validation error, Field month is missing');
156+
}
157+
if (!dayOfWeek) {
158+
throw new Error('Validation error, Field dayOfWeek is missing');
159+
}
149160

150161
if (month.values.length === 1) {
151-
assert(
152-
parseInt(dayOfMonth.values[0] as string, 10) <= CronMonth.daysInMonth[month.values[0] - 1],
153-
'Invalid explicit day of month definition',
154-
);
162+
if (!(parseInt(dayOfMonth.values[0] as string, 10) <= CronMonth.daysInMonth[month.values[0] - 1])) {
163+
throw new Error('Invalid explicit day of month definition');
164+
}
155165
}
156166

157167
this.#second = second;
@@ -332,14 +342,26 @@ export class CronFieldCollection {
332342
}
333343

334344
const multiplier = range.start === 0 ? range.count - 1 : range.count;
335-
assert(step, 'Unexpected range step');
336-
assert(range.end, 'Unexpected range end');
345+
/* istanbul ignore if */
346+
if (!step) {
347+
throw new Error('Unexpected range step');
348+
}
349+
/* istanbul ignore if */
350+
if (!range.end) {
351+
throw new Error('Unexpected range end');
352+
}
337353
if (step * multiplier > range.end) {
338354
const mapFn = (_: number, index: number) => {
339-
assert(typeof range.start === 'number', 'Unexpected range start');
355+
/* istanbul ignore if */
356+
if (typeof range.start !== 'number') {
357+
throw new Error('Unexpected range start');
358+
}
340359
return index % step === 0 ? range.start + index : null;
341360
};
342-
assert(typeof range.start === 'number', 'Unexpected range start');
361+
/* istanbul ignore if */
362+
if (typeof range.start !== 'number') {
363+
throw new Error('Unexpected range start');
364+
}
343365
const seed = { length: range.end - range.start + 1 };
344366
return Array.from(seed, mapFn)
345367
.filter((value) => value !== null)

src/fields/CronField.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from 'assert';
21
import { CronChars, CronConstraints, CronFieldType, CronMax, CronMin } from './types';
32

43
export type SerializedCronField = {
@@ -64,8 +63,12 @@ export abstract class CronField {
6463
values: (number | string)[],
6564
/* istanbul ignore next - we always pass a value */ wildcard = false,
6665
) {
67-
assert(Array.isArray(values), `${this.constructor.name} Validation error, values is not an array`);
68-
assert(values.length > 0, `${this.constructor.name} Validation error, values contains no values`);
66+
if (!Array.isArray(values)) {
67+
throw new Error(`${this.constructor.name} Validation error, values is not an array`);
68+
}
69+
if (!(values.length > 0)) {
70+
throw new Error(`${this.constructor.name} Validation error, values contains no values`);
71+
}
6972
this.#values = values.sort(CronField.sorter);
7073
this.#wildcard = wildcard;
7174
}
@@ -153,12 +156,15 @@ export abstract class CronField {
153156
return typeof value === 'number' ? value >= this.min && value <= this.max : this.chars.some(charTest(value));
154157
};
155158
const isValidRange = this.#values.every(rangeTest);
156-
assert(
157-
isValidRange,
158-
`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`,
159-
);
159+
if (!isValidRange) {
160+
throw new Error(
161+
`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`,
162+
);
163+
}
160164
// check for duplicate value in this.#values array
161165
const duplicate = this.#values.find((value, index) => this.#values.indexOf(value) !== index);
162-
assert(!duplicate, `${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
166+
if (duplicate) {
167+
throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
168+
}
163169
}
164170
}

src/fields/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type CronFieldType = SixtyRange[] | HourRange[] | DayOfMonthRange[] | Mon
1414
export type CronChars = 'L' | 'W';
1515
export type CronMin = 0 | 1;
1616
export type CronMax = 7 | 12 | 23 | 31 | 59;
17-
export type ParseRageResponse = number[] | string[] | number | string;
17+
export type ParseRangeResponse = number[] | string[] | number | string;
1818

1919
export type CronConstraints = {
2020
min: CronMin;

tests/CronExpressionParser.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ describe('CronExpressionParser', () => {
1717
);
1818
});
1919

20+
test('range order is invalid', () => {
21+
expect(() => CronExpressionParser.parse('30-20 * * * * *')).toThrow('Invalid range: 30-20, min(30) > max(20)');
22+
});
23+
2024
test('invalid range', () => {
2125
expect(() => CronExpressionParser.parse('- * * * * *')).toThrow(
2226
'Constraint error, got range NaN-NaN expected range 0-59',
@@ -153,10 +157,19 @@ describe('CronExpressionParser', () => {
153157
}
154158
});
155159

160+
test('both dayOfMonth and dayOfWeek used together in strict mode', () => {
161+
const expression = '0 0 12 1-31 * 1';
162+
expect(() => CronExpressionParser.parse(expression, { strict: true })).toThrow();
163+
});
164+
156165
test('missing fields when in strict mode', () => {
157166
const expression = '20 15 * *';
158167
expect(() => CronExpressionParser.parse(expression, { strict: true })).toThrow();
159168
});
169+
170+
test('empty expression in strict mode', () => {
171+
expect(() => CronExpressionParser.parse('', { strict: true })).toThrow();
172+
});
160173
});
161174

162175
describe('take multiple dates', () => {
@@ -1007,6 +1020,7 @@ describe('CronExpressionParser', () => {
10071020
expect(interval.includesDate(goodDate)).toBe(true);
10081021
expect(interval.includesDate(badDateBefore)).toBe(false);
10091022
expect(interval.includesDate(badDateAfter)).toBe(false);
1023+
expect(() => interval.includesDate(new Date('invalid'))).toThrow();
10101024
});
10111025

10121026
test('correctly handle 0 12 1-31 * 1 (#284)', () => {

0 commit comments

Comments
 (0)