Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/isValid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export function validatePreparseResult(pr: PreparseResult, options?: ParserOptio
const year = toGregorianYear(pr.Y, options) ?? base.Y;
const month = pr.M ?? base.M;

return pr._index > 0
return !pr._error
&& pr._index > 0
&& pr._length > 0
&& pr._index === pr._length
&& pr._match > 0
Expand Down
9 changes: 8 additions & 1 deletion src/preparse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export interface PreparseResult extends ParsedComponents {
* Number of matched tokens
*/
_match: number;

/**
* True when a required format token could not be matched.
*/
_error?: true;
}

const wildcard = ' ';
Expand Down Expand Up @@ -70,21 +75,23 @@ export function preparse(dateString: string, arg: string | CompiledObject, optio

if (result) {
if (!result.length) {
pr._error = true;
break;
}
if (result.token && validateToken(result.token)) {
pr[result.token] = result.value + 0;
}
pr._index += result.length;
pr._match++;
} else if (token === str[0] || token === wildcard) {
} else if (token === str[0] || token === wildcard && str.length > 0) {
pr._index++;
} else if (COMMENT.test(token) && !str.indexOf(token.replace(COMMENT, '$1'))) {
pr._index += token.length - 2;
} else if (token === ellipsis) {
pr._index = dateString.length;
break;
} else {
pr._error = true;
break;
}
}
Expand Down
49 changes: 49 additions & 0 deletions tests/incomplete-format.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, test } from 'vitest';
import { compile, isValid, parse, preparse } from '@/index.ts';
import { parser } from '@/plugins/day-of-week.ts';

describe('incomplete format matching', () => {
test.each([
['2025', 'YYYY-MM-DD'],
['2025-', 'YYYY-MM-DD'],
['2025-01', 'YYYY-MM-DD'],
['2025-01-', 'YYYY-MM-DD'],
['2025-01-01', 'YYYY-MM-DD HH:mm'],
['2025', 'YYYY[ required]'],
['2025', 'YYYY '],
['2025', 'YYYY ...'],
['2025', 'YYYY ...'],
['2025 ', 'YYYY ...']
])('rejects %s when %s still requires input', (input, format) => {
for (const pattern of [format, compile(format)]) {
expect(isValid(input, pattern)).toBe(false);
expect(Number.isNaN(parse(input, pattern).getTime())).toBe(true);
}
});

test('rejects an absent plugin token', () => {
const options = { plugins: [parser] };
expect(isValid('2025-01-01 ', 'YYYY-MM-DD dddd', options)).toBe(false);
expect(Number.isNaN(parse('2025-01-01 ', 'YYYY-MM-DD dddd', options).getTime())).toBe(true);
});

test('preserves parsed components when matching fails', () => {
expect(preparse('2025-01', 'YYYY-MM-DD')).toMatchObject({
Y: 2025, M: 1, _error: true, _index: 7, _length: 7, _match: 2
});
});

test.each([
['2025', 'YYYY'],
['2025-01', 'YYYY-MM'],
['2025-01-01', 'YYYY-MM-DD'],
['2025', 'YYYY...'],
['2025 additional text', 'YYYY...'],
['2025 ', 'YYYY '],
['2025 ', 'YYYY ...'],
['2025 ', 'YYYY ...']
])('accepts %s when %s is fully matched', (input, pattern) => {
expect(isValid(input, pattern)).toBe(true);
expect(Number.isNaN(parse(input, pattern).getTime())).toBe(false);
});
});
12 changes: 6 additions & 6 deletions tests/preparse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('MMMM', () => {
});

test('Zero', () => {
const dt = { _index: 0, _length: 4, _match: 0 };
const dt = { _error: true, _index: 0, _length: 4, _match: 0 };
expect(preparse('Zero', 'MMMM')).toEqual(dt);
});
});
Expand All @@ -147,7 +147,7 @@ describe('MMM', () => {
});

test('Zero', () => {
const dt = { _index: 0, _length: 4, _match: 0 };
const dt = { _error: true, _index: 0, _length: 4, _match: 0 };
expect(preparse('Zero', 'MMM')).toEqual(dt);
});
});
Expand Down Expand Up @@ -300,7 +300,7 @@ describe('AA', () => {
});

test('M.M.', () => {
const dt = { _index: 0, _length: 4, _match: 0 };
const dt = { _error: true, _index: 0, _length: 4, _match: 0 };
expect(preparse('M.M.', 'AA')).toEqual(dt);
});
});
Expand All @@ -317,7 +317,7 @@ describe('A', () => {
});

test('MM', () => {
const dt = { _index: 0, _length: 2, _match: 0 };
const dt = { _error: true, _index: 0, _length: 2, _match: 0 };
expect(preparse('MM', 'A')).toEqual(dt);
});
});
Expand All @@ -334,7 +334,7 @@ describe('aa', () => {
});

test('m.m.', () => {
const dt = { _index: 0, _length: 4, _match: 0 };
const dt = { _error: true, _index: 0, _length: 4, _match: 0 };
expect(preparse('m.m.', 'aa')).toEqual(dt);
});
});
Expand All @@ -351,7 +351,7 @@ describe('a', () => {
});

test('mm', () => {
const dt = { _index: 0, _length: 2, _match: 0 };
const dt = { _error: true, _index: 0, _length: 2, _match: 0 };
expect(preparse('mm', 'a')).toEqual(dt);
});
});
Expand Down
Loading