@depup/eslint-plugin-jsdoc 63.2.2-depup.1 → 63.3.1-depup.0

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/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/eslint-plugin-jsdoc
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [eslint-plugin-jsdoc](https://www.npmjs.com/package/eslint-plugin-jsdoc) @ 63.2.2 |
17
- | Processed | 2026-07-25 |
16
+ | Original | [eslint-plugin-jsdoc](https://www.npmjs.com/package/eslint-plugin-jsdoc) @ 63.3.1 |
17
+ | Processed | 2026-07-27 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 3 |
20
20
 
package/changes.json CHANGED
@@ -13,6 +13,6 @@
13
13
  "to": "^5.0.0"
14
14
  }
15
15
  },
16
- "timestamp": "2026-07-25T00:41:29.726Z",
16
+ "timestamp": "2026-07-27T09:08:01.484Z",
17
17
  "totalUpdated": 3
18
18
  }
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc.cjs"));
8
8
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
- const markdownLinkRegex = /(?<!\\)\[([^`\]\r\n]+)\]\(([^`\(\)\s]+)\)/gv;
10
- const prefixLinkRegex = /(?<!\\)\[([^\]\r\n]+)\]\{@link\s+([^\}\s\|]+)\}/gv;
11
- const pipeLinkRegex = /\{@link\s+([^\}\s\|]+)\s*\|\s*([^\}\r\n]+)\}/gv;
9
+ const markdownLinkRegex = /(?<!\\)\[((?:[^\\`\]\r\n]|\\[^\r\n])+)\]\(([^`\(\)\s]+)\)/gv;
10
+ const prefixLinkRegex = /(?<!\\)\[((?:[^\\\]\r\n]|\\[^\r\n])+)\]\{@link\s+([^\}\s\|]+)\}/gv;
11
+ const pipeLinkRegex = /\{@link\s+([^\}\s\|]+)\s*\|\s*((?:[^\\\}\r\n]|\\[^\r\n])+)\}/gv;
12
12
  const markdownLinkAttemptRegex = /(?<!\\)\[[^\]\r\n]*\]\([^\r\n]*(?:\)|$)/v;
13
13
  const prefixLinkAttemptRegex = /(?<!\\)\[[^\]\r\n]*\]\{@link[^\}\r\n]*(?:\}|$)/v;
14
14
  const pipeLinkAttemptRegex = /\{@link[^\}\r\n]*\|[^\}\r\n]*(?:\}|$)/v;
@@ -17,18 +17,26 @@ const markdownCodeSpanRegex = /(?<!\\)(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)/gv;
17
17
  const bareHttpUrlRegex = /^https?:\S+$/v;
18
18
 
19
19
  /**
20
- * @param {'pipe'|'prefix'} canonicalForm
21
20
  * @param {string} label
22
21
  * @returns {boolean}
23
22
  */
24
- const cannotSafelyFormatLink = (canonicalForm, label) => {
25
- if (!label.trim()) {
26
- return true;
27
- }
28
- if (canonicalForm === 'pipe') {
29
- return label.includes('}');
30
- }
31
- return label.includes(']');
23
+ const cannotSafelyFormatLink = label => {
24
+ return !label.trim();
25
+ };
26
+
27
+ /**
28
+ * @param {'pipe'|'prefix'} canonicalForm
29
+ * @param {string} label
30
+ * @param {'markdown'|'pipe'|'prefix'} sourceForm
31
+ * @returns {string}
32
+ */
33
+ const escapeLinkLabel = (canonicalForm, label, sourceForm) => {
34
+ const sourceEscapePattern = sourceForm === 'pipe' ? /\\([\\\}])/gv : sourceForm === 'prefix' ? /\\([\\\]])/gv : /\\([\\\}\]])/gv;
35
+ const targetEscapePattern = canonicalForm === 'pipe' ? /\\(?=$|[\\\}])|\}/gv : /\\(?=$|[\\\]])|\]/gv;
36
+ const decodedLabel = label.trim().replaceAll(sourceEscapePattern, '$1');
37
+ return decodedLabel.replaceAll(targetEscapePattern, character => {
38
+ return `\\${character}`;
39
+ });
32
40
  };
33
41
 
34
42
  /**
@@ -36,7 +44,7 @@ const cannotSafelyFormatLink = (canonicalForm, label) => {
36
44
  * @returns {string}
37
45
  */
38
46
  const encodeLinkTarget = target => {
39
- return encodeURI(target).replaceAll(/%25(?=[\dA-F]{2})/giv, '%');
47
+ return target.replaceAll(/%(?![\dA-F]{2})/giv, '%25').replaceAll('|', '%7C').replaceAll('{', '%7B').replaceAll('}', '%7D');
40
48
  };
41
49
 
42
50
  /**
@@ -89,12 +97,14 @@ const isInsideMarkdownCodeSpan = (description, index) => {
89
97
  /**
90
98
  * @param {'pipe'|'prefix'} canonicalForm
91
99
  * @param {string} label
100
+ * @param {'markdown'|'pipe'|'prefix'} sourceForm
92
101
  * @param {string} target
93
102
  * @returns {string}
94
103
  */
95
- const formatLink = (canonicalForm, label, target) => {
104
+ const formatLink = (canonicalForm, label, sourceForm, target) => {
96
105
  const encodedTarget = encodeLinkTarget(target);
97
- return canonicalForm === 'pipe' ? `{@link ${encodedTarget}|${label.trim()}}` : `[${label.trim()}]{@link ${encodedTarget}}`;
106
+ const escapedLabel = escapeLinkLabel(canonicalForm, label, sourceForm);
107
+ return canonicalForm === 'pipe' ? `{@link ${encodedTarget}|${escapedLabel}}` : `[${escapedLabel}]{@link ${encodedTarget}}`;
98
108
  };
99
109
 
100
110
  /**
@@ -104,15 +114,15 @@ const formatLink = (canonicalForm, label, target) => {
104
114
  */
105
115
  const normalizeDescription = (description, canonicalForm) => {
106
116
  const markdownNormalized = description.replaceAll(new RegExp(markdownLinkRegex, 'gv'), (_match, label, target) => {
107
- return formatLink(canonicalForm, label, target);
117
+ return formatLink(canonicalForm, label, 'markdown', target);
108
118
  });
109
119
  if (canonicalForm === 'pipe') {
110
120
  return markdownNormalized.replaceAll(new RegExp(prefixLinkRegex, 'gv'), (_match, label, target) => {
111
- return formatLink(canonicalForm, label, target);
121
+ return formatLink(canonicalForm, label, 'prefix', target);
112
122
  });
113
123
  }
114
124
  return markdownNormalized.replaceAll(new RegExp(pipeLinkRegex, 'gv'), (_match, target, label) => {
115
- return formatLink(canonicalForm, label, target);
125
+ return formatLink(canonicalForm, label, 'pipe', target);
116
126
  });
117
127
  };
118
128
  var _default = exports.default = (0, _iterateJsdoc.default)(({
@@ -148,7 +158,7 @@ var _default = exports.default = (0, _iterateJsdoc.default)(({
148
158
  for (const match of rawDescription.matchAll(markdownLinkRegex)) {
149
159
  const label = match[1];
150
160
  const target = match[2];
151
- if (isInsideMarkdownCodeSpan(rawDescription, match.index) || scopedPackageNameRegex.test(target) || cannotSafelyFormatLink(canonicalForm, label)) {
161
+ if (isInsideMarkdownCodeSpan(rawDescription, match.index) || scopedPackageNameRegex.test(target) || cannotSafelyFormatLink(label)) {
152
162
  hasAmbiguousLink = true;
153
163
  } else {
154
164
  hasNoncanonicalLink = true;
@@ -162,7 +172,7 @@ var _default = exports.default = (0, _iterateJsdoc.default)(({
162
172
  hasAmbiguousLink = true;
163
173
  continue;
164
174
  }
165
- if (cannotSafelyFormatLink(canonicalForm, inlineTag.text)) {
175
+ if (cannotSafelyFormatLink(inlineTag.text)) {
166
176
  hasAmbiguousLink = true;
167
177
  continue;
168
178
  }
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeSeeLinks.cjs","names":["iterateJsdoc"],"sources":["../../src/rules/normalizeSeeLinks.js"],"sourcesContent":["import iterateJsdoc from '../iterateJsdoc.js';\n\nconst markdownLinkRegex = /(?<!\\\\)\\[([^`\\]\\r\\n]+)\\]\\(([^`\\(\\)\\s]+)\\)/gv;\nconst prefixLinkRegex = /(?<!\\\\)\\[([^\\]\\r\\n]+)\\]\\{@link\\s+([^\\}\\s\\|]+)\\}/gv;\nconst pipeLinkRegex = /\\{@link\\s+([^\\}\\s\\|]+)\\s*\\|\\s*([^\\}\\r\\n]+)\\}/gv;\n\nconst markdownLinkAttemptRegex = /(?<!\\\\)\\[[^\\]\\r\\n]*\\]\\([^\\r\\n]*(?:\\)|$)/v;\nconst prefixLinkAttemptRegex = /(?<!\\\\)\\[[^\\]\\r\\n]*\\]\\{@link[^\\}\\r\\n]*(?:\\}|$)/v;\nconst pipeLinkAttemptRegex = /\\{@link[^\\}\\r\\n]*\\|[^\\}\\r\\n]*(?:\\}|$)/v;\n\nconst scopedPackageNameRegex = /^@[\\w.\\-]+\\/[\\w.\\-]+/v;\nconst markdownCodeSpanRegex = /(?<!\\\\)(`+)(?!`)[\\s\\S]*?(?<!`)\\1(?!`)/gv;\nconst bareHttpUrlRegex = /^https?:\\S+$/v;\n\n/**\n * @param {'pipe'|'prefix'} canonicalForm\n * @param {string} label\n * @returns {boolean}\n */\nconst cannotSafelyFormatLink = (canonicalForm, label) => {\n if (!label.trim()) {\n return true;\n }\n\n if (canonicalForm === 'pipe') {\n return label.includes('}');\n }\n\n return label.includes(']');\n};\n\n/**\n * @param {string} target\n * @returns {string}\n */\nconst encodeLinkTarget = (target) => {\n return encodeURI(target).replaceAll(/%25(?=[\\dA-F]{2})/giv, '%');\n};\n\n/**\n * @param {string} description\n * @param {boolean} enabled\n * @returns {string|null}\n */\nconst getBareHttpUrl = (description, enabled) => {\n if (!enabled) {\n return null;\n }\n\n const trimmedDescription = description.trim();\n\n return bareHttpUrlRegex.test(trimmedDescription) &&\n URL.canParse(trimmedDescription) ?\n trimmedDescription : null;\n};\n\n/**\n * @param {string} target\n * @returns {string}\n */\nconst formatBareUrl = (target) => {\n return `{@link ${encodeLinkTarget(target)}}`;\n};\n\n/**\n * @param {string|undefined} bareUrl\n * @param {'pipe'|'prefix'} canonicalForm\n * @returns {string}\n */\nconst getExpectedMessage = (bareUrl, canonicalForm) => {\n return bareUrl ?\n 'Expected bare @see URL to use a {@link} tag.' :\n `Expected @see link to use the ${canonicalForm} form.`;\n};\n\n/**\n * @param {string} description\n * @param {number} index\n * @returns {boolean}\n */\nconst isInsideMarkdownCodeSpan = (description, index) => {\n for (const match of description.matchAll(markdownCodeSpanRegex)) {\n const delimiterLength = match[1].length;\n const contentStart = match.index + delimiterLength;\n const contentEnd = match.index + match[0].length - delimiterLength;\n\n if (index >= contentStart && index < contentEnd) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * @param {'pipe'|'prefix'} canonicalForm\n * @param {string} label\n * @param {string} target\n * @returns {string}\n */\nconst formatLink = (canonicalForm, label, target) => {\n const encodedTarget = encodeLinkTarget(target);\n\n return canonicalForm === 'pipe' ?\n `{@link ${encodedTarget}|${label.trim()}}` :\n `[${label.trim()}]{@link ${encodedTarget}}`;\n};\n\n/**\n * @param {string} description\n * @param {'pipe'|'prefix'} canonicalForm\n * @returns {string}\n */\nconst normalizeDescription = (description, canonicalForm) => {\n const markdownNormalized = description.replaceAll(\n new RegExp(markdownLinkRegex, 'gv'),\n (_match, label, target) => {\n return formatLink(canonicalForm, label, target);\n },\n );\n\n if (canonicalForm === 'pipe') {\n return markdownNormalized.replaceAll(\n new RegExp(prefixLinkRegex, 'gv'),\n (_match, label, target) => {\n return formatLink(canonicalForm, label, target);\n },\n );\n }\n\n return markdownNormalized.replaceAll(\n new RegExp(pipeLinkRegex, 'gv'),\n (_match, target, label) => {\n return formatLink(canonicalForm, label, target);\n },\n );\n};\n\nexport default iterateJsdoc(({\n context,\n jsdoc,\n utils,\n}) => {\n const {\n canonicalForm = 'pipe',\n enableFixer = true,\n wrapBareUrls = false,\n } = context.options[0] || {};\n\n const descriptionMatcher = canonicalForm === 'pipe' ?\n new RegExp(`${markdownLinkRegex.source}|${prefixLinkRegex.source}`, 'v') :\n new RegExp(`${markdownLinkRegex.source}|${pipeLinkRegex.source}`, 'v');\n\n /** @type {import('comment-parser').Spec[]} */\n const fixableTags = [];\n /** @type {Map<import('comment-parser').Spec, string>} */\n const bareUrlTags = new Map();\n\n for (const tag of jsdoc.tags) {\n if (tag.tag !== 'see') {\n continue;\n }\n\n const firstTokens = tag.source[0].tokens;\n const tagDescription = String(utils.getTagDescription(tag));\n const rawDescription = firstTokens.name ?\n firstTokens.name + firstTokens.postName + tagDescription :\n tagDescription;\n\n let hasAmbiguousLink = false;\n let hasNoncanonicalLink = false;\n const bareUrl = getBareHttpUrl(rawDescription, wrapBareUrls);\n\n if (bareUrl) {\n bareUrlTags.set(tag, bareUrl);\n hasNoncanonicalLink = true;\n }\n\n for (const match of rawDescription.matchAll(markdownLinkRegex)) {\n const label = match[1];\n const target = match[2];\n\n if (\n isInsideMarkdownCodeSpan(rawDescription, match.index) ||\n scopedPackageNameRegex.test(target) ||\n cannotSafelyFormatLink(canonicalForm, label)\n ) {\n hasAmbiguousLink = true;\n } else {\n hasNoncanonicalLink = true;\n }\n }\n\n for (const inlineTag of tag.inlineTags) {\n if (inlineTag.tag !== 'link' || !inlineTag.text) {\n continue;\n }\n\n if (inlineTag.format === 'space') {\n hasAmbiguousLink = true;\n continue;\n }\n\n if (cannotSafelyFormatLink(\n canonicalForm,\n inlineTag.text,\n )) {\n hasAmbiguousLink = true;\n continue;\n }\n\n if (inlineTag.format === canonicalForm) {\n continue;\n }\n\n const {\n start,\n } = /** @type {typeof inlineTag & {start: number}} */ (inlineTag);\n\n if (\n isInsideMarkdownCodeSpan(rawDescription, start) ||\n scopedPackageNameRegex.test(inlineTag.namepathOrURL)\n ) {\n hasAmbiguousLink = true;\n } else {\n hasNoncanonicalLink = true;\n }\n }\n\n const unrecognizedLinkText = rawDescription\n .replaceAll(new RegExp(markdownLinkRegex, 'gv'), '')\n .replaceAll(new RegExp(prefixLinkRegex, 'gv'), '')\n .replaceAll(new RegExp(pipeLinkRegex, 'gv'), '');\n\n hasAmbiguousLink ||= markdownLinkAttemptRegex.test(unrecognizedLinkText) ||\n prefixLinkAttemptRegex.test(unrecognizedLinkText) ||\n pipeLinkAttemptRegex.test(unrecognizedLinkText);\n\n if (hasAmbiguousLink) {\n utils.reportJSDoc('@see link cannot be safely normalized.', {\n line: tag.source[0].number,\n }, null);\n continue;\n }\n\n if (!hasNoncanonicalLink) {\n continue;\n }\n\n fixableTags.push(tag);\n }\n\n for (const [\n reportIdx,\n tag,\n ] of fixableTags.entries()) {\n const bareUrl = bareUrlTags.get(tag);\n\n utils.reportJSDoc(\n getExpectedMessage(bareUrl, canonicalForm),\n {\n line: tag.source[0].number,\n },\n enableFixer && reportIdx === 0 ?\n () => {\n for (const fixableTag of fixableTags) {\n const firstTokens = fixableTag.source[0].tokens;\n\n if (firstTokens.name) {\n firstTokens.description = firstTokens.name +\n firstTokens.postName + firstTokens.description;\n firstTokens.name = '';\n firstTokens.postName = '';\n }\n\n const fixableBareUrl = bareUrlTags.get(fixableTag);\n\n if (fixableBareUrl) {\n for (const source of fixableTag.source) {\n if (!source.tokens.description.includes(fixableBareUrl)) {\n continue;\n }\n\n source.tokens.description = source.tokens.description.replace(\n fixableBareUrl,\n () => {\n return formatBareUrl(fixableBareUrl);\n },\n );\n break;\n }\n\n continue;\n }\n\n for (\n let sourceIdx = 0;\n sourceIdx < fixableTag.source.length;\n sourceIdx++\n ) {\n if (!descriptionMatcher.test(\n fixableTag.source[sourceIdx].tokens.description,\n )) {\n continue;\n }\n\n utils.setTagDescription(\n fixableTag,\n descriptionMatcher,\n (description) => {\n return normalizeDescription(description, canonicalForm);\n },\n );\n }\n }\n } :\n null,\n true,\n );\n }\n}, {\n iterateAllJsdocs: true,\n meta: {\n docs: {\n description: 'Normalizes labeled links in `@see` tags to a canonical `{@link}` form.',\n url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/normalize-see-links.md#repos-sticky-header',\n },\n fixable: 'code',\n schema: [\n {\n additionalProperties: false,\n properties: {\n canonicalForm: {\n description: 'The canonical `{@link}` form: `\"pipe\"` produces `{@link url|label}`, while `\"prefix\"` produces `[label]{@link url}`. Defaults to `\"pipe\"`.',\n enum: [\n 'pipe',\n 'prefix',\n ],\n type: 'string',\n },\n enableFixer: {\n description: 'Whether to enable the fixer. Defaults to `true`.',\n type: 'boolean',\n },\n wrapBareUrls: {\n description: 'Whether to wrap an `@see` description containing only a lowercase `http:` or `https:` URL (for example, `@see https://example.com`) in a plain `{@link https://example.com}`. Defaults to `false`.',\n type: 'boolean',\n },\n },\n type: 'object',\n },\n ],\n type: 'suggestion',\n },\n});\n"],"mappings":";;;;;;AAAA;AAA8C;AAE9C,MAAM,iBAAiB,GAAG,6CAA6C;AACvE,MAAM,eAAe,GAAG,mDAAmD;AAC3E,MAAM,aAAa,GAAG,gDAAgD;AAEtE,MAAM,wBAAwB,GAAG,0CAA0C;AAC3E,MAAM,sBAAsB,GAAG,iDAAiD;AAChF,MAAM,oBAAoB,GAAG,wCAAwC;AAErE,MAAM,sBAAsB,GAAG,uBAAuB;AACtD,MAAM,qBAAqB,GAAG,yCAAyC;AACvE,MAAM,gBAAgB,GAAG,eAAe;;AAExC;AACA;AACA;AACA;AACA;AACA,MAAM,sBAAsB,GAAG,CAAC,aAAa,EAAE,KAAK,KAAK;EACvD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE;IACjB,OAAO,IAAI;EACb;EAEA,IAAI,aAAa,KAAK,MAAM,EAAE;IAC5B,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;EAC5B;EAEA,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;AAC5B,CAAC;;AAED;AACA;AACA;AACA;AACA,MAAM,gBAAgB,GAAI,MAAM,IAAK;EACnC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,sBAAsB,EAAE,GAAG,CAAC;AAClE,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,OAAO,KAAK;EAC/C,IAAI,CAAC,OAAO,EAAE;IACZ,OAAO,IAAI;EACb;EAEA,MAAM,kBAAkB,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;EAE7C,OAAO,gBAAgB,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAC9C,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAChC,kBAAkB,GAAG,IAAI;AAC7B,CAAC;;AAED;AACA;AACA;AACA;AACA,MAAM,aAAa,GAAI,MAAM,IAAK;EAChC,OAAO,UAAU,gBAAgB,CAAC,MAAM,CAAC,GAAG;AAC9C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,aAAa,KAAK;EACrD,OAAO,OAAO,GACZ,8CAA8C,GAC9C,iCAAiC,aAAa,QAAQ;AAC1D,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,MAAM,wBAAwB,GAAG,CAAC,WAAW,EAAE,KAAK,KAAK;EACvD,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE;IAC/D,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;IACvC,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,GAAG,eAAe;IAClD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,eAAe;IAElE,IAAI,KAAK,IAAI,YAAY,IAAI,KAAK,GAAG,UAAU,EAAE;MAC/C,OAAO,IAAI;IACb;EACF;EAEA,OAAO,KAAK;AACd,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,UAAU,GAAG,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK;EACnD,MAAM,aAAa,GAAG,gBAAgB,CAAC,MAAM,CAAC;EAE9C,OAAO,aAAa,KAAK,MAAM,GAC7B,UAAU,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,GAC1C,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,WAAW,aAAa,GAAG;AAC/C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,MAAM,oBAAoB,GAAG,CAAC,WAAW,EAAE,aAAa,KAAK;EAC3D,MAAM,kBAAkB,GAAG,WAAW,CAAC,UAAU,CAC/C,IAAI,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,EACnC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK;IACzB,OAAO,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC;EACjD,CACF,CAAC;EAED,IAAI,aAAa,KAAK,MAAM,EAAE;IAC5B,OAAO,kBAAkB,CAAC,UAAU,CAClC,IAAI,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,EACjC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK;MACzB,OAAO,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC;IACjD,CACF,CAAC;EACH;EAEA,OAAO,kBAAkB,CAAC,UAAU,CAClC,IAAI,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,EAC/B,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,KAAK;IACzB,OAAO,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC;EACjD,CACF,CAAC;AACH,CAAC;AAAC,iCAEa,IAAAA,qBAAY,EAAC,CAAC;EAC3B,OAAO;EACP,KAAK;EACL;AACF,CAAC,KAAK;EACJ,MAAM;IACJ,aAAa,GAAG,MAAM;IACtB,WAAW,GAAG,IAAI;IAClB,YAAY,GAAG;EACjB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;EAE5B,MAAM,kBAAkB,GAAG,aAAa,KAAK,MAAM,GACjD,IAAI,MAAM,CAAC,GAAG,iBAAiB,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,GACxE,IAAI,MAAM,CAAC,GAAG,iBAAiB,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC;;EAExE;EACA,MAAM,WAAW,GAAG,EAAE;EACtB;EACA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC;EAE7B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE;IAC5B,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,EAAE;MACrB;IACF;IAEA,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM;IACxC,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC3D,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,GACrC,WAAW,CAAC,IAAI,GAAG,WAAW,CAAC,QAAQ,GAAG,cAAc,GACxD,cAAc;IAEhB,IAAI,gBAAgB,GAAG,KAAK;IAC5B,IAAI,mBAAmB,GAAG,KAAK;IAC/B,MAAM,OAAO,GAAG,cAAc,CAAC,cAAc,EAAE,YAAY,CAAC;IAE5D,IAAI,OAAO,EAAE;MACX,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;MAC7B,mBAAmB,GAAG,IAAI;IAC5B;IAEA,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;MAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;MACtB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;MAEvB,IACE,wBAAwB,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,IACrD,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,IACnC,sBAAsB,CAAC,aAAa,EAAE,KAAK,CAAC,EAC5C;QACA,gBAAgB,GAAG,IAAI;MACzB,CAAC,MAAM;QACL,mBAAmB,GAAG,IAAI;MAC5B;IACF;IAEA,KAAK,MAAM,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE;MACtC,IAAI,SAAS,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;QAC/C;MACF;MAEA,IAAI,SAAS,CAAC,MAAM,KAAK,OAAO,EAAE;QAChC,gBAAgB,GAAG,IAAI;QACvB;MACF;MAEA,IAAI,sBAAsB,CACxB,aAAa,EACb,SAAS,CAAC,IACZ,CAAC,EAAE;QACD,gBAAgB,GAAG,IAAI;QACvB;MACF;MAEA,IAAI,SAAS,CAAC,MAAM,KAAK,aAAa,EAAE;QACtC;MACF;MAEA,MAAM;QACJ;MACF,CAAC,GAAG,iDAAmD,SAAU;MAEjE,IACE,wBAAwB,CAAC,cAAc,EAAE,KAAK,CAAC,IAC/C,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,EACpD;QACA,gBAAgB,GAAG,IAAI;MACzB,CAAC,MAAM;QACL,mBAAmB,GAAG,IAAI;MAC5B;IACF;IAEA,MAAM,oBAAoB,GAAG,cAAc,CACxC,UAAU,CAAC,IAAI,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CACnD,UAAU,CAAC,IAAI,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CACjD,UAAU,CAAC,IAAI,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;IAElD,gBAAgB,KAAK,wBAAwB,CAAC,IAAI,CAAC,oBAAoB,CAAC,IACtE,sBAAsB,CAAC,IAAI,CAAC,oBAAoB,CAAC,IACjD,oBAAoB,CAAC,IAAI,CAAC,oBAAoB,CAAC;IAEjD,IAAI,gBAAgB,EAAE;MACpB,KAAK,CAAC,WAAW,CAAC,wCAAwC,EAAE;QAC1D,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;MACtB,CAAC,EAAE,IAAI,CAAC;MACR;IACF;IAEA,IAAI,CAAC,mBAAmB,EAAE;MACxB;IACF;IAEA,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;EACvB;EAEA,KAAK,MAAM,CACT,SAAS,EACT,GAAG,CACJ,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE;IAC1B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;IAEpC,KAAK,CAAC,WAAW,CACf,kBAAkB,CAAC,OAAO,EAAE,aAAa,CAAC,EAC1C;MACE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,EACD,WAAW,IAAI,SAAS,KAAK,CAAC,GAC5B,MAAM;MACJ,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE;QACpC,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM;QAE/C,IAAI,WAAW,CAAC,IAAI,EAAE;UACpB,WAAW,CAAC,WAAW,GAAG,WAAW,CAAC,IAAI,GACxC,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,WAAW;UAChD,WAAW,CAAC,IAAI,GAAG,EAAE;UACrB,WAAW,CAAC,QAAQ,GAAG,EAAE;QAC3B;QAEA,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC;QAElD,IAAI,cAAc,EAAE;UAClB,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,MAAM,EAAE;YACtC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;cACvD;YACF;YAEA,MAAM,CAAC,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAC3D,cAAc,EACd,MAAM;cACJ,OAAO,aAAa,CAAC,cAAc,CAAC;YACtC,CACF,CAAC;YACD;UACF;UAEA;QACF;QAEA,KACE,IAAI,SAAS,GAAG,CAAC,EACjB,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,EACpC,SAAS,EAAE,EACX;UACA,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAC1B,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,WACtC,CAAC,EAAE;YACD;UACF;UAEA,KAAK,CAAC,iBAAiB,CACrB,UAAU,EACV,kBAAkB,EACjB,WAAW,IAAK;YACf,OAAO,oBAAoB,CAAC,WAAW,EAAE,aAAa,CAAC;UACzD,CACF,CAAC;QACH;MACF;IACF,CAAC,GACD,IAAI,EACN,IACF,CAAC;EACH;AACF,CAAC,EAAE;EACD,gBAAgB,EAAE,IAAI;EACtB,IAAI,EAAE;IACJ,IAAI,EAAE;MACJ,WAAW,EAAE,wEAAwE;MACrF,GAAG,EAAE;IACP,CAAC;IACD,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,CACN;MACE,oBAAoB,EAAE,KAAK;MAC3B,UAAU,EAAE;QACV,aAAa,EAAE;UACb,WAAW,EAAE,4IAA4I;UACzJ,IAAI,EAAE,CACJ,MAAM,EACN,QAAQ,CACT;UACD,IAAI,EAAE;QACR,CAAC;QACD,WAAW,EAAE;UACX,WAAW,EAAE,kDAAkD;UAC/D,IAAI,EAAE;QACR,CAAC;QACD,YAAY,EAAE;UACZ,WAAW,EAAE,oMAAoM;UACjN,IAAI,EAAE;QACR;MACF,CAAC;MACD,IAAI,EAAE;IACR,CAAC,CACF;IACD,IAAI,EAAE;EACR;AACF,CAAC,CAAC;AAAA","ignoreList":[]}
1
+ {"version":3,"file":"normalizeSeeLinks.cjs","names":["iterateJsdoc"],"sources":["../../src/rules/normalizeSeeLinks.js"],"sourcesContent":["import iterateJsdoc from '../iterateJsdoc.js';\n\nconst markdownLinkRegex = /(?<!\\\\)\\[((?:[^\\\\`\\]\\r\\n]|\\\\[^\\r\\n])+)\\]\\(([^`\\(\\)\\s]+)\\)/gv;\nconst prefixLinkRegex = /(?<!\\\\)\\[((?:[^\\\\\\]\\r\\n]|\\\\[^\\r\\n])+)\\]\\{@link\\s+([^\\}\\s\\|]+)\\}/gv;\nconst pipeLinkRegex = /\\{@link\\s+([^\\}\\s\\|]+)\\s*\\|\\s*((?:[^\\\\\\}\\r\\n]|\\\\[^\\r\\n])+)\\}/gv;\n\nconst markdownLinkAttemptRegex = /(?<!\\\\)\\[[^\\]\\r\\n]*\\]\\([^\\r\\n]*(?:\\)|$)/v;\nconst prefixLinkAttemptRegex = /(?<!\\\\)\\[[^\\]\\r\\n]*\\]\\{@link[^\\}\\r\\n]*(?:\\}|$)/v;\nconst pipeLinkAttemptRegex = /\\{@link[^\\}\\r\\n]*\\|[^\\}\\r\\n]*(?:\\}|$)/v;\n\nconst scopedPackageNameRegex = /^@[\\w.\\-]+\\/[\\w.\\-]+/v;\nconst markdownCodeSpanRegex = /(?<!\\\\)(`+)(?!`)[\\s\\S]*?(?<!`)\\1(?!`)/gv;\nconst bareHttpUrlRegex = /^https?:\\S+$/v;\n\n/**\n * @param {string} label\n * @returns {boolean}\n */\nconst cannotSafelyFormatLink = (label) => {\n return !label.trim();\n};\n\n/**\n * @param {'pipe'|'prefix'} canonicalForm\n * @param {string} label\n * @param {'markdown'|'pipe'|'prefix'} sourceForm\n * @returns {string}\n */\nconst escapeLinkLabel = (canonicalForm, label, sourceForm) => {\n const sourceEscapePattern = sourceForm === 'pipe' ?\n /\\\\([\\\\\\}])/gv :\n (sourceForm === 'prefix' ?\n /\\\\([\\\\\\]])/gv :\n /\\\\([\\\\\\}\\]])/gv);\n const targetEscapePattern = canonicalForm === 'pipe' ?\n /\\\\(?=$|[\\\\\\}])|\\}/gv :\n /\\\\(?=$|[\\\\\\]])|\\]/gv;\n const decodedLabel = label.trim().replaceAll(sourceEscapePattern, '$1');\n\n return decodedLabel.replaceAll(targetEscapePattern, (character) => {\n return `\\\\${character}`;\n });\n};\n\n/**\n * @param {string} target\n * @returns {string}\n */\nconst encodeLinkTarget = (target) => {\n return target\n .replaceAll(/%(?![\\dA-F]{2})/giv, '%25')\n .replaceAll('|', '%7C')\n .replaceAll('{', '%7B')\n .replaceAll('}', '%7D');\n};\n\n/**\n * @param {string} description\n * @param {boolean} enabled\n * @returns {string|null}\n */\nconst getBareHttpUrl = (description, enabled) => {\n if (!enabled) {\n return null;\n }\n\n const trimmedDescription = description.trim();\n\n return bareHttpUrlRegex.test(trimmedDescription) &&\n URL.canParse(trimmedDescription) ?\n trimmedDescription : null;\n};\n\n/**\n * @param {string} target\n * @returns {string}\n */\nconst formatBareUrl = (target) => {\n return `{@link ${encodeLinkTarget(target)}}`;\n};\n\n/**\n * @param {string|undefined} bareUrl\n * @param {'pipe'|'prefix'} canonicalForm\n * @returns {string}\n */\nconst getExpectedMessage = (bareUrl, canonicalForm) => {\n return bareUrl ?\n 'Expected bare @see URL to use a {@link} tag.' :\n `Expected @see link to use the ${canonicalForm} form.`;\n};\n\n/**\n * @param {string} description\n * @param {number} index\n * @returns {boolean}\n */\nconst isInsideMarkdownCodeSpan = (description, index) => {\n for (const match of description.matchAll(markdownCodeSpanRegex)) {\n const delimiterLength = match[1].length;\n const contentStart = match.index + delimiterLength;\n const contentEnd = match.index + match[0].length - delimiterLength;\n\n if (index >= contentStart && index < contentEnd) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * @param {'pipe'|'prefix'} canonicalForm\n * @param {string} label\n * @param {'markdown'|'pipe'|'prefix'} sourceForm\n * @param {string} target\n * @returns {string}\n */\nconst formatLink = (canonicalForm, label, sourceForm, target) => {\n const encodedTarget = encodeLinkTarget(target);\n const escapedLabel = escapeLinkLabel(canonicalForm, label, sourceForm);\n\n return canonicalForm === 'pipe' ?\n `{@link ${encodedTarget}|${escapedLabel}}` :\n `[${escapedLabel}]{@link ${encodedTarget}}`;\n};\n\n/**\n * @param {string} description\n * @param {'pipe'|'prefix'} canonicalForm\n * @returns {string}\n */\nconst normalizeDescription = (description, canonicalForm) => {\n const markdownNormalized = description.replaceAll(\n new RegExp(markdownLinkRegex, 'gv'),\n (_match, label, target) => {\n return formatLink(canonicalForm, label, 'markdown', target);\n },\n );\n\n if (canonicalForm === 'pipe') {\n return markdownNormalized.replaceAll(\n new RegExp(prefixLinkRegex, 'gv'),\n (_match, label, target) => {\n return formatLink(canonicalForm, label, 'prefix', target);\n },\n );\n }\n\n return markdownNormalized.replaceAll(\n new RegExp(pipeLinkRegex, 'gv'),\n (_match, target, label) => {\n return formatLink(canonicalForm, label, 'pipe', target);\n },\n );\n};\n\nexport default iterateJsdoc(({\n context,\n jsdoc,\n utils,\n}) => {\n const {\n canonicalForm = 'pipe',\n enableFixer = true,\n wrapBareUrls = false,\n } = context.options[0] || {};\n\n const descriptionMatcher = canonicalForm === 'pipe' ?\n new RegExp(`${markdownLinkRegex.source}|${prefixLinkRegex.source}`, 'v') :\n new RegExp(`${markdownLinkRegex.source}|${pipeLinkRegex.source}`, 'v');\n\n /** @type {import('comment-parser').Spec[]} */\n const fixableTags = [];\n /** @type {Map<import('comment-parser').Spec, string>} */\n const bareUrlTags = new Map();\n\n for (const tag of jsdoc.tags) {\n if (tag.tag !== 'see') {\n continue;\n }\n\n const firstTokens = tag.source[0].tokens;\n const tagDescription = String(utils.getTagDescription(tag));\n const rawDescription = firstTokens.name ?\n firstTokens.name + firstTokens.postName + tagDescription :\n tagDescription;\n\n let hasAmbiguousLink = false;\n let hasNoncanonicalLink = false;\n const bareUrl = getBareHttpUrl(rawDescription, wrapBareUrls);\n\n if (bareUrl) {\n bareUrlTags.set(tag, bareUrl);\n hasNoncanonicalLink = true;\n }\n\n for (const match of rawDescription.matchAll(markdownLinkRegex)) {\n const label = match[1];\n const target = match[2];\n\n if (\n isInsideMarkdownCodeSpan(rawDescription, match.index) ||\n scopedPackageNameRegex.test(target) ||\n cannotSafelyFormatLink(label)\n ) {\n hasAmbiguousLink = true;\n } else {\n hasNoncanonicalLink = true;\n }\n }\n\n for (const inlineTag of tag.inlineTags) {\n if (inlineTag.tag !== 'link' || !inlineTag.text) {\n continue;\n }\n\n if (inlineTag.format === 'space') {\n hasAmbiguousLink = true;\n continue;\n }\n\n if (cannotSafelyFormatLink(inlineTag.text)) {\n hasAmbiguousLink = true;\n continue;\n }\n\n if (inlineTag.format === canonicalForm) {\n continue;\n }\n\n const {\n start,\n } = /** @type {typeof inlineTag & {start: number}} */ (inlineTag);\n\n if (\n isInsideMarkdownCodeSpan(rawDescription, start) ||\n scopedPackageNameRegex.test(inlineTag.namepathOrURL)\n ) {\n hasAmbiguousLink = true;\n } else {\n hasNoncanonicalLink = true;\n }\n }\n\n const unrecognizedLinkText = rawDescription\n .replaceAll(new RegExp(markdownLinkRegex, 'gv'), '')\n .replaceAll(new RegExp(prefixLinkRegex, 'gv'), '')\n .replaceAll(new RegExp(pipeLinkRegex, 'gv'), '');\n\n hasAmbiguousLink ||= markdownLinkAttemptRegex.test(unrecognizedLinkText) ||\n prefixLinkAttemptRegex.test(unrecognizedLinkText) ||\n pipeLinkAttemptRegex.test(unrecognizedLinkText);\n\n if (hasAmbiguousLink) {\n utils.reportJSDoc('@see link cannot be safely normalized.', {\n line: tag.source[0].number,\n }, null);\n continue;\n }\n\n if (!hasNoncanonicalLink) {\n continue;\n }\n\n fixableTags.push(tag);\n }\n\n for (const [\n reportIdx,\n tag,\n ] of fixableTags.entries()) {\n const bareUrl = bareUrlTags.get(tag);\n\n utils.reportJSDoc(\n getExpectedMessage(bareUrl, canonicalForm),\n {\n line: tag.source[0].number,\n },\n enableFixer && reportIdx === 0 ?\n () => {\n for (const fixableTag of fixableTags) {\n const firstTokens = fixableTag.source[0].tokens;\n\n if (firstTokens.name) {\n firstTokens.description = firstTokens.name +\n firstTokens.postName + firstTokens.description;\n firstTokens.name = '';\n firstTokens.postName = '';\n }\n\n const fixableBareUrl = bareUrlTags.get(fixableTag);\n\n if (fixableBareUrl) {\n for (const source of fixableTag.source) {\n if (!source.tokens.description.includes(fixableBareUrl)) {\n continue;\n }\n\n source.tokens.description = source.tokens.description.replace(\n fixableBareUrl,\n () => {\n return formatBareUrl(fixableBareUrl);\n },\n );\n break;\n }\n\n continue;\n }\n\n for (\n let sourceIdx = 0;\n sourceIdx < fixableTag.source.length;\n sourceIdx++\n ) {\n if (!descriptionMatcher.test(\n fixableTag.source[sourceIdx].tokens.description,\n )) {\n continue;\n }\n\n utils.setTagDescription(\n fixableTag,\n descriptionMatcher,\n (description) => {\n return normalizeDescription(description, canonicalForm);\n },\n );\n }\n }\n } :\n null,\n true,\n );\n }\n}, {\n iterateAllJsdocs: true,\n meta: {\n docs: {\n description: 'Normalizes labeled links in `@see` tags to a canonical `{@link}` form.',\n url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/normalize-see-links.md#repos-sticky-header',\n },\n fixable: 'code',\n schema: [\n {\n additionalProperties: false,\n properties: {\n canonicalForm: {\n description: 'The canonical `{@link}` form: `\"pipe\"` produces `{@link url|label}`, while `\"prefix\"` produces `[label]{@link url}`. Defaults to `\"pipe\"`.',\n enum: [\n 'pipe',\n 'prefix',\n ],\n type: 'string',\n },\n enableFixer: {\n description: 'Whether to enable the fixer. Defaults to `true`.',\n type: 'boolean',\n },\n wrapBareUrls: {\n description: 'Whether to wrap an `@see` description containing only a lowercase `http:` or `https:` URL (for example, `@see https://example.com`) in a plain `{@link https://example.com}`. Defaults to `false`.',\n type: 'boolean',\n },\n },\n type: 'object',\n },\n ],\n type: 'suggestion',\n },\n});\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -177,6 +177,7 @@
177
177
  "create-rule": "node ./src/bin/generateRule.js",
178
178
  "create-options": "node ./src/bin/generateOptions.js",
179
179
  "install-offline": "CI=1 pnpm install --prefer-offline --no-frozen-lockfile",
180
+ "inspect-config": "eslint --inspect-config",
180
181
  "lint": "eslint",
181
182
  "lint-fix": "eslint --fix",
182
183
  "test-no-cov": "BABEL_ENV=test mocha",
@@ -184,7 +185,7 @@
184
185
  "test-cov": "TIMING=1 c8 --reporter text pnpm run test-no-cov",
185
186
  "test-index": "pnpm run test-no-cov test/rules/index.js"
186
187
  },
187
- "version": "63.2.2-depup.1",
188
+ "version": "63.3.1-depup.0",
188
189
  "depup": {
189
190
  "changes": {
190
191
  "@es-joy/jsdoccomment": {
@@ -202,8 +203,8 @@
202
203
  },
203
204
  "depsUpdated": 3,
204
205
  "originalPackage": "eslint-plugin-jsdoc",
205
- "originalVersion": "63.2.2",
206
- "processedAt": "2026-07-25T00:41:53.919Z",
206
+ "originalVersion": "63.3.1",
207
+ "processedAt": "2026-07-27T09:08:28.645Z",
207
208
  "smokeTest": "passed"
208
209
  }
209
210
  }
@@ -1,8 +1,8 @@
1
1
  import iterateJsdoc from '../iterateJsdoc.js';
2
2
 
3
- const markdownLinkRegex = /(?<!\\)\[([^`\]\r\n]+)\]\(([^`\(\)\s]+)\)/gv;
4
- const prefixLinkRegex = /(?<!\\)\[([^\]\r\n]+)\]\{@link\s+([^\}\s\|]+)\}/gv;
5
- const pipeLinkRegex = /\{@link\s+([^\}\s\|]+)\s*\|\s*([^\}\r\n]+)\}/gv;
3
+ const markdownLinkRegex = /(?<!\\)\[((?:[^\\`\]\r\n]|\\[^\r\n])+)\]\(([^`\(\)\s]+)\)/gv;
4
+ const prefixLinkRegex = /(?<!\\)\[((?:[^\\\]\r\n]|\\[^\r\n])+)\]\{@link\s+([^\}\s\|]+)\}/gv;
5
+ const pipeLinkRegex = /\{@link\s+([^\}\s\|]+)\s*\|\s*((?:[^\\\}\r\n]|\\[^\r\n])+)\}/gv;
6
6
 
7
7
  const markdownLinkAttemptRegex = /(?<!\\)\[[^\]\r\n]*\]\([^\r\n]*(?:\)|$)/v;
8
8
  const prefixLinkAttemptRegex = /(?<!\\)\[[^\]\r\n]*\]\{@link[^\}\r\n]*(?:\}|$)/v;
@@ -13,20 +13,33 @@ const markdownCodeSpanRegex = /(?<!\\)(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)/gv;
13
13
  const bareHttpUrlRegex = /^https?:\S+$/v;
14
14
 
15
15
  /**
16
- * @param {'pipe'|'prefix'} canonicalForm
17
16
  * @param {string} label
18
17
  * @returns {boolean}
19
18
  */
20
- const cannotSafelyFormatLink = (canonicalForm, label) => {
21
- if (!label.trim()) {
22
- return true;
23
- }
24
-
25
- if (canonicalForm === 'pipe') {
26
- return label.includes('}');
27
- }
19
+ const cannotSafelyFormatLink = (label) => {
20
+ return !label.trim();
21
+ };
28
22
 
29
- return label.includes(']');
23
+ /**
24
+ * @param {'pipe'|'prefix'} canonicalForm
25
+ * @param {string} label
26
+ * @param {'markdown'|'pipe'|'prefix'} sourceForm
27
+ * @returns {string}
28
+ */
29
+ const escapeLinkLabel = (canonicalForm, label, sourceForm) => {
30
+ const sourceEscapePattern = sourceForm === 'pipe' ?
31
+ /\\([\\\}])/gv :
32
+ (sourceForm === 'prefix' ?
33
+ /\\([\\\]])/gv :
34
+ /\\([\\\}\]])/gv);
35
+ const targetEscapePattern = canonicalForm === 'pipe' ?
36
+ /\\(?=$|[\\\}])|\}/gv :
37
+ /\\(?=$|[\\\]])|\]/gv;
38
+ const decodedLabel = label.trim().replaceAll(sourceEscapePattern, '$1');
39
+
40
+ return decodedLabel.replaceAll(targetEscapePattern, (character) => {
41
+ return `\\${character}`;
42
+ });
30
43
  };
31
44
 
32
45
  /**
@@ -34,7 +47,11 @@ const cannotSafelyFormatLink = (canonicalForm, label) => {
34
47
  * @returns {string}
35
48
  */
36
49
  const encodeLinkTarget = (target) => {
37
- return encodeURI(target).replaceAll(/%25(?=[\dA-F]{2})/giv, '%');
50
+ return target
51
+ .replaceAll(/%(?![\dA-F]{2})/giv, '%25')
52
+ .replaceAll('|', '%7C')
53
+ .replaceAll('{', '%7B')
54
+ .replaceAll('}', '%7D');
38
55
  };
39
56
 
40
57
  /**
@@ -95,15 +112,17 @@ const isInsideMarkdownCodeSpan = (description, index) => {
95
112
  /**
96
113
  * @param {'pipe'|'prefix'} canonicalForm
97
114
  * @param {string} label
115
+ * @param {'markdown'|'pipe'|'prefix'} sourceForm
98
116
  * @param {string} target
99
117
  * @returns {string}
100
118
  */
101
- const formatLink = (canonicalForm, label, target) => {
119
+ const formatLink = (canonicalForm, label, sourceForm, target) => {
102
120
  const encodedTarget = encodeLinkTarget(target);
121
+ const escapedLabel = escapeLinkLabel(canonicalForm, label, sourceForm);
103
122
 
104
123
  return canonicalForm === 'pipe' ?
105
- `{@link ${encodedTarget}|${label.trim()}}` :
106
- `[${label.trim()}]{@link ${encodedTarget}}`;
124
+ `{@link ${encodedTarget}|${escapedLabel}}` :
125
+ `[${escapedLabel}]{@link ${encodedTarget}}`;
107
126
  };
108
127
 
109
128
  /**
@@ -115,7 +134,7 @@ const normalizeDescription = (description, canonicalForm) => {
115
134
  const markdownNormalized = description.replaceAll(
116
135
  new RegExp(markdownLinkRegex, 'gv'),
117
136
  (_match, label, target) => {
118
- return formatLink(canonicalForm, label, target);
137
+ return formatLink(canonicalForm, label, 'markdown', target);
119
138
  },
120
139
  );
121
140
 
@@ -123,7 +142,7 @@ const normalizeDescription = (description, canonicalForm) => {
123
142
  return markdownNormalized.replaceAll(
124
143
  new RegExp(prefixLinkRegex, 'gv'),
125
144
  (_match, label, target) => {
126
- return formatLink(canonicalForm, label, target);
145
+ return formatLink(canonicalForm, label, 'prefix', target);
127
146
  },
128
147
  );
129
148
  }
@@ -131,7 +150,7 @@ const normalizeDescription = (description, canonicalForm) => {
131
150
  return markdownNormalized.replaceAll(
132
151
  new RegExp(pipeLinkRegex, 'gv'),
133
152
  (_match, target, label) => {
134
- return formatLink(canonicalForm, label, target);
153
+ return formatLink(canonicalForm, label, 'pipe', target);
135
154
  },
136
155
  );
137
156
  };
@@ -183,7 +202,7 @@ export default iterateJsdoc(({
183
202
  if (
184
203
  isInsideMarkdownCodeSpan(rawDescription, match.index) ||
185
204
  scopedPackageNameRegex.test(target) ||
186
- cannotSafelyFormatLink(canonicalForm, label)
205
+ cannotSafelyFormatLink(label)
187
206
  ) {
188
207
  hasAmbiguousLink = true;
189
208
  } else {
@@ -201,10 +220,7 @@ export default iterateJsdoc(({
201
220
  continue;
202
221
  }
203
222
 
204
- if (cannotSafelyFormatLink(
205
- canonicalForm,
206
- inlineTag.text,
207
- )) {
223
+ if (cannotSafelyFormatLink(inlineTag.text)) {
208
224
  hasAmbiguousLink = true;
209
225
  continue;
210
226
  }