@asciidoctor/core 3.0.3 → 4.0.0-alpha.1

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 (91) hide show
  1. package/README.md +42 -10
  2. package/build/browser/index.js +24154 -0
  3. package/build/node/index.cjs +24735 -0
  4. package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
  5. package/package.json +53 -100
  6. package/src/abstract_block.js +849 -0
  7. package/src/abstract_node.js +954 -0
  8. package/src/attribute_entry.js +12 -0
  9. package/src/attribute_list.js +380 -0
  10. package/src/block.js +168 -0
  11. package/src/browser/asset.js +22 -0
  12. package/src/browser/reader.js +138 -0
  13. package/src/browser.js +121 -0
  14. package/src/callouts.js +85 -0
  15. package/src/compliance.js +54 -0
  16. package/src/constants.js +665 -0
  17. package/src/convert.js +370 -0
  18. package/src/converter/composite.js +83 -0
  19. package/src/converter/docbook5.js +1031 -0
  20. package/src/converter/html5.js +1899 -0
  21. package/src/converter/manpage.js +935 -0
  22. package/src/converter/template.js +459 -0
  23. package/src/converter.js +478 -0
  24. package/src/data/stylesheet-data.js +2 -0
  25. package/src/document.js +2134 -0
  26. package/src/extensions.js +1952 -0
  27. package/src/footnote.js +28 -0
  28. package/src/helpers.js +355 -0
  29. package/src/index.js +138 -0
  30. package/src/inline.js +158 -0
  31. package/src/list.js +240 -0
  32. package/src/load.js +276 -0
  33. package/src/logging.js +526 -0
  34. package/src/parser.js +3661 -0
  35. package/src/path_resolver.js +472 -0
  36. package/src/reader.js +1755 -0
  37. package/src/rx.js +829 -0
  38. package/src/section.js +354 -0
  39. package/src/stylesheets.js +30 -0
  40. package/src/substitutors.js +2241 -0
  41. package/src/syntaxHighlighter/highlightjs.js +90 -0
  42. package/src/syntaxHighlighter/html_pipeline.js +33 -0
  43. package/src/syntax_highlighter.js +304 -0
  44. package/src/table.js +952 -0
  45. package/src/timings.js +78 -0
  46. package/types/abstract_block.d.ts +343 -0
  47. package/types/abstract_node.d.ts +471 -0
  48. package/types/attribute_entry.d.ts +7 -0
  49. package/types/attribute_list.d.ts +52 -0
  50. package/types/block.d.ts +55 -0
  51. package/types/browser/asset.d.ts +7 -0
  52. package/types/browser/reader.d.ts +29 -0
  53. package/types/callouts.d.ts +36 -0
  54. package/types/compliance.d.ts +23 -0
  55. package/types/constants.d.ts +268 -0
  56. package/types/convert.d.ts +34 -0
  57. package/types/converter/composite.d.ts +20 -0
  58. package/types/converter/docbook5.d.ts +41 -0
  59. package/types/converter/html5.d.ts +51 -0
  60. package/types/converter/manpage.d.ts +59 -0
  61. package/types/converter/template.d.ts +83 -0
  62. package/types/converter.d.ts +150 -0
  63. package/types/data/stylesheet-data.d.ts +2 -0
  64. package/types/document.d.ts +495 -0
  65. package/types/extensions.d.ts +876 -0
  66. package/types/footnote.d.ts +18 -0
  67. package/types/helpers.d.ts +146 -0
  68. package/types/index.d.ts +73 -3731
  69. package/types/inline.d.ts +69 -0
  70. package/types/list.d.ts +114 -0
  71. package/types/load.d.ts +39 -0
  72. package/types/logging.d.ts +187 -0
  73. package/types/parser.d.ts +114 -0
  74. package/types/path_resolver.d.ts +103 -0
  75. package/types/reader.d.ts +184 -0
  76. package/types/rx.d.ts +513 -0
  77. package/types/section.d.ts +122 -0
  78. package/types/stylesheets.d.ts +10 -0
  79. package/types/substitutors.d.ts +208 -0
  80. package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
  81. package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
  82. package/types/syntax_highlighter.d.ts +167 -0
  83. package/types/table.d.ts +231 -0
  84. package/types/timings.d.ts +25 -0
  85. package/types/tsconfig.json +9 -0
  86. package/LICENSE +0 -21
  87. package/dist/browser/asciidoctor.js +0 -47654
  88. package/dist/browser/asciidoctor.min.js +0 -1452
  89. package/dist/graalvm/asciidoctor.js +0 -47402
  90. package/dist/node/asciidoctor.cjs +0 -21567
  91. package/dist/node/asciidoctor.js +0 -23037
@@ -0,0 +1,472 @@
1
+ // ESM conversion of path_resolver.rb
2
+ //
3
+ // Ruby-to-JavaScript notes:
4
+ // - Ruby's File::ALT_SEPARATOR / File::SEPARATOR / Dir.pwd → process.cwd() on Node.js.
5
+ // - Ruby's Pathname#relative_path_from → manual relative-path computation.
6
+ // - Ruby's require 'pathname' is not needed; logic is inlined.
7
+ // - The Opal / JRuby conditional root? overloads are omitted (Node.js only).
8
+ // - Logging mixin is applied via applyLogging() after class definition.
9
+
10
+ import { applyLogging } from './logging.js'
11
+ import { UriSniffRx } from './rx.js'
12
+
13
+ const DOT = '.'
14
+ const DOT_DOT = '..'
15
+ const DOT_SLASH = './'
16
+ const SLASH = '/'
17
+ const BACKSLASH = '\\'
18
+ const DOUBLE_SLASH = '//'
19
+ const URI_CLASSLOADER = 'uri:classloader:'
20
+ const WINDOWS_ROOT_RX = /^(?:[a-zA-Z]:)?[\\/]/
21
+
22
+ /** Handles all operations for resolving, cleaning and joining paths. */
23
+ export class PathResolver {
24
+ /**
25
+ * Construct a new PathResolver.
26
+ * @param {string|null} fileSeparator - The file separator (default: '/' or '\\' on Windows).
27
+ * @param {string|null} workingDir - The working directory (default: process.cwd()).
28
+ */
29
+ constructor(fileSeparator = null, workingDir = null) {
30
+ this.fileSeparator = fileSeparator ?? _platformSeparator()
31
+ if (workingDir) {
32
+ this.workingDir = this.root(workingDir)
33
+ ? this.posixify(workingDir)
34
+ : _expandPath(workingDir)
35
+ } else {
36
+ this.workingDir = typeof process !== 'undefined' ? process.cwd() : '/'
37
+ }
38
+ this._partitionPathSys = {}
39
+ this._partitionPathWeb = {}
40
+ }
41
+
42
+ /**
43
+ * Check whether the specified path is an absolute path.
44
+ * @param {string} path
45
+ * @returns {boolean}
46
+ */
47
+ absolutePath(path) {
48
+ return (
49
+ path.startsWith(SLASH) ||
50
+ (this.fileSeparator === BACKSLASH && WINDOWS_ROOT_RX.test(path)) ||
51
+ UriSniffRx.test(path)
52
+ )
53
+ }
54
+
55
+ /**
56
+ * Check if the specified path is an absolute root path.
57
+ * @param {string} path
58
+ * @returns {boolean}
59
+ */
60
+ root(path) {
61
+ return this.absolutePath(path)
62
+ }
63
+
64
+ /**
65
+ * Determine if the path is a UNC (root) path.
66
+ * @param {string} path
67
+ * @returns {boolean}
68
+ */
69
+ unc(path) {
70
+ return path.startsWith(DOUBLE_SLASH)
71
+ }
72
+
73
+ /**
74
+ * Determine if the path is an absolute (root) web path.
75
+ * @param {string} path
76
+ * @returns {boolean}
77
+ */
78
+ webRoot(path) {
79
+ return path.startsWith(SLASH)
80
+ }
81
+
82
+ /**
83
+ * Determine whether path descends from base.
84
+ * @param {string} path
85
+ * @param {string} base
86
+ * @returns {number|false} Offset if path descends from base, false otherwise.
87
+ */
88
+ descendsFrom(path, base) {
89
+ if (base === path) return 0
90
+ if (base === SLASH) return path.startsWith(SLASH) ? 1 : false
91
+ return path.startsWith(base + SLASH) ? base.length + 1 : false
92
+ }
93
+
94
+ /**
95
+ * Calculate the relative path to this absolute path from the specified base directory.
96
+ * @param {string} path
97
+ * @param {string} base
98
+ * @returns {string} Relative path, or the original path if it cannot be made relative.
99
+ */
100
+ relativePath(path, base) {
101
+ if (this.root(path)) {
102
+ const posixBase = this.posixify(base)
103
+ const offset = this.descendsFrom(path, posixBase)
104
+ if (offset !== false) return path.slice(offset)
105
+ try {
106
+ return _computeRelativePath(path, posixBase)
107
+ } catch {
108
+ return path
109
+ }
110
+ }
111
+ return path
112
+ }
113
+
114
+ /**
115
+ * Normalize path by converting backslashes to forward slashes.
116
+ * @param {string} path
117
+ * @returns {string} The posixified path.
118
+ */
119
+ posixify(path) {
120
+ if (!path) return ''
121
+ return this.fileSeparator === BACKSLASH && path.includes(BACKSLASH)
122
+ ? path.replace(/\\/g, SLASH)
123
+ : path
124
+ }
125
+
126
+ /**
127
+ * @param {string} path
128
+ * @returns {string}
129
+ */
130
+ posixfy(path) {
131
+ return this.posixify(path)
132
+ }
133
+
134
+ /**
135
+ * Expand the path by resolving parent references (..) and removing self references (.).
136
+ * @param {string} path
137
+ * @returns {string} The expanded path.
138
+ */
139
+ expandPath(path) {
140
+ const [pathSegments, pathRoot] = this.partitionPath(path)
141
+ if (path.includes(DOT_DOT)) {
142
+ const resolved = []
143
+ for (const seg of pathSegments) {
144
+ seg === DOT_DOT ? resolved.pop() : resolved.push(seg)
145
+ }
146
+ return this.joinPath(resolved, pathRoot)
147
+ }
148
+ return this.joinPath(pathSegments, pathRoot)
149
+ }
150
+
151
+ /**
152
+ * Partition the path into segments and a root prefix.
153
+ * @param {string} path - The path to partition.
154
+ * @param {boolean} [web=false] - Treat as web path.
155
+ * @returns {[string[], string|null]} A 2-item array [segments, root] where root may be null.
156
+ */
157
+ partitionPath(path, web = false) {
158
+ const cache = web ? this._partitionPathWeb : this._partitionPathSys
159
+ if (cache[path]) return cache[path]
160
+
161
+ const posixPath = this.posixify(path)
162
+ let root = null
163
+
164
+ if (web) {
165
+ if (this.webRoot(posixPath)) {
166
+ root = SLASH
167
+ } else if (posixPath.startsWith(DOT_SLASH)) {
168
+ root = DOT_SLASH
169
+ }
170
+ } else if (this.root(posixPath)) {
171
+ if (this.unc(posixPath)) {
172
+ root = DOUBLE_SLASH
173
+ } else if (posixPath.startsWith(SLASH)) {
174
+ root = SLASH
175
+ } else if (posixPath.startsWith(URI_CLASSLOADER)) {
176
+ root = URI_CLASSLOADER
177
+ } else {
178
+ const extracted = this._extractUriPrefix(posixPath)
179
+ root = Array.isArray(extracted)
180
+ ? extracted[1] // URL scheme, e.g. 'http://'
181
+ : posixPath.slice(0, posixPath.indexOf(SLASH) + 1) // Windows drive, e.g. 'C:/'
182
+ }
183
+ } else if (posixPath.startsWith(DOT_SLASH)) {
184
+ root = DOT_SLASH
185
+ }
186
+
187
+ const relative = root ? posixPath.slice(root.length) : posixPath
188
+ let segments = relative.split(SLASH).filter((s) => s !== DOT && s !== '')
189
+ // Re-add non-empty-string DOT segments removal is as above; preserve empty for UNC
190
+ segments = relative.split(SLASH).filter((s) => s !== DOT)
191
+ // Remove any empty segments (trailing slash artifacts) except retain intent
192
+ segments = segments.filter((s) => s !== '')
193
+
194
+ const result = [segments, root]
195
+ cache[path] = result
196
+ return result
197
+ }
198
+
199
+ /**
200
+ * Join segments with posix separator, prepending root if provided.
201
+ * @param {string[]} segments
202
+ * @param {string|null} [root=null]
203
+ * @returns {string} The joined path.
204
+ */
205
+ joinPath(segments, root = null) {
206
+ return root ? `${root}${segments.join(SLASH)}` : segments.join(SLASH)
207
+ }
208
+
209
+ /**
210
+ * Securely resolve a system path.
211
+ * @param {string} target - The target path.
212
+ * @param {string|null} [start=null] - The start path.
213
+ * @param {string|null} [jail=null] - The jail path.
214
+ * @param {Object} [opts={}] - Options.
215
+ * @param {boolean} [opts.recover=true] - Recover from jail escapes instead of throwing.
216
+ * @param {string} [opts.targetName='path'] - Name used in error messages.
217
+ * @returns {string} An absolute posix path.
218
+ */
219
+ systemPath(target, start = null, jail = null, opts = {}) {
220
+ const recover = opts.recover !== false
221
+ const targetName = opts.targetName ?? opts.target_name ?? 'path'
222
+
223
+ if (jail) {
224
+ if (!this.root(jail))
225
+ throw new Error(`Jail is not an absolute path: ${jail}`)
226
+ jail = this.posixify(jail)
227
+ }
228
+
229
+ let targetSegments
230
+ if (target) {
231
+ if (this.root(target)) {
232
+ const targetPath = this.expandPath(target)
233
+ if (jail && this.descendsFrom(targetPath, jail) === false) {
234
+ if (!recover)
235
+ throw new SecurityError(
236
+ `${targetName} ${target} is outside of jail: ${jail} (disallowed in safe mode)`
237
+ )
238
+ this.logger.warn(
239
+ `${targetName} is outside of jail; recovering automatically`
240
+ )
241
+ const [ts] = this.partitionPath(targetPath)
242
+ const [js, jr] = this.partitionPath(jail)
243
+ return this.joinPath(js.concat(ts), jr)
244
+ }
245
+ return targetPath
246
+ }
247
+ ;[targetSegments] = this.partitionPath(target)
248
+ } else {
249
+ targetSegments = []
250
+ }
251
+
252
+ let startSegments, jailRoot, recheck
253
+
254
+ if (targetSegments.length === 0) {
255
+ if (!start) {
256
+ return jail ?? this.workingDir
257
+ } else if (this.root(start)) {
258
+ if (!jail) return this.expandPath(start)
259
+ start = this.posixify(start)
260
+ } else {
261
+ ;[targetSegments] = this.partitionPath(start)
262
+ start = jail ?? this.workingDir
263
+ }
264
+ } else if (!start) {
265
+ start = jail ?? this.workingDir
266
+ } else if (this.root(start)) {
267
+ if (jail) start = this.posixify(start)
268
+ } else {
269
+ start = `${(jail ?? this.workingDir).replace(/\/$/, '')}/${start}`
270
+ }
271
+
272
+ // Check if start is within jail
273
+ if (
274
+ jail &&
275
+ (recheck = this.descendsFrom(start, jail) === false) &&
276
+ this.fileSeparator === BACKSLASH
277
+ ) {
278
+ const [ss, sr] = this.partitionPath(start)
279
+ const [js, jr] = this.partitionPath(jail)
280
+ if (sr !== jr) {
281
+ if (!recover)
282
+ throw new SecurityError(
283
+ `start path for ${targetName} ${start} refers to location outside jail root: ${jail} (disallowed in safe mode)`
284
+ )
285
+ this.logger.warn(
286
+ `start path for ${targetName} is outside of jail root; recovering automatically`
287
+ )
288
+ startSegments = js
289
+ jailRoot = jr
290
+ recheck = false
291
+ } else {
292
+ ;[startSegments, jailRoot] = [ss, sr]
293
+ }
294
+ } else {
295
+ ;[startSegments, jailRoot] = this.partitionPath(start)
296
+ }
297
+
298
+ let resolvedSegments = startSegments.concat(targetSegments)
299
+
300
+ if (resolvedSegments.includes(DOT_DOT)) {
301
+ const unresolved = resolvedSegments
302
+ resolvedSegments = []
303
+
304
+ if (jail) {
305
+ let jailSegments
306
+ ;[jailSegments] = this.partitionPath(jail)
307
+ let warned = false
308
+ for (const seg of unresolved) {
309
+ if (seg === DOT_DOT) {
310
+ if (resolvedSegments.length > jailSegments.length) {
311
+ resolvedSegments.pop()
312
+ } else if (recover) {
313
+ if (!warned) {
314
+ this.logger.warn(
315
+ `${targetName} has illegal reference to ancestor of jail; recovering automatically`
316
+ )
317
+ warned = true
318
+ }
319
+ } else {
320
+ throw new SecurityError(
321
+ `${targetName} ${target} refers to location outside jail: ${jail} (disallowed in safe mode)`
322
+ )
323
+ }
324
+ } else {
325
+ resolvedSegments.push(seg)
326
+ }
327
+ }
328
+ } else {
329
+ for (const seg of unresolved) {
330
+ seg === DOT_DOT ? resolvedSegments.pop() : resolvedSegments.push(seg)
331
+ }
332
+ }
333
+ }
334
+
335
+ if (recheck) {
336
+ const targetPath = this.joinPath(resolvedSegments, jailRoot)
337
+ if (this.descendsFrom(targetPath, jail) !== false) {
338
+ return targetPath
339
+ } else if (recover) {
340
+ this.logger.warn(
341
+ `${targetName} is outside of jail; recovering automatically`
342
+ )
343
+ const [jailSegments] = this.partitionPath(jail)
344
+ return this.joinPath(jailSegments.concat(targetSegments), jailRoot)
345
+ } else {
346
+ throw new SecurityError(
347
+ `${targetName} ${target} is outside of jail: ${jail} (disallowed in safe mode)`
348
+ )
349
+ }
350
+ }
351
+
352
+ return this.joinPath(resolvedSegments, jailRoot)
353
+ }
354
+
355
+ /**
356
+ * Resolve a web path from the target and start paths.
357
+ * @param {string} target - The target path.
358
+ * @param {string|null} [start=null] - The start (parent) path.
359
+ * @returns {string} Path with parent references resolved and self references removed.
360
+ */
361
+ webPath(target, start = null) {
362
+ target = this.posixify(target)
363
+ start = this.posixify(start)
364
+
365
+ let uriPrefix = null
366
+ if (start && !this.webRoot(target)) {
367
+ const combined = `${start}${start.endsWith(SLASH) ? '' : SLASH}${target}`
368
+ const extracted = this._extractUriPrefix(combined)
369
+ if (Array.isArray(extracted)) {
370
+ ;[target, uriPrefix] = extracted
371
+ } else {
372
+ target = extracted
373
+ }
374
+ }
375
+
376
+ const [targetSegments, targetRoot] = this.partitionPath(target, true)
377
+ const resolved = []
378
+ for (const seg of targetSegments) {
379
+ if (seg === DOT_DOT) {
380
+ if (resolved.length === 0) {
381
+ if (!targetRoot || targetRoot === DOT_SLASH) resolved.push(seg)
382
+ } else if (resolved[resolved.length - 1] === DOT_DOT) {
383
+ resolved.push(seg)
384
+ } else {
385
+ resolved.pop()
386
+ }
387
+ } else {
388
+ resolved.push(seg)
389
+ }
390
+ }
391
+
392
+ let resolvedPath = this.joinPath(resolved, targetRoot)
393
+ if (resolvedPath.includes(' '))
394
+ resolvedPath = resolvedPath.replace(/ /g, '%20')
395
+
396
+ return uriPrefix ? `${uriPrefix}${resolvedPath}` : resolvedPath
397
+ }
398
+
399
+ /**
400
+ * Extract the URI prefix from a string if it is a URI.
401
+ * @param {string} str
402
+ * @returns {[string, string]|string} [string_without_prefix, prefix] if URI, or the original string.
403
+ * @internal
404
+ */
405
+ _extractUriPrefix(str) {
406
+ if (str.includes(':')) {
407
+ const m = str.match(UriSniffRx)
408
+ if (m) return [str.slice(m[0].length), m[0]]
409
+ }
410
+ return str
411
+ }
412
+ }
413
+
414
+ applyLogging(PathResolver.prototype)
415
+
416
+ // ── Helpers ───────────────────────────────────────────────────────────────────
417
+
418
+ /**
419
+ * @returns {string}
420
+ * @internal
421
+ */
422
+ function _platformSeparator() {
423
+ if (typeof process !== 'undefined' && process.platform === 'win32')
424
+ return '\\'
425
+ return '/'
426
+ }
427
+
428
+ /**
429
+ * @param {string} p
430
+ * @returns {string}
431
+ * @internal
432
+ */
433
+ function _expandPath(p) {
434
+ if (typeof process !== 'undefined') {
435
+ // Lazy import to avoid top-level await
436
+ try {
437
+ const path = require('node:path')
438
+ return path.resolve(p).replace(/\\/g, '/')
439
+ } catch {}
440
+ }
441
+ return p
442
+ }
443
+
444
+ /**
445
+ * @param {string} target
446
+ * @param {string} base
447
+ * @returns {string}
448
+ * @internal
449
+ */
450
+ function _computeRelativePath(target, base) {
451
+ const targetParts = target.split('/').filter(Boolean)
452
+ const baseParts = base.split('/').filter(Boolean)
453
+ let common = 0
454
+ while (
455
+ common < targetParts.length &&
456
+ common < baseParts.length &&
457
+ targetParts[common] === baseParts[common]
458
+ ) {
459
+ common++
460
+ }
461
+ const up = baseParts.length - common
462
+ const down = targetParts.slice(common)
463
+ return [...Array(up).fill('..'), ...down].join('/') || '.'
464
+ }
465
+
466
+ // Simple SecurityError class (Ruby raises SecurityError).
467
+ class SecurityError extends Error {
468
+ constructor(msg) {
469
+ super(msg)
470
+ this.name = 'SecurityError'
471
+ }
472
+ }