@md2do/core 0.2.2 → 0.3.0
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/CHANGELOG.md +31 -0
- package/coverage/coverage-final.json +9 -9
- package/coverage/index.html +14 -14
- package/coverage/lcov-report/index.html +14 -14
- package/coverage/lcov-report/src/filters/index.html +1 -1
- package/coverage/lcov-report/src/filters/index.ts.html +1 -1
- package/coverage/lcov-report/src/index.html +1 -1
- package/coverage/lcov-report/src/index.ts.html +1 -1
- package/coverage/lcov-report/src/parser/index.html +9 -9
- package/coverage/lcov-report/src/parser/index.ts.html +334 -124
- package/coverage/lcov-report/src/parser/patterns.ts.html +1 -1
- package/coverage/lcov-report/src/scanner/index.html +7 -7
- package/coverage/lcov-report/src/scanner/index.ts.html +208 -70
- package/coverage/lcov-report/src/sorting/index.html +1 -1
- package/coverage/lcov-report/src/sorting/index.ts.html +1 -1
- package/coverage/lcov-report/src/utils/dates.ts.html +21 -21
- package/coverage/lcov-report/src/utils/id.ts.html +8 -8
- package/coverage/lcov-report/src/utils/index.html +1 -1
- package/coverage/lcov-report/src/writer/index.html +1 -1
- package/coverage/lcov-report/src/writer/index.ts.html +1 -1
- package/coverage/lcov.info +479 -342
- package/coverage/src/filters/index.html +1 -1
- package/coverage/src/filters/index.ts.html +1 -1
- package/coverage/src/index.html +1 -1
- package/coverage/src/index.ts.html +1 -1
- package/coverage/src/parser/index.html +9 -9
- package/coverage/src/parser/index.ts.html +334 -124
- package/coverage/src/parser/patterns.ts.html +1 -1
- package/coverage/src/scanner/index.html +7 -7
- package/coverage/src/scanner/index.ts.html +208 -70
- package/coverage/src/sorting/index.html +1 -1
- package/coverage/src/sorting/index.ts.html +1 -1
- package/coverage/src/utils/dates.ts.html +21 -21
- package/coverage/src/utils/id.ts.html +8 -8
- package/coverage/src/utils/index.html +1 -1
- package/coverage/src/writer/index.html +1 -1
- package/coverage/src/writer/index.ts.html +1 -1
- package/dist/index.d.mts +64 -3
- package/dist/index.d.ts +64 -3
- package/dist/index.js +154 -0
- package/dist/index.mjs +152 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/parser/index.ts +103 -0
- package/src/scanner/index.ts +54 -0
- package/src/types/index.ts +31 -2
- package/src/warnings/filter.ts +93 -0
- package/tests/parser/index.test.ts +107 -1
- package/tests/scanner/index.test.ts +94 -4
package/dist/index.d.mts
CHANGED
|
@@ -47,11 +47,20 @@ interface ScanResult {
|
|
|
47
47
|
parseErrors: number;
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
type WarningSeverity = 'info' | 'warning' | 'error';
|
|
51
|
+
type WarningCode = 'unsupported-bullet' | 'malformed-checkbox' | 'missing-space-after' | 'missing-space-before' | 'relative-date-no-context' | 'missing-due-date' | 'missing-completed-date' | 'duplicate-todoist-id' | 'file-read-error';
|
|
50
52
|
interface Warning {
|
|
51
53
|
file: string;
|
|
52
54
|
line: number;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
column?: number;
|
|
56
|
+
severity: WarningSeverity;
|
|
57
|
+
source: 'md2do';
|
|
58
|
+
ruleId: WarningCode;
|
|
59
|
+
message: string;
|
|
60
|
+
text?: string;
|
|
61
|
+
url?: string;
|
|
62
|
+
/** @deprecated Use message instead */
|
|
63
|
+
reason?: string;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
/**
|
|
@@ -812,6 +821,58 @@ declare namespace index {
|
|
|
812
821
|
export { type index_TaskComparator as TaskComparator, index_byAssignee as byAssignee, index_byCompletionStatus as byCompletionStatus, index_byCreatedDate as byCreatedDate, index_byDueDate as byDueDate, index_byFile as byFile, index_byPerson as byPerson, index_byPriority as byPriority, index_byProject as byProject, index_combineComparators as combineComparators, index_reverse as reverse };
|
|
813
822
|
}
|
|
814
823
|
|
|
824
|
+
interface WarningFilterConfig {
|
|
825
|
+
enabled?: boolean | undefined;
|
|
826
|
+
rules?: Record<string, 'error' | 'warn' | 'info' | 'off'> | undefined;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Filter warnings based on configuration rules
|
|
830
|
+
*
|
|
831
|
+
* This function applies warning configuration rules to filter out disabled warnings
|
|
832
|
+
* and optionally all warnings if globally disabled.
|
|
833
|
+
*
|
|
834
|
+
* @param warnings - Array of warnings to filter
|
|
835
|
+
* @param config - Warning configuration with enabled flag and rule overrides
|
|
836
|
+
* @returns Filtered array of warnings
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* ```typescript
|
|
840
|
+
* const config = {
|
|
841
|
+
* enabled: true,
|
|
842
|
+
* rules: {
|
|
843
|
+
* 'missing-due-date': 'off',
|
|
844
|
+
* 'duplicate-todoist-id': 'error',
|
|
845
|
+
* },
|
|
846
|
+
* };
|
|
847
|
+
*
|
|
848
|
+
* const filtered = filterWarnings(allWarnings, config);
|
|
849
|
+
* // Returns warnings except those with ruleId 'missing-due-date'
|
|
850
|
+
* ```
|
|
851
|
+
*/
|
|
852
|
+
declare function filterWarnings(warnings: Warning[], config?: WarningFilterConfig): Warning[];
|
|
853
|
+
/**
|
|
854
|
+
* Group warnings by severity
|
|
855
|
+
*
|
|
856
|
+
* Useful for displaying warnings in order of importance or
|
|
857
|
+
* treating errors differently from warnings.
|
|
858
|
+
*
|
|
859
|
+
* @param warnings - Array of warnings to group
|
|
860
|
+
* @returns Object with warnings grouped by severity level
|
|
861
|
+
*
|
|
862
|
+
* @example
|
|
863
|
+
* ```typescript
|
|
864
|
+
* const grouped = groupWarningsBySeverity(warnings);
|
|
865
|
+
* console.log(`${grouped.error.length} errors`);
|
|
866
|
+
* console.log(`${grouped.warning.length} warnings`);
|
|
867
|
+
* console.log(`${grouped.info.length} info messages`);
|
|
868
|
+
* ```
|
|
869
|
+
*/
|
|
870
|
+
declare function groupWarningsBySeverity(warnings: Warning[]): {
|
|
871
|
+
error: Warning[];
|
|
872
|
+
warning: Warning[];
|
|
873
|
+
info: Warning[];
|
|
874
|
+
};
|
|
875
|
+
|
|
815
876
|
/**
|
|
816
877
|
* Parse an absolute date string in various formats
|
|
817
878
|
*
|
|
@@ -876,4 +937,4 @@ declare function extractDateFromHeading(line: string): Date | null;
|
|
|
876
937
|
*/
|
|
877
938
|
declare function generateTaskId(file: string, line: number, text: string): string;
|
|
878
939
|
|
|
879
|
-
export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, index$1 as filters, generateTaskId, parseAbsoluteDate, parseTask, resolveRelativeDate, index as sorting, updateTask, updateTasks };
|
|
940
|
+
export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WarningCode, type WarningFilterConfig, type WarningSeverity, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, filterWarnings, index$1 as filters, generateTaskId, groupWarningsBySeverity, parseAbsoluteDate, parseTask, resolveRelativeDate, index as sorting, updateTask, updateTasks };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,11 +47,20 @@ interface ScanResult {
|
|
|
47
47
|
parseErrors: number;
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
type WarningSeverity = 'info' | 'warning' | 'error';
|
|
51
|
+
type WarningCode = 'unsupported-bullet' | 'malformed-checkbox' | 'missing-space-after' | 'missing-space-before' | 'relative-date-no-context' | 'missing-due-date' | 'missing-completed-date' | 'duplicate-todoist-id' | 'file-read-error';
|
|
50
52
|
interface Warning {
|
|
51
53
|
file: string;
|
|
52
54
|
line: number;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
column?: number;
|
|
56
|
+
severity: WarningSeverity;
|
|
57
|
+
source: 'md2do';
|
|
58
|
+
ruleId: WarningCode;
|
|
59
|
+
message: string;
|
|
60
|
+
text?: string;
|
|
61
|
+
url?: string;
|
|
62
|
+
/** @deprecated Use message instead */
|
|
63
|
+
reason?: string;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
/**
|
|
@@ -812,6 +821,58 @@ declare namespace index {
|
|
|
812
821
|
export { type index_TaskComparator as TaskComparator, index_byAssignee as byAssignee, index_byCompletionStatus as byCompletionStatus, index_byCreatedDate as byCreatedDate, index_byDueDate as byDueDate, index_byFile as byFile, index_byPerson as byPerson, index_byPriority as byPriority, index_byProject as byProject, index_combineComparators as combineComparators, index_reverse as reverse };
|
|
813
822
|
}
|
|
814
823
|
|
|
824
|
+
interface WarningFilterConfig {
|
|
825
|
+
enabled?: boolean | undefined;
|
|
826
|
+
rules?: Record<string, 'error' | 'warn' | 'info' | 'off'> | undefined;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Filter warnings based on configuration rules
|
|
830
|
+
*
|
|
831
|
+
* This function applies warning configuration rules to filter out disabled warnings
|
|
832
|
+
* and optionally all warnings if globally disabled.
|
|
833
|
+
*
|
|
834
|
+
* @param warnings - Array of warnings to filter
|
|
835
|
+
* @param config - Warning configuration with enabled flag and rule overrides
|
|
836
|
+
* @returns Filtered array of warnings
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* ```typescript
|
|
840
|
+
* const config = {
|
|
841
|
+
* enabled: true,
|
|
842
|
+
* rules: {
|
|
843
|
+
* 'missing-due-date': 'off',
|
|
844
|
+
* 'duplicate-todoist-id': 'error',
|
|
845
|
+
* },
|
|
846
|
+
* };
|
|
847
|
+
*
|
|
848
|
+
* const filtered = filterWarnings(allWarnings, config);
|
|
849
|
+
* // Returns warnings except those with ruleId 'missing-due-date'
|
|
850
|
+
* ```
|
|
851
|
+
*/
|
|
852
|
+
declare function filterWarnings(warnings: Warning[], config?: WarningFilterConfig): Warning[];
|
|
853
|
+
/**
|
|
854
|
+
* Group warnings by severity
|
|
855
|
+
*
|
|
856
|
+
* Useful for displaying warnings in order of importance or
|
|
857
|
+
* treating errors differently from warnings.
|
|
858
|
+
*
|
|
859
|
+
* @param warnings - Array of warnings to group
|
|
860
|
+
* @returns Object with warnings grouped by severity level
|
|
861
|
+
*
|
|
862
|
+
* @example
|
|
863
|
+
* ```typescript
|
|
864
|
+
* const grouped = groupWarningsBySeverity(warnings);
|
|
865
|
+
* console.log(`${grouped.error.length} errors`);
|
|
866
|
+
* console.log(`${grouped.warning.length} warnings`);
|
|
867
|
+
* console.log(`${grouped.info.length} info messages`);
|
|
868
|
+
* ```
|
|
869
|
+
*/
|
|
870
|
+
declare function groupWarningsBySeverity(warnings: Warning[]): {
|
|
871
|
+
error: Warning[];
|
|
872
|
+
warning: Warning[];
|
|
873
|
+
info: Warning[];
|
|
874
|
+
};
|
|
875
|
+
|
|
815
876
|
/**
|
|
816
877
|
* Parse an absolute date string in various formats
|
|
817
878
|
*
|
|
@@ -876,4 +937,4 @@ declare function extractDateFromHeading(line: string): Date | null;
|
|
|
876
937
|
*/
|
|
877
938
|
declare function generateTaskId(file: string, line: number, text: string): string;
|
|
878
939
|
|
|
879
|
-
export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, index$1 as filters, generateTaskId, parseAbsoluteDate, parseTask, resolveRelativeDate, index as sorting, updateTask, updateTasks };
|
|
940
|
+
export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WarningCode, type WarningFilterConfig, type WarningSeverity, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, filterWarnings, index$1 as filters, generateTaskId, groupWarningsBySeverity, parseAbsoluteDate, parseTask, resolveRelativeDate, index as sorting, updateTask, updateTasks };
|
package/dist/index.js
CHANGED
|
@@ -57,8 +57,10 @@ __export(index_exports, {
|
|
|
57
57
|
extractProjectFromPath: () => extractProjectFromPath,
|
|
58
58
|
extractTags: () => extractTags,
|
|
59
59
|
extractTodoistId: () => extractTodoistId,
|
|
60
|
+
filterWarnings: () => filterWarnings,
|
|
60
61
|
filters: () => filters_exports,
|
|
61
62
|
generateTaskId: () => generateTaskId,
|
|
63
|
+
groupWarningsBySeverity: () => groupWarningsBySeverity,
|
|
62
64
|
parseAbsoluteDate: () => parseAbsoluteDate,
|
|
63
65
|
parseTask: () => parseTask,
|
|
64
66
|
resolveRelativeDate: () => resolveRelativeDate,
|
|
@@ -216,11 +218,15 @@ function extractDueDate(text, context) {
|
|
|
216
218
|
return {
|
|
217
219
|
date: void 0,
|
|
218
220
|
warning: {
|
|
221
|
+
severity: "warning",
|
|
222
|
+
source: "md2do",
|
|
223
|
+
ruleId: "relative-date-no-context",
|
|
219
224
|
file: "",
|
|
220
225
|
// Will be filled in by caller
|
|
221
226
|
line: 0,
|
|
222
227
|
// Will be filled in by caller
|
|
223
228
|
text: text.trim(),
|
|
229
|
+
message: "Relative due date without context date from heading. Add a heading with a date above this task.",
|
|
224
230
|
reason: "Relative due date without context date from heading. Add a heading with a date above this task."
|
|
225
231
|
}
|
|
226
232
|
};
|
|
@@ -235,6 +241,58 @@ function cleanTaskText(text) {
|
|
|
235
241
|
}
|
|
236
242
|
function parseTask(line, lineNumber, file, context) {
|
|
237
243
|
const warnings = [];
|
|
244
|
+
if (/^\s*[*+]\s+\[[ xX]\]/.test(line)) {
|
|
245
|
+
warnings.push({
|
|
246
|
+
severity: "warning",
|
|
247
|
+
source: "md2do",
|
|
248
|
+
ruleId: "unsupported-bullet",
|
|
249
|
+
file,
|
|
250
|
+
line: lineNumber,
|
|
251
|
+
text: line.trim(),
|
|
252
|
+
message: "Unsupported bullet marker (* or +). Use dash (-) for task lists.",
|
|
253
|
+
reason: "Unsupported bullet marker (* or +). Use dash (-) for task lists."
|
|
254
|
+
});
|
|
255
|
+
return { task: null, warnings };
|
|
256
|
+
}
|
|
257
|
+
if (/^\s*-\s+\[[xX]\s+\]/.test(line) || /^\s*-\s+\[\s+[xX]\]/.test(line)) {
|
|
258
|
+
warnings.push({
|
|
259
|
+
severity: "warning",
|
|
260
|
+
source: "md2do",
|
|
261
|
+
ruleId: "malformed-checkbox",
|
|
262
|
+
file,
|
|
263
|
+
line: lineNumber,
|
|
264
|
+
text: line.trim(),
|
|
265
|
+
message: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.",
|
|
266
|
+
reason: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces."
|
|
267
|
+
});
|
|
268
|
+
return { task: null, warnings };
|
|
269
|
+
}
|
|
270
|
+
if (/^\s*-\s+\[[ xX]\][^\s]/.test(line)) {
|
|
271
|
+
warnings.push({
|
|
272
|
+
severity: "warning",
|
|
273
|
+
source: "md2do",
|
|
274
|
+
ruleId: "missing-space-after",
|
|
275
|
+
file,
|
|
276
|
+
line: lineNumber,
|
|
277
|
+
text: line.trim(),
|
|
278
|
+
message: 'Missing space after checkbox. Use "- [x] Task" format.',
|
|
279
|
+
reason: 'Missing space after checkbox. Use "- [x] Task" format.'
|
|
280
|
+
});
|
|
281
|
+
return { task: null, warnings };
|
|
282
|
+
}
|
|
283
|
+
if (/^\s*-\[[ xX]\]/.test(line)) {
|
|
284
|
+
warnings.push({
|
|
285
|
+
severity: "warning",
|
|
286
|
+
source: "md2do",
|
|
287
|
+
ruleId: "missing-space-before",
|
|
288
|
+
file,
|
|
289
|
+
line: lineNumber,
|
|
290
|
+
text: line.trim(),
|
|
291
|
+
message: 'Missing space before checkbox. Use "- [x] Task" format.',
|
|
292
|
+
reason: 'Missing space before checkbox. Use "- [x] Task" format.'
|
|
293
|
+
});
|
|
294
|
+
return { task: null, warnings };
|
|
295
|
+
}
|
|
238
296
|
const taskMatch = line.match(PATTERNS.TASK_CHECKBOX);
|
|
239
297
|
if (!taskMatch?.[0] || !taskMatch[2]) {
|
|
240
298
|
return { task: null, warnings };
|
|
@@ -254,6 +312,30 @@ function parseTask(line, lineNumber, file, context) {
|
|
|
254
312
|
line: lineNumber
|
|
255
313
|
});
|
|
256
314
|
}
|
|
315
|
+
if (!completed && !dueDateResult.date && !context.currentDate) {
|
|
316
|
+
warnings.push({
|
|
317
|
+
severity: "info",
|
|
318
|
+
source: "md2do",
|
|
319
|
+
ruleId: "missing-due-date",
|
|
320
|
+
file,
|
|
321
|
+
line: lineNumber,
|
|
322
|
+
text: fullText.trim(),
|
|
323
|
+
message: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.",
|
|
324
|
+
reason: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date."
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (completed && !completedDate) {
|
|
328
|
+
warnings.push({
|
|
329
|
+
severity: "info",
|
|
330
|
+
source: "md2do",
|
|
331
|
+
ruleId: "missing-completed-date",
|
|
332
|
+
file,
|
|
333
|
+
line: lineNumber,
|
|
334
|
+
text: fullText.trim(),
|
|
335
|
+
message: "Completed task missing completion date. Add [completed: YYYY-MM-DD].",
|
|
336
|
+
reason: "Completed task missing completion date. Add [completed: YYYY-MM-DD]."
|
|
337
|
+
});
|
|
338
|
+
}
|
|
257
339
|
const cleanText = cleanTaskText(fullText);
|
|
258
340
|
const id = generateTaskId(file, lineNumber, cleanText);
|
|
259
341
|
const task = {
|
|
@@ -299,6 +381,7 @@ var MarkdownScanner = class {
|
|
|
299
381
|
scanFile(filePath, content) {
|
|
300
382
|
const tasks = [];
|
|
301
383
|
const warnings = [];
|
|
384
|
+
const todoistIds = /* @__PURE__ */ new Map();
|
|
302
385
|
const context = {};
|
|
303
386
|
const project = extractProjectFromPath(filePath);
|
|
304
387
|
if (project !== void 0) context.project = project;
|
|
@@ -318,6 +401,26 @@ var MarkdownScanner = class {
|
|
|
318
401
|
const result = parseTask(line, lineNumber, filePath, context);
|
|
319
402
|
if (result.task) {
|
|
320
403
|
tasks.push(result.task);
|
|
404
|
+
if (result.task.todoistId) {
|
|
405
|
+
const existing = todoistIds.get(result.task.todoistId);
|
|
406
|
+
if (existing) {
|
|
407
|
+
warnings.push({
|
|
408
|
+
severity: "error",
|
|
409
|
+
source: "md2do",
|
|
410
|
+
ruleId: "duplicate-todoist-id",
|
|
411
|
+
file: filePath,
|
|
412
|
+
line: lineNumber,
|
|
413
|
+
text: result.task.text,
|
|
414
|
+
message: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
|
|
415
|
+
reason: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`
|
|
416
|
+
});
|
|
417
|
+
} else {
|
|
418
|
+
todoistIds.set(result.task.todoistId, {
|
|
419
|
+
file: filePath,
|
|
420
|
+
line: lineNumber
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
321
424
|
}
|
|
322
425
|
if (result.warnings.length > 0) {
|
|
323
426
|
warnings.push(...result.warnings);
|
|
@@ -337,10 +440,33 @@ var MarkdownScanner = class {
|
|
|
337
440
|
scanFiles(files) {
|
|
338
441
|
const allTasks = [];
|
|
339
442
|
const allWarnings = [];
|
|
443
|
+
const todoistIds = /* @__PURE__ */ new Map();
|
|
340
444
|
for (const file of files) {
|
|
341
445
|
const result = this.scanFile(file.path, file.content);
|
|
342
446
|
allTasks.push(...result.tasks);
|
|
343
447
|
allWarnings.push(...result.warnings);
|
|
448
|
+
for (const task of result.tasks) {
|
|
449
|
+
if (task.todoistId) {
|
|
450
|
+
const existing = todoistIds.get(task.todoistId);
|
|
451
|
+
if (existing && existing.file !== task.file) {
|
|
452
|
+
allWarnings.push({
|
|
453
|
+
severity: "error",
|
|
454
|
+
source: "md2do",
|
|
455
|
+
ruleId: "duplicate-todoist-id",
|
|
456
|
+
file: task.file,
|
|
457
|
+
line: task.line,
|
|
458
|
+
text: task.text,
|
|
459
|
+
message: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
|
|
460
|
+
reason: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`
|
|
461
|
+
});
|
|
462
|
+
} else if (!existing) {
|
|
463
|
+
todoistIds.set(task.todoistId, {
|
|
464
|
+
file: task.file,
|
|
465
|
+
line: task.line
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
344
470
|
}
|
|
345
471
|
return {
|
|
346
472
|
tasks: allTasks,
|
|
@@ -779,6 +905,32 @@ function combineComparators(comparators) {
|
|
|
779
905
|
function reverse(comparator) {
|
|
780
906
|
return (a, b) => -comparator(a, b);
|
|
781
907
|
}
|
|
908
|
+
|
|
909
|
+
// src/warnings/filter.ts
|
|
910
|
+
function filterWarnings(warnings, config = {}) {
|
|
911
|
+
if (config.enabled === false) {
|
|
912
|
+
return [];
|
|
913
|
+
}
|
|
914
|
+
if (!config.rules) {
|
|
915
|
+
return warnings;
|
|
916
|
+
}
|
|
917
|
+
return warnings.filter((warning) => {
|
|
918
|
+
const level = config.rules?.[warning.ruleId];
|
|
919
|
+
if (level === void 0) {
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
return level !== "off";
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
function groupWarningsBySeverity(warnings) {
|
|
926
|
+
return warnings.reduce(
|
|
927
|
+
(acc, warning) => {
|
|
928
|
+
acc[warning.severity].push(warning);
|
|
929
|
+
return acc;
|
|
930
|
+
},
|
|
931
|
+
{ error: [], warning: [], info: [] }
|
|
932
|
+
);
|
|
933
|
+
}
|
|
782
934
|
// Annotate the CommonJS export names for ESM import in node:
|
|
783
935
|
0 && (module.exports = {
|
|
784
936
|
ASSIGNEE,
|
|
@@ -808,8 +960,10 @@ function reverse(comparator) {
|
|
|
808
960
|
extractProjectFromPath,
|
|
809
961
|
extractTags,
|
|
810
962
|
extractTodoistId,
|
|
963
|
+
filterWarnings,
|
|
811
964
|
filters,
|
|
812
965
|
generateTaskId,
|
|
966
|
+
groupWarningsBySeverity,
|
|
813
967
|
parseAbsoluteDate,
|
|
814
968
|
parseTask,
|
|
815
969
|
resolveRelativeDate,
|
package/dist/index.mjs
CHANGED
|
@@ -159,11 +159,15 @@ function extractDueDate(text, context) {
|
|
|
159
159
|
return {
|
|
160
160
|
date: void 0,
|
|
161
161
|
warning: {
|
|
162
|
+
severity: "warning",
|
|
163
|
+
source: "md2do",
|
|
164
|
+
ruleId: "relative-date-no-context",
|
|
162
165
|
file: "",
|
|
163
166
|
// Will be filled in by caller
|
|
164
167
|
line: 0,
|
|
165
168
|
// Will be filled in by caller
|
|
166
169
|
text: text.trim(),
|
|
170
|
+
message: "Relative due date without context date from heading. Add a heading with a date above this task.",
|
|
167
171
|
reason: "Relative due date without context date from heading. Add a heading with a date above this task."
|
|
168
172
|
}
|
|
169
173
|
};
|
|
@@ -178,6 +182,58 @@ function cleanTaskText(text) {
|
|
|
178
182
|
}
|
|
179
183
|
function parseTask(line, lineNumber, file, context) {
|
|
180
184
|
const warnings = [];
|
|
185
|
+
if (/^\s*[*+]\s+\[[ xX]\]/.test(line)) {
|
|
186
|
+
warnings.push({
|
|
187
|
+
severity: "warning",
|
|
188
|
+
source: "md2do",
|
|
189
|
+
ruleId: "unsupported-bullet",
|
|
190
|
+
file,
|
|
191
|
+
line: lineNumber,
|
|
192
|
+
text: line.trim(),
|
|
193
|
+
message: "Unsupported bullet marker (* or +). Use dash (-) for task lists.",
|
|
194
|
+
reason: "Unsupported bullet marker (* or +). Use dash (-) for task lists."
|
|
195
|
+
});
|
|
196
|
+
return { task: null, warnings };
|
|
197
|
+
}
|
|
198
|
+
if (/^\s*-\s+\[[xX]\s+\]/.test(line) || /^\s*-\s+\[\s+[xX]\]/.test(line)) {
|
|
199
|
+
warnings.push({
|
|
200
|
+
severity: "warning",
|
|
201
|
+
source: "md2do",
|
|
202
|
+
ruleId: "malformed-checkbox",
|
|
203
|
+
file,
|
|
204
|
+
line: lineNumber,
|
|
205
|
+
text: line.trim(),
|
|
206
|
+
message: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.",
|
|
207
|
+
reason: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces."
|
|
208
|
+
});
|
|
209
|
+
return { task: null, warnings };
|
|
210
|
+
}
|
|
211
|
+
if (/^\s*-\s+\[[ xX]\][^\s]/.test(line)) {
|
|
212
|
+
warnings.push({
|
|
213
|
+
severity: "warning",
|
|
214
|
+
source: "md2do",
|
|
215
|
+
ruleId: "missing-space-after",
|
|
216
|
+
file,
|
|
217
|
+
line: lineNumber,
|
|
218
|
+
text: line.trim(),
|
|
219
|
+
message: 'Missing space after checkbox. Use "- [x] Task" format.',
|
|
220
|
+
reason: 'Missing space after checkbox. Use "- [x] Task" format.'
|
|
221
|
+
});
|
|
222
|
+
return { task: null, warnings };
|
|
223
|
+
}
|
|
224
|
+
if (/^\s*-\[[ xX]\]/.test(line)) {
|
|
225
|
+
warnings.push({
|
|
226
|
+
severity: "warning",
|
|
227
|
+
source: "md2do",
|
|
228
|
+
ruleId: "missing-space-before",
|
|
229
|
+
file,
|
|
230
|
+
line: lineNumber,
|
|
231
|
+
text: line.trim(),
|
|
232
|
+
message: 'Missing space before checkbox. Use "- [x] Task" format.',
|
|
233
|
+
reason: 'Missing space before checkbox. Use "- [x] Task" format.'
|
|
234
|
+
});
|
|
235
|
+
return { task: null, warnings };
|
|
236
|
+
}
|
|
181
237
|
const taskMatch = line.match(PATTERNS.TASK_CHECKBOX);
|
|
182
238
|
if (!taskMatch?.[0] || !taskMatch[2]) {
|
|
183
239
|
return { task: null, warnings };
|
|
@@ -197,6 +253,30 @@ function parseTask(line, lineNumber, file, context) {
|
|
|
197
253
|
line: lineNumber
|
|
198
254
|
});
|
|
199
255
|
}
|
|
256
|
+
if (!completed && !dueDateResult.date && !context.currentDate) {
|
|
257
|
+
warnings.push({
|
|
258
|
+
severity: "info",
|
|
259
|
+
source: "md2do",
|
|
260
|
+
ruleId: "missing-due-date",
|
|
261
|
+
file,
|
|
262
|
+
line: lineNumber,
|
|
263
|
+
text: fullText.trim(),
|
|
264
|
+
message: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.",
|
|
265
|
+
reason: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date."
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (completed && !completedDate) {
|
|
269
|
+
warnings.push({
|
|
270
|
+
severity: "info",
|
|
271
|
+
source: "md2do",
|
|
272
|
+
ruleId: "missing-completed-date",
|
|
273
|
+
file,
|
|
274
|
+
line: lineNumber,
|
|
275
|
+
text: fullText.trim(),
|
|
276
|
+
message: "Completed task missing completion date. Add [completed: YYYY-MM-DD].",
|
|
277
|
+
reason: "Completed task missing completion date. Add [completed: YYYY-MM-DD]."
|
|
278
|
+
});
|
|
279
|
+
}
|
|
200
280
|
const cleanText = cleanTaskText(fullText);
|
|
201
281
|
const id = generateTaskId(file, lineNumber, cleanText);
|
|
202
282
|
const task = {
|
|
@@ -242,6 +322,7 @@ var MarkdownScanner = class {
|
|
|
242
322
|
scanFile(filePath, content) {
|
|
243
323
|
const tasks = [];
|
|
244
324
|
const warnings = [];
|
|
325
|
+
const todoistIds = /* @__PURE__ */ new Map();
|
|
245
326
|
const context = {};
|
|
246
327
|
const project = extractProjectFromPath(filePath);
|
|
247
328
|
if (project !== void 0) context.project = project;
|
|
@@ -261,6 +342,26 @@ var MarkdownScanner = class {
|
|
|
261
342
|
const result = parseTask(line, lineNumber, filePath, context);
|
|
262
343
|
if (result.task) {
|
|
263
344
|
tasks.push(result.task);
|
|
345
|
+
if (result.task.todoistId) {
|
|
346
|
+
const existing = todoistIds.get(result.task.todoistId);
|
|
347
|
+
if (existing) {
|
|
348
|
+
warnings.push({
|
|
349
|
+
severity: "error",
|
|
350
|
+
source: "md2do",
|
|
351
|
+
ruleId: "duplicate-todoist-id",
|
|
352
|
+
file: filePath,
|
|
353
|
+
line: lineNumber,
|
|
354
|
+
text: result.task.text,
|
|
355
|
+
message: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
|
|
356
|
+
reason: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
todoistIds.set(result.task.todoistId, {
|
|
360
|
+
file: filePath,
|
|
361
|
+
line: lineNumber
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
264
365
|
}
|
|
265
366
|
if (result.warnings.length > 0) {
|
|
266
367
|
warnings.push(...result.warnings);
|
|
@@ -280,10 +381,33 @@ var MarkdownScanner = class {
|
|
|
280
381
|
scanFiles(files) {
|
|
281
382
|
const allTasks = [];
|
|
282
383
|
const allWarnings = [];
|
|
384
|
+
const todoistIds = /* @__PURE__ */ new Map();
|
|
283
385
|
for (const file of files) {
|
|
284
386
|
const result = this.scanFile(file.path, file.content);
|
|
285
387
|
allTasks.push(...result.tasks);
|
|
286
388
|
allWarnings.push(...result.warnings);
|
|
389
|
+
for (const task of result.tasks) {
|
|
390
|
+
if (task.todoistId) {
|
|
391
|
+
const existing = todoistIds.get(task.todoistId);
|
|
392
|
+
if (existing && existing.file !== task.file) {
|
|
393
|
+
allWarnings.push({
|
|
394
|
+
severity: "error",
|
|
395
|
+
source: "md2do",
|
|
396
|
+
ruleId: "duplicate-todoist-id",
|
|
397
|
+
file: task.file,
|
|
398
|
+
line: task.line,
|
|
399
|
+
text: task.text,
|
|
400
|
+
message: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
|
|
401
|
+
reason: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`
|
|
402
|
+
});
|
|
403
|
+
} else if (!existing) {
|
|
404
|
+
todoistIds.set(task.todoistId, {
|
|
405
|
+
file: task.file,
|
|
406
|
+
line: task.line
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
287
411
|
}
|
|
288
412
|
return {
|
|
289
413
|
tasks: allTasks,
|
|
@@ -722,6 +846,32 @@ function combineComparators(comparators) {
|
|
|
722
846
|
function reverse(comparator) {
|
|
723
847
|
return (a, b) => -comparator(a, b);
|
|
724
848
|
}
|
|
849
|
+
|
|
850
|
+
// src/warnings/filter.ts
|
|
851
|
+
function filterWarnings(warnings, config = {}) {
|
|
852
|
+
if (config.enabled === false) {
|
|
853
|
+
return [];
|
|
854
|
+
}
|
|
855
|
+
if (!config.rules) {
|
|
856
|
+
return warnings;
|
|
857
|
+
}
|
|
858
|
+
return warnings.filter((warning) => {
|
|
859
|
+
const level = config.rules?.[warning.ruleId];
|
|
860
|
+
if (level === void 0) {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
return level !== "off";
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
function groupWarningsBySeverity(warnings) {
|
|
867
|
+
return warnings.reduce(
|
|
868
|
+
(acc, warning) => {
|
|
869
|
+
acc[warning.severity].push(warning);
|
|
870
|
+
return acc;
|
|
871
|
+
},
|
|
872
|
+
{ error: [], warning: [], info: [] }
|
|
873
|
+
);
|
|
874
|
+
}
|
|
725
875
|
export {
|
|
726
876
|
ASSIGNEE,
|
|
727
877
|
COMPLETED_DATE,
|
|
@@ -750,8 +900,10 @@ export {
|
|
|
750
900
|
extractProjectFromPath,
|
|
751
901
|
extractTags,
|
|
752
902
|
extractTodoistId,
|
|
903
|
+
filterWarnings,
|
|
753
904
|
filters_exports as filters,
|
|
754
905
|
generateTaskId,
|
|
906
|
+
groupWarningsBySeverity,
|
|
755
907
|
parseAbsoluteDate,
|
|
756
908
|
parseTask,
|
|
757
909
|
resolveRelativeDate,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -19,6 +19,9 @@ export * as filters from './filters/index.js';
|
|
|
19
19
|
// Sorting (export as namespace to avoid conflicts)
|
|
20
20
|
export * as sorting from './sorting/index.js';
|
|
21
21
|
|
|
22
|
+
// Warnings
|
|
23
|
+
export * from './warnings/filter.js';
|
|
24
|
+
|
|
22
25
|
// Utilities
|
|
23
26
|
export * from './utils/dates.js';
|
|
24
27
|
export * from './utils/id.js';
|