@md2do/core 0.2.3 → 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 +10 -0
- package/dist/index.d.mts +64 -3
- package/dist/index.d.ts +64 -3
- package/dist/index.js +66 -0
- package/dist/index.mjs +64 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/parser/index.ts +33 -0
- package/src/scanner/index.ts +8 -0
- package/src/types/index.ts +31 -2
- package/src/warnings/filter.ts +93 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @md2do/core
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add comprehensive warning system for markdown validation and VSCode extension
|
|
8
|
+
- Add warning configuration system with customizable rules
|
|
9
|
+
- Add VSCode extension with task explorer, diagnostics, hover tooltips, and smart completion
|
|
10
|
+
- Support for validating malformed checkboxes, missing metadata, duplicate IDs, and more
|
|
11
|
+
- Add .vsix distribution for beta testing
|
|
12
|
+
|
|
3
13
|
## 0.2.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
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
|
};
|
|
@@ -237,36 +243,52 @@ function parseTask(line, lineNumber, file, context) {
|
|
|
237
243
|
const warnings = [];
|
|
238
244
|
if (/^\s*[*+]\s+\[[ xX]\]/.test(line)) {
|
|
239
245
|
warnings.push({
|
|
246
|
+
severity: "warning",
|
|
247
|
+
source: "md2do",
|
|
248
|
+
ruleId: "unsupported-bullet",
|
|
240
249
|
file,
|
|
241
250
|
line: lineNumber,
|
|
242
251
|
text: line.trim(),
|
|
252
|
+
message: "Unsupported bullet marker (* or +). Use dash (-) for task lists.",
|
|
243
253
|
reason: "Unsupported bullet marker (* or +). Use dash (-) for task lists."
|
|
244
254
|
});
|
|
245
255
|
return { task: null, warnings };
|
|
246
256
|
}
|
|
247
257
|
if (/^\s*-\s+\[[xX]\s+\]/.test(line) || /^\s*-\s+\[\s+[xX]\]/.test(line)) {
|
|
248
258
|
warnings.push({
|
|
259
|
+
severity: "warning",
|
|
260
|
+
source: "md2do",
|
|
261
|
+
ruleId: "malformed-checkbox",
|
|
249
262
|
file,
|
|
250
263
|
line: lineNumber,
|
|
251
264
|
text: line.trim(),
|
|
265
|
+
message: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.",
|
|
252
266
|
reason: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces."
|
|
253
267
|
});
|
|
254
268
|
return { task: null, warnings };
|
|
255
269
|
}
|
|
256
270
|
if (/^\s*-\s+\[[ xX]\][^\s]/.test(line)) {
|
|
257
271
|
warnings.push({
|
|
272
|
+
severity: "warning",
|
|
273
|
+
source: "md2do",
|
|
274
|
+
ruleId: "missing-space-after",
|
|
258
275
|
file,
|
|
259
276
|
line: lineNumber,
|
|
260
277
|
text: line.trim(),
|
|
278
|
+
message: 'Missing space after checkbox. Use "- [x] Task" format.',
|
|
261
279
|
reason: 'Missing space after checkbox. Use "- [x] Task" format.'
|
|
262
280
|
});
|
|
263
281
|
return { task: null, warnings };
|
|
264
282
|
}
|
|
265
283
|
if (/^\s*-\[[ xX]\]/.test(line)) {
|
|
266
284
|
warnings.push({
|
|
285
|
+
severity: "warning",
|
|
286
|
+
source: "md2do",
|
|
287
|
+
ruleId: "missing-space-before",
|
|
267
288
|
file,
|
|
268
289
|
line: lineNumber,
|
|
269
290
|
text: line.trim(),
|
|
291
|
+
message: 'Missing space before checkbox. Use "- [x] Task" format.',
|
|
270
292
|
reason: 'Missing space before checkbox. Use "- [x] Task" format.'
|
|
271
293
|
});
|
|
272
294
|
return { task: null, warnings };
|
|
@@ -292,17 +314,25 @@ function parseTask(line, lineNumber, file, context) {
|
|
|
292
314
|
}
|
|
293
315
|
if (!completed && !dueDateResult.date && !context.currentDate) {
|
|
294
316
|
warnings.push({
|
|
317
|
+
severity: "info",
|
|
318
|
+
source: "md2do",
|
|
319
|
+
ruleId: "missing-due-date",
|
|
295
320
|
file,
|
|
296
321
|
line: lineNumber,
|
|
297
322
|
text: fullText.trim(),
|
|
323
|
+
message: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.",
|
|
298
324
|
reason: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date."
|
|
299
325
|
});
|
|
300
326
|
}
|
|
301
327
|
if (completed && !completedDate) {
|
|
302
328
|
warnings.push({
|
|
329
|
+
severity: "info",
|
|
330
|
+
source: "md2do",
|
|
331
|
+
ruleId: "missing-completed-date",
|
|
303
332
|
file,
|
|
304
333
|
line: lineNumber,
|
|
305
334
|
text: fullText.trim(),
|
|
335
|
+
message: "Completed task missing completion date. Add [completed: YYYY-MM-DD].",
|
|
306
336
|
reason: "Completed task missing completion date. Add [completed: YYYY-MM-DD]."
|
|
307
337
|
});
|
|
308
338
|
}
|
|
@@ -375,9 +405,13 @@ var MarkdownScanner = class {
|
|
|
375
405
|
const existing = todoistIds.get(result.task.todoistId);
|
|
376
406
|
if (existing) {
|
|
377
407
|
warnings.push({
|
|
408
|
+
severity: "error",
|
|
409
|
+
source: "md2do",
|
|
410
|
+
ruleId: "duplicate-todoist-id",
|
|
378
411
|
file: filePath,
|
|
379
412
|
line: lineNumber,
|
|
380
413
|
text: result.task.text,
|
|
414
|
+
message: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
|
|
381
415
|
reason: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`
|
|
382
416
|
});
|
|
383
417
|
} else {
|
|
@@ -416,9 +450,13 @@ var MarkdownScanner = class {
|
|
|
416
450
|
const existing = todoistIds.get(task.todoistId);
|
|
417
451
|
if (existing && existing.file !== task.file) {
|
|
418
452
|
allWarnings.push({
|
|
453
|
+
severity: "error",
|
|
454
|
+
source: "md2do",
|
|
455
|
+
ruleId: "duplicate-todoist-id",
|
|
419
456
|
file: task.file,
|
|
420
457
|
line: task.line,
|
|
421
458
|
text: task.text,
|
|
459
|
+
message: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
|
|
422
460
|
reason: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`
|
|
423
461
|
});
|
|
424
462
|
} else if (!existing) {
|
|
@@ -867,6 +905,32 @@ function combineComparators(comparators) {
|
|
|
867
905
|
function reverse(comparator) {
|
|
868
906
|
return (a, b) => -comparator(a, b);
|
|
869
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
|
+
}
|
|
870
934
|
// Annotate the CommonJS export names for ESM import in node:
|
|
871
935
|
0 && (module.exports = {
|
|
872
936
|
ASSIGNEE,
|
|
@@ -896,8 +960,10 @@ function reverse(comparator) {
|
|
|
896
960
|
extractProjectFromPath,
|
|
897
961
|
extractTags,
|
|
898
962
|
extractTodoistId,
|
|
963
|
+
filterWarnings,
|
|
899
964
|
filters,
|
|
900
965
|
generateTaskId,
|
|
966
|
+
groupWarningsBySeverity,
|
|
901
967
|
parseAbsoluteDate,
|
|
902
968
|
parseTask,
|
|
903
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
|
};
|
|
@@ -180,36 +184,52 @@ function parseTask(line, lineNumber, file, context) {
|
|
|
180
184
|
const warnings = [];
|
|
181
185
|
if (/^\s*[*+]\s+\[[ xX]\]/.test(line)) {
|
|
182
186
|
warnings.push({
|
|
187
|
+
severity: "warning",
|
|
188
|
+
source: "md2do",
|
|
189
|
+
ruleId: "unsupported-bullet",
|
|
183
190
|
file,
|
|
184
191
|
line: lineNumber,
|
|
185
192
|
text: line.trim(),
|
|
193
|
+
message: "Unsupported bullet marker (* or +). Use dash (-) for task lists.",
|
|
186
194
|
reason: "Unsupported bullet marker (* or +). Use dash (-) for task lists."
|
|
187
195
|
});
|
|
188
196
|
return { task: null, warnings };
|
|
189
197
|
}
|
|
190
198
|
if (/^\s*-\s+\[[xX]\s+\]/.test(line) || /^\s*-\s+\[\s+[xX]\]/.test(line)) {
|
|
191
199
|
warnings.push({
|
|
200
|
+
severity: "warning",
|
|
201
|
+
source: "md2do",
|
|
202
|
+
ruleId: "malformed-checkbox",
|
|
192
203
|
file,
|
|
193
204
|
line: lineNumber,
|
|
194
205
|
text: line.trim(),
|
|
206
|
+
message: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.",
|
|
195
207
|
reason: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces."
|
|
196
208
|
});
|
|
197
209
|
return { task: null, warnings };
|
|
198
210
|
}
|
|
199
211
|
if (/^\s*-\s+\[[ xX]\][^\s]/.test(line)) {
|
|
200
212
|
warnings.push({
|
|
213
|
+
severity: "warning",
|
|
214
|
+
source: "md2do",
|
|
215
|
+
ruleId: "missing-space-after",
|
|
201
216
|
file,
|
|
202
217
|
line: lineNumber,
|
|
203
218
|
text: line.trim(),
|
|
219
|
+
message: 'Missing space after checkbox. Use "- [x] Task" format.',
|
|
204
220
|
reason: 'Missing space after checkbox. Use "- [x] Task" format.'
|
|
205
221
|
});
|
|
206
222
|
return { task: null, warnings };
|
|
207
223
|
}
|
|
208
224
|
if (/^\s*-\[[ xX]\]/.test(line)) {
|
|
209
225
|
warnings.push({
|
|
226
|
+
severity: "warning",
|
|
227
|
+
source: "md2do",
|
|
228
|
+
ruleId: "missing-space-before",
|
|
210
229
|
file,
|
|
211
230
|
line: lineNumber,
|
|
212
231
|
text: line.trim(),
|
|
232
|
+
message: 'Missing space before checkbox. Use "- [x] Task" format.',
|
|
213
233
|
reason: 'Missing space before checkbox. Use "- [x] Task" format.'
|
|
214
234
|
});
|
|
215
235
|
return { task: null, warnings };
|
|
@@ -235,17 +255,25 @@ function parseTask(line, lineNumber, file, context) {
|
|
|
235
255
|
}
|
|
236
256
|
if (!completed && !dueDateResult.date && !context.currentDate) {
|
|
237
257
|
warnings.push({
|
|
258
|
+
severity: "info",
|
|
259
|
+
source: "md2do",
|
|
260
|
+
ruleId: "missing-due-date",
|
|
238
261
|
file,
|
|
239
262
|
line: lineNumber,
|
|
240
263
|
text: fullText.trim(),
|
|
264
|
+
message: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.",
|
|
241
265
|
reason: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date."
|
|
242
266
|
});
|
|
243
267
|
}
|
|
244
268
|
if (completed && !completedDate) {
|
|
245
269
|
warnings.push({
|
|
270
|
+
severity: "info",
|
|
271
|
+
source: "md2do",
|
|
272
|
+
ruleId: "missing-completed-date",
|
|
246
273
|
file,
|
|
247
274
|
line: lineNumber,
|
|
248
275
|
text: fullText.trim(),
|
|
276
|
+
message: "Completed task missing completion date. Add [completed: YYYY-MM-DD].",
|
|
249
277
|
reason: "Completed task missing completion date. Add [completed: YYYY-MM-DD]."
|
|
250
278
|
});
|
|
251
279
|
}
|
|
@@ -318,9 +346,13 @@ var MarkdownScanner = class {
|
|
|
318
346
|
const existing = todoistIds.get(result.task.todoistId);
|
|
319
347
|
if (existing) {
|
|
320
348
|
warnings.push({
|
|
349
|
+
severity: "error",
|
|
350
|
+
source: "md2do",
|
|
351
|
+
ruleId: "duplicate-todoist-id",
|
|
321
352
|
file: filePath,
|
|
322
353
|
line: lineNumber,
|
|
323
354
|
text: result.task.text,
|
|
355
|
+
message: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
|
|
324
356
|
reason: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`
|
|
325
357
|
});
|
|
326
358
|
} else {
|
|
@@ -359,9 +391,13 @@ var MarkdownScanner = class {
|
|
|
359
391
|
const existing = todoistIds.get(task.todoistId);
|
|
360
392
|
if (existing && existing.file !== task.file) {
|
|
361
393
|
allWarnings.push({
|
|
394
|
+
severity: "error",
|
|
395
|
+
source: "md2do",
|
|
396
|
+
ruleId: "duplicate-todoist-id",
|
|
362
397
|
file: task.file,
|
|
363
398
|
line: task.line,
|
|
364
399
|
text: task.text,
|
|
400
|
+
message: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
|
|
365
401
|
reason: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`
|
|
366
402
|
});
|
|
367
403
|
} else if (!existing) {
|
|
@@ -810,6 +846,32 @@ function combineComparators(comparators) {
|
|
|
810
846
|
function reverse(comparator) {
|
|
811
847
|
return (a, b) => -comparator(a, b);
|
|
812
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
|
+
}
|
|
813
875
|
export {
|
|
814
876
|
ASSIGNEE,
|
|
815
877
|
COMPLETED_DATE,
|
|
@@ -838,8 +900,10 @@ export {
|
|
|
838
900
|
extractProjectFromPath,
|
|
839
901
|
extractTags,
|
|
840
902
|
extractTodoistId,
|
|
903
|
+
filterWarnings,
|
|
841
904
|
filters_exports as filters,
|
|
842
905
|
generateTaskId,
|
|
906
|
+
groupWarningsBySeverity,
|
|
843
907
|
parseAbsoluteDate,
|
|
844
908
|
parseTask,
|
|
845
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';
|
package/src/parser/index.ts
CHANGED
|
@@ -126,9 +126,14 @@ export function extractDueDate(
|
|
|
126
126
|
return {
|
|
127
127
|
date: undefined,
|
|
128
128
|
warning: {
|
|
129
|
+
severity: 'warning',
|
|
130
|
+
source: 'md2do',
|
|
131
|
+
ruleId: 'relative-date-no-context',
|
|
129
132
|
file: '', // Will be filled in by caller
|
|
130
133
|
line: 0, // Will be filled in by caller
|
|
131
134
|
text: text.trim(),
|
|
135
|
+
message:
|
|
136
|
+
'Relative due date without context date from heading. Add a heading with a date above this task.',
|
|
132
137
|
reason:
|
|
133
138
|
'Relative due date without context date from heading. Add a heading with a date above this task.',
|
|
134
139
|
},
|
|
@@ -212,9 +217,14 @@ export function parseTask(
|
|
|
212
217
|
// Check for asterisk/plus bullet markers (not supported)
|
|
213
218
|
if (/^\s*[*+]\s+\[[ xX]\]/.test(line)) {
|
|
214
219
|
warnings.push({
|
|
220
|
+
severity: 'warning',
|
|
221
|
+
source: 'md2do',
|
|
222
|
+
ruleId: 'unsupported-bullet',
|
|
215
223
|
file,
|
|
216
224
|
line: lineNumber,
|
|
217
225
|
text: line.trim(),
|
|
226
|
+
message:
|
|
227
|
+
'Unsupported bullet marker (* or +). Use dash (-) for task lists.',
|
|
218
228
|
reason:
|
|
219
229
|
'Unsupported bullet marker (* or +). Use dash (-) for task lists.',
|
|
220
230
|
});
|
|
@@ -224,9 +234,14 @@ export function parseTask(
|
|
|
224
234
|
// Check for extra spaces inside checkbox: [x ] or [ x]
|
|
225
235
|
if (/^\s*-\s+\[[xX]\s+\]/.test(line) || /^\s*-\s+\[\s+[xX]\]/.test(line)) {
|
|
226
236
|
warnings.push({
|
|
237
|
+
severity: 'warning',
|
|
238
|
+
source: 'md2do',
|
|
239
|
+
ruleId: 'malformed-checkbox',
|
|
227
240
|
file,
|
|
228
241
|
line: lineNumber,
|
|
229
242
|
text: line.trim(),
|
|
243
|
+
message:
|
|
244
|
+
'Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.',
|
|
230
245
|
reason:
|
|
231
246
|
'Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.',
|
|
232
247
|
});
|
|
@@ -236,9 +251,13 @@ export function parseTask(
|
|
|
236
251
|
// Check for missing space after checkbox: [x]Task
|
|
237
252
|
if (/^\s*-\s+\[[ xX]\][^\s]/.test(line)) {
|
|
238
253
|
warnings.push({
|
|
254
|
+
severity: 'warning',
|
|
255
|
+
source: 'md2do',
|
|
256
|
+
ruleId: 'missing-space-after',
|
|
239
257
|
file,
|
|
240
258
|
line: lineNumber,
|
|
241
259
|
text: line.trim(),
|
|
260
|
+
message: 'Missing space after checkbox. Use "- [x] Task" format.',
|
|
242
261
|
reason: 'Missing space after checkbox. Use "- [x] Task" format.',
|
|
243
262
|
});
|
|
244
263
|
return { task: null, warnings };
|
|
@@ -247,9 +266,13 @@ export function parseTask(
|
|
|
247
266
|
// Check for missing space before checkbox: -[x]
|
|
248
267
|
if (/^\s*-\[[ xX]\]/.test(line)) {
|
|
249
268
|
warnings.push({
|
|
269
|
+
severity: 'warning',
|
|
270
|
+
source: 'md2do',
|
|
271
|
+
ruleId: 'missing-space-before',
|
|
250
272
|
file,
|
|
251
273
|
line: lineNumber,
|
|
252
274
|
text: line.trim(),
|
|
275
|
+
message: 'Missing space before checkbox. Use "- [x] Task" format.',
|
|
253
276
|
reason: 'Missing space before checkbox. Use "- [x] Task" format.',
|
|
254
277
|
});
|
|
255
278
|
return { task: null, warnings };
|
|
@@ -288,9 +311,14 @@ export function parseTask(
|
|
|
288
311
|
// Incomplete tasks without any date (no explicit due date and no context date)
|
|
289
312
|
if (!completed && !dueDateResult.date && !context.currentDate) {
|
|
290
313
|
warnings.push({
|
|
314
|
+
severity: 'info',
|
|
315
|
+
source: 'md2do',
|
|
316
|
+
ruleId: 'missing-due-date',
|
|
291
317
|
file,
|
|
292
318
|
line: lineNumber,
|
|
293
319
|
text: fullText.trim(),
|
|
320
|
+
message:
|
|
321
|
+
'Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.',
|
|
294
322
|
reason:
|
|
295
323
|
'Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.',
|
|
296
324
|
});
|
|
@@ -299,9 +327,14 @@ export function parseTask(
|
|
|
299
327
|
// Completed tasks should have completion dates
|
|
300
328
|
if (completed && !completedDate) {
|
|
301
329
|
warnings.push({
|
|
330
|
+
severity: 'info',
|
|
331
|
+
source: 'md2do',
|
|
332
|
+
ruleId: 'missing-completed-date',
|
|
302
333
|
file,
|
|
303
334
|
line: lineNumber,
|
|
304
335
|
text: fullText.trim(),
|
|
336
|
+
message:
|
|
337
|
+
'Completed task missing completion date. Add [completed: YYYY-MM-DD].',
|
|
305
338
|
reason:
|
|
306
339
|
'Completed task missing completion date. Add [completed: YYYY-MM-DD].',
|
|
307
340
|
});
|
package/src/scanner/index.ts
CHANGED
|
@@ -114,9 +114,13 @@ export class MarkdownScanner {
|
|
|
114
114
|
const existing = todoistIds.get(result.task.todoistId);
|
|
115
115
|
if (existing) {
|
|
116
116
|
warnings.push({
|
|
117
|
+
severity: 'error',
|
|
118
|
+
source: 'md2do',
|
|
119
|
+
ruleId: 'duplicate-todoist-id',
|
|
117
120
|
file: filePath,
|
|
118
121
|
line: lineNumber,
|
|
119
122
|
text: result.task.text,
|
|
123
|
+
message: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
|
|
120
124
|
reason: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
|
|
121
125
|
});
|
|
122
126
|
} else {
|
|
@@ -168,9 +172,13 @@ export class MarkdownScanner {
|
|
|
168
172
|
// Only add warning if duplicate is in a different file
|
|
169
173
|
// (same-file duplicates are already caught by scanFile)
|
|
170
174
|
allWarnings.push({
|
|
175
|
+
severity: 'error',
|
|
176
|
+
source: 'md2do',
|
|
177
|
+
ruleId: 'duplicate-todoist-id',
|
|
171
178
|
file: task.file,
|
|
172
179
|
line: task.line,
|
|
173
180
|
text: task.text,
|
|
181
|
+
message: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
|
|
174
182
|
reason: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
|
|
175
183
|
});
|
|
176
184
|
} else if (!existing) {
|
package/src/types/index.ts
CHANGED
|
@@ -65,9 +65,38 @@ export interface ScanResult {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export type WarningSeverity = 'info' | 'warning' | 'error';
|
|
69
|
+
|
|
70
|
+
export type WarningCode =
|
|
71
|
+
| 'unsupported-bullet' // * or + instead of -
|
|
72
|
+
| 'malformed-checkbox' // [x ] or [ x]
|
|
73
|
+
| 'missing-space-after' // -[x]Task
|
|
74
|
+
| 'missing-space-before' // -[x] Task
|
|
75
|
+
| 'relative-date-no-context' // [due: tomorrow] without heading date
|
|
76
|
+
| 'missing-due-date' // Incomplete task with no due date
|
|
77
|
+
| 'missing-completed-date' // [x] without [completed: date]
|
|
78
|
+
| 'duplicate-todoist-id' // Same Todoist ID in multiple tasks
|
|
79
|
+
| 'file-read-error'; // Failed to read file
|
|
80
|
+
|
|
68
81
|
export interface Warning {
|
|
82
|
+
// Position
|
|
69
83
|
file: string;
|
|
70
84
|
line: number;
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
column?: number;
|
|
86
|
+
|
|
87
|
+
// Classification
|
|
88
|
+
severity: WarningSeverity;
|
|
89
|
+
source: 'md2do';
|
|
90
|
+
ruleId: WarningCode;
|
|
91
|
+
|
|
92
|
+
// Content
|
|
93
|
+
message: string; // User-facing message
|
|
94
|
+
text?: string; // The actual text that triggered it
|
|
95
|
+
|
|
96
|
+
// Documentation (optional - for future use)
|
|
97
|
+
url?: string;
|
|
98
|
+
|
|
99
|
+
// Legacy field for backward compatibility (deprecated)
|
|
100
|
+
/** @deprecated Use message instead */
|
|
101
|
+
reason?: string;
|
|
73
102
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Warning } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export interface WarningFilterConfig {
|
|
4
|
+
enabled?: boolean | undefined;
|
|
5
|
+
rules?: Record<string, 'error' | 'warn' | 'info' | 'off'> | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Filter warnings based on configuration rules
|
|
10
|
+
*
|
|
11
|
+
* This function applies warning configuration rules to filter out disabled warnings
|
|
12
|
+
* and optionally all warnings if globally disabled.
|
|
13
|
+
*
|
|
14
|
+
* @param warnings - Array of warnings to filter
|
|
15
|
+
* @param config - Warning configuration with enabled flag and rule overrides
|
|
16
|
+
* @returns Filtered array of warnings
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const config = {
|
|
21
|
+
* enabled: true,
|
|
22
|
+
* rules: {
|
|
23
|
+
* 'missing-due-date': 'off',
|
|
24
|
+
* 'duplicate-todoist-id': 'error',
|
|
25
|
+
* },
|
|
26
|
+
* };
|
|
27
|
+
*
|
|
28
|
+
* const filtered = filterWarnings(allWarnings, config);
|
|
29
|
+
* // Returns warnings except those with ruleId 'missing-due-date'
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function filterWarnings(
|
|
33
|
+
warnings: Warning[],
|
|
34
|
+
config: WarningFilterConfig = {},
|
|
35
|
+
): Warning[] {
|
|
36
|
+
// If warnings are globally disabled, return empty array
|
|
37
|
+
if (config.enabled === false) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If no rules specified, return all warnings
|
|
42
|
+
if (!config.rules) {
|
|
43
|
+
return warnings;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Filter based on rule configuration
|
|
47
|
+
return warnings.filter((warning) => {
|
|
48
|
+
const level = config.rules?.[warning.ruleId];
|
|
49
|
+
|
|
50
|
+
// If rule is not configured, include the warning (default: show)
|
|
51
|
+
if (level === undefined) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Exclude if rule is set to 'off'
|
|
56
|
+
return level !== 'off';
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Group warnings by severity
|
|
62
|
+
*
|
|
63
|
+
* Useful for displaying warnings in order of importance or
|
|
64
|
+
* treating errors differently from warnings.
|
|
65
|
+
*
|
|
66
|
+
* @param warnings - Array of warnings to group
|
|
67
|
+
* @returns Object with warnings grouped by severity level
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const grouped = groupWarningsBySeverity(warnings);
|
|
72
|
+
* console.log(`${grouped.error.length} errors`);
|
|
73
|
+
* console.log(`${grouped.warning.length} warnings`);
|
|
74
|
+
* console.log(`${grouped.info.length} info messages`);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function groupWarningsBySeverity(warnings: Warning[]): {
|
|
78
|
+
error: Warning[];
|
|
79
|
+
warning: Warning[];
|
|
80
|
+
info: Warning[];
|
|
81
|
+
} {
|
|
82
|
+
return warnings.reduce(
|
|
83
|
+
(acc, warning) => {
|
|
84
|
+
acc[warning.severity].push(warning);
|
|
85
|
+
return acc;
|
|
86
|
+
},
|
|
87
|
+
{ error: [], warning: [], info: [] } as {
|
|
88
|
+
error: Warning[];
|
|
89
|
+
warning: Warning[];
|
|
90
|
+
info: Warning[];
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
}
|