Skip to content

Commit 6efd9e9

Browse files
authored
fix(valid-types): parse distinctly for names vs. namepaths (#1552)
Also: - chore: update jsdocccomment and jsdoc-type-pratt-parser - chore: avoid `eval` in build script
1 parent e67e474 commit 6efd9e9

File tree

8 files changed

+137
-32
lines changed

8 files changed

+137
-32
lines changed

docs/rules/valid-types.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,18 @@ function quux() {
233233
/**
234234
* @typedef {string} UserStr%ng
235235
*/
236-
// Message: Syntax error in namepath: UserStr%ng
236+
// Message: Syntax error in name: UserStr%ng
237+
238+
/**
239+
* @typedef {string} module:abc/def
240+
*/
241+
// Message: Syntax error in name: module:abc/def
242+
243+
/**
244+
* @typedef {string} module:abc/def
245+
*/
246+
// Settings: {"jsdoc":{"mode":"permissive"}}
247+
// Message: Syntax error in name: module:abc/def
237248

238249
/**
239250
* @this

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"url": "http://gajus.com"
66
},
77
"dependencies": {
8-
"@es-joy/jsdoccomment": "~0.67.2",
8+
"@es-joy/jsdoccomment": "~0.68.1",
99
"are-docs-informative": "^0.0.2",
1010
"comment-parser": "1.4.1",
1111
"debug": "^4.4.3",
@@ -27,7 +27,7 @@
2727
"@babel/plugin-syntax-class-properties": "^7.12.13",
2828
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
2929
"@babel/preset-env": "^7.28.3",
30-
"@es-joy/escodegen": "^4.0.3",
30+
"@es-joy/escodegen": "^4.2.0",
3131
"@es-joy/jsdoc-eslint-parser": "^0.24.0",
3232
"@eslint/core": "^0.16.0",
3333
"@hkdobrev/run-if-changed": "^0.6.3",
@@ -58,7 +58,7 @@
5858
"glob": "^11.0.3",
5959
"globals": "^16.4.0",
6060
"husky": "^9.1.7",
61-
"jsdoc-type-pratt-parser": "^6.3.2",
61+
"jsdoc-type-pratt-parser": "^6.3.3",
6262
"json-schema": "^0.4.0",
6363
"json-schema-to-typescript": "^15.0.4",
6464
"lint-staged": "^16.2.3",

pnpm-lock.yaml

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bin/generateOptions.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ for (const file of dirContents) {
3636
' Property[key.name="meta"] Property[key.name="schema"]',
3737
);
3838
if (results[0]?.value) {
39-
const schema = generate(results[0]?.value);
40-
41-
// eslint-disable-next-line no-eval -- Need some parser
42-
const json = eval('JSON.stringify(' + schema + ', null, 2)');
43-
const parsed = JSON.parse(json);
39+
const schema = generate(results[0]?.value, {
40+
format: {
41+
json: true,
42+
quotes: 'double',
43+
},
44+
});
45+
const parsed = JSON.parse(schema);
4446

4547
let initial = '';
4648
if (Array.isArray(parsed)) {

src/iterateJsdoc.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import esquery from 'esquery';
9696
* isNamepathReferencingTag: IsNamepathX,
9797
* isNamepathOrUrlReferencingTag: IsNamepathX,
9898
* tagMightHaveNameOrNamepath: IsNamepathX,
99+
* tagMightHaveName: IsNamepathX
99100
* }} BasicUtils
100101
*/
101102

@@ -530,6 +531,8 @@ import esquery from 'esquery';
530531
* isNamepathReferencingTag: IsNamepathX,
531532
* isNamepathOrUrlReferencingTag: IsNamepathX,
532533
* tagMightHaveNameOrNamepath: IsNamepathX,
534+
* tagMightHaveName: IsNamepathX,
535+
* tagMightHaveNamepath: IsNamepathX,
533536
* getTagStructureForMode: GetTagStructureForMode,
534537
* mayBeUndefinedTypeTag: MayBeUndefinedTypeTag,
535538
* hasValueOrExecutorHasNonEmptyResolveValue: HasValueOrExecutorHasNonEmptyResolveValue,
@@ -618,14 +621,16 @@ const getBasicUtils = (context, {
618621
'isNamepathReferencingTag',
619622
'isNamepathOrUrlReferencingTag',
620623
'tagMightHaveNameOrNamepath',
624+
'tagMightHaveName',
625+
'tagMightHaveNamepath',
621626
]) {
622627
/** @type {IsNamepathX} */
623628
utils[
624-
/** @type {"isNameOrNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNameOrNamepath"} */ (
629+
/** @type {"isNameOrNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNameOrNamepath"|"tagMightHaveName"} */ (
625630
method
626631
)] = (tagName) => {
627632
return jsdocUtils[
628-
/** @type {"isNameOrNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNameOrNamepath"} */
633+
/** @type {"isNameOrNamepathDefiningTag"|"isNamepathReferencingTag"|"isNamepathOrUrlReferencingTag"|"tagMightHaveNameOrNamepath"|"tagMightHaveName"} */
629634
(method)
630635
](tagName);
631636
};

src/jsdocUtils.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,37 @@ const tagMightHaveNameOrNamepath = (tag, tagMap = tagStructure) => {
11451145
namepathTypes.has(/** @type {string} */ (nampathRole));
11461146
};
11471147

1148+
/**
1149+
* @param {string} tag
1150+
* @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
1151+
* @returns {boolean}
1152+
*/
1153+
const tagMightHaveNamepath = (tag, tagMap = tagStructure) => {
1154+
const tagStruct = ensureMap(tagMap, tag);
1155+
1156+
const nampathRole = tagStruct.get('namepathRole');
1157+
1158+
return nampathRole !== false &&
1159+
[
1160+
'namepath-defining',
1161+
'namepath-referencing',
1162+
].includes(/** @type {string} */ (nampathRole));
1163+
};
1164+
1165+
/**
1166+
* @param {string} tag
1167+
* @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
1168+
* @returns {boolean}
1169+
*/
1170+
const tagMightHaveName = (tag, tagMap = tagStructure) => {
1171+
const tagStruct = ensureMap(tagMap, tag);
1172+
1173+
const nampathRole = tagStruct.get('namepathRole');
1174+
1175+
return nampathRole !== false &&
1176+
nampathRole === 'name-defining';
1177+
};
1178+
11481179
/**
11491180
* @param {string} tag
11501181
* @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
@@ -1907,7 +1938,9 @@ export {
19071938
setTagStructure,
19081939
strictNativeTypes,
19091940
tagMightHaveEitherTypeOrNamePosition,
1941+
tagMightHaveName,
19101942
tagMightHaveNameOrNamepath,
1943+
tagMightHaveNamepath,
19111944
tagMightHaveNamePosition,
19121945
tagMightHaveTypePosition,
19131946
tagMissingRequiredTypeOrNamepath,

src/rules/validTypes.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import iterateJsdoc from '../iterateJsdoc.js';
22
import {
33
parse,
4-
// parseName,
4+
parseName,
55
parseNamePath,
66
traverse,
77
tryParse,
@@ -99,6 +99,23 @@ const tryParsePathIgnoreError = (path, mode) => {
9999
return false;
100100
};
101101

102+
/**
103+
* @param {string} name
104+
* @param {import('jsdoc-type-pratt-parser').ParseMode|"permissive"} mode
105+
* @returns {boolean}
106+
*/
107+
const tryParseNameIgnoreError = (name, mode) => {
108+
try {
109+
parseName(name, mode === 'permissive' ? 'jsdoc' : mode);
110+
111+
return true;
112+
} catch {
113+
// Keep the original error for including the whole type
114+
}
115+
116+
return false;
117+
};
118+
102119
// eslint-disable-next-line complexity
103120
export default iterateJsdoc(({
104121
context,
@@ -350,12 +367,12 @@ export default iterateJsdoc(({
350367
}
351368

352369
// VALID NAME/NAMEPATH
353-
const hasNameOrNamepathPosition = (
370+
const hasNamepathPosition = (
354371
tagMustHaveNamePosition !== false ||
355-
utils.tagMightHaveNameOrNamepath(tag.tag)
372+
utils.tagMightHaveNamepath(tag.tag)
356373
) && Boolean(tag.name);
357374

358-
if (hasNameOrNamepathPosition) {
375+
if (hasNamepathPosition) {
359376
if (mode !== 'jsdoc' && tag.tag === 'template') {
360377
if (!tryParsePathIgnoreError(
361378
// May be an issue with the commas of
@@ -373,6 +390,12 @@ export default iterateJsdoc(({
373390
}
374391
}
375392

393+
const hasNamePosition = utils.tagMightHaveName(tag.tag) &&
394+
Boolean(tag.name);
395+
if (hasNamePosition && !tryParseNameIgnoreError(tag.name, mode)) {
396+
report(`Syntax error in name: ${tag.name}`, null, tag);
397+
}
398+
376399
for (const inlineTag of tag.inlineTags) {
377400
if (inlineTags.has(inlineTag.tag) && !inlineTag.text && !inlineTag.namepathOrURL) {
378401
report(`Inline tag "${inlineTag.tag}" missing content`, null, tag);

test/rules/assertions/validTypes.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,41 @@ export default /** @type {import('../index.js').TestCases} */ ({
233233
errors: [
234234
{
235235
line: 3,
236-
message: 'Syntax error in namepath: UserStr%ng',
236+
message: 'Syntax error in name: UserStr%ng',
237237
},
238238
],
239239
},
240+
{
241+
code: `
242+
/**
243+
* @typedef {string} module:abc/def
244+
*/
245+
`,
246+
errors: [
247+
{
248+
line: 3,
249+
message: 'Syntax error in name: module:abc/def',
250+
},
251+
],
252+
},
253+
{
254+
code: `
255+
/**
256+
* @typedef {string} module:abc/def
257+
*/
258+
`,
259+
errors: [
260+
{
261+
line: 3,
262+
message: 'Syntax error in name: module:abc/def',
263+
},
264+
],
265+
settings: {
266+
jsdoc: {
267+
mode: 'permissive',
268+
},
269+
},
270+
},
240271
{
241272
code: `
242273
/**

0 commit comments

Comments
 (0)