@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,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};