Skip to content

Commit a92efb0

Browse files
committed
feat(util): fromDocs
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent 54540a4 commit a92efb0

24 files changed

+2523
-72
lines changed

.commitlintrc.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ const config: UserConfig = {
2121
'scope-enum': [Severity.Error, 'always', scopes([
2222
'chore',
2323
'lexer',
24+
'markdown',
2425
'parser',
25-
'reader'
26+
'util'
2627
])]
2728
}
2829
}

.dictionary.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
attw
22
cefc
3+
codecov
34
commitlintrc
45
dedupe
56
dessant
@@ -14,6 +15,7 @@ iife
1415
jchen
1516
jsdoc
1617
kaisugi
18+
lcov
1719
lintstagedrc
1820
mdast
1921
mkbuild

.github/infrastructure.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ labels:
7575
- name: scope:install
7676
description: package install
7777
color: 74cefc
78+
- name: scope:markdown
79+
description: markdown integration
80+
color: 74cefc
81+
- name: scope:mdast
82+
description: mdast
83+
color: 74cefc
84+
- name: scope:micromark
85+
description: micromark
86+
color: 74cefc
7887
- name: scope:parser
7988
description: file parser
8089
color: 74cefc
@@ -172,7 +181,7 @@ repository:
172181
automated_security_fixes: true
173182
default_branch: main
174183
delete_branch_on_merge: true
175-
description: docast utility for parsing docblocks
184+
description: docast utility to parse docblocks
176185
has_issues: true
177186
has_projects: true
178187
has_wiki: false

__fixtures__/dbl-linear.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
*
1010
* 1. The number `u(0) = 1` is the first one in `u`
1111
* 2. For each `x` in `u`, `y = 2x + 1` and `z = 3x + 1` must be in `u` too
12+
* - some
13+
* - additional
14+
* - info
1215
* 3. There are no other numbers in `u`
16+
* - some
17+
* - more
18+
* - additional
19+
* - info
1320
*
1421
* Given an index, `n`, the function returns the element at `u(n)`.
1522
*

__fixtures__/detect-syntax.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ import { hasCJSSyntax, hasESMSyntax } from '@flex-development/mlly'
1919
* console.debug(detectSyntax('export default {}'))
2020
*
2121
* @param {string} code - Code to evaluate
22-
* @return {{ cjs: boolean; esm: boolean; mixed: boolean }} Detection result
22+
* @return {{
23+
* cjs: boolean;
24+
* esm: boolean;
25+
* mixed: boolean
26+
* }} Detection result
2327
*/
2428
const detectSyntax = (
2529
code: string

__fixtures__/reader.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/**
2+
* @file Fixtures - Reader
3+
* @module fixtures/Reader
4+
*/
5+
6+
import type { Point } from '@flex-development/docast'
7+
import { at, clamp, fallback, isInteger } from '@flex-development/tutils'
8+
import { ok } from 'devlop'
9+
import type { Code } from 'micromark-util-types'
10+
import type { VFile } from 'vfile'
11+
12+
/**
13+
* Source document reader.
14+
*
15+
* @class
16+
*/
17+
class Reader {
18+
/**
19+
* Source document.
20+
*
21+
* @public
22+
* @readonly
23+
* @instance
24+
* @member {string} document
25+
*/
26+
public readonly document: string
27+
28+
/**
29+
* List, where each index is a line number (`0`-based), and each value is the
30+
* number of columns in the line.
31+
*
32+
* @protected
33+
* @readonly
34+
* @instance
35+
* @member {number[]} indices
36+
*/
37+
protected readonly indices: number[]
38+
39+
/**
40+
* Current position in {@linkcode document}.
41+
*
42+
* @protected
43+
* @instance
44+
* @member {number} position
45+
*/
46+
protected position: number
47+
48+
/**
49+
* Create a new source document reader.
50+
*
51+
* @see {@linkcode VFile}
52+
*
53+
* @param {VFile | string} source - Source document or file
54+
*/
55+
constructor(source: VFile | string) {
56+
this.document = source = String(source)
57+
this.indices = [...source.matchAll(/^.*/gm)].map(([m]) => m.length + 1)
58+
this.position = 0
59+
}
60+
61+
/**
62+
* Check if the reader is at the end of the document.
63+
*
64+
* @example
65+
* ```ts
66+
* while (!reader.eof) {
67+
* // do something
68+
* }
69+
* ```
70+
*
71+
* @public
72+
* @instance
73+
*
74+
* @return {boolean} `true` if at end of document
75+
*/
76+
public get eof(): boolean {
77+
return this.index >= this.document.length
78+
}
79+
80+
/**
81+
* Get the current position in the document.
82+
*
83+
* @public
84+
* @instance
85+
*
86+
* @return {number} Current position in document
87+
*/
88+
public get index(): number {
89+
return this.position
90+
}
91+
92+
/**
93+
* Get the next `k`-th character code from the document without changing the
94+
* position of the reader, with `null` denoting the end of the document.
95+
*
96+
* @see {@linkcode Code}
97+
*
98+
* @public
99+
* @instance
100+
*
101+
* @param {number} [k=1] - Difference between index of next `k`-th character
102+
* code and current position in document
103+
* @return {Code} Peeked character code or `null`
104+
*/
105+
public peek(k: number = 1): Code {
106+
ok(isInteger(k), 'expected k to be an integer')
107+
return fallback(this.document.codePointAt(this.index + k), null)
108+
}
109+
110+
/**
111+
* Get a [*point*][1] in the document by `offset`.
112+
*
113+
* The given `offset` must be an integer in the range `[0, document.length]`.\
114+
* If invalid, `point.column` and `point.line` will have a value of `-1`.
115+
*
116+
* > **Point** represents one place in a source [*file*][2].
117+
* >
118+
* > The `line` field (`1`-indexed integer) represents a line in a source
119+
* > file. The `column` field (`1`-indexed integer) represents a column in a
120+
* > source file. The `offset` field (`0`-indexed integer) represents a
121+
* > character in a source file.
122+
* >
123+
* > The term character means a (UTF-16) code unit which is defined in the
124+
* > [Web IDL][3] specification.
125+
*
126+
* [1]: https://github.com/syntax-tree/unist#point
127+
* [2]: https://github.com/syntax-tree/unist#file
128+
* [3]: https://webidl.spec.whatwg.org/
129+
*
130+
* @see {@linkcode Point}
131+
* @see https://github.com/syntax-tree/unist#point
132+
*
133+
* @public
134+
* @instance
135+
*
136+
* @param {number?} [offset=this.index] - Index of character in document
137+
* @return {Point} Point in document
138+
*/
139+
public point(offset: number = this.index): Point {
140+
/**
141+
* Current offset (`0`-indexed integer).
142+
*
143+
* @var {number} index
144+
*/
145+
let index: number = 0
146+
147+
/**
148+
* Current line (`1`-indexed integer).
149+
*
150+
* @var {number} line
151+
*/
152+
let line: number = -1
153+
154+
/**
155+
* Current {@linkcode Point} in source document.
156+
*
157+
* @const {Point} point
158+
*/
159+
const point: Point = { column: -1, line, offset }
160+
161+
// convert offset to point
162+
if (isInteger(offset) && offset > -1 && offset <= this.document.length) {
163+
while (++line < this.indices.length) {
164+
for (let j = 0; j < at(this.indices, line)!; j++) {
165+
if (index++ === offset) {
166+
point.column = j + 1
167+
point.line = line + 1
168+
break
169+
}
170+
}
171+
}
172+
}
173+
174+
return point
175+
}
176+
177+
/**
178+
* Get the next `k`-th character code from the document, with `null` denoting
179+
* the end of the document.
180+
*
181+
* Unlike {@linkcode peek}, this method changes the position of the reader.
182+
*
183+
* @see {@linkcode Code}
184+
*
185+
* @public
186+
* @instance
187+
*
188+
* @param {number} [k=1] - Difference between index of next `k`-th character
189+
* code and current position in document
190+
* @return {Code} Next `k`-th character or `null`
191+
*/
192+
public read(k: number = 1): Code {
193+
ok(isInteger(k), 'expected k to be an integer')
194+
ok(!this.eof, 'cannot read past end of document')
195+
this.position = clamp(this.position + k, 0, this.document.length)
196+
return fallback(this.document.codePointAt(this.position), null)
197+
}
198+
}
199+
200+
export default Reader

__fixtures__/to-relative-specifier.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @file Fixtures - toRelativeSpecifier
3+
* @module fixtures/toRelativeSpecifier
4+
*/
5+
6+
import type { NodeError } from '@flex-development/errnode'
7+
import pathe from '@flex-development/pathe'
8+
import { DOT } from '@flex-development/tutils'
9+
import { URL, fileURLToPath } from 'node:url'
10+
import validateURLString from './validate-url-string'
11+
12+
/**
13+
* Converts `specifier` into a relative specifier.
14+
*
15+
* :::info
16+
* The relative specifier will only include a file extension if `specifier`
17+
* includes a file extension.
18+
* :::
19+
*
20+
* @see {@linkcode URL}
21+
* @see https://nodejs.org/api/esm.html#terminology
22+
*
23+
* @param {URL | string} specifier - Module specifier to convert
24+
* @param {URL | string} parent - Parent module URL or path to resolve from
25+
* @return {string} `specifier` as relative specifier
26+
* @throws {NodeError<TypeError>} If either `specifier` or `parent` is not a
27+
* string or an instance of {@linkcode URL}
28+
*/
29+
const toRelativeSpecifier = (
30+
specifier: URL | string,
31+
parent: URL | string
32+
): string => {
33+
validateURLString(specifier, 'specifier')
34+
validateURLString(parent, 'parent')
35+
36+
// convert file url objects to file url strings
37+
if (parent instanceof URL) parent = parent.href
38+
if (specifier instanceof URL) specifier = specifier.href
39+
40+
// convert file url strings to paths
41+
if (parent.startsWith('file:')) parent = fileURLToPath(parent)
42+
if (specifier.startsWith('file:')) specifier = fileURLToPath(specifier)
43+
44+
// convert specifier to relative specifier
45+
specifier = pathe
46+
.relative(pathe.resolve(parent), pathe.resolve(specifier))
47+
.replace(/^\.\.\/?/, '')
48+
.replace(/^(\w)/, './$1')
49+
50+
// set specifier to dot character if empty string
51+
// this occurs when specifier is a directory, but is not fully specified
52+
if (!specifier) specifier = DOT
53+
54+
return specifier
55+
}
56+
57+
export default toRelativeSpecifier

__fixtures__/validate-url-string.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @file Fixtures - validateURLString
3+
* @module fixtures/validateURLString
4+
*/
5+
6+
import { ERR_INVALID_ARG_TYPE, type NodeError } from '@flex-development/errnode'
7+
import { isString } from '@flex-development/tutils'
8+
import { URL } from 'node:url'
9+
10+
/**
11+
* Checks if `value` is an instance of {@linkcode URL} or a string.
12+
*
13+
* Throws [`ERR_INVALID_ARG_TYPE`][1] if `value` is of neither type.
14+
*
15+
* [1]: https://nodejs.org/api/errors.html#err_invalid_arg_value
16+
*
17+
* @see {@linkcode ERR_INVALID_ARG_TYPE}
18+
*
19+
* @internal
20+
*
21+
* @param {unknown} value - Value supplied by user
22+
* @param {string} name - Name of invalid argument or property
23+
* @return {value is URL | string} `true` if `value` is `URL` instance or string
24+
* @throws {NodeError<TypeError>} If `value` is not `URL` instance or string
25+
*/
26+
const validateURLString = (
27+
value: unknown,
28+
name: string
29+
): value is URL | string => {
30+
if (value instanceof URL || isString(value)) return true
31+
throw new ERR_INVALID_ARG_TYPE(name, ['URL', 'string'], value)
32+
}
33+
34+
export default validateURLString

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@flex-development/docast-util-from-docs",
3-
"description": "docast utility for parsing docblocks",
3+
"description": "docast utility to parse docblocks",
44
"version": "1.0.0-alpha.1",
55
"keywords": [
66
"comment",
@@ -140,6 +140,8 @@
140140
"is-ci": "3.0.1",
141141
"jsonc-eslint-parser": "2.4.0",
142142
"lint-staged": "15.2.2",
143+
"mdast-util-directive": "3.0.0",
144+
"micromark-extension-directive": "3.0.0",
143145
"micromark-util-types": "2.0.0",
144146
"node-notifier": "10.0.1",
145147
"prettier": "3.2.5",

0 commit comments

Comments
 (0)