From 9910d83d37d83ec1ed6584f3593f44ac309626ef Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Thu, 11 Jan 2024 20:10:40 +0300 Subject: [PATCH 1/7] Add formatDate test util --- test/lib/utils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/lib/utils.ts b/test/lib/utils.ts index 6bb2b93f..3e72a990 100644 --- a/test/lib/utils.ts +++ b/test/lib/utils.ts @@ -218,3 +218,17 @@ export function expectedDate( ): Date { return dateInTimeZone(startDate, targetZone) } + +export function formatDate(d: Date, timeZone?: string) { + return new Intl.DateTimeFormat('sv-SE', { + timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + timeZoneName: 'short', + }).format(d) +} From e26677a7d81f9b503b2abbecd503db51b02cf55f Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Wed, 10 Jan 2024 17:24:29 +0300 Subject: [PATCH 2/7] Remove wildcard from test script Adding testRegex allows to run test for specific file, e.g "yarn test test/rrule.test.ts" --- jest.config.js | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index 8bb37208..6e43f799 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,5 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', + testRegex: '(\\.|/)(test|spec)\\.(j|t)s(x?)$', } diff --git a/package.json b/package.json index a31a1116..95f57953 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "format": "yarn prettier --write .", "format-check": "yarn prettier --check .", "run-ts": "TS_NODE_PROJECT=tsconfig.json node --loader ts-node/esm", - "test": "jest **/*.test.ts", - "test-ci": "yarn run-ts ./node_modules/.bin/nyc jest **/*.test.ts" + "test": "jest", + "test-ci": "yarn run-ts ./node_modules/.bin/nyc jest" }, "nyc": { "extension": [ From 477bbf6cf20d82e7e686a881b9bc024accf88a63 Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Wed, 10 Jan 2024 20:15:31 +0300 Subject: [PATCH 3/7] Make tests simpler --- test/datewithzone.test.ts | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/test/datewithzone.test.ts b/test/datewithzone.test.ts index 2e13774c..12ae1224 100644 --- a/test/datewithzone.test.ts +++ b/test/datewithzone.test.ts @@ -1,6 +1,5 @@ import { DateWithZone } from '../src/datewithzone' -import { set as setMockDate, reset as resetMockDate } from 'mockdate' -import { datetime, expectedDate } from './lib/utils' +import { datetime, formatDate } from './lib/utils' describe('toString', () => { it('returns the date when no tzid is present', () => { @@ -33,24 +32,16 @@ describe('rezonedDate', () => { it('returns the original date when no zone is given', () => { const d = datetime(2010, 10, 5, 11, 0, 0) const dt = new DateWithZone(d) - expect(dt.rezonedDate()).toEqual(d) + expect(dt.rezonedDate()).toStrictEqual(d) }) it('returns the date in the correct zone when given', () => { - const targetZone = 'America/New_York' - const currentLocalDate = new Date(2000, 1, 6, 1, 0, 0) - setMockDate(currentLocalDate) - - const d = new Date(Date.parse('2010-10-05T11:00:00')) - const dt = new DateWithZone(d, targetZone) - expect(dt.rezonedDate()).toEqual( - expectedDate( - new Date(Date.parse('2010-10-05T11:00:00')), - currentLocalDate, - targetZone - ) + const dt = new DateWithZone( + new Date('2010-10-05T11:00:00'), + 'America/New_York' + ) + expect(formatDate(dt.rezonedDate(), 'America/New_York')).toStrictEqual( + '2010-10-05 11:00:00 GMT−4' ) - - resetMockDate() }) }) From 75f1a1c4d5aa9074237dddfe9adbe6f959ca3e89 Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Wed, 10 Jan 2024 21:36:01 +0300 Subject: [PATCH 4/7] Use for..of --- src/iter/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/iter/index.ts b/src/iter/index.ts index 8fd6bc26..be7d7dc0 100644 --- a/src/iter/index.ts +++ b/src/iter/index.ts @@ -67,8 +67,7 @@ export function iter( } const date = fromOrdinal(ii.yearordinal + currentDay) - for (let k = 0; k < timeset.length; k++) { - const time = timeset[k] + for (const time of timeset) { const res = combine(date, time) if (until && res > until) { return emitResult(iterResult) From 6f304540f9aa3832fc1d5095aebcc682d8c80517 Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Thu, 11 Jan 2024 00:42:21 +0300 Subject: [PATCH 5/7] Enable no-console rule --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 66f39300..33a9ee46 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -94,6 +94,7 @@ module.exports = { 'no-caller': 'error', 'no-case-declarations': 'off', 'no-cond-assign': 'error', + 'no-console': 'error', 'no-constant-condition': 'error', 'no-control-regex': 'error', 'no-duplicate-imports': 'error', From 348523fb92ef5fd22d90cfc592aaa166938a36ee Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Thu, 11 Jan 2024 18:52:20 +0300 Subject: [PATCH 6/7] Add typecheck scripts --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 95f57953..e0f2c51c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "lint": "yarn eslint . --fix --config .eslintrc.js", "format": "yarn prettier --write .", "format-check": "yarn prettier --check .", + "typecheck": "tsc --noEmit", + "typecheck:watch": "tsc --noEmit --watch", "run-ts": "TS_NODE_PROJECT=tsconfig.json node --loader ts-node/esm", "test": "jest", "test-ci": "yarn run-ts ./node_modules/.bin/nyc jest" From 3447c7e1b7a62b3070d540786b0b64d9ed02e600 Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Thu, 11 Jan 2024 20:09:21 +0300 Subject: [PATCH 7/7] Enable strict typescript --- .eslintrc.js | 1 + src/cache.ts | 5 +- src/dateutil.ts | 2 +- src/datewithzone.ts | 2 +- src/iter/index.ts | 10 ++-- src/iterinfo/index.ts | 23 ++++--- src/iterinfo/monthinfo.ts | 3 +- src/iterresult.ts | 12 ++-- src/iterset.ts | 2 +- src/nlp/parsetext.ts | 12 ++-- src/nlp/totext.ts | 122 +++++++++++++++++++------------------- src/optionstostring.ts | 2 +- src/rrule.ts | 2 +- src/rruleset.ts | 3 +- src/rrulestr.ts | 2 +- test/datewithzone.test.ts | 3 +- test/lib/utils.ts | 30 ++++++---- test/nlp.test.ts | 2 +- test/rrule.test.ts | 7 ++- tsconfig.json | 1 + 20 files changed, 132 insertions(+), 114 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 33a9ee46..7a72eaf4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,6 +41,7 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-new': 'error', + '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unnecessary-qualifier': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unused-expressions': [ diff --git a/src/cache.ts b/src/cache.ts index 02ed58e3..4ebb99b3 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -44,6 +44,7 @@ export class Cache { if (what === 'all') { this.all = value as Date[] } else { + args = args || {} args._value = value this[what].push(args as IterArgs) } @@ -65,7 +66,7 @@ export class Cache { const findCacheDiff = function (item: IterArgs) { for (let i = 0; i < argsKeys.length; i++) { const key = argsKeys[i] - if (!argsMatch(args[key], item[key])) { + if (!argsMatch(args?.[key], item[key])) { return true } } @@ -89,7 +90,7 @@ export class Cache { if (!cached && this.all) { // Not in the cache, but we already know all the occurrences, // so we can find the correct dates from the cached ones. - const iterResult = new IterResult(what, args) + const iterResult = new IterResult(what, args ?? {}) for (let i = 0; i < (this.all as Date[]).length; i++) { if (!iterResult.accept((this.all as Date[])[i])) break } diff --git a/src/dateutil.ts b/src/dateutil.ts index b2cd176a..a4dcdd78 100644 --- a/src/dateutil.ts +++ b/src/dateutil.ts @@ -211,7 +211,7 @@ const dateTZtoISO8601 = function (date: Date, timeZone: string) { return dateStr.replace(' ', 'T') + 'Z' } -export const dateInTimeZone = function (date: Date, timeZone: string) { +export const dateInTimeZone = function (date: Date, timeZone?: string) { const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone // Date constructor can only reliably parse dates in ISO8601 format const dateInLocalTZ = new Date(dateTZtoISO8601(date, localTimeZone)) diff --git a/src/datewithzone.ts b/src/datewithzone.ts index e05fcc2e..83da1141 100644 --- a/src/datewithzone.ts +++ b/src/datewithzone.ts @@ -30,7 +30,7 @@ export class DateWithZone { } public rezonedDate() { - if (this.isUTC) { + if (this.isUTC || !this.tzid) { return this.date } diff --git a/src/iter/index.ts b/src/iter/index.ts index be7d7dc0..6a754ec9 100644 --- a/src/iter/index.ts +++ b/src/iter/index.ts @@ -130,7 +130,7 @@ function isFiltered( return ( (notEmpty(bymonth) && !includes(bymonth, ii.mmask[currentDay])) || - (notEmpty(byweekno) && !ii.wnomask[currentDay]) || + (notEmpty(byweekno) && !ii.wnomask?.[currentDay]) || (notEmpty(byweekday) && !includes(byweekday, ii.wdaymask[currentDay])) || (notEmpty(ii.nwdaymask) && !ii.nwdaymask[currentDay]) || (byeaster !== null && !includes(ii.eastermask, currentDay)) || @@ -166,9 +166,11 @@ function removeFilteredDays( for (let dayCounter = start; dayCounter < end; dayCounter++) { const currentDay = dayset[dayCounter] - filtered = isFiltered(ii, currentDay, options) + if (currentDay !== null) { + filtered = isFiltered(ii, currentDay, options) - if (filtered) dayset[currentDay] = null + if (filtered) dayset[currentDay] = null + } } return filtered @@ -178,7 +180,7 @@ function makeTimeset( ii: Iterinfo, counterDate: DateTime, options: ParsedOptions -): Time[] | null { +): Time[] { const { freq, byhour, byminute, bysecond } = options if (freqIsDailyOrGreater(freq)) { diff --git a/src/iterinfo/index.ts b/src/iterinfo/index.ts index ac3cec3a..b79aa5b0 100644 --- a/src/iterinfo/index.ts +++ b/src/iterinfo/index.ts @@ -14,9 +14,9 @@ export type GetDayset = () => DaySet // ============================================================================= export default class Iterinfo { - public yearinfo: YearInfo - public monthinfo: MonthInfo - public eastermask: number[] | null + public yearinfo!: YearInfo + public monthinfo!: MonthInfo + public eastermask!: number[] // eslint-disable-next-line no-empty-function constructor(private options: ParsedOptions) {} @@ -32,7 +32,7 @@ export default class Iterinfo { notEmpty(options.bynweekday) && (month !== this.lastmonth || year !== this.lastyear) ) { - const { yearlen, mrange, wdaymask } = this.yearinfo + const { yearlen, mrange, wdaymask } = this.yearinfo ?? {} this.monthinfo = rebuildMonth( year, month, @@ -153,15 +153,20 @@ export default class Iterinfo { getdayset(freq: Frequency): (y: number, m: number, d: number) => DaySet { switch (freq) { case Frequency.YEARLY: - return this.ydayset.bind(this) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.ydayset.bind(this) as any case Frequency.MONTHLY: - return this.mdayset.bind(this) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.mdayset.bind(this) as any case Frequency.WEEKLY: - return this.wdayset.bind(this) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.wdayset.bind(this) as any case Frequency.DAILY: - return this.ddayset.bind(this) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.ddayset.bind(this) as any default: - return this.ddayset.bind(this) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.ddayset.bind(this) as any } } diff --git a/src/iterinfo/monthinfo.ts b/src/iterinfo/monthinfo.ts index 96dea471..15259249 100644 --- a/src/iterinfo/monthinfo.ts +++ b/src/iterinfo/monthinfo.ts @@ -49,9 +49,8 @@ export function rebuildMonth( const first = rang[0] const last = rang[1] - 1 - for (let k = 0; k < options.bynweekday.length; k++) { + for (const [wday, n] of options.bynweekday ?? []) { let i - const [wday, n] = options.bynweekday[k] if (n < 0) { i = last + (n + 1) * 7 i -= pymod(wdaymask[i] - wday, 7) diff --git a/src/iterresult.ts b/src/iterresult.ts index 49650c71..e40d550f 100644 --- a/src/iterresult.ts +++ b/src/iterresult.ts @@ -29,13 +29,15 @@ export default class IterResult { if (method === 'between') { this.maxDate = args.inc - ? args.before - : new Date(args.before.getTime() - 1) - this.minDate = args.inc ? args.after : new Date(args.after.getTime() + 1) + ? args.before! + : new Date(args.before!.getTime() - 1) + this.minDate = args.inc + ? args.after! + : new Date(args.after!.getTime() + 1) } else if (method === 'before') { - this.maxDate = args.inc ? args.dt : new Date(args.dt.getTime() - 1) + this.maxDate = args.inc ? args.dt! : new Date(args.dt!.getTime() - 1) } else if (method === 'after') { - this.minDate = args.inc ? args.dt : new Date(args.dt.getTime() + 1) + this.minDate = args.inc ? args.dt! : new Date(args.dt!.getTime() + 1) } } diff --git a/src/iterset.ts b/src/iterset.ts index 79395189..11753896 100644 --- a/src/iterset.ts +++ b/src/iterset.ts @@ -43,7 +43,7 @@ export function iterSet( } if (iterResult.method === 'between') { - evalExdate(iterResult.args.after, iterResult.args.before) + evalExdate(iterResult.args.after!, iterResult.args.before!) iterResult.accept = function (date) { const dt = Number(date) if (!_exdateHash[dt]) { diff --git a/src/nlp/parsetext.ts b/src/nlp/parsetext.ts index fbaeccf9..65d0c656 100644 --- a/src/nlp/parsetext.ts +++ b/src/nlp/parsetext.ts @@ -9,9 +9,9 @@ import { WeekdayStr } from '../weekday' class Parser { private readonly rules: { [k: string]: RegExp } - public text: string - public symbol: string | null - public value: RegExpExecArray | null + public text!: string + public symbol!: string | null + public value!: RegExpExecArray | null private done = true constructor(rules: { [k: string]: RegExp }) { @@ -30,7 +30,7 @@ class Parser { nextSymbol() { let best: RegExpExecArray | null - let bestSymbol: string + let bestSymbol = '' this.symbol = null this.value = null @@ -390,7 +390,7 @@ export default function parseText(text: string, language: Language = ENGLISH) { ttr.nextSymbol() return ttr.accept('last') ? -3 : 3 case 'nth': - const v = parseInt(ttr.value[1], 10) + const v = parseInt(ttr.value![1], 10) if (v < -366 || v > 366) throw new Error('Nth out of range: ' + v) ttr.nextSymbol() @@ -431,7 +431,7 @@ export default function parseText(text: string, language: Language = ENGLISH) { if (!date) throw new Error('Cannot parse until date:' + ttr.text) options.until = new Date(date) } else if (ttr.accept('for')) { - options.count = parseInt(ttr.value[0], 10) + options.count = parseInt(ttr.value![0], 10) ttr.expect('number') // ttr.expect('times') } diff --git a/src/nlp/totext.ts b/src/nlp/totext.ts index e77fd123..789c7a8b 100644 --- a/src/nlp/totext.ts +++ b/src/nlp/totext.ts @@ -48,7 +48,7 @@ export default class ToText { private language: Language private options: Partial private origOptions: Partial - private bymonthday: Options['bymonthday'] | null + private bymonthday: Options['bymonthday'] | null = null private byweekday: { allWeeks: ByWeekday[] | null someWeeks: ByWeekday[] | null @@ -71,8 +71,8 @@ export default class ToText { this.origOptions = rrule.origOptions if (this.origOptions.bymonthday) { - const bymonthday = ([] as number[]).concat(this.options.bymonthday) - const bynmonthday = ([] as number[]).concat(this.options.bynmonthday) + const bymonthday = ([] as number[]).concat(this.options.bymonthday!) + const bynmonthday = ([] as number[]).concat(this.options.bynmonthday!) bymonthday.sort((a, b) => a - b) bynmonthday.sort((a, b) => b - a) @@ -88,11 +88,11 @@ export default class ToText { const days = String(byweekday) this.byweekday = { - allWeeks: byweekday.filter(function (weekday: Weekday) { - return !weekday.n + allWeeks: byweekday.filter((weekday) => { + return !(weekday as Weekday).n }), - someWeeks: byweekday.filter(function (weekday: Weekday) { - return Boolean(weekday.n) + someWeeks: byweekday.filter((weekday) => { + return !!(weekday as Weekday).n }), isWeekdays: days.indexOf('MO') !== -1 && @@ -112,20 +112,22 @@ export default class ToText { days.indexOf('SU') !== -1, } - const sortWeekDays = function (a: Weekday, b: Weekday) { - return a.weekday - b.weekday - } + const sortWeekDays = (a: Weekday, b: Weekday) => a.weekday - b.weekday - this.byweekday.allWeeks.sort(sortWeekDays) - this.byweekday.someWeeks.sort(sortWeekDays) + ;(this.byweekday?.allWeeks as Weekday[]).sort(sortWeekDays) + ;(this.byweekday?.someWeeks as Weekday[]).sort(sortWeekDays) - if (!this.byweekday.allWeeks.length) this.byweekday.allWeeks = null - if (!this.byweekday.someWeeks.length) this.byweekday.someWeeks = null + if (!this.byweekday.allWeeks?.length) this.byweekday.allWeeks = null + if (!this.byweekday.someWeeks?.length) this.byweekday.someWeeks = null } else { this.byweekday = null } } + get interval() { + return this.options.interval! + } + /** * Test whether the rrule can be fully converted to text. * @@ -160,7 +162,7 @@ export default class ToText { toString() { const gettext = this.gettext - if (!(this.options.freq in ToText.IMPLEMENTED)) { + if (!(this.options.freq! in ToText.IMPLEMENTED)) { return gettext('RRule error: Unable to fully convert this rrule to text') } @@ -195,40 +197,32 @@ export default class ToText { HOURLY() { const gettext = this.gettext - if (this.options.interval !== 1) this.add(this.options.interval.toString()) + if (this.interval !== 1) this.add(this.interval.toString()) - this.add( - this.plural(this.options.interval) ? gettext('hours') : gettext('hour') - ) + this.add(this.plural(this.interval) ? gettext('hours') : gettext('hour')) } MINUTELY() { const gettext = this.gettext - if (this.options.interval !== 1) this.add(this.options.interval.toString()) + if (this.options.interval !== 1) this.add(this.interval.toString()) this.add( - this.plural(this.options.interval) - ? gettext('minutes') - : gettext('minute') + this.plural(this.interval) ? gettext('minutes') : gettext('minute') ) } DAILY() { const gettext = this.gettext - if (this.options.interval !== 1) this.add(this.options.interval.toString()) + if (this.interval !== 1) this.add(this.interval.toString()) if (this.byweekday && this.byweekday.isWeekdays) { this.add( - this.plural(this.options.interval) - ? gettext('weekdays') - : gettext('weekday') + this.plural(this.interval) ? gettext('weekdays') : gettext('weekday') ) } else { - this.add( - this.plural(this.options.interval) ? gettext('days') : gettext('day') - ) + this.add(this.plural(this.interval) ? gettext('days') : gettext('day')) } if (this.origOptions.bymonth) { @@ -249,8 +243,8 @@ export default class ToText { const gettext = this.gettext if (this.options.interval !== 1) { - this.add(this.options.interval.toString()).add( - this.plural(this.options.interval) ? gettext('weeks') : gettext('week') + this.add(this.interval.toString()).add( + this.plural(this.interval) ? gettext('weeks') : gettext('week') ) } @@ -265,9 +259,7 @@ export default class ToText { this.add(gettext('on')).add(gettext('weekdays')) } } else if (this.byweekday && this.byweekday.isEveryDay) { - this.add( - this.plural(this.options.interval) ? gettext('days') : gettext('day') - ) + this.add(this.plural(this.interval) ? gettext('days') : gettext('day')) } else { if (this.options.interval === 1) this.add(gettext('week')) @@ -292,21 +284,19 @@ export default class ToText { const gettext = this.gettext if (this.origOptions.bymonth) { - if (this.options.interval !== 1) { - this.add(this.options.interval.toString()).add(gettext('months')) - if (this.plural(this.options.interval)) this.add(gettext('in')) + if (this.interval !== 1) { + this.add(this.interval.toString()).add(gettext('months')) + if (this.plural(this.interval)) this.add(gettext('in')) } else { // this.add(gettext('MONTH')) } this._bymonth() } else { - if (this.options.interval !== 1) { - this.add(this.options.interval.toString()) + if (this.interval !== 1) { + this.add(this.interval.toString()) } this.add( - this.plural(this.options.interval) - ? gettext('months') - : gettext('month') + this.plural(this.interval) ? gettext('months') : gettext('month') ) } if (this.bymonthday) { @@ -323,7 +313,7 @@ export default class ToText { if (this.origOptions.bymonth) { if (this.options.interval !== 1) { - this.add(this.options.interval.toString()) + this.add(this.interval.toString()) this.add(gettext('years')) } else { // this.add(gettext('YEAR')) @@ -331,11 +321,9 @@ export default class ToText { this._bymonth() } else { if (this.options.interval !== 1) { - this.add(this.options.interval.toString()) + this.add(this.interval.toString()) } - this.add( - this.plural(this.options.interval) ? gettext('years') : gettext('year') - ) + this.add(this.plural(this.interval) ? gettext('years') : gettext('year')) } if (this.bymonthday) { @@ -369,10 +357,10 @@ export default class ToText { this.list(this.byweekday.allWeeks, this.weekdaytext, gettext('or')) ) .add(gettext('the')) - .add(this.list(this.bymonthday, this.nth, gettext('or'))) + .add(this.list(this.bymonthday!, this.nth, gettext('or'))) } else { this.add(gettext('on the')).add( - this.list(this.bymonthday, this.nth, gettext('and')) + this.list(this.bymonthday!, this.nth, gettext('and')) ) } // this.add(gettext('DAY')) @@ -380,17 +368,17 @@ export default class ToText { private _byweekday() { const gettext = this.gettext - if (this.byweekday.allWeeks && !this.byweekday.isWeekdays) { + if (this.byweekday!.allWeeks && !this.byweekday!.isWeekdays) { this.add(gettext('on')).add( - this.list(this.byweekday.allWeeks, this.weekdaytext) + this.list(this.byweekday!.allWeeks, this.weekdaytext) ) } - if (this.byweekday.someWeeks) { - if (this.byweekday.allWeeks) this.add(gettext('and')) + if (this.byweekday!.someWeeks) { + if (this.byweekday!.allWeeks) this.add(gettext('and')) this.add(gettext('on the')).add( - this.list(this.byweekday.someWeeks, this.weekdaytext, gettext('and')) + this.list(this.byweekday!.someWeeks, this.weekdaytext, gettext('and')) ) } } @@ -399,17 +387,21 @@ export default class ToText { const gettext = this.gettext this.add(gettext('at')).add( - this.list(this.origOptions.byhour, undefined, gettext('and')) + this.list(this.origOptions.byhour!, undefined, gettext('and')) ) } private _bymonth() { this.add( - this.list(this.options.bymonth, this.monthtext, this.gettext('and')) + this.list( + this.options.bymonth!, + this.monthtext as GetText, + this.gettext('and') + ) ) } - nth(n: number | string) { + nth(n: number | string | Weekday) { n = parseInt(n.toString(), 10) let nth: string const gettext = this.gettext @@ -442,10 +434,14 @@ export default class ToText { return this.language.monthNames[m - 1] } - weekdaytext(wday: Weekday | number) { + weekdaytext(wday: number | string | Weekday) { + if (typeof wday === 'string') { + throw new Error('unexpected string in weekdaytext') + } const weekday = isNumber(wday) ? (wday + 1) % 7 : wday.getJsWeekday() return ( - ((wday as Weekday).n ? this.nth((wday as Weekday).n) + ' ' : '') + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ((wday as Weekday).n ? this.nth((wday as Weekday).n as any) + ' ' : '') + this.language.dayNames[weekday] ) } @@ -500,7 +496,11 @@ export default class ToText { } if (finalDelim) { - return delimJoin(arr.map(realCallback), delim, finalDelim) + return delimJoin( + arr.map(realCallback).filter((s): s is NonNullable => !!s), + delim, + finalDelim + ) } else { return arr.map(realCallback).join(delim + ' ') } diff --git a/src/optionstostring.ts b/src/optionstostring.ts index 10086692..c6c1550b 100644 --- a/src/optionstostring.ts +++ b/src/optionstostring.ts @@ -23,7 +23,7 @@ export function optionsToString(options: Partial) { switch (key) { case 'FREQ': - outValue = RRule.FREQUENCIES[options.freq] + outValue = RRule.FREQUENCIES[options.freq!] break case 'WKST': if (isNumber(value)) { diff --git a/src/rrule.ts b/src/rrule.ts index cab2d1dd..4bb80f0e 100644 --- a/src/rrule.ts +++ b/src/rrule.ts @@ -204,7 +204,7 @@ export class RRule implements QueryMethods { * * @return Date or null */ - before(dt: Date, inc = false): Date | null { + before(dt?: Date, inc = false): Date | null { if (!isValidDate(dt)) { throw new Error('Invalid date passed in to RRule.before') } diff --git a/src/rruleset.ts b/src/rruleset.ts index ebf9d01d..bbb7dc3b 100644 --- a/src/rruleset.ts +++ b/src/rruleset.ts @@ -7,7 +7,8 @@ import { QueryMethodTypes, IterResultType } from './types' import { rrulestr } from './rrulestr' import { optionsToString } from './optionstostring' -function createGetterSetter(fieldName: string) { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function createGetterSetter(this: any, fieldName: string) { return (field?: T) => { if (field !== undefined) { this[`_${fieldName}`] = field diff --git a/src/rrulestr.ts b/src/rrulestr.ts index 61f3707d..8816810c 100644 --- a/src/rrulestr.ts +++ b/src/rrulestr.ts @@ -129,7 +129,7 @@ function buildRule(s: string, options: Partial) { rset.exdate(date) }) - if (options.compatible && options.dtstart) rset.rdate(dtstart) + if (options.compatible && dtstart) rset.rdate(dtstart) return rset } diff --git a/test/datewithzone.test.ts b/test/datewithzone.test.ts index 12ae1224..64cd3ff0 100644 --- a/test/datewithzone.test.ts +++ b/test/datewithzone.test.ts @@ -23,7 +23,8 @@ it('returns the time of the date', () => { }) it('rejects invalid dates', () => { - expect(() => new DateWithZone(new Date(undefined))).toThrow( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(() => new DateWithZone(new Date(undefined as any))).toThrow( 'Invalid date passed to DateWithZone' ) }) diff --git a/test/lib/utils.ts b/test/lib/utils.ts index 3e72a990..327c67ec 100644 --- a/test/lib/utils.ts +++ b/test/lib/utils.ts @@ -10,11 +10,15 @@ export const TEST_CTX = { } const assertDatesEqual = function ( - actual: Date | Date[], - expected: Date | Date[] + actual: Date | Date[] | null | undefined, + expected: Date | Date[] | null | undefined ) { - if (!(actual instanceof Array)) actual = [actual] - if (!(expected instanceof Array)) expected = [expected] + if (actual && !(actual instanceof Array)) actual = [actual] + if (expected && !(expected instanceof Array)) expected = [expected] + + if (!actual && !expected) return + if (!actual) throw new Error('actual null') + if (!expected) throw new Error('expected null') if (expected.length > 1) { expect(actual).toHaveLength(expected.length) @@ -36,7 +40,7 @@ const extractTime = function (date: Date) { */ export const parse = function (str: string) { const parts = str.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/) - const [, y, m, d, h, i, s] = parts + const [, y, m, d, h, i, s] = parts! const year = Number(y) const month = Number(m[0] === '0' ? m[1] : m) const day = Number(d[0] === '0' ? d[1] : d) @@ -100,9 +104,7 @@ export const testRecurring = function ( itFunc(msg, function () { let time = Date.now() - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - let actualDates = rule[method](...args) + let actualDates = rule[method](...(args as [never, never]))! time = Date.now() - time const maxTestDuration = 200 @@ -202,19 +204,21 @@ export const testRecurring = function ( }) } as TestRecurring -testRecurring.only = function (...args) { - testRecurring.apply(it, [...args, it.only]) +testRecurring.only = (...args) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testRecurring.apply(it, [...(args as any[]), it.only] as any) } -testRecurring.skip = function ([description]: [string]) { +testRecurring.skip = (args) => { + const description = (args as [string])[0] // eslint-disable-next-line @typescript-eslint/no-empty-function, no-empty-function it.skip(description, () => {}) } export function expectedDate( startDate: Date, - currentLocalDate: Date, - targetZone: string + currentLocalDate?: Date, + targetZone?: string ): Date { return dateInTimeZone(startDate, targetZone) } diff --git a/test/nlp.test.ts b/test/nlp.test.ts index 8948616c..ef280a3e 100644 --- a/test/nlp.test.ts +++ b/test/nlp.test.ts @@ -63,7 +63,7 @@ describe('NLP', () => { texts.forEach(function (item) { const text = item[0] const str = item[1] - expect(optionsToString(RRule.parseText(text))).toBe(str) + expect(optionsToString(RRule.parseText(text)!)).toBe(str) }) }) diff --git a/test/rrule.test.ts b/test/rrule.test.ts index 4269413d..e839ab12 100644 --- a/test/rrule.test.ts +++ b/test/rrule.test.ts @@ -3985,9 +3985,9 @@ describe('RRule', function () { }) expect(date.getTime()).toBe(rr.options.dtstart.getTime()) - const res: Date = rr.before(rr.after(rr.options.dtstart)) + const res: Date | null = rr.before(rr.after(rr.options.dtstart)!) - let resTimestamp: number + let resTimestamp: number | null = null if (res != null) resTimestamp = res.getTime() expect(resTimestamp).toBe(rr.options.dtstart.getTime()) }) @@ -4218,7 +4218,8 @@ describe('RRule', function () { }) it('throws an error when dtstart is invalid', () => { - const invalidDate = new Date(undefined) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const invalidDate = new Date(undefined as any) const validDate = datetime(2017, 1, 1) expect(() => new RRule({ dtstart: invalidDate })).toThrow( 'Invalid options: dtstart' diff --git a/tsconfig.json b/tsconfig.json index d89ed13a..98246c21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "module": "commonjs", "noEmitOnError": true, "esModuleInterop": true, + "strict": true, "target": "es6", "baseUrl": ".", "rootDirs": ["./src/", "./test/"]