@markup-carve/carve-lsp 0.1.1 → 0.1.3

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.
Files changed (109) hide show
  1. package/README.md +65 -2
  2. package/dist/analyze.d.ts +26 -2
  3. package/dist/analyze.js +227 -14
  4. package/dist/analyze.js.map +1 -1
  5. package/dist/analyze.test.js +106 -1
  6. package/dist/analyze.test.js.map +1 -1
  7. package/dist/captions.d.ts +49 -0
  8. package/dist/captions.js +124 -0
  9. package/dist/captions.js.map +1 -0
  10. package/dist/completion.d.ts +1 -1
  11. package/dist/completion.js +49 -3
  12. package/dist/completion.js.map +1 -1
  13. package/dist/composite-figure-crossref.test.d.ts +1 -0
  14. package/dist/composite-figure-crossref.test.js +265 -0
  15. package/dist/composite-figure-crossref.test.js.map +1 -0
  16. package/dist/composite-figure.test.d.ts +1 -0
  17. package/dist/composite-figure.test.js +76 -0
  18. package/dist/composite-figure.test.js.map +1 -0
  19. package/dist/definition.d.ts +4 -2
  20. package/dist/definition.js +50 -7
  21. package/dist/definition.js.map +1 -1
  22. package/dist/dependencies.d.ts +15 -0
  23. package/dist/dependencies.js +59 -0
  24. package/dist/dependencies.js.map +1 -0
  25. package/dist/diagnostic-scheduler.d.ts +58 -0
  26. package/dist/diagnostic-scheduler.js +70 -0
  27. package/dist/diagnostic-scheduler.js.map +1 -0
  28. package/dist/diagnostic-scheduler.test.d.ts +1 -0
  29. package/dist/diagnostic-scheduler.test.js +75 -0
  30. package/dist/diagnostic-scheduler.test.js.map +1 -0
  31. package/dist/folding.d.ts +1 -0
  32. package/dist/folding.js +36 -1
  33. package/dist/folding.js.map +1 -1
  34. package/dist/folding.test.js +6 -1
  35. package/dist/folding.test.js.map +1 -1
  36. package/dist/hover.js +80 -26
  37. package/dist/hover.js.map +1 -1
  38. package/dist/include-cache.d.ts +26 -0
  39. package/dist/include-cache.js +74 -0
  40. package/dist/include-cache.js.map +1 -0
  41. package/dist/include-definition-denial.test.d.ts +1 -0
  42. package/dist/include-definition-denial.test.js +86 -0
  43. package/dist/include-definition-denial.test.js.map +1 -0
  44. package/dist/include-definition.d.ts +3 -0
  45. package/dist/include-definition.js +37 -0
  46. package/dist/include-definition.js.map +1 -0
  47. package/dist/include-directive.d.ts +61 -0
  48. package/dist/include-directive.js +158 -0
  49. package/dist/include-directive.js.map +1 -0
  50. package/dist/include-directive.test.d.ts +1 -0
  51. package/dist/include-directive.test.js +83 -0
  52. package/dist/include-directive.test.js.map +1 -0
  53. package/dist/include-features.test.d.ts +1 -0
  54. package/dist/include-features.test.js +133 -0
  55. package/dist/include-features.test.js.map +1 -0
  56. package/dist/include-path.d.ts +69 -0
  57. package/dist/include-path.js +164 -0
  58. package/dist/include-path.js.map +1 -0
  59. package/dist/include-path.test.d.ts +1 -0
  60. package/dist/include-path.test.js +189 -0
  61. package/dist/include-path.test.js.map +1 -0
  62. package/dist/include-security-conformance.test.d.ts +1 -0
  63. package/dist/include-security-conformance.test.js +100 -0
  64. package/dist/include-security-conformance.test.js.map +1 -0
  65. package/dist/include-settings.d.ts +52 -0
  66. package/dist/include-settings.js +136 -0
  67. package/dist/include-settings.js.map +1 -0
  68. package/dist/include-settings.test.d.ts +1 -0
  69. package/dist/include-settings.test.js +191 -0
  70. package/dist/include-settings.test.js.map +1 -0
  71. package/dist/includes.d.ts +86 -0
  72. package/dist/includes.js +179 -0
  73. package/dist/includes.js.map +1 -0
  74. package/dist/includes.test.d.ts +1 -0
  75. package/dist/includes.test.js +202 -0
  76. package/dist/includes.test.js.map +1 -0
  77. package/dist/inline-text.d.ts +24 -0
  78. package/dist/inline-text.js +34 -0
  79. package/dist/inline-text.js.map +1 -0
  80. package/dist/inline-text.test.d.ts +1 -0
  81. package/dist/inline-text.test.js +32 -0
  82. package/dist/inline-text.test.js.map +1 -0
  83. package/dist/position-encoding.test.d.ts +1 -0
  84. package/dist/position-encoding.test.js +143 -0
  85. package/dist/position-encoding.test.js.map +1 -0
  86. package/dist/position.d.ts +82 -0
  87. package/dist/position.js +137 -0
  88. package/dist/position.js.map +1 -0
  89. package/dist/position.test.d.ts +1 -0
  90. package/dist/position.test.js +61 -0
  91. package/dist/position.test.js.map +1 -0
  92. package/dist/references.d.ts +2 -0
  93. package/dist/references.js +116 -21
  94. package/dist/references.js.map +1 -1
  95. package/dist/semantic.d.ts +14 -0
  96. package/dist/semantic.js +112 -13
  97. package/dist/semantic.js.map +1 -1
  98. package/dist/semantic.test.d.ts +1 -0
  99. package/dist/semantic.test.js +99 -0
  100. package/dist/semantic.test.js.map +1 -0
  101. package/dist/server.js +166 -26
  102. package/dist/server.js.map +1 -1
  103. package/dist/versioned-cache.d.ts +8 -0
  104. package/dist/versioned-cache.js +17 -0
  105. package/dist/versioned-cache.js.map +1 -0
  106. package/dist/versioned-cache.test.d.ts +1 -0
  107. package/dist/versioned-cache.test.js +13 -0
  108. package/dist/versioned-cache.test.js.map +1 -0
  109. package/package.json +2 -2
package/README.md CHANGED
@@ -23,10 +23,10 @@ The server communicates over **stdio** (`--stdio` flag).
23
23
  | Capability | Details |
24
24
  |---|---|
25
25
  | Diagnostics | Syntax errors from the Carve parser; advisory warnings for Djot/Markdown delimiter collisions |
26
- | Document symbols | Headings as an outline tree |
26
+ | Document symbols | Headings as an outline tree, including headings attributed to included files |
27
27
  | Hover | Contextual information on hover |
28
28
  | Completion | Trigger characters `:` `#` `^` `[` |
29
- | Go to definition | Jump to heading / reference targets |
29
+ | Go to definition | Jump to heading / reference targets and included files |
30
30
  | Find references | All uses of a heading id or reference label |
31
31
  | Rename | Prepare + apply renames across the document |
32
32
  | Code actions | Migration quick-fixes for deprecated Carve syntax |
@@ -34,6 +34,69 @@ The server communicates over **stdio** (`--stdio` flag).
34
34
  | Folding ranges | Fold sections and block containers |
35
35
  | Formatting | Format the whole document |
36
36
  | Semantic tokens | Token-based syntax highlighting |
37
+ | File inclusion | Resolves `{{ path }}` directives, watches dependencies, and reports failures as diagnostics - **off by default**, see below |
38
+
39
+ ## File inclusion
40
+
41
+ Carve's `{{ path }}` directive is a processor-level feature (PART 9 §19 of the
42
+ grammar), not part of the parser. This server resolves it so that the thin
43
+ editor clients - `helix-carve`, `emacs-carve`, `vim-carve`, `zed-carve`,
44
+ `sublime-carve` - get inclusion support without each implementing it.
45
+
46
+ Resolution reads files from disk, so it is **opt-in and off by default**. The
47
+ server enables it only when it is asked to, and only inside a containment root.
48
+
49
+ ### Settings
50
+
51
+ Pass these under `carve.includes`, either in `initializationOptions` at
52
+ initialize time or through `workspace/didChangeConfiguration`. Changing them
53
+ re-publishes diagnostics for every open document.
54
+
55
+ | Setting | Default | Meaning |
56
+ |---|---|---|
57
+ | `enabled` | `"auto"` | `"auto"` enables inclusion only for a workspace the client reports as trusted; `"on"` always; `"off"` never. A client that reports no trust gets inclusion off. |
58
+ | `includeRoot` | workspace root | Containment root override. With no workspace, the document's own directory is used - never the server's working directory. |
59
+ | `allowAbsolute` | `false` | Allow absolute include paths. They still have to canonicalize inside the root. |
60
+ | `allowedRemoteHosts` | `[]` | Hosts a remote include may name. This server has no fetcher, so a remote target is refused either way; the list exists so the gate is explicit. |
61
+ | `maxDepth` | `16` | Maximum transitive include depth. |
62
+ | `maxBytes` | `max(1 MiB, 8x document)` | Total byte budget across the whole include graph, charged per occurrence. |
63
+
64
+ Client trust is read from `initializationOptions.workspaceTrusted`.
65
+
66
+ ```json
67
+ {
68
+ "carve": {
69
+ "includes": {
70
+ "enabled": "auto",
71
+ "maxDepth": 8
72
+ }
73
+ },
74
+ "workspaceTrusted": true
75
+ }
76
+ ```
77
+
78
+ ### What is enforced
79
+
80
+ Every target has to canonicalize (symlinks resolved) to a file inside the
81
+ containment root. A symlink pointing out of the root, a `..` path that leaves
82
+ it, and an absolute path outside it are all refused. A `..` path whose real
83
+ target stays inside the root is fine - a chapter reaching a shared glossary is
84
+ ordinary layout. Remote URLs are never fetched. Recursion depth and total
85
+ expanded bytes are both bounded, so a file that includes another many times
86
+ cannot amplify without limit.
87
+
88
+ A refused target produces an `include-unresolved` diagnostic on the directive,
89
+ and the directive stays literal. The diagnostic deliberately does not say WHICH
90
+ check refused it: a distinguishable denial is a way to probe the layout of the
91
+ machine the server runs on.
92
+
93
+ The server watches every legal include target, including a missing target that
94
+ may be created later. A child change invalidates its cached source and parsed
95
+ tree, then revalidates every open document that includes it. Go to definition
96
+ on a directive opens the resolved child, and child headings appear in the
97
+ including document's symbol result with locations in the child file. All three
98
+ features use the same contained resolver as diagnostics; refused paths are
99
+ never registered as watchers or navigation targets.
37
100
 
38
101
  ## Editor setup
39
102
 
package/dist/analyze.d.ts CHANGED
@@ -1,6 +1,30 @@
1
- import { DocumentSymbol, type Diagnostic } from 'vscode-languageserver/node.js';
1
+ import { DocumentSymbol, SymbolInformation, type Diagnostic, type Range } from 'vscode-languageserver/node.js';
2
+ import { type IncludeDependency, type IncludeOptions } from './includes.js';
3
+ import type { IncludeParseCache } from './include-cache.js';
4
+ export interface AnalyzeOptions {
5
+ /** URI used for diagnostic related-information locations. */
6
+ uri?: string;
7
+ /**
8
+ * Include resolution settings (PART 9 §19). ABSENT MEANS OFF: with no
9
+ * options - or with options carrying no resolver - no include directive is
10
+ * acted on and no file is read. §19 requires includes to be opt-in, so the
11
+ * capability has to be handed in rather than defaulted into existence.
12
+ */
13
+ includes?: IncludeOptions;
14
+ includedParseCache?: IncludeParseCache;
15
+ }
2
16
  export interface Analysis {
3
17
  diagnostics: Diagnostic[];
4
18
  symbols: DocumentSymbol[];
19
+ /**
20
+ * Include targets touched while analyzing, resolved or not. Empty unless
21
+ * include resolution was enabled. Hosts watch these so a change in an
22
+ * INCLUDED document can re-publish diagnostics for the INCLUDING one.
23
+ */
24
+ dependencies: IncludeDependency[];
25
+ /** Outline entries from included files, carrying their child-file URI. */
26
+ includedSymbols: SymbolInformation[];
5
27
  }
6
- export declare function analyzeCarve(source: string): Analysis;
28
+ export declare function analyzeCarve(source: string, options?: AnalyzeOptions): Analysis;
29
+ export declare function parseErrorRange(source: string, message: string): Range;
30
+ export declare function tolerantHeadingSymbols(source: string): DocumentSymbol[];
package/dist/analyze.js CHANGED
@@ -1,17 +1,23 @@
1
1
  import { DiagnosticSeverity, SymbolKind, } from 'vscode-languageserver/node.js';
2
2
  import { djotMigrationWarnings, lintCarve, parse, resolve, } from '@markup-carve/carve';
3
- export function analyzeCarve(source) {
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { smartPunctuationText } from './inline-text.js';
6
+ import { panelLetter } from './captions.js';
7
+ import { resolveIncludes } from './includes.js';
8
+ export function analyzeCarve(source, options = {}) {
4
9
  const diagnostics = [];
5
10
  let doc = null;
6
11
  try {
7
12
  doc = resolve(parse(source));
8
13
  }
9
14
  catch (error) {
15
+ const message = error instanceof Error ? error.message : String(error);
10
16
  diagnostics.push({
11
17
  severity: DiagnosticSeverity.Error,
12
- range: rangeAt(source, 0, 1),
18
+ range: parseErrorRange(source, message),
13
19
  source: 'carve',
14
- message: error instanceof Error ? error.message : String(error),
20
+ message,
15
21
  });
16
22
  }
17
23
  // warning.start/end are offsets into the line-ending-normalized source;
@@ -44,7 +50,7 @@ export function analyzeCarve(source) {
44
50
  // attributes, legacy raw fences, leaked block markers).
45
51
  for (const warning of lintCarve(source)) {
46
52
  const len = Math.max(1, warning.end - warning.start);
47
- diagnostics.push({
53
+ const diagnostic = {
48
54
  severity: DiagnosticSeverity.Warning,
49
55
  range: {
50
56
  start: { line: warning.line - 1, character: warning.column - 1 },
@@ -53,21 +59,148 @@ export function analyzeCarve(source) {
53
59
  source: 'carve',
54
60
  code: warning.rule,
55
61
  message: warning.message,
62
+ };
63
+ const related = options.uri ? firstDuplicateDeclaration(source, warning.rule, warning.message, warning.line - 1) : null;
64
+ if (options.uri && related)
65
+ diagnostic.relatedInformation = [{
66
+ location: { uri: options.uri, range: related },
67
+ message: 'First declaration is here.',
68
+ }];
69
+ diagnostics.push(diagnostic);
70
+ }
71
+ // Include resolution (PART 9 §19). Inert without a resolver, so a document
72
+ // in an untrusted or unconfigured workspace keeps its `{{ … }}` literal and
73
+ // nothing on disk is touched.
74
+ const includes = resolveIncludes(norm, options.includes ?? {});
75
+ for (const warning of includes.warnings) {
76
+ diagnostics.push({
77
+ severity: DiagnosticSeverity.Warning,
78
+ range: {
79
+ start: positionAt(norm, warning.start),
80
+ end: positionAt(norm, warning.end),
81
+ },
82
+ source: 'carve',
83
+ code: warning.rule,
84
+ // `warning.detail` is deliberately not folded in: §19 I7 keeps the
85
+ // failure class out of author-visible text so a denial cannot be used
86
+ // to probe host layout. The attributed child is named relative to the
87
+ // include root for the same reason - never as an absolute path.
88
+ message: attributeToChild(warning.message, warning.file, options.includes),
56
89
  });
57
90
  }
58
91
  return {
59
92
  diagnostics,
60
- symbols: doc ? documentSymbols(doc) : [],
93
+ symbols: doc ? documentSymbols(doc) : tolerantHeadingSymbols(source),
94
+ dependencies: includes.dependencies,
95
+ includedSymbols: includedSymbols(includes.documents, options.includedParseCache),
61
96
  };
62
97
  }
98
+ export function parseErrorRange(source, message) {
99
+ const located = /(?:line|at)\s+(\d+)(?::|,\s*column\s+)(\d+)/i.exec(message);
100
+ if (!located)
101
+ return rangeAt(source, 0, 1);
102
+ const line = Math.max(0, Number(located[1]) - 1);
103
+ const character = Math.max(0, Number(located[2]) - 1);
104
+ return { start: { line, character }, end: { line, character: character + 1 } };
105
+ }
106
+ export function tolerantHeadingSymbols(source) {
107
+ const symbols = [];
108
+ for (const [line, text] of source.replace(/\r\n?/g, '\n').split('\n').entries()) {
109
+ const match = /^(#{1,6}) (.+)$/.exec(text);
110
+ if (!match)
111
+ continue;
112
+ const range = { start: { line, character: 0 }, end: { line, character: text.length } };
113
+ symbols.push({ name: match[2], kind: SymbolKind.String, range, selectionRange: range });
114
+ }
115
+ return symbols;
116
+ }
117
+ function firstDuplicateDeclaration(source, rule, message, repeatedLine) {
118
+ const headingKey = rule === 'duplicate-heading-id' ? /heading id "([^"]+)"/.exec(message)?.[1] : undefined;
119
+ const footnoteKey = rule === 'duplicate-footnote-definition' ? /\[\^([^\]]+)\]/.exec(message)?.[1] : undefined;
120
+ const key = headingKey ?? footnoteKey;
121
+ if (!key)
122
+ return null;
123
+ const lines = source.replace(/\r\n?/g, '\n').split('\n');
124
+ const declarations = [];
125
+ for (let line = 0; line < lines.length; line += 1) {
126
+ const text = lines[line];
127
+ const match = headingKey
128
+ ? /^\{[^}]*#([^\s}]+)[^}]*\}\s*$/.exec(text)
129
+ : /^(?: {0,3})\[\^([^\]]+)\]:/.exec(text);
130
+ if (!match || match[1].toLocaleLowerCase() !== key.toLocaleLowerCase())
131
+ continue;
132
+ const character = text.indexOf(match[1]);
133
+ if (line >= repeatedLine - 1)
134
+ break;
135
+ declarations.push({
136
+ start: { line, character },
137
+ end: { line, character: character + match[1].length },
138
+ });
139
+ }
140
+ return declarations[0] ?? null;
141
+ }
142
+ function includedSymbols(documents, cache) {
143
+ const symbols = [];
144
+ for (const child of documents) {
145
+ let document = child.version === undefined ? undefined : cache?.get(child.id, child.version);
146
+ if (!document) {
147
+ try {
148
+ document = resolve(parse(child.source));
149
+ }
150
+ catch {
151
+ continue;
152
+ }
153
+ if (child.version !== undefined)
154
+ cache?.set(child.id, child.version, document);
155
+ }
156
+ const uri = pathToFileURL(child.id).toString();
157
+ for (const entry of walkOutline(document.children)) {
158
+ // An INCLUDED file contributes its headings, flat. A composite figure is
159
+ // a landmark inside its own file rather than a navigable entry in
160
+ // another one, so it stays out of this list.
161
+ if (entry.type !== 'heading')
162
+ continue;
163
+ const symbol = headingSymbol(entry);
164
+ symbols.push({
165
+ name: symbol.name,
166
+ kind: symbol.kind,
167
+ location: { uri, range: symbol.range },
168
+ containerName: path.basename(child.id),
169
+ });
170
+ }
171
+ }
172
+ return symbols;
173
+ }
174
+ /**
175
+ * Name the file a nested include warning arose in, relative to the include
176
+ * root. A warning raised in the document the client already has open adds
177
+ * nothing and is left alone.
178
+ */
179
+ function attributeToChild(message, file, includes) {
180
+ if (file === undefined || file === includes?.sourcePath)
181
+ return message;
182
+ return `${message} (in ${relativeToRoot(file, includes?.includeRoot)})`;
183
+ }
184
+ /**
185
+ * A child's identity as the author wrote it: relative to the include root,
186
+ * segment-wise, so a sibling directory named `..foo` is not mistaken for an
187
+ * escape. A file that is not under the root at all is named as it came, since
188
+ * inventing a relative form for it would be a lie.
189
+ */
190
+ function relativeToRoot(file, root) {
191
+ if (root === undefined)
192
+ return file;
193
+ const relative = path.relative(root, file);
194
+ if (!relative || path.isAbsolute(relative))
195
+ return file;
196
+ if (relative.split(path.sep)[0] === '..')
197
+ return file;
198
+ return relative;
199
+ }
63
200
  function documentSymbols(doc) {
64
201
  const stack = [];
65
202
  const roots = [];
66
- for (const heading of walkHeadings(doc.children)) {
67
- const symbol = headingSymbol(heading);
68
- while (stack.length && stack[stack.length - 1].level >= heading.level) {
69
- stack.pop();
70
- }
203
+ const place = (symbol) => {
71
204
  const parent = stack[stack.length - 1];
72
205
  if (parent) {
73
206
  parent.symbol.children ??= [];
@@ -76,24 +209,97 @@ function documentSymbols(doc) {
76
209
  else {
77
210
  roots.push(symbol);
78
211
  }
79
- stack.push({ level: heading.level, symbol });
212
+ };
213
+ for (const entry of walkOutline(doc.children)) {
214
+ if (entry.type === 'figure_group') {
215
+ // A composite figure is a structural landmark: one figure holding
216
+ // ordered panels (PART 9 §4c). It takes NO level, so it never pops the
217
+ // heading stack - it hangs under the section it appears in, the way a
218
+ // heading's own children do.
219
+ place(figureGroupSymbol(entry));
220
+ continue;
221
+ }
222
+ while (stack.length && stack[stack.length - 1].level >= entry.level) {
223
+ stack.pop();
224
+ }
225
+ const symbol = headingSymbol(entry);
226
+ place(symbol);
227
+ stack.push({ level: entry.level, symbol });
80
228
  }
81
229
  return roots;
82
230
  }
83
- function* walkHeadings(nodes) {
231
+ /**
232
+ * The outline's entries in source order: headings, and composite figures.
233
+ *
234
+ * A group is worth an entry where a plain figure is not, because it is a
235
+ * CONTAINER an author folds, navigates and loses their place inside - the same
236
+ * reason it folds. Its panels come with it, so the outline says how many there
237
+ * are without scrolling the fence.
238
+ */
239
+ function* walkOutline(nodes) {
84
240
  for (const node of nodes) {
85
241
  if (node.type === 'heading')
86
242
  yield node;
243
+ if (node.type === 'figure_group') {
244
+ yield node;
245
+ // Its panels ride on the group's own symbol - a `figure` or `table`
246
+ // yields no entry of its own here - but the walk still DESCENDS through
247
+ // them. A panel can wrap a quote holding headings, and skipping the panel
248
+ // node dropped those headings out of the outline entirely.
249
+ yield* walkOutline(node.children);
250
+ continue;
251
+ }
87
252
  if ('children' in node && Array.isArray(node.children)) {
88
- yield* walkHeadings(node.children.filter(isBlockNode));
253
+ yield* walkOutline(node.children.filter(isBlockNode));
89
254
  }
90
255
  if (node.type === 'figure') {
91
256
  if ('children' in node.target && Array.isArray(node.target.children)) {
92
- yield* walkHeadings(node.target.children.filter(isBlockNode));
257
+ yield* walkOutline(node.target.children.filter(isBlockNode));
93
258
  }
94
259
  }
95
260
  }
96
261
  }
262
+ /** A group's panels are its direct `figure` and `table` children (§4c). */
263
+ function isPanel(node) {
264
+ return node.type === 'figure' || node.type === 'table';
265
+ }
266
+ function figureGroupSymbol(group) {
267
+ const range = blockRange(group);
268
+ const panels = group.children.filter(isPanel);
269
+ return {
270
+ name: (group.caption ? plainText(group.caption) : '') || 'Composite figure',
271
+ detail: panels.length === 1 ? '1 panel' : `${panels.length} panels`,
272
+ kind: SymbolKind.Struct,
273
+ range,
274
+ selectionRange: range,
275
+ children: panels.map((panel, index) => panelSymbol(panel, index)),
276
+ };
277
+ }
278
+ /**
279
+ * A panel is named by its own caption, and falls back to the LETTER a crossref
280
+ * would use for it (§4c) - not to a number, which would read as a figure number
281
+ * the panel does not have.
282
+ */
283
+ function panelSymbol(panel, index) {
284
+ const caption = panel.caption;
285
+ const range = blockRange(panel);
286
+ return {
287
+ name: (caption ? plainText(caption) : '') || `Panel ${panelLetter(index)}`,
288
+ kind: panel.type === 'table' ? SymbolKind.Array : SymbolKind.Object,
289
+ range,
290
+ selectionRange: range,
291
+ children: [],
292
+ };
293
+ }
294
+ function blockRange(node) {
295
+ if (!node.pos) {
296
+ return { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
297
+ }
298
+ return {
299
+ start: { line: node.pos.startLine - 1, character: 0 },
300
+ end: { line: node.pos.endLine - 1, character: 200 },
301
+ };
302
+ }
97
303
  function isBlockNode(node) {
98
304
  return Boolean(node && typeof node === 'object' && 'type' in node);
99
305
  }
@@ -129,12 +335,19 @@ function plainText(nodes) {
129
335
  // verbatim content to the outline symbol name just as a code span does.
130
336
  else if (node.type === 'literal_inline')
131
337
  out += node.content;
338
+ // A caption's resolved number. Without it a composite figure appeared in
339
+ // the outline as "Figure : Group caption", with the gap where the number
340
+ // the reader is looking for should be.
341
+ else if (node.type === 'caption_number')
342
+ out += String(node.n);
132
343
  else if (node.type === 'symbol')
133
344
  out += `:${node.name}:`;
134
345
  else if (node.type === 'mention')
135
346
  out += `@${node.user}`;
136
347
  else if (node.type === 'tag')
137
348
  out += `#${node.name}`;
349
+ else
350
+ out += smartPunctuationText(node);
138
351
  }
139
352
  return out.trim();
140
353
  }
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
- import { analyzeCarve } from './analyze.js';
3
+ import { analyzeCarve, parseErrorRange, tolerantHeadingSymbols } from './analyze.js';
4
4
  import { hoverAt } from './hover.js';
5
5
  import { migrationCodeActions, migrationFixes } from './migration-actions.js';
6
6
  import { semanticTokens } from './semantic.js';
@@ -21,6 +21,21 @@ test('inline literal gets a string semantic token spanning the whole construct',
21
21
  const result = semanticTokens('Say !`/kaet/` now.\n');
22
22
  assert.deepEqual(result.map((token) => [token.line, token.character, token.length, token.type]), [[0, 4, 9, 'string']]);
23
23
  });
24
+ test('both footnote forms get a variable token across the AST split', () => {
25
+ // carve-js split `footnote` into `footnote_ref` and `inline_footnote`
26
+ // (markup-carve/carve#405). Asserted through source text rather than the type
27
+ // name, so it holds against the pinned engine and after the pin is raised -
28
+ // which is what makes the split safe to release in either order.
29
+ const tokens = semanticTokens('a[^r] and ^[n]\n\n[^r]: d\n').filter((t) => t.type === 'variable');
30
+ assert.equal(tokens.length, 2);
31
+ });
32
+ test('a critic comment gets a comment token under either AST spelling', () => {
33
+ // carve-js renamed this AST type from `critic-comment` to `critic_comment`
34
+ // (carve#401). This asserts through the source text rather than the type
35
+ // name, so it holds both against the currently pinned engine and after the
36
+ // pin is raised - which is what makes the rename safe to land out of order.
37
+ assert.deepEqual(semanticTokens('a {# note #} b\n').map((token) => [token.character, token.length, token.type]), [[2, 10, 'comment']]);
38
+ });
24
39
  test('reports carve-breakage migration warnings', () => {
25
40
  const result = analyzeCarve('**bold**');
26
41
  assert.equal(result.diagnostics.length, 1);
@@ -215,4 +230,94 @@ test('returns semantic tokens for YAML frontmatter', () => {
215
230
  [2, 0, 3, 'string'],
216
231
  ]);
217
232
  });
233
+ // ---------------------------------------------------------------------------
234
+ // Include resolution (PART 9 §19)
235
+ // ---------------------------------------------------------------------------
236
+ test('includes are OFF by default: the directive stays literal and nothing is read', () => {
237
+ // §19 requires opt-in. `analyzeCarve` with no include options must not
238
+ // construct a capability, so a resolver is never consulted and no include
239
+ // diagnostic appears - the directive is just text.
240
+ const result = analyzeCarve('See {{ secret.crv }} here.\n');
241
+ assert.deepEqual(result.diagnostics.filter((d) => String(d.code ?? '').startsWith('include-')), []);
242
+ assert.deepEqual(result.dependencies, []);
243
+ });
244
+ test('include options carrying no resolver are still inert', () => {
245
+ const result = analyzeCarve('{{ child.crv }}\n', { includes: { sourcePath: '/doc.crv' } });
246
+ assert.deepEqual(result.dependencies, []);
247
+ assert.deepEqual(result.diagnostics.filter((d) => String(d.code ?? '').startsWith('include-')), []);
248
+ });
249
+ test('an enabled include publishes its warning as a diagnostic on the directive range', () => {
250
+ const source = 'intro\n\n{{ absent.crv }}\n';
251
+ const result = analyzeCarve(source, {
252
+ includes: {
253
+ resolver: (includePath) => ({ ok: false, id: includePath, denial: 'not-found' }),
254
+ sourcePath: '/doc.crv',
255
+ },
256
+ });
257
+ const diagnostic = result.diagnostics.find((d) => d.code === 'include-unresolved');
258
+ assert.ok(diagnostic, 'expected an include-unresolved diagnostic');
259
+ assert.deepEqual(diagnostic.range, {
260
+ start: { line: 2, character: 0 },
261
+ end: { line: 2, character: 16 },
262
+ });
263
+ assert.equal(diagnostic.source, 'carve');
264
+ assert.deepEqual(result.dependencies, [{ id: 'absent.crv', resolved: false }]);
265
+ });
266
+ test('a denial class never reaches the published diagnostic message', () => {
267
+ // §19 I7: the message names the failure class in the processor's own words.
268
+ // The internal denial (`outside-root`) is diagnostic detail, not author text,
269
+ // because a distinguishable denial is a probe for host layout.
270
+ const result = analyzeCarve('{{ ../secret.crv }}\n', {
271
+ includes: {
272
+ resolver: (includePath) => ({ ok: false, id: includePath, denial: 'outside-root' }),
273
+ sourcePath: '/doc.crv',
274
+ },
275
+ });
276
+ const diagnostic = result.diagnostics.find((d) => d.code === 'include-unresolved');
277
+ assert.ok(diagnostic);
278
+ assert.ok(!diagnostic.message.includes('outside-root'));
279
+ });
280
+ test('a nested warning names the child relative to the include root, not absolutely', () => {
281
+ const files = {
282
+ '/ws/a.crv': 'child\n\n{{ absent.crv }}',
283
+ };
284
+ const result = analyzeCarve('{{ a.crv }}\n', {
285
+ includes: {
286
+ resolver: (includePath) => {
287
+ const id = includePath.startsWith('/') ? includePath : `/ws/${includePath}`;
288
+ const source = files[id];
289
+ if (source === undefined)
290
+ return { ok: false, id, denial: 'not-found' };
291
+ return { ok: true, id, source, bytes: Buffer.byteLength(source, 'utf8') };
292
+ },
293
+ sourcePath: '/ws/doc.crv',
294
+ includeRoot: '/ws',
295
+ },
296
+ });
297
+ const diagnostic = result.diagnostics.find((d) => d.code === 'include-unresolved');
298
+ assert.ok(diagnostic);
299
+ assert.ok(diagnostic.message.includes('(in a.crv)'), diagnostic.message);
300
+ assert.ok(!diagnostic.message.includes('/ws/a.crv'));
301
+ });
302
+ test('locates parser errors that carry a line and column', () => {
303
+ assert.deepEqual(parseErrorRange('one\ntwo\n', 'Parse error at 2:3'), {
304
+ start: { line: 1, character: 2 },
305
+ end: { line: 1, character: 3 },
306
+ });
307
+ });
308
+ test('keeps a usable heading outline when resolved analysis is unavailable', () => {
309
+ assert.deepEqual(tolerantHeadingSymbols('# One\n\n## Two\n').map((symbol) => symbol.name), ['One', 'Two']);
310
+ });
311
+ test('duplicate diagnostics point back to the first declaration', () => {
312
+ const uri = 'file:///document.crv';
313
+ const heading = analyzeCarve('{#same}\n# One\n\n{#same}\n# Two\n', { uri });
314
+ const headingDuplicate = heading.diagnostics.find((item) => item.code === 'duplicate-heading-id');
315
+ assert.deepEqual(headingDuplicate?.relatedInformation, [{
316
+ location: { uri, range: { start: { line: 0, character: 2 }, end: { line: 0, character: 6 } } },
317
+ message: 'First declaration is here.',
318
+ }]);
319
+ const footnote = analyzeCarve('[^same]: one\n\n[^same]: two\n', { uri });
320
+ assert.equal(footnote.diagnostics.find((item) => item.code === 'duplicate-footnote-definition')
321
+ ?.relatedInformation?.[0]?.location.range.start.line, 0);
322
+ });
218
323
  //# sourceMappingURL=analyze.test.js.map