@cjser/es-joy__jsdoccomment 0.96.0-cjser.2

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.
@@ -0,0 +1,234 @@
1
+ import {stringify as prattStringify} from '@cjser/jsdoc-type-pratt-parser';
2
+
3
+ import {encodeInlineTagText} from './inlineTagEscapes.js';
4
+
5
+ /* eslint-disable jsdoc/reject-function-type -- Different functions */
6
+ /** @type {Record<string, Function>} */
7
+ const stringifiers = {
8
+ /* eslint-enable jsdoc/reject-function-type -- Different functions */
9
+ JsdocBlock,
10
+
11
+ /**
12
+ * @param {import('./commentParserToESTree.js').JsdocDescriptionLine} node
13
+ * @returns {string}
14
+ */
15
+ JsdocDescriptionLine ({
16
+ initial, delimiter, postDelimiter, description
17
+ }) {
18
+ return `${initial}${delimiter}${postDelimiter}${description}`;
19
+ },
20
+
21
+ /**
22
+ * @param {import('./commentParserToESTree.js').JsdocTypeLine} node
23
+ * @returns {string}
24
+ */
25
+ JsdocTypeLine ({
26
+ initial, delimiter, postDelimiter, rawType
27
+ }) {
28
+ return `${initial}${delimiter}${postDelimiter}${rawType}`;
29
+ },
30
+
31
+ /**
32
+ * @param {import('./commentParserToESTree.js').JsdocInlineTag} node
33
+ */
34
+ JsdocInlineTag ({format, namepathOrURL, tag, text}) {
35
+ const encodedText = encodeInlineTagText(text, format);
36
+ return format === 'pipe'
37
+ ? `{@${tag} ${namepathOrURL}|${encodedText}}`
38
+ : format === 'plain'
39
+ ? `{@${tag} ${namepathOrURL}}`
40
+ : format === 'prefix'
41
+ ? `[${encodedText}]{@${tag} ${namepathOrURL}}`
42
+ // "space"
43
+ : `{@${tag} ${namepathOrURL} ${encodedText}}`;
44
+ },
45
+
46
+ JsdocTag
47
+ };
48
+
49
+ /**
50
+ * @todo convert for use by escodegen (until may be patched to support
51
+ * custom entries?).
52
+ * @param {import('./commentParserToESTree.js').JsdocBlock|
53
+ * import('./commentParserToESTree.js').JsdocDescriptionLine|
54
+ * import('./commentParserToESTree.js').JsdocTypeLine|
55
+ * import('./commentParserToESTree.js').JsdocTag|
56
+ * import('./commentParserToESTree.js').JsdocInlineTag|
57
+ * import('@cjser/jsdoc-type-pratt-parser').RootResult
58
+ * } node
59
+ * @param {import('./index.js').ESTreeToStringOptions} [opts]
60
+ * @throws {Error}
61
+ * @returns {string}
62
+ */
63
+ function estreeToString (node, opts = {}) {
64
+ if (Object.hasOwn(stringifiers, node.type)) {
65
+ return stringifiers[
66
+ /**
67
+ * @type {import('./commentParserToESTree.js').JsdocBlock|
68
+ * import('./commentParserToESTree.js').JsdocDescriptionLine|
69
+ * import('./commentParserToESTree.js').JsdocTypeLine|
70
+ * import('./commentParserToESTree.js').JsdocTag}
71
+ */
72
+ (node).type
73
+ ](
74
+ node,
75
+ opts
76
+ );
77
+ }
78
+
79
+ // We use raw type instead but it is a key as other apps may wish to traverse
80
+ if (node.type.startsWith('JsdocType')) {
81
+ return opts.preferRawType
82
+ ? ''
83
+ : `{${prattStringify(
84
+ /** @type {import('@cjser/jsdoc-type-pratt-parser').RootResult} */ (
85
+ node
86
+ ),
87
+ opts.jtppStringificationRules
88
+ )}}`;
89
+ }
90
+
91
+ throw new Error(`Unhandled node type: ${node.type}`);
92
+ }
93
+
94
+ /**
95
+ * @param {import('./commentParserToESTree.js').JsdocBlock} node
96
+ * @param {import('./index.js').ESTreeToStringOptions} opts
97
+ * @returns {string}
98
+ */
99
+ function JsdocBlock (node, opts) {
100
+ const {delimiter, delimiterLineBreak, descriptionLines,
101
+ initial, postDelimiter, preterminalLineBreak, tags, terminal} = node;
102
+
103
+ const terminalPrepend = preterminalLineBreak !== ''
104
+ ? `${preterminalLineBreak}${initial} `
105
+ : '';
106
+
107
+ let result = `${initial}${delimiter}${postDelimiter}${delimiterLineBreak}`;
108
+
109
+ for (let i = 0; i < descriptionLines.length; i++) {
110
+ result += estreeToString(descriptionLines[i]);
111
+
112
+ if (i !== descriptionLines.length - 1 || tags.length) {
113
+ result += '\n';
114
+ }
115
+ }
116
+
117
+ for (let i = 0; i < tags.length; i++) {
118
+ result += estreeToString(tags[i], opts);
119
+
120
+ if (i !== tags.length - 1) {
121
+ result += '\n';
122
+ }
123
+ }
124
+
125
+ result += `${terminalPrepend}${terminal}`;
126
+
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * @param {import('./commentParserToESTree.js').JsdocTag} node
132
+ * @param {import('./index.js').ESTreeToStringOptions} opts
133
+ * @returns {string}
134
+ */
135
+ function JsdocTag (node, opts) {
136
+ const {
137
+ delimiter, descriptionLines, initial, name, parsedType, postDelimiter,
138
+ postName, postTag, postType, tag, typeLines
139
+ } = node;
140
+
141
+ let result = `${initial}${delimiter}${postDelimiter}@${tag}${postTag}`;
142
+
143
+ // Could do `rawType` but may have been changed; could also do
144
+ // `typeLines` but not as likely to be changed
145
+ // parsedType
146
+ // Comment this out later in favor of `parsedType`
147
+ // We can't use raw `typeLines` as first argument has delimiter on it
148
+ if (!parsedType || opts.preferRawType) {
149
+ if (typeLines.length) {
150
+ result += '{';
151
+
152
+ for (let i = 0; i < typeLines.length; i++) {
153
+ result += estreeToString(typeLines[i]);
154
+
155
+ if (i !== typeLines.length - 1) {
156
+ result += '\n';
157
+ }
158
+ }
159
+
160
+ result += '}';
161
+ }
162
+ /* v8 ignore start */
163
+ } else if (parsedType?.type.startsWith('JsdocType')) {
164
+ result += `{${prattStringify(
165
+ /** @type {import('@cjser/jsdoc-type-pratt-parser').RootResult} */ (
166
+ parsedType
167
+ )
168
+ )}}`;
169
+ /* v8 ignore stop */
170
+ }
171
+
172
+ // Check if the name appears on a separate line from the tag/type
173
+ // This is indicated by postTag and postType both being empty
174
+ let nameLineOffset = 0;
175
+ if (name && postTag === '' && postType === '') {
176
+ // The name is on a separate line from the tag
177
+ result += '\n';
178
+
179
+ if (descriptionLines.length > 0) {
180
+ const firstDescLine = descriptionLines[0];
181
+ /* v8 ignore next 3 */
182
+ if (firstDescLine.description === '' && firstDescLine.delimiter === '' &&
183
+ firstDescLine.postDelimiter === '' && firstDescLine.initial === '') {
184
+ // Empty first line, check if there's a second line with formatting
185
+ if (descriptionLines.length > 1) {
186
+ const nameLine = descriptionLines[1];
187
+ result += `${
188
+ nameLine.initial
189
+ }${nameLine.delimiter}${
190
+ nameLine.postDelimiter
191
+ }${name}${postName}${
192
+ nameLine.description
193
+ }`;
194
+ nameLineOffset = 2;
195
+ } else {
196
+ /* v8 ignore start */
197
+ // Fallback: just add name (shouldn't normally happen)
198
+ result += name + postName;
199
+ nameLineOffset = 1;
200
+ /* v8 ignore stop */
201
+ }
202
+ } else {
203
+ /* v8 ignore start */
204
+ // First description line has content, use it for the name line
205
+ const nameLine = descriptionLines[0];
206
+ result += `${nameLine.initial}${
207
+ nameLine.delimiter
208
+ }${nameLine.postDelimiter}${name}${postName}${nameLine.description}`;
209
+ nameLineOffset = 1;
210
+ /* v8 ignore stop */
211
+ }
212
+ } else {
213
+ // No description lines, need to construct the name line
214
+ // Use the same formatting as the tag line
215
+ result += `${initial}${delimiter}${postDelimiter}${name}${postName}`;
216
+ }
217
+ } else {
218
+ result += name ? `${postType}${name}${postName}` : postType;
219
+ }
220
+
221
+ for (let i = nameLineOffset; i < descriptionLines.length; i++) {
222
+ const descriptionLine = descriptionLines[i];
223
+
224
+ if (i > 0 || nameLineOffset > 0) {
225
+ result += '\n';
226
+ }
227
+
228
+ result += estreeToString(descriptionLine);
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ export {estreeToString};
package/src/index.js ADDED
@@ -0,0 +1,59 @@
1
+ /* eslint-disable unicorn/no-barrel-files -- Reexports with typedefs */
2
+ /**
3
+ * An inline tag whose `text` is the unescaped label value.
4
+ * @typedef {import('./commentParserToESTree.js').JsdocInlineTagNoType & {
5
+ * start: number,
6
+ * end: number,
7
+ * }} InlineTag
8
+ */
9
+
10
+ /**
11
+ * @typedef {import('comment-parser').Spec & {
12
+ * line?: import('./commentParserToESTree.js').Integer,
13
+ * inlineTags: (import('./commentParserToESTree.js').JsdocInlineTagNoType & {
14
+ * line?: import('./commentParserToESTree.js').Integer
15
+ * })[]
16
+ * }} JsdocTagWithInline
17
+ */
18
+
19
+ /**
20
+ * Expands on comment-parser's `Block` interface.
21
+ * @typedef {{
22
+ * description: string,
23
+ * source: import('comment-parser').Line[],
24
+ * problems: import('comment-parser').Problem[],
25
+ * tags: JsdocTagWithInline[],
26
+ * inlineTags: (import('./commentParserToESTree.js').JsdocInlineTagNoType & {
27
+ * line?: import('./commentParserToESTree.js').Integer
28
+ * })[]
29
+ * }} JsdocBlockWithInline
30
+ */
31
+
32
+ /* eslint-disable jsdoc/reject-any-type -- API */
33
+ /**
34
+ * @typedef {{
35
+ * preferRawType?: boolean,
36
+ * jtppStringificationRules?: (
37
+ * node: import('estree').Node, options?: any
38
+ * ) => string
39
+ * }} ESTreeToStringOptions
40
+ */
41
+ /* eslint-enable jsdoc/reject-any-type -- API */
42
+
43
+ /**
44
+ * @callback CommentHandler
45
+ * @param {string} commentSelector
46
+ * @param {import('./index.js').JsdocBlockWithInline} jsdoc
47
+ * @returns {boolean}
48
+ */
49
+
50
+ export {visitorKeys as jsdocTypeVisitorKeys} from '@cjser/jsdoc-type-pratt-parser';
51
+
52
+ export * from '@cjser/jsdoc-type-pratt-parser';
53
+
54
+ export * from './commentHandler.js';
55
+ export * from './commentParserToESTree.js';
56
+ export * from './estreeToString.js';
57
+ export * from './jsdoccomment.js';
58
+ export * from './parseComment.js';
59
+ export * from './parseInlineTags.js';
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @typedef {'pipe' | 'plain' | 'prefix' | 'space'} InlineTagFormat
3
+ */
4
+
5
+ /**
6
+ * Gets the label delimiter for an inline-tag format.
7
+ * @param {InlineTagFormat} format
8
+ * @returns {string}
9
+ */
10
+ function getLabelDelimiter (format) {
11
+ return format === 'prefix' ? ']' : '}';
12
+ }
13
+
14
+ /**
15
+ * Decodes context-specific escape pairs in an inline-tag label.
16
+ * @param {string} text
17
+ * @param {InlineTagFormat} format
18
+ * @returns {string}
19
+ */
20
+ export function decodeInlineTagText (text, format) {
21
+ const delimiter = getLabelDelimiter(format);
22
+ let decoded = '';
23
+ let idx = 0;
24
+
25
+ while (idx < text.length) {
26
+ const character = text[idx];
27
+ if (character === '\\') {
28
+ const nextCharacter = text[idx + 1];
29
+ if (nextCharacter === '\\' || nextCharacter === delimiter) {
30
+ decoded += nextCharacter;
31
+ idx += 2;
32
+ continue;
33
+ }
34
+ }
35
+ decoded += character;
36
+ idx++;
37
+ }
38
+
39
+ return decoded;
40
+ }
41
+
42
+ /**
43
+ * Minimally encodes an inline-tag label for its output context.
44
+ * @param {string} text
45
+ * @param {InlineTagFormat} format
46
+ * @returns {string}
47
+ */
48
+ export function encodeInlineTagText (text, format) {
49
+ const delimiter = getLabelDelimiter(format);
50
+ let encoded = '';
51
+
52
+ for (let idx = 0; idx < text.length; idx++) {
53
+ const character = text[idx];
54
+ if (character === delimiter) {
55
+ encoded += `\\${delimiter}`;
56
+ continue;
57
+ }
58
+
59
+ if (character === '\\') {
60
+ const nextCharacter = text[idx + 1];
61
+ if (
62
+ [undefined, '\\', delimiter].includes(nextCharacter)
63
+ ) {
64
+ encoded += '\\\\';
65
+ continue;
66
+ }
67
+ }
68
+
69
+ encoded += character;
70
+ }
71
+
72
+ return encoded;
73
+ }