patram 0.11.0 → 0.12.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/bin/patram.js +4 -4
- package/lib/cli/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +10 -20
- package/lib/cli/commands/query.js +1 -8
- package/lib/cli/commands/refs.js +3 -10
- package/lib/cli/commands/show.js +1 -8
- package/lib/cli/help-metadata.js +71 -106
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +165 -59
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +2 -2
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +8 -33
- package/lib/config/load-patram-config.js +9 -33
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +4 -4
- package/lib/config/manage-stored-queries-helpers.js +91 -33
- package/lib/config/manage-stored-queries.d.ts +4 -4
- package/lib/config/manage-stored-queries.js +11 -5
- package/lib/config/patram-config.d.ts +34 -34
- package/lib/config/patram-config.js +3 -3
- package/lib/config/patram-config.types.d.ts +5 -11
- package/lib/config/resolve-patram-graph-config.d.ts +5 -1
- package/lib/config/resolve-patram-graph-config.js +3 -119
- package/lib/config/schema.d.ts +158 -269
- package/lib/config/schema.js +72 -210
- package/lib/config/validate-patram-config-value.js +6 -31
- package/lib/config/validation.d.ts +2 -12
- package/lib/config/validation.js +125 -483
- package/lib/find-close-match.d.ts +4 -1
- package/lib/graph/build-graph-identity.d.ts +1 -32
- package/lib/graph/build-graph-identity.js +5 -269
- package/lib/graph/build-graph.d.ts +13 -4
- package/lib/graph/build-graph.js +347 -488
- package/lib/graph/build-graph.types.d.ts +8 -9
- package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
- package/lib/graph/check-directive-metadata-helpers.js +126 -0
- package/lib/graph/check-directive-metadata.d.ts +8 -9
- package/lib/graph/check-directive-metadata.js +70 -561
- package/lib/graph/check-directive-path-target.d.ts +6 -13
- package/lib/graph/check-directive-path-target.js +26 -57
- package/lib/graph/check-directive-value.d.ts +1 -5
- package/lib/graph/check-directive-value.js +40 -180
- package/lib/graph/check-graph.d.ts +5 -5
- package/lib/graph/check-graph.js +8 -6
- package/lib/graph/document-node-identity.d.ts +23 -7
- package/lib/graph/document-node-identity.js +417 -160
- package/lib/graph/graph-node.d.ts +42 -0
- package/lib/graph/graph-node.js +83 -0
- package/lib/graph/inspect-reverse-references.js +16 -11
- package/lib/graph/load-project-graph.d.ts +7 -7
- package/lib/graph/load-project-graph.js +7 -7
- package/lib/graph/parse-where-clause.types.d.ts +3 -2
- package/lib/graph/query/cypher-reader.d.ts +59 -0
- package/lib/graph/query/cypher-reader.js +151 -0
- package/lib/graph/query/cypher-support.d.ts +79 -0
- package/lib/graph/query/cypher-support.js +213 -0
- package/lib/graph/query/cypher-tokenize.d.ts +13 -0
- package/lib/graph/query/cypher-tokenize.js +225 -0
- package/lib/graph/query/cypher.types.d.ts +43 -0
- package/lib/graph/query/execute.d.ts +7 -7
- package/lib/graph/query/execute.js +71 -33
- package/lib/graph/query/inspect.js +58 -24
- package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
- package/lib/graph/query/parse-cypher-patterns.js +382 -0
- package/lib/graph/query/parse-cypher.d.ts +7 -0
- package/lib/graph/query/parse-cypher.js +580 -0
- package/lib/graph/query/parse-query.d.ts +13 -0
- package/lib/graph/query/parse-query.js +97 -0
- package/lib/graph/query/resolve.js +77 -23
- package/lib/output/command-output.js +12 -5
- package/lib/output/compact-layout.js +221 -0
- package/lib/output/format-output-item-block.js +31 -1
- package/lib/output/format-output-metadata.js +16 -29
- package/lib/output/format-stored-query-block.js +95 -0
- package/lib/output/layout-incoming-references.js +101 -19
- package/lib/output/layout-stored-queries.js +23 -330
- package/lib/output/list-queries.js +1 -1
- package/lib/output/render-field-discovery.js +11 -2
- package/lib/output/render-output-view.js +9 -5
- package/lib/output/renderers/json.js +5 -26
- package/lib/output/renderers/plain.js +155 -35
- package/lib/output/renderers/rich.js +250 -36
- package/lib/output/resolved-link-layout.js +43 -0
- package/lib/output/rich-source/render.js +193 -35
- package/lib/output/show-document.js +25 -18
- package/lib/output/view-model/index.js +124 -103
- package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
- package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
- package/lib/parse/markdown/parse-markdown-claims.js +99 -62
- package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
- package/lib/parse/markdown/parse-markdown-directives.js +104 -18
- package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
- package/lib/parse/markdown/parse-markdown-prose.js +243 -0
- package/lib/parse/parse-claims.d.ts +2 -6
- package/lib/parse/parse-claims.js +11 -53
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
- package/lib/parse/yaml/parse-yaml-claims.js +4 -4
- package/lib/patram.d.ts +3 -5
- package/lib/patram.js +1 -1
- package/lib/scan/discover-fields.js +194 -55
- package/lib/scan/list-source-files.d.ts +4 -4
- package/lib/scan/list-source-files.js +4 -4
- package/package.json +1 -1
- package/lib/directive-validation-test-helpers.js +0 -87
- package/lib/graph/query/parse.d.ts +0 -75
- package/lib/graph/query/parse.js +0 -1064
- package/lib/output/derived-summary.js +0 -280
- package/lib/output/format-derived-summary-row.js +0 -9
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import { createClaim, isPathLikeTarget } from '../claim-helpers.js';
|
|
7
7
|
import {
|
|
8
|
+
getMarkdownDescription,
|
|
9
|
+
getMarkdownTitle,
|
|
10
|
+
} from './parse-markdown-prose.js';
|
|
11
|
+
import {
|
|
12
|
+
collectVisibleDirectiveFields,
|
|
8
13
|
matchHiddenDirectiveFields,
|
|
9
|
-
matchVisibleDirectiveFields,
|
|
10
14
|
parseFrontMatterDirectiveFields,
|
|
11
15
|
} from './parse-markdown-directives.js';
|
|
12
16
|
|
|
@@ -16,17 +20,16 @@ import {
|
|
|
16
20
|
* Extracts document titles, directives, and links from markdown source while
|
|
17
21
|
* ignoring fenced-code link noise.
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* kind: parse
|
|
24
|
+
* status: active
|
|
25
|
+
* tracked_in: ../../../docs/plans/v0/source-anchor-dogfooding.md
|
|
26
|
+
* decided_by: ../../../docs/decisions/markdown-metadata-directive-syntax.md
|
|
27
|
+
* decided_by: ../../../docs/decisions/markdown-link-claim-scope.md
|
|
24
28
|
* @patram
|
|
25
29
|
* @see {@link ../parse-claims.js}
|
|
26
30
|
* @see {@link ../../../docs/decisions/markdown-metadata-directive-syntax.md}
|
|
27
31
|
*/
|
|
28
32
|
|
|
29
|
-
const HEADING_PATTERN = /^#\s+(.+)$/du;
|
|
30
33
|
const MARKDOWN_FENCE_PATTERN = /^([`~]{3,})/du;
|
|
31
34
|
const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/dgu;
|
|
32
35
|
|
|
@@ -37,30 +40,19 @@ const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/dgu;
|
|
|
37
40
|
*/
|
|
38
41
|
export function parseMarkdownClaims(parse_input, parse_options) {
|
|
39
42
|
const lines = parse_input.source.split('\n');
|
|
40
|
-
|
|
41
|
-
/** @type {PatramClaim[]} */
|
|
42
|
-
const claims = [];
|
|
43
43
|
const front_matter_result = parseFrontMatterDirectiveFields(
|
|
44
44
|
parse_input.path,
|
|
45
45
|
lines,
|
|
46
46
|
parse_options,
|
|
47
47
|
);
|
|
48
|
+
/** @type {PatramClaim[]} */
|
|
49
|
+
const claims = collectMarkdownMetadataClaims(
|
|
50
|
+
parse_input.path,
|
|
51
|
+
lines,
|
|
52
|
+
front_matter_result.body_start,
|
|
53
|
+
);
|
|
48
54
|
/** @type {{ character: string, length: number } | null} */
|
|
49
55
|
let open_fence = null;
|
|
50
|
-
const title_result = getMarkdownTitle(lines, front_matter_result.body_start);
|
|
51
|
-
|
|
52
|
-
if (title_result) {
|
|
53
|
-
claims.push(
|
|
54
|
-
createClaim(parse_input.path, claims.length + 1, 'document.title', {
|
|
55
|
-
origin: {
|
|
56
|
-
column: 1,
|
|
57
|
-
line: title_result.line,
|
|
58
|
-
path: parse_input.path,
|
|
59
|
-
},
|
|
60
|
-
value: title_result.value,
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
56
|
|
|
65
57
|
for (const directive_fields of front_matter_result.directive_fields) {
|
|
66
58
|
claims.push(
|
|
@@ -70,11 +62,12 @@ export function parseMarkdownClaims(parse_input, parse_options) {
|
|
|
70
62
|
);
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
for (
|
|
65
|
+
for (let line_index = 0; line_index < lines.length; line_index += 1) {
|
|
74
66
|
if (line_index < front_matter_result.body_start) {
|
|
75
67
|
continue;
|
|
76
68
|
}
|
|
77
69
|
|
|
70
|
+
const line = lines[line_index];
|
|
78
71
|
const line_number = line_index + 1;
|
|
79
72
|
|
|
80
73
|
if (open_fence) {
|
|
@@ -92,7 +85,19 @@ export function parseMarkdownClaims(parse_input, parse_options) {
|
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
collectMarkdownLinkClaims(parse_input.path, line, line_number, claims);
|
|
95
|
-
|
|
88
|
+
|
|
89
|
+
const next_line_index = collectVisibleDirectiveClaims(
|
|
90
|
+
parse_input.path,
|
|
91
|
+
lines,
|
|
92
|
+
line_index,
|
|
93
|
+
claims,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (next_line_index !== null) {
|
|
97
|
+
line_index = next_line_index - 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
96
101
|
collectHiddenDirectiveClaims(parse_input.path, line, line_number, claims);
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -102,6 +107,34 @@ export function parseMarkdownClaims(parse_input, parse_options) {
|
|
|
102
107
|
};
|
|
103
108
|
}
|
|
104
109
|
|
|
110
|
+
/**
|
|
111
|
+
* @param {string} file_path
|
|
112
|
+
* @param {string[]} lines
|
|
113
|
+
* @param {number} start_line_index
|
|
114
|
+
* @returns {PatramClaim[]}
|
|
115
|
+
*/
|
|
116
|
+
function collectMarkdownMetadataClaims(file_path, lines, start_line_index) {
|
|
117
|
+
/** @type {PatramClaim[]} */
|
|
118
|
+
const claims = [];
|
|
119
|
+
const title_result = getMarkdownTitle(lines, start_line_index);
|
|
120
|
+
|
|
121
|
+
if (title_result) {
|
|
122
|
+
claims.push(createDocumentTitleClaim(file_path, title_result));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const description_result = getMarkdownDescription(
|
|
126
|
+
file_path,
|
|
127
|
+
lines,
|
|
128
|
+
title_result,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (description_result) {
|
|
132
|
+
claims.push(createDocumentDescriptionClaim(file_path, description_result));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return claims;
|
|
136
|
+
}
|
|
137
|
+
|
|
105
138
|
/**
|
|
106
139
|
* @param {string} file_path
|
|
107
140
|
* @param {string} line
|
|
@@ -138,20 +171,25 @@ function collectMarkdownLinkClaims(file_path, line, line_number, claims) {
|
|
|
138
171
|
|
|
139
172
|
/**
|
|
140
173
|
* @param {string} file_path
|
|
141
|
-
* @param {string}
|
|
142
|
-
* @param {number}
|
|
174
|
+
* @param {string[]} lines
|
|
175
|
+
* @param {number} line_index
|
|
143
176
|
* @param {PatramClaim[]} claims
|
|
177
|
+
* @returns {number | null}
|
|
144
178
|
*/
|
|
145
|
-
function collectVisibleDirectiveClaims(file_path,
|
|
146
|
-
const
|
|
179
|
+
function collectVisibleDirectiveClaims(file_path, lines, line_index, claims) {
|
|
180
|
+
const directive_result = collectVisibleDirectiveFields(
|
|
147
181
|
file_path,
|
|
148
|
-
|
|
149
|
-
|
|
182
|
+
lines,
|
|
183
|
+
line_index,
|
|
150
184
|
);
|
|
151
185
|
|
|
152
|
-
if (
|
|
153
|
-
|
|
186
|
+
if (!directive_result) {
|
|
187
|
+
return null;
|
|
154
188
|
}
|
|
189
|
+
|
|
190
|
+
pushDirectiveClaim(file_path, claims, directive_result.directive_fields);
|
|
191
|
+
|
|
192
|
+
return directive_result.next_line_index;
|
|
155
193
|
}
|
|
156
194
|
|
|
157
195
|
/**
|
|
@@ -173,36 +211,35 @@ function collectHiddenDirectiveClaims(file_path, line, line_number, claims) {
|
|
|
173
211
|
}
|
|
174
212
|
|
|
175
213
|
/**
|
|
176
|
-
* @param {string
|
|
177
|
-
* @param {number}
|
|
178
|
-
* @returns {
|
|
214
|
+
* @param {string} file_path
|
|
215
|
+
* @param {{ line: number, value: string }} title_result
|
|
216
|
+
* @returns {PatramClaim}
|
|
179
217
|
*/
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const heading_match = trimmed_line.match(HEADING_PATTERN);
|
|
194
|
-
|
|
195
|
-
if (heading_match) {
|
|
196
|
-
return {
|
|
197
|
-
line: start_line_index + 1,
|
|
198
|
-
value: heading_match[1].trim(),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
218
|
+
function createDocumentTitleClaim(file_path, title_result) {
|
|
219
|
+
return createClaim(file_path, 1, 'document.title', {
|
|
220
|
+
origin: {
|
|
221
|
+
column: 1,
|
|
222
|
+
line: title_result.line,
|
|
223
|
+
path: file_path,
|
|
224
|
+
},
|
|
225
|
+
value: title_result.value,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
201
228
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
229
|
+
/**
|
|
230
|
+
* @param {string} file_path
|
|
231
|
+
* @param {{ column: number, line: number, value: string }} description_result
|
|
232
|
+
* @returns {PatramClaim}
|
|
233
|
+
*/
|
|
234
|
+
function createDocumentDescriptionClaim(file_path, description_result) {
|
|
235
|
+
return createClaim(file_path, 2, 'document.description', {
|
|
236
|
+
origin: {
|
|
237
|
+
column: description_result.column,
|
|
238
|
+
line: description_result.line,
|
|
239
|
+
path: file_path,
|
|
240
|
+
},
|
|
241
|
+
value: description_result.value,
|
|
242
|
+
});
|
|
206
243
|
}
|
|
207
244
|
|
|
208
245
|
/**
|
|
@@ -20,15 +20,19 @@ export function parseFrontMatterDirectiveFields(file_path: string, lines: string
|
|
|
20
20
|
export function matchVisibleDirectiveFields(file_path: string, line: string, line_number: number): PatramClaimFields | null;
|
|
21
21
|
/**
|
|
22
22
|
* @param {string} file_path
|
|
23
|
-
* @param {string}
|
|
24
|
-
* @param {number}
|
|
25
|
-
* @returns {PatramClaimFields | null}
|
|
23
|
+
* @param {string[]} lines
|
|
24
|
+
* @param {number} line_index
|
|
25
|
+
* @returns {{ directive_fields: PatramClaimFields, next_line_index: number } | null}
|
|
26
26
|
*/
|
|
27
|
-
export function
|
|
27
|
+
export function collectVisibleDirectiveFields(file_path: string, lines: string[], line_index: number): {
|
|
28
|
+
directive_fields: PatramClaimFields;
|
|
29
|
+
next_line_index: number;
|
|
30
|
+
} | null;
|
|
28
31
|
/**
|
|
29
32
|
* @param {string} directive_label
|
|
30
|
-
* @returns {string}
|
|
33
|
+
* @returns {string | null}
|
|
31
34
|
*/
|
|
32
|
-
export function normalizeDirectiveName(directive_label: string): string;
|
|
35
|
+
export function normalizeDirectiveName(directive_label: string): string | null;
|
|
36
|
+
export function matchHiddenDirectiveFields(...args: unknown[]): PatramClaimFields | null;
|
|
33
37
|
import type { PatramDiagnostic } from '../../config/load-patram-config.types.d.ts';
|
|
34
38
|
import type { PatramClaimFields } from '../parse-claims.types.d.ts';
|
|
@@ -6,10 +6,16 @@
|
|
|
6
6
|
import { parseYamlDirectiveFields } from '../yaml/parse-yaml-claims.js';
|
|
7
7
|
|
|
8
8
|
const FRONT_MATTER_BOUNDARY_PATTERN = /^---$/du;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const FIELD_NAME_PATTERN = '[a-z][a-z0-9_]*';
|
|
10
|
+
const LIST_ITEM_DIRECTIVE_PATTERN = new RegExp(
|
|
11
|
+
`^-\\s+(${FIELD_NAME_PATTERN}):\\s+(.+)$`,
|
|
12
|
+
'du',
|
|
13
|
+
);
|
|
14
|
+
const LIST_ITEM_DIRECTIVE_CONTINUATION_PATTERN = /^ {2,3}\S/u;
|
|
15
|
+
const VISIBLE_DIRECTIVE_PATTERN = new RegExp(
|
|
16
|
+
`^(${FIELD_NAME_PATTERN}):\\s+(.+)$`,
|
|
17
|
+
'du',
|
|
18
|
+
);
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* @param {string} file_path
|
|
@@ -85,20 +91,68 @@ export function matchVisibleDirectiveFields(file_path, line, line_number) {
|
|
|
85
91
|
|
|
86
92
|
/**
|
|
87
93
|
* @param {string} file_path
|
|
88
|
-
* @param {string}
|
|
89
|
-
* @param {number}
|
|
90
|
-
* @returns {PatramClaimFields | null}
|
|
94
|
+
* @param {string[]} lines
|
|
95
|
+
* @param {number} line_index
|
|
96
|
+
* @returns {{ directive_fields: PatramClaimFields, next_line_index: number } | null}
|
|
91
97
|
*/
|
|
92
|
-
export function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
export function collectVisibleDirectiveFields(file_path, lines, line_index) {
|
|
99
|
+
const line = lines[line_index];
|
|
100
|
+
|
|
101
|
+
if (line === undefined) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const directive_fields = matchVisibleDirectiveFields(
|
|
96
106
|
file_path,
|
|
97
107
|
line,
|
|
98
|
-
|
|
108
|
+
line_index + 1,
|
|
99
109
|
);
|
|
110
|
+
|
|
111
|
+
if (!directive_fields) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (directive_fields.markdown_style !== 'list_item') {
|
|
116
|
+
return {
|
|
117
|
+
directive_fields,
|
|
118
|
+
next_line_index: line_index + 1,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const continuation_result = collectListItemDirectiveContinuation(
|
|
123
|
+
lines,
|
|
124
|
+
line_index + 1,
|
|
125
|
+
);
|
|
126
|
+
const directive_value = directive_fields.value;
|
|
127
|
+
|
|
128
|
+
if (
|
|
129
|
+
continuation_result.value.length === 0 ||
|
|
130
|
+
typeof directive_value !== 'string'
|
|
131
|
+
) {
|
|
132
|
+
return {
|
|
133
|
+
directive_fields,
|
|
134
|
+
next_line_index: continuation_result.next_line_index,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
directive_fields: {
|
|
140
|
+
...directive_fields,
|
|
141
|
+
value: `${directive_value} ${continuation_result.value}`,
|
|
142
|
+
},
|
|
143
|
+
next_line_index: continuation_result.next_line_index,
|
|
144
|
+
};
|
|
100
145
|
}
|
|
101
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @param {...unknown} args
|
|
149
|
+
* @returns {PatramClaimFields | null}
|
|
150
|
+
*/
|
|
151
|
+
export const matchHiddenDirectiveFields = (...args) => {
|
|
152
|
+
void args;
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
|
|
102
156
|
/**
|
|
103
157
|
* @param {string[]} lines
|
|
104
158
|
* @returns {number}
|
|
@@ -134,9 +188,15 @@ function matchDirectiveFields(
|
|
|
134
188
|
return null;
|
|
135
189
|
}
|
|
136
190
|
|
|
191
|
+
const directive_name = normalizeDirectiveName(directive_match[1]);
|
|
192
|
+
|
|
193
|
+
if (!directive_name) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
137
197
|
return {
|
|
138
198
|
markdown_style,
|
|
139
|
-
name:
|
|
199
|
+
name: directive_name,
|
|
140
200
|
origin: {
|
|
141
201
|
column: 1,
|
|
142
202
|
line: line_number,
|
|
@@ -147,13 +207,39 @@ function matchDirectiveFields(
|
|
|
147
207
|
};
|
|
148
208
|
}
|
|
149
209
|
|
|
210
|
+
/**
|
|
211
|
+
* @param {string[]} lines
|
|
212
|
+
* @param {number} line_index
|
|
213
|
+
* @returns {{ next_line_index: number, value: string }}
|
|
214
|
+
*/
|
|
215
|
+
function collectListItemDirectiveContinuation(lines, line_index) {
|
|
216
|
+
/** @type {string[]} */
|
|
217
|
+
const continuation_lines = [];
|
|
218
|
+
let next_line_index = line_index;
|
|
219
|
+
|
|
220
|
+
while (next_line_index < lines.length) {
|
|
221
|
+
const line = lines[next_line_index];
|
|
222
|
+
|
|
223
|
+
if (!LIST_ITEM_DIRECTIVE_CONTINUATION_PATTERN.test(line)) {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
continuation_lines.push(line.trim());
|
|
228
|
+
next_line_index += 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
next_line_index,
|
|
233
|
+
value: continuation_lines.join(' '),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
150
237
|
/**
|
|
151
238
|
* @param {string} directive_label
|
|
152
|
-
* @returns {string}
|
|
239
|
+
* @returns {string | null}
|
|
153
240
|
*/
|
|
154
241
|
export function normalizeDirectiveName(directive_label) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.replaceAll(/[\s-]+/dgu, '_');
|
|
242
|
+
const normalized_name = directive_label.trim();
|
|
243
|
+
|
|
244
|
+
return /^[a-z][a-z0-9_]*$/du.test(normalized_name) ? normalized_name : null;
|
|
159
245
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string[]} lines
|
|
3
|
+
* @param {number} start_line_index
|
|
4
|
+
* @returns {{ is_heading: boolean, line: number, line_index: number, value: string } | null}
|
|
5
|
+
*/
|
|
6
|
+
export function getMarkdownTitle(lines: string[], start_line_index: number): {
|
|
7
|
+
is_heading: boolean;
|
|
8
|
+
line: number;
|
|
9
|
+
line_index: number;
|
|
10
|
+
value: string;
|
|
11
|
+
} | null;
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} file_path
|
|
14
|
+
* @param {string[]} lines
|
|
15
|
+
* @param {{ is_heading: boolean, line: number, line_index: number, value: string } | null} title_result
|
|
16
|
+
* @returns {{ column: number, line: number, value: string } | null}
|
|
17
|
+
*/
|
|
18
|
+
export function getMarkdownDescription(file_path: string, lines: string[], title_result: {
|
|
19
|
+
is_heading: boolean;
|
|
20
|
+
line: number;
|
|
21
|
+
line_index: number;
|
|
22
|
+
value: string;
|
|
23
|
+
} | null): {
|
|
24
|
+
column: number;
|
|
25
|
+
line: number;
|
|
26
|
+
value: string;
|
|
27
|
+
} | null;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectVisibleDirectiveFields,
|
|
3
|
+
matchHiddenDirectiveFields,
|
|
4
|
+
} from './parse-markdown-directives.js';
|
|
5
|
+
|
|
6
|
+
const HEADING_PATTERN = /^#{1,6}\s+(.+)$/du;
|
|
7
|
+
const BLOCKQUOTE_PATTERN = /^\s*>/u;
|
|
8
|
+
const INDENTED_CODE_PATTERN = /^(?: {4}|\t)\S/u;
|
|
9
|
+
const MARKDOWN_DESCRIPTION_SENTENCE_PATTERN = /^(.+?[.!?])(?:\s+|$)[\s\S]*$/du;
|
|
10
|
+
const MARKDOWN_DESCRIPTION_LENGTH_LIMIT = 120;
|
|
11
|
+
const MARKDOWN_FENCE_PATTERN = /^([`~]{3,})/du;
|
|
12
|
+
const MARKDOWN_ORDERED_LIST_PATTERN = /^\s*\d+[.)]\s+/u;
|
|
13
|
+
const MARKDOWN_TABLE_PATTERN = /^\s*\|/u;
|
|
14
|
+
const MARKDOWN_UNORDERED_LIST_PATTERN = /^\s*[-+*]\s+/u;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string[]} lines
|
|
18
|
+
* @param {number} start_line_index
|
|
19
|
+
* @returns {{ is_heading: boolean, line: number, line_index: number, value: string } | null}
|
|
20
|
+
*/
|
|
21
|
+
export function getMarkdownTitle(lines, start_line_index) {
|
|
22
|
+
const first_line_index = skipBlankLines(lines, start_line_index);
|
|
23
|
+
const first_line = lines[first_line_index];
|
|
24
|
+
|
|
25
|
+
if (first_line === undefined) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const trimmed_line = first_line.trim();
|
|
30
|
+
const heading_match = trimmed_line.match(HEADING_PATTERN);
|
|
31
|
+
|
|
32
|
+
if (heading_match) {
|
|
33
|
+
return {
|
|
34
|
+
is_heading: true,
|
|
35
|
+
line: first_line_index + 1,
|
|
36
|
+
line_index: first_line_index,
|
|
37
|
+
value: heading_match[1].trim(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
is_heading: false,
|
|
43
|
+
line: first_line_index + 1,
|
|
44
|
+
line_index: first_line_index,
|
|
45
|
+
value: trimmed_line,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} file_path
|
|
51
|
+
* @param {string[]} lines
|
|
52
|
+
* @param {{ is_heading: boolean, line: number, line_index: number, value: string } | null} title_result
|
|
53
|
+
* @returns {{ column: number, line: number, value: string } | null}
|
|
54
|
+
*/
|
|
55
|
+
export function getMarkdownDescription(file_path, lines, title_result) {
|
|
56
|
+
if (!title_result) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let line_index = title_result.is_heading
|
|
61
|
+
? title_result.line_index + 1
|
|
62
|
+
: title_result.line_index;
|
|
63
|
+
|
|
64
|
+
line_index = skipBlankLines(lines, line_index);
|
|
65
|
+
|
|
66
|
+
if (line_index >= lines.length) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
title_result.is_heading &&
|
|
72
|
+
isMarkdownDirectiveLine(file_path, lines, line_index)
|
|
73
|
+
) {
|
|
74
|
+
line_index = skipMarkdownDirectiveBlock(file_path, lines, line_index);
|
|
75
|
+
line_index = skipBlankLines(lines, line_index);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const paragraph_result = collectMarkdownParagraph(lines, line_index);
|
|
79
|
+
|
|
80
|
+
if (!paragraph_result) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
column: paragraph_result.column,
|
|
86
|
+
line: paragraph_result.line,
|
|
87
|
+
value: summarizeMarkdownParagraph(paragraph_result.value),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string[]} lines
|
|
93
|
+
* @param {number} line_index
|
|
94
|
+
* @returns {{ column: number, line: number, value: string } | null}
|
|
95
|
+
*/
|
|
96
|
+
function collectMarkdownParagraph(lines, line_index) {
|
|
97
|
+
const first_line = lines[line_index];
|
|
98
|
+
|
|
99
|
+
if (first_line === undefined || !isMarkdownParagraphLine(first_line)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const paragraph_lines = [first_line.trim()];
|
|
104
|
+
let next_line_index = line_index + 1;
|
|
105
|
+
|
|
106
|
+
while (next_line_index < lines.length) {
|
|
107
|
+
const next_line = lines[next_line_index];
|
|
108
|
+
|
|
109
|
+
if (next_line.trim().length === 0 || !isMarkdownParagraphLine(next_line)) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
paragraph_lines.push(next_line.trim());
|
|
114
|
+
next_line_index += 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
column: getLineColumn(first_line),
|
|
119
|
+
line: line_index + 1,
|
|
120
|
+
value: paragraph_lines.join(' '),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {string} file_path
|
|
126
|
+
* @param {string[]} lines
|
|
127
|
+
* @param {number} line_index
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
function isMarkdownDirectiveLine(file_path, lines, line_index) {
|
|
131
|
+
return (
|
|
132
|
+
collectVisibleDirectiveFields(file_path, lines, line_index) !== null ||
|
|
133
|
+
matchHiddenDirectiveFields(file_path, lines[line_index], line_index + 1) !==
|
|
134
|
+
null
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} file_path
|
|
140
|
+
* @param {string[]} lines
|
|
141
|
+
* @param {number} line_index
|
|
142
|
+
* @returns {number}
|
|
143
|
+
*/
|
|
144
|
+
function skipMarkdownDirectiveBlock(file_path, lines, line_index) {
|
|
145
|
+
let next_line_index = line_index;
|
|
146
|
+
|
|
147
|
+
while (next_line_index < lines.length) {
|
|
148
|
+
const line = lines[next_line_index];
|
|
149
|
+
|
|
150
|
+
if (line.trim().length === 0) {
|
|
151
|
+
next_line_index += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const visible_directive_result = collectVisibleDirectiveFields(
|
|
156
|
+
file_path,
|
|
157
|
+
lines,
|
|
158
|
+
next_line_index,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (visible_directive_result) {
|
|
162
|
+
next_line_index = visible_directive_result.next_line_index;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
matchHiddenDirectiveFields(file_path, line, next_line_index + 1) === null
|
|
168
|
+
) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
next_line_index += 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return next_line_index;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {string} line
|
|
180
|
+
* @returns {number}
|
|
181
|
+
*/
|
|
182
|
+
function getLineColumn(line) {
|
|
183
|
+
const first_character_index = line.search(/\S/u);
|
|
184
|
+
|
|
185
|
+
return first_character_index < 0 ? 1 : first_character_index + 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {string} paragraph_text
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
function summarizeMarkdownParagraph(paragraph_text) {
|
|
193
|
+
if (paragraph_text.length <= MARKDOWN_DESCRIPTION_LENGTH_LIMIT) {
|
|
194
|
+
return paragraph_text;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const sentence_match = paragraph_text.match(
|
|
198
|
+
MARKDOWN_DESCRIPTION_SENTENCE_PATTERN,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!sentence_match) {
|
|
202
|
+
return paragraph_text;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return sentence_match[1].trim();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {string} line
|
|
210
|
+
* @returns {boolean}
|
|
211
|
+
*/
|
|
212
|
+
function isMarkdownParagraphLine(line) {
|
|
213
|
+
const trimmed_line = line.trim();
|
|
214
|
+
|
|
215
|
+
if (trimmed_line.length === 0) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return !(
|
|
220
|
+
HEADING_PATTERN.test(trimmed_line) ||
|
|
221
|
+
MARKDOWN_FENCE_PATTERN.test(trimmed_line) ||
|
|
222
|
+
MARKDOWN_ORDERED_LIST_PATTERN.test(line) ||
|
|
223
|
+
MARKDOWN_TABLE_PATTERN.test(line) ||
|
|
224
|
+
MARKDOWN_UNORDERED_LIST_PATTERN.test(line) ||
|
|
225
|
+
BLOCKQUOTE_PATTERN.test(line) ||
|
|
226
|
+
INDENTED_CODE_PATTERN.test(line)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @param {string[]} lines
|
|
232
|
+
* @param {number} start_line_index
|
|
233
|
+
* @returns {number}
|
|
234
|
+
*/
|
|
235
|
+
function skipBlankLines(lines, start_line_index) {
|
|
236
|
+
let line_index = start_line_index;
|
|
237
|
+
|
|
238
|
+
while (line_index < lines.length && lines[line_index].trim().length === 0) {
|
|
239
|
+
line_index += 1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return line_index;
|
|
243
|
+
}
|