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
16 changes: 15 additions & 1 deletion src/parser/jsonParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ const formats = {
'ipv6': { errorMessage: l10n.t('String is not an IPv6 address.'), pattern: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i },
};

// The date and date-time patterns check syntax; RFC 3339 section 5.7 also limits the day by month and year.
function isValidDate(year: number, month: number, day: number): boolean {
let daysInMonth = 31;
if (month === 2) {
const isLeapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
daysInMonth = isLeapYear ? 29 : 28;
} else if (month === 4 || month === 6 || month === 9 || month === 11) {
daysInMonth = 30;
}
return day <= daysInMonth;
}

export interface IProblem {
location: IRange;
severity?: DiagnosticSeverity;
Expand Down Expand Up @@ -974,7 +986,9 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
case 'ipv4':
case 'ipv6':
const format = formats[schema.format];
if (!node.value || !format.pattern.exec(node.value)) {
const match = format.pattern.exec(node.value);
const hasDate = schema.format === 'date' || schema.format === 'date-time';
if (!match || (hasDate && !isValidDate(Number(match[1]), Number(match[2]), Number(match[3])))) {
validationResult.problems.push({
location: { offset: node.offset, length: node.length },
message: schema.patternErrorMessage || schema.errorMessage || format.errorMessage
Expand Down
28 changes: 28 additions & 0 deletions src/test/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,34 @@ suite('JSON Parser', () => {
assert.strictEqual(semanticErrors![0].message, 'String is not a valid UUID.');
});

for (const format of ['date', 'date-time']) {
test(`${format} calendar dates`, function () {
const schema = { type: 'string', format };
const validDates = [
'2023-01-31', '2023-02-28', '2023-03-31', '2023-04-30',
'2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31',
'2023-09-30', '2023-10-31', '2023-11-30', '2023-12-31',
'2024-02-29', '2000-02-29', '0000-02-29', '0096-02-29', '0400-02-29'
];
const invalidDates = [
'2023-02-29', '2023-02-30', '2024-02-30', '2024-02-31',
'2023-04-31', '2023-06-31', '2023-09-31', '2023-11-31',
'1900-02-29', '2100-02-29', '0100-02-29', '0001-02-29'
];
for (const date of validDates) {
const value = format === 'date' ? date : `${date}T23:30:00-02:00`;
assert.strictEqual(validate(JSON.stringify(value), schema)!.length, 0, value);
}
for (const date of invalidDates) {
const value = format === 'date' ? date : `${date}T00:30:00+02:00`;
const diagnostics = validate(JSON.stringify(value), schema)!;
assert.strictEqual(diagnostics.length, 1, value);
assert.strictEqual(diagnostics[0].message, `String is not a RFC3339 ${format}.`);
assert.deepStrictEqual(diagnostics[0].range, toRange(JSON.stringify(value), 0, value.length + 2));
}
});
}

test('Numbers', function () {

const str = '{"one": 13.45e+1}';
Expand Down