todoosy 0.3.4 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/formatter.d.ts +2 -1
- package/dist/cjs/formatter.js +11 -4
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/linter.d.ts +2 -1
- package/dist/cjs/linter.js +140 -126
- package/dist/cjs/parser.d.ts +11 -4
- package/dist/cjs/parser.js +269 -287
- package/dist/cjs/query.d.ts +2 -1
- package/dist/cjs/query.js +12 -5
- package/dist/cjs/relative-date.d.ts +34 -0
- package/dist/cjs/relative-date.js +233 -0
- package/dist/cjs/settings.js +1 -1
- package/dist/cjs/types.d.ts +2 -0
- package/dist/formatter.d.ts +2 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +11 -4
- package/dist/formatter.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/linter.d.ts +2 -1
- package/dist/linter.d.ts.map +1 -1
- package/dist/linter.js +140 -126
- package/dist/linter.js.map +1 -1
- package/dist/parser.d.ts +11 -4
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +269 -287
- package/dist/parser.js.map +1 -1
- package/dist/query.d.ts +2 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +12 -5
- package/dist/query.js.map +1 -1
- package/dist/relative-date.d.ts +35 -0
- package/dist/relative-date.d.ts.map +1 -0
- package/dist/relative-date.js +229 -0
- package/dist/relative-date.js.map +1 -0
- package/dist/settings.js +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/cjs/formatter.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Todoosy Formatter
|
|
3
3
|
*/
|
|
4
|
+
import { type ParseOptions } from './parser.js';
|
|
4
5
|
import type { Scheme } from './types.js';
|
|
5
|
-
export declare function format(text: string, scheme?: Scheme, filename?: string): string;
|
|
6
|
+
export declare function format(text: string, scheme?: Scheme, filename?: string, options?: ParseOptions): string;
|
package/dist/cjs/formatter.js
CHANGED
|
@@ -18,8 +18,15 @@ function parseMiscLocation(misc) {
|
|
|
18
18
|
function formatMetadata(metadata) {
|
|
19
19
|
const parts = [];
|
|
20
20
|
if (metadata.due) {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if (metadata.due_start) {
|
|
22
|
+
// Span form: `due start~end`. The `~` between dates carries the soft semantics;
|
|
23
|
+
// a leading `~` is never used in conjunction with a span.
|
|
24
|
+
parts.push(`due ${metadata.due_start}~${metadata.due}`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const softPrefix = metadata.due_soft ? '~' : '';
|
|
28
|
+
parts.push(`due ${softPrefix}${metadata.due}`);
|
|
29
|
+
}
|
|
23
30
|
}
|
|
24
31
|
if (metadata.progress) {
|
|
25
32
|
parts.push(metadata.progress);
|
|
@@ -70,8 +77,8 @@ function formatComments(comments, isListItem, indent) {
|
|
|
70
77
|
// Heading comments are not indented
|
|
71
78
|
return comments;
|
|
72
79
|
}
|
|
73
|
-
function format(text, scheme, filename) {
|
|
74
|
-
const { ast } = (0, parser_js_1.parse)(text);
|
|
80
|
+
function format(text, scheme, filename, options) {
|
|
81
|
+
const { ast } = (0, parser_js_1.parse)(text, options);
|
|
75
82
|
const lines = [];
|
|
76
83
|
const itemMap = new Map();
|
|
77
84
|
for (const item of ast.items) {
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Todoosy - Markdown-based todo system
|
|
3
3
|
*/
|
|
4
4
|
export { parse, parseTokensInParenGroup, extractParenGroups } from './parser.js';
|
|
5
|
-
export type { ParseResult } from './parser.js';
|
|
5
|
+
export type { ParseResult, ParseOptions } from './parser.js';
|
|
6
|
+
export { resolveNow, resolveKeyword, tryParseRelative } from './relative-date.js';
|
|
7
|
+
export type { ResolvedNow } from './relative-date.js';
|
|
6
8
|
export { format } from './formatter.js';
|
|
7
9
|
export { lint } from './linter.js';
|
|
8
10
|
export { queryUpcoming, queryMisc, queryByHashtag, listHashtags } from './query.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* Todoosy - Markdown-based todo system
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.convertToBullets = exports.convertToSequence = exports.removeSequencedItem = exports.insertSequencedItem = exports.renumberChildren = exports.analyzeSequence = exports.parseSettings = exports.parseScheme = exports.listHashtags = exports.queryByHashtag = exports.queryMisc = exports.queryUpcoming = exports.lint = exports.format = exports.extractParenGroups = exports.parseTokensInParenGroup = exports.parse = void 0;
|
|
6
|
+
exports.convertToBullets = exports.convertToSequence = exports.removeSequencedItem = exports.insertSequencedItem = exports.renumberChildren = exports.analyzeSequence = exports.parseSettings = exports.parseScheme = exports.listHashtags = exports.queryByHashtag = exports.queryMisc = exports.queryUpcoming = exports.lint = exports.format = exports.tryParseRelative = exports.resolveKeyword = exports.resolveNow = exports.extractParenGroups = exports.parseTokensInParenGroup = exports.parse = void 0;
|
|
7
7
|
var parser_js_1 = require("./parser.js");
|
|
8
8
|
Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parser_js_1.parse; } });
|
|
9
9
|
Object.defineProperty(exports, "parseTokensInParenGroup", { enumerable: true, get: function () { return parser_js_1.parseTokensInParenGroup; } });
|
|
10
10
|
Object.defineProperty(exports, "extractParenGroups", { enumerable: true, get: function () { return parser_js_1.extractParenGroups; } });
|
|
11
|
+
var relative_date_js_1 = require("./relative-date.js");
|
|
12
|
+
Object.defineProperty(exports, "resolveNow", { enumerable: true, get: function () { return relative_date_js_1.resolveNow; } });
|
|
13
|
+
Object.defineProperty(exports, "resolveKeyword", { enumerable: true, get: function () { return relative_date_js_1.resolveKeyword; } });
|
|
14
|
+
Object.defineProperty(exports, "tryParseRelative", { enumerable: true, get: function () { return relative_date_js_1.tryParseRelative; } });
|
|
11
15
|
var formatter_js_1 = require("./formatter.js");
|
|
12
16
|
Object.defineProperty(exports, "format", { enumerable: true, get: function () { return formatter_js_1.format; } });
|
|
13
17
|
var linter_js_1 = require("./linter.js");
|
package/dist/cjs/linter.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Todoosy Linter
|
|
3
3
|
*/
|
|
4
|
+
import { type ParseOptions } from './parser.js';
|
|
4
5
|
import type { LintResult, Scheme } from './types.js';
|
|
5
|
-
export declare function lint(text: string, scheme?: Scheme, filename?: string): LintResult;
|
|
6
|
+
export declare function lint(text: string, scheme?: Scheme, filename?: string, options?: ParseOptions): LintResult;
|
|
6
7
|
export declare function lintScheme(scheme: Scheme): LintResult;
|
package/dist/cjs/linter.js
CHANGED
|
@@ -76,8 +76,8 @@ function parseMiscLocation(misc) {
|
|
|
76
76
|
heading: misc.substring(slashIndex + 1),
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
function lint(text, scheme, filename) {
|
|
80
|
-
const { ast } = (0, parser_js_1.parse)(text);
|
|
79
|
+
function lint(text, scheme, filename, options) {
|
|
80
|
+
const { ast } = (0, parser_js_1.parse)(text, options);
|
|
81
81
|
const warnings = [];
|
|
82
82
|
const lines = text.split('\n');
|
|
83
83
|
// Determine misc location from scheme or use default
|
|
@@ -102,6 +102,12 @@ function lint(text, scheme, filename) {
|
|
|
102
102
|
miscSectionSpan = item.item_span;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
// If the parser already recognized a due date for this item, skip the
|
|
106
|
+
// regex-based date-format validation entirely. The parser accepts a
|
|
107
|
+
// richer surface than these regexes know about (today/tomorrow keywords,
|
|
108
|
+
// relative offsets like "in 2 weeks", spans like `start~end`); trusting
|
|
109
|
+
// the parser is both more correct and avoids divergence between the two.
|
|
110
|
+
const skipDateFormatChecks = item.metadata.due !== null;
|
|
105
111
|
// Check for invalid date formats in parentheses
|
|
106
112
|
const parenMatches = rawLine.matchAll(/\(([^)]+)\)/g);
|
|
107
113
|
for (const match of parenMatches) {
|
|
@@ -114,7 +120,7 @@ function lint(text, scheme, filename) {
|
|
|
114
120
|
for (const textDueMatch of textDueMatches) {
|
|
115
121
|
textDueIndices.add(textDueMatch.index || 0);
|
|
116
122
|
const dateStr = textDueMatch[1];
|
|
117
|
-
if (!isValidDate(dateStr)) {
|
|
123
|
+
if (!isValidDate(dateStr) && !skipDateFormatChecks) {
|
|
118
124
|
const tokenStart = parenStart + 1 + (textDueMatch.index || 0);
|
|
119
125
|
warnings.push({
|
|
120
126
|
code: 'INVALID_DATE_FORMAT',
|
|
@@ -130,7 +136,7 @@ function lint(text, scheme, filename) {
|
|
|
130
136
|
for (const textDueMatch of textDueDayFirstMatches) {
|
|
131
137
|
textDueIndices.add(textDueMatch.index || 0);
|
|
132
138
|
const dateStr = textDueMatch[1];
|
|
133
|
-
if (!isValidDate(dateStr)) {
|
|
139
|
+
if (!isValidDate(dateStr) && !skipDateFormatChecks) {
|
|
134
140
|
const tokenStart = parenStart + 1 + (textDueMatch.index || 0);
|
|
135
141
|
warnings.push({
|
|
136
142
|
code: 'INVALID_DATE_FORMAT',
|
|
@@ -156,7 +162,7 @@ function lint(text, scheme, filename) {
|
|
|
156
162
|
// Skip if this looks like a day number (could be start of day-first date, with or without ~)
|
|
157
163
|
if (/^~?\d{1,2}$/.test(dateStr))
|
|
158
164
|
continue;
|
|
159
|
-
if (!isValidDate(dateStr)) {
|
|
165
|
+
if (!isValidDate(dateStr) && !skipDateFormatChecks) {
|
|
160
166
|
const tokenStart = parenStart + 1 + (dueMatch.index || 0);
|
|
161
167
|
warnings.push({
|
|
162
168
|
code: 'INVALID_DATE_FORMAT',
|
|
@@ -167,134 +173,138 @@ function lint(text, scheme, filename) {
|
|
|
167
173
|
});
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
|
-
// Check for multiple due dates across all paren groups
|
|
176
|
+
// Check for multiple due dates across all paren groups.
|
|
177
|
+
// Span syntax (`start~end`) legitimately contains two ISO/text dates,
|
|
178
|
+
// so skip duplicate detection when the parser recognized a span.
|
|
171
179
|
const allDueDates = [];
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
180
|
+
if (item.metadata.due_start === null) {
|
|
181
|
+
const allParenMatches = rawLine.matchAll(/\(([^)]+)\)/g);
|
|
182
|
+
for (const pMatch of allParenMatches) {
|
|
183
|
+
const pContent = pMatch[1];
|
|
184
|
+
const pStart = item.item_span[0] + (pMatch.index || 0);
|
|
185
|
+
const matchedPositions = new Set();
|
|
186
|
+
// Check text dates first (month-first, year can be 2 or 4 digits) with "due" prefix
|
|
187
|
+
// Allow optional ~ prefix for soft dates
|
|
188
|
+
const textDueDatesInGroup = pContent.matchAll(/\bdue\s+(~?(?:january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)\s+\d{1,2}(?:\s+\d{2,4})?)/gi);
|
|
189
|
+
for (const tdm of textDueDatesInGroup) {
|
|
190
|
+
const ds = tdm[1];
|
|
191
|
+
if (isValidDate(ds)) {
|
|
192
|
+
matchedPositions.add(tdm.index || 0);
|
|
193
|
+
const dStart = pStart + 1 + (tdm.index || 0);
|
|
194
|
+
allDueDates.push({
|
|
195
|
+
dateStr: ds,
|
|
196
|
+
span: [dStart, dStart + tdm[0].length],
|
|
197
|
+
});
|
|
198
|
+
}
|
|
189
199
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
200
|
+
// Check day-first text dates (year can be 2 or 4 digits) with "due" prefix
|
|
201
|
+
// Allow optional ~ prefix for soft dates
|
|
202
|
+
const textDueDayFirstInGroup = pContent.matchAll(/\bdue\s+(~?\d{1,2}\s+(?:january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)(?:\s+\d{2,4})?)/gi);
|
|
203
|
+
for (const tdm of textDueDayFirstInGroup) {
|
|
204
|
+
const ds = tdm[1];
|
|
205
|
+
if (isValidDate(ds)) {
|
|
206
|
+
matchedPositions.add(tdm.index || 0);
|
|
207
|
+
const dStart = pStart + 1 + (tdm.index || 0);
|
|
208
|
+
allDueDates.push({
|
|
209
|
+
dateStr: ds,
|
|
210
|
+
span: [dStart, dStart + tdm[0].length],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
203
213
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
214
|
+
// Check standard dates with "due" prefix
|
|
215
|
+
// Allow optional ~ prefix for soft dates
|
|
216
|
+
const dueDatesInGroup = pContent.matchAll(/\bdue\s+(~?[^\s,)]+)/gi);
|
|
217
|
+
for (const dm of dueDatesInGroup) {
|
|
218
|
+
// Skip if already matched as text date
|
|
219
|
+
if (matchedPositions.has(dm.index || 0))
|
|
220
|
+
continue;
|
|
221
|
+
const ds = dm[1];
|
|
222
|
+
// Skip if it's a month name (part of text date, with or without ~)
|
|
223
|
+
const strippedDs = ds.startsWith('~') ? ds.slice(1) : ds;
|
|
224
|
+
if (MONTH_NAMES.has(strippedDs.toLowerCase()))
|
|
225
|
+
continue;
|
|
226
|
+
// Skip if it's a day number (part of day-first text date, with or without ~)
|
|
227
|
+
if (/^~?\d{1,2}$/.test(ds))
|
|
228
|
+
continue;
|
|
229
|
+
if (isValidDate(ds)) {
|
|
230
|
+
matchedPositions.add(dm.index || 0);
|
|
231
|
+
const dStart = pStart + 1 + (dm.index || 0);
|
|
232
|
+
allDueDates.push({
|
|
233
|
+
dateStr: ds,
|
|
234
|
+
span: [dStart, dStart + dm[0].length],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
227
237
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
238
|
+
// Check standalone text dates (without "due" prefix, not preceded by digit+space which indicates day-first)
|
|
239
|
+
// Allow optional ~ prefix for soft dates
|
|
240
|
+
// Exclude if preceded by "due " or "due ~"
|
|
241
|
+
const standaloneTextDatesInGroup = pContent.matchAll(/(?<!\bdue\s)(?<!\bdue\s~)(?<!\d\s)(~?(?:january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)\s+\d{1,2}(?:\s+\d{2,4})?)/gi);
|
|
242
|
+
for (const stm of standaloneTextDatesInGroup) {
|
|
243
|
+
// Skip if already matched
|
|
244
|
+
if (matchedPositions.has(stm.index || 0))
|
|
245
|
+
continue;
|
|
246
|
+
const ds = stm[1];
|
|
247
|
+
if (isValidDate(ds)) {
|
|
248
|
+
matchedPositions.add(stm.index || 0);
|
|
249
|
+
const dStart = pStart + 1 + (stm.index || 0);
|
|
250
|
+
allDueDates.push({
|
|
251
|
+
dateStr: ds,
|
|
252
|
+
span: [dStart, dStart + stm[0].length],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
245
255
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
256
|
+
// Check standalone day-first text dates (without "due" prefix, not preceded by digit)
|
|
257
|
+
// Note: Day-first soft dates would be like ~15 Feb, but this is unusual; keep pattern simple
|
|
258
|
+
// Exclude if preceded by "due " or "due ~"
|
|
259
|
+
const standaloneDayFirstInGroup = pContent.matchAll(/(?<!\bdue\s)(?<!\bdue\s~)(?<!\d)(~?\d{1,2}\s+(?:january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)(?:\s+\d{2,4})?)/gi);
|
|
260
|
+
for (const stm of standaloneDayFirstInGroup) {
|
|
261
|
+
// Skip if already matched
|
|
262
|
+
if (matchedPositions.has(stm.index || 0))
|
|
263
|
+
continue;
|
|
264
|
+
const ds = stm[1];
|
|
265
|
+
if (isValidDate(ds)) {
|
|
266
|
+
matchedPositions.add(stm.index || 0);
|
|
267
|
+
const dStart = pStart + 1 + (stm.index || 0);
|
|
268
|
+
allDueDates.push({
|
|
269
|
+
dateStr: ds,
|
|
270
|
+
span: [dStart, dStart + stm[0].length],
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Check standalone ISO/slash dates (without "due" prefix)
|
|
275
|
+
// Use word boundary to avoid matching partial dates
|
|
276
|
+
// Allow optional ~ prefix for soft dates
|
|
277
|
+
// Exclude if preceded by "due " or "due ~"
|
|
278
|
+
const standaloneNumericDates = pContent.matchAll(/(?<!\bdue\s)(?<!\bdue\s~)(?<!\d)(~?\d{2,4}[-\/]\d{1,2}[-\/]\d{1,4})(?!\d)/gi);
|
|
279
|
+
for (const snm of standaloneNumericDates) {
|
|
280
|
+
// Skip if already matched
|
|
281
|
+
if (matchedPositions.has(snm.index || 0))
|
|
282
|
+
continue;
|
|
283
|
+
const ds = snm[1];
|
|
284
|
+
if (isValidDate(ds)) {
|
|
285
|
+
matchedPositions.add(snm.index || 0);
|
|
286
|
+
const dStart = pStart + 1 + (snm.index || 0);
|
|
287
|
+
allDueDates.push({
|
|
288
|
+
dateStr: ds,
|
|
289
|
+
span: [dStart, dStart + snm[0].length],
|
|
290
|
+
});
|
|
291
|
+
}
|
|
263
292
|
}
|
|
264
293
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const ds = snm[1];
|
|
275
|
-
if (isValidDate(ds)) {
|
|
276
|
-
matchedPositions.add(snm.index || 0);
|
|
277
|
-
const dStart = pStart + 1 + (snm.index || 0);
|
|
278
|
-
allDueDates.push({
|
|
279
|
-
dateStr: ds,
|
|
280
|
-
span: [dStart, dStart + snm[0].length],
|
|
294
|
+
if (allDueDates.length > 1) {
|
|
295
|
+
// Warn for the second and subsequent ones
|
|
296
|
+
for (let i = 1; i < allDueDates.length; i++) {
|
|
297
|
+
warnings.push({
|
|
298
|
+
code: 'DUPLICATE_DUE_DATE',
|
|
299
|
+
message: 'Multiple due dates found, using last value',
|
|
300
|
+
line: item.line,
|
|
301
|
+
column: allDueDates[i].span[0] - item.item_span[0] + 1,
|
|
302
|
+
span: allDueDates[i].span,
|
|
281
303
|
});
|
|
282
304
|
}
|
|
305
|
+
break; // Only warn once per item
|
|
283
306
|
}
|
|
284
|
-
}
|
|
285
|
-
if (allDueDates.length > 1) {
|
|
286
|
-
// Warn for the second and subsequent ones
|
|
287
|
-
for (let i = 1; i < allDueDates.length; i++) {
|
|
288
|
-
warnings.push({
|
|
289
|
-
code: 'DUPLICATE_DUE_DATE',
|
|
290
|
-
message: 'Multiple due dates found, using last value',
|
|
291
|
-
line: item.line,
|
|
292
|
-
column: allDueDates[i].span[0] - item.item_span[0] + 1,
|
|
293
|
-
span: allDueDates[i].span,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
break; // Only warn once per item
|
|
297
|
-
}
|
|
307
|
+
} // end if (item.metadata.due_start === null)
|
|
298
308
|
// Check for standalone dates (without "due" prefix)
|
|
299
309
|
// Standalone text dates: Month Day [Year] (not preceded by "due" or digit+space)
|
|
300
310
|
// Allow optional ~ prefix for soft dates
|
|
@@ -302,7 +312,7 @@ function lint(text, scheme, filename) {
|
|
|
302
312
|
const standaloneTextDates = content.matchAll(/(?<!\bdue\s)(?<!\bdue\s~)(?<!\d\s)(~?(?:january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)\s+\d{1,2}(?:\s+\d{2,4})?)/gi);
|
|
303
313
|
for (const stdMatch of standaloneTextDates) {
|
|
304
314
|
const dateStr = stdMatch[1];
|
|
305
|
-
if (!isValidDate(dateStr)) {
|
|
315
|
+
if (!isValidDate(dateStr) && !skipDateFormatChecks) {
|
|
306
316
|
const tokenStart = parenStart + 1 + (stdMatch.index || 0);
|
|
307
317
|
warnings.push({
|
|
308
318
|
code: 'INVALID_DATE_FORMAT',
|
|
@@ -319,7 +329,7 @@ function lint(text, scheme, filename) {
|
|
|
319
329
|
const standaloneDayFirstDates = content.matchAll(/(?<!\bdue\s)(?<!\bdue\s~)(?<!\d)(~?\d{1,2}\s+(?:january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)(?:\s+\d{2,4})?)/gi);
|
|
320
330
|
for (const stdMatch of standaloneDayFirstDates) {
|
|
321
331
|
const dateStr = stdMatch[1];
|
|
322
|
-
if (!isValidDate(dateStr)) {
|
|
332
|
+
if (!isValidDate(dateStr) && !skipDateFormatChecks) {
|
|
323
333
|
const tokenStart = parenStart + 1 + (stdMatch.index || 0);
|
|
324
334
|
warnings.push({
|
|
325
335
|
code: 'INVALID_DATE_FORMAT',
|
|
@@ -350,7 +360,11 @@ function lint(text, scheme, filename) {
|
|
|
350
360
|
const invalidEstimates = content.matchAll(/\b(\d+)([a-zA-Z])(?![mhdMHD])\b/gi);
|
|
351
361
|
for (const ieMatch of invalidEstimates) {
|
|
352
362
|
const unit = ieMatch[2].toLowerCase();
|
|
353
|
-
|
|
363
|
+
// Allow relative-date single-letter units `w` (week) and `y` (year)
|
|
364
|
+
// alongside the existing estimate units m/h/d. Multi-letter forms
|
|
365
|
+
// (`2wk`, `2week`, `2years`, `2 days`, `in 2 weeks`) don't match this
|
|
366
|
+
// regex anyway because the trailing word boundary fails on extra letters.
|
|
367
|
+
if (unit !== 'm' && unit !== 'h' && unit !== 'd' && unit !== 'w' && unit !== 'y') {
|
|
354
368
|
const tokenStart = parenStart + 1 + (ieMatch.index || 0);
|
|
355
369
|
warnings.push({
|
|
356
370
|
code: 'INVALID_TOKEN',
|
package/dist/cjs/parser.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Todoosy Parser
|
|
3
3
|
*/
|
|
4
|
-
import type { AST, ParenGroup, Warning } from './types.js';
|
|
4
|
+
import type { AST, ParenGroup, Warning, Settings } from './types.js';
|
|
5
|
+
import { type ResolvedNow } from './relative-date.js';
|
|
6
|
+
export interface ParseOptions {
|
|
7
|
+
/** Override "now" for deterministic tests. */
|
|
8
|
+
now?: Date;
|
|
9
|
+
/** Settings (timezone) used when resolving relative dates. */
|
|
10
|
+
settings?: Settings;
|
|
11
|
+
}
|
|
5
12
|
export interface ParseResult {
|
|
6
13
|
ast: AST;
|
|
7
14
|
warnings: Warning[];
|
|
8
15
|
}
|
|
9
|
-
export declare function parseTokensInParenGroup(content: string, groupStart: number): ParenGroup;
|
|
10
|
-
export declare function extractParenGroups(line: string,
|
|
11
|
-
export declare function parse(text: string): ParseResult;
|
|
16
|
+
export declare function parseTokensInParenGroup(content: string, groupStart: number, now?: ResolvedNow): ParenGroup;
|
|
17
|
+
export declare function extractParenGroups(line: string, _lineStart: number, now?: ResolvedNow): ParenGroup[];
|
|
18
|
+
export declare function parse(text: string, options?: ParseOptions): ParseResult;
|