@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.
- package/CHANGES.md +883 -0
- package/LICENSE-MIT.txt +20 -0
- package/README.md +250 -0
- package/dist/index.d.ts +421 -0
- package/dist-cjser/index.cjs +1224 -0
- package/package.json +144 -0
- package/src/commentHandler.js +48 -0
- package/src/commentParserToESTree.js +525 -0
- package/src/estreeToString.js +234 -0
- package/src/index.js +59 -0
- package/src/inlineTagEscapes.js +73 -0
- package/src/jsdoccomment.js +705 -0
- package/src/parseComment.js +190 -0
- package/src/parseInlineTags.js +114 -0
- package/src/toCamelCase.js +13 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/* eslint-disable prefer-named-capture-group -- Temporary */
|
|
2
|
+
import {
|
|
3
|
+
parse as commentParser,
|
|
4
|
+
tokenizers
|
|
5
|
+
} from 'comment-parser';
|
|
6
|
+
|
|
7
|
+
import {parseInlineTags} from './parseInlineTags.js';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
name: nameTokenizer,
|
|
11
|
+
tag: tagTokenizer,
|
|
12
|
+
type: typeTokenizer,
|
|
13
|
+
description: descriptionTokenizer
|
|
14
|
+
} = tokenizers;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('comment-parser').Spec} spec
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export const hasSeeWithLink = (spec) => {
|
|
21
|
+
return spec.tag === 'see' && (/\{@link.+?\}/v).test(spec.source[0].source);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const defaultNoTypes = [
|
|
25
|
+
'default', 'defaultvalue', 'description', 'example',
|
|
26
|
+
'file', 'fileoverview', 'license',
|
|
27
|
+
'overview', 'see', 'summary'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const defaultNoNames = [
|
|
31
|
+
'access', 'author',
|
|
32
|
+
'default', 'defaultvalue',
|
|
33
|
+
'description',
|
|
34
|
+
'example', 'exception', 'file', 'fileoverview',
|
|
35
|
+
'kind',
|
|
36
|
+
'license', 'overview',
|
|
37
|
+
'return', 'returns',
|
|
38
|
+
'since', 'summary',
|
|
39
|
+
'throws',
|
|
40
|
+
'version', 'variation'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const optionalBrackets = /^\[(?<name>[^=]*)=[^\]]*\]/v;
|
|
44
|
+
const preserveTypeTokenizer = typeTokenizer('preserve');
|
|
45
|
+
const preserveDescriptionTokenizer = descriptionTokenizer('preserve');
|
|
46
|
+
const plainNameTokenizer = nameTokenizer();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Can't import `comment-parser/es6/parser/tokenizers/index.js`,
|
|
50
|
+
* so we redefine here.
|
|
51
|
+
* @typedef {(spec: import('comment-parser').Spec) =>
|
|
52
|
+
* import('comment-parser').Spec} CommentParserTokenizer
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {object} [cfg]
|
|
57
|
+
* @param {string[]} [cfg.noTypes]
|
|
58
|
+
* @param {string[]} [cfg.noNames]
|
|
59
|
+
* @returns {CommentParserTokenizer[]}
|
|
60
|
+
*/
|
|
61
|
+
const getTokenizers = ({
|
|
62
|
+
noTypes = defaultNoTypes,
|
|
63
|
+
noNames = defaultNoNames
|
|
64
|
+
} = {}) => {
|
|
65
|
+
// trim
|
|
66
|
+
return [
|
|
67
|
+
// Tag
|
|
68
|
+
tagTokenizer(),
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Type tokenizer.
|
|
72
|
+
* @param {import('comment-parser').Spec} spec
|
|
73
|
+
* @returns {import('comment-parser').Spec}
|
|
74
|
+
*/
|
|
75
|
+
(spec) => {
|
|
76
|
+
if (noTypes.includes(spec.tag)) {
|
|
77
|
+
return spec;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return preserveTypeTokenizer(spec);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Name tokenizer.
|
|
85
|
+
* @param {import('comment-parser').Spec} spec
|
|
86
|
+
* @returns {import('comment-parser').Spec}
|
|
87
|
+
*/
|
|
88
|
+
(spec) => {
|
|
89
|
+
if (spec.tag === 'template') {
|
|
90
|
+
// const preWS = spec.postTag;
|
|
91
|
+
const remainder = spec.source[0].tokens.description;
|
|
92
|
+
|
|
93
|
+
let pos;
|
|
94
|
+
if (remainder.startsWith('[') && remainder.includes(']')) {
|
|
95
|
+
const endingBracketPos = remainder.lastIndexOf(']');
|
|
96
|
+
pos = remainder.slice(endingBracketPos).search(/(?<![\s,])\s/v);
|
|
97
|
+
if (pos > -1) { // Add offset to starting point if space found
|
|
98
|
+
pos += endingBracketPos;
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
pos = remainder.search(/(?<![\s,])\s/v);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const name = pos === -1 ? remainder : remainder.slice(0, pos);
|
|
105
|
+
const extra = remainder.slice(pos);
|
|
106
|
+
let postName = '', description = '', lineEnd = '';
|
|
107
|
+
if (pos > -1) {
|
|
108
|
+
[, postName, description, lineEnd] = /** @type {RegExpMatchArray} */ (
|
|
109
|
+
extra.match(/(\s*)([^\r]*)(\r)?/v)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
spec.optional = optionalBrackets.test(name);
|
|
114
|
+
// name = /** @type {string} */ (
|
|
115
|
+
// /** @type {RegExpMatchArray} */ (
|
|
116
|
+
// name.match(optionalBrackets)
|
|
117
|
+
// )?.groups?.name
|
|
118
|
+
// );
|
|
119
|
+
|
|
120
|
+
spec.name = name;
|
|
121
|
+
const {tokens} = spec.source[0];
|
|
122
|
+
tokens.name = name;
|
|
123
|
+
tokens.postName = postName;
|
|
124
|
+
tokens.description = description;
|
|
125
|
+
tokens.lineEnd = lineEnd || '';
|
|
126
|
+
|
|
127
|
+
return spec;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (noNames.includes(spec.tag) || hasSeeWithLink(spec)) {
|
|
131
|
+
return spec;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return plainNameTokenizer(spec);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Description tokenizer.
|
|
139
|
+
* @param {import('comment-parser').Spec} spec
|
|
140
|
+
* @returns {import('comment-parser').Spec}
|
|
141
|
+
*/
|
|
142
|
+
(spec) => {
|
|
143
|
+
return preserveDescriptionTokenizer(spec);
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Accepts a comment token or complete comment string and converts it into
|
|
150
|
+
* `comment-parser` AST.
|
|
151
|
+
* @param {string | {value: string}} commentOrNode
|
|
152
|
+
* @param {string} [indent] Whitespace
|
|
153
|
+
* @returns {import('./index.js').JsdocBlockWithInline}
|
|
154
|
+
*/
|
|
155
|
+
const parseComment = (commentOrNode, indent = '') => {
|
|
156
|
+
let result;
|
|
157
|
+
|
|
158
|
+
switch (typeof commentOrNode) {
|
|
159
|
+
case 'string':
|
|
160
|
+
// Preserve JSDoc block start/end indentation.
|
|
161
|
+
result = commentParser(`${indent}${commentOrNode}`, {
|
|
162
|
+
// @see https://github.com/yavorskiy/comment-parser/issues/21
|
|
163
|
+
tokenizers: getTokenizers()
|
|
164
|
+
});
|
|
165
|
+
break;
|
|
166
|
+
case 'object':
|
|
167
|
+
if (commentOrNode === null) {
|
|
168
|
+
throw new TypeError(`'commentOrNode' is not a string or object.`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Preserve JSDoc block start/end indentation.
|
|
172
|
+
result = commentParser(`${indent}/*${commentOrNode.value}*/`, {
|
|
173
|
+
// @see https://github.com/yavorskiy/comment-parser/issues/21
|
|
174
|
+
tokenizers: getTokenizers()
|
|
175
|
+
});
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
default:
|
|
179
|
+
throw new TypeError(`'commentOrNode' is not a string or object.`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!result.length) {
|
|
183
|
+
throw new Error('There were no results for comment parsing');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const [block] = result;
|
|
187
|
+
return parseInlineTags(block);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export {getTokenizers, parseComment};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {decodeInlineTagText} from './inlineTagEscapes.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {RegExpMatchArray & {
|
|
5
|
+
* indices: {
|
|
6
|
+
* groups: {
|
|
7
|
+
* [key: string]: [number, number]
|
|
8
|
+
* }
|
|
9
|
+
* }
|
|
10
|
+
* groups: {[key: string]: string}
|
|
11
|
+
* }} match An inline tag regexp match.
|
|
12
|
+
* @returns {'pipe' | 'plain' | 'prefix' | 'space'}
|
|
13
|
+
*/
|
|
14
|
+
function determineFormat (match) {
|
|
15
|
+
const {separator, text} = match.groups;
|
|
16
|
+
const [, textEnd] = match.indices.groups.text;
|
|
17
|
+
const [tagStart] = match.indices.groups.tag;
|
|
18
|
+
if (!text) {
|
|
19
|
+
return 'plain';
|
|
20
|
+
}
|
|
21
|
+
if (separator === '|') {
|
|
22
|
+
return 'pipe';
|
|
23
|
+
}
|
|
24
|
+
if (textEnd < tagStart) {
|
|
25
|
+
return 'prefix';
|
|
26
|
+
}
|
|
27
|
+
return 'space';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts inline tags from a description.
|
|
32
|
+
* @param {string} description
|
|
33
|
+
* @returns {import('./index.js').InlineTag[]} Array of inline tags from
|
|
34
|
+
* the description.
|
|
35
|
+
*/
|
|
36
|
+
function parseDescription (description) {
|
|
37
|
+
/** @type {import('./index.js').InlineTag[]} */
|
|
38
|
+
const result = [];
|
|
39
|
+
|
|
40
|
+
// This could have been expressed in a single pattern,
|
|
41
|
+
// but having two avoids a potentially exponential time regex.
|
|
42
|
+
|
|
43
|
+
const prefixedTextPattern = /(?:\[(?<text>(?:[^\\\]]|\\[\s\S])+)\])\{@(?<tag>[^\}\s]+)\s?(?<namepathOrURL>[^\}\s\|]*)\}/gvd;
|
|
44
|
+
// The pattern used to match for text after tag uses a negative lookbehind
|
|
45
|
+
// on the ']' char to avoid matching the prefixed case too.
|
|
46
|
+
const suffixedAfterPattern = /(?<!\])\{@(?<tag>[^\}\s]+)\s?(?<namepathOrURL>[^\}\s\|]*)\s*(?<separator>[\s\|])?\s*(?<text>(?:[^\\\}]|\\[\s\S])*)\}/gvd;
|
|
47
|
+
|
|
48
|
+
const matches = [
|
|
49
|
+
...description.matchAll(prefixedTextPattern),
|
|
50
|
+
...description.matchAll(suffixedAfterPattern)
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
for (const mtch of matches) {
|
|
54
|
+
const match = /**
|
|
55
|
+
* @type {RegExpMatchArray & {
|
|
56
|
+
* indices: {
|
|
57
|
+
* groups: {
|
|
58
|
+
* [key: string]: [number, number]
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* groups: {[key: string]: string}
|
|
62
|
+
* }}
|
|
63
|
+
*/ (
|
|
64
|
+
mtch
|
|
65
|
+
);
|
|
66
|
+
const {tag, namepathOrURL, text} = match.groups;
|
|
67
|
+
const [start, end] = match.indices[0];
|
|
68
|
+
const format = determineFormat(match);
|
|
69
|
+
const decodedText = decodeInlineTagText(text, format);
|
|
70
|
+
|
|
71
|
+
result.push({
|
|
72
|
+
tag,
|
|
73
|
+
namepathOrURL,
|
|
74
|
+
text: decodedText,
|
|
75
|
+
format,
|
|
76
|
+
start,
|
|
77
|
+
end
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Splits the `{@ prefix}` from remaining `Spec.lines[].token.description`
|
|
86
|
+
* into the `inlineTags` tokens, and populates `spec.inlineTags`
|
|
87
|
+
* @param {import('comment-parser').Block} block
|
|
88
|
+
* @returns {import('./index.js').JsdocBlockWithInline}
|
|
89
|
+
*/
|
|
90
|
+
export function parseInlineTags (block) {
|
|
91
|
+
const inlineTags =
|
|
92
|
+
/**
|
|
93
|
+
* @type {(import('./commentParserToESTree.js').JsdocInlineTagNoType & {
|
|
94
|
+
* line?: import('./commentParserToESTree.js').Integer
|
|
95
|
+
* })[]}
|
|
96
|
+
*/ (
|
|
97
|
+
parseDescription(block.description)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
/** @type {import('./index.js').JsdocBlockWithInline} */ (
|
|
101
|
+
block
|
|
102
|
+
).inlineTags = inlineTags;
|
|
103
|
+
|
|
104
|
+
for (const tag of block.tags) {
|
|
105
|
+
/**
|
|
106
|
+
* @type {import('./index.js').JsdocTagWithInline}
|
|
107
|
+
*/ (tag).inlineTags = parseDescription(tag.description);
|
|
108
|
+
}
|
|
109
|
+
return (
|
|
110
|
+
/**
|
|
111
|
+
* @type {import('./index.js').JsdocBlockWithInline}
|
|
112
|
+
*/ (block)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} str
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
const toCamelCase = (str) => {
|
|
6
|
+
return str.toLowerCase().replaceAll(/^[a-z]/gv, (init) => {
|
|
7
|
+
return init.toUpperCase();
|
|
8
|
+
}).replaceAll(/_(?<wordInit>[a-z])/gv, (_, n1, o, s, {wordInit}) => {
|
|
9
|
+
return wordInit.toUpperCase();
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export {toCamelCase};
|