Skip to content

Commit 22e3274

Browse files
committed
fix(require-template, check-template-names): check @Property tags; fixes #1269
1 parent 736a23b commit 22e3274

File tree

6 files changed

+156
-39
lines changed

6 files changed

+156
-39
lines changed

docs/rules/check-template-names.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ export type Extras<D, U> = [D, U | undefined];
8787
* @typedef {[D, U | undefined]} Extras
8888
*/
8989
// Message: @template V not in use
90+
91+
/**
92+
* @template D
93+
* @template V
94+
* @typedef Pairs
95+
* @property {V} foo
96+
*/
97+
// Message: @template D not in use
9098
````
9199

92100

@@ -130,5 +138,13 @@ export type Extras<D, U, V> = [D, U, V | undefined];
130138
* @typedef Foo
131139
* @prop {string} bar
132140
*/
141+
142+
/**
143+
* @template D
144+
* @template V
145+
* @typedef Pairs
146+
* @property {D} foo
147+
* @property {V} bar
148+
*/
133149
````
134150

docs/rules/require-template.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ export type Pairs<D, V> = [D, V | undefined];
106106
*/
107107
// "jsdoc/require-template": ["error"|"warn", {"requireSeparateTemplates":true}]
108108
// Message: Missing separate @template for V
109+
110+
/**
111+
* @template X
112+
* @typedef {object} Pairs
113+
* @property {D} foo
114+
* @property {X} bar
115+
*/
116+
// Message: Missing @template D
109117
````
110118

111119

@@ -148,5 +156,13 @@ export type Extras<D, U, V> = [D, U, V | undefined];
148156
* @typedef Foo
149157
* @prop {string} bar
150158
*/
159+
160+
/**
161+
* @template D
162+
* @template V
163+
* @typedef {object} Pairs
164+
* @property {D} foo
165+
* @property {V} bar
166+
*/
151167
````
152168

src/rules/checkTemplateNames.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,40 @@ export default iterateJsdoc(({
6464
return;
6565
}
6666

67-
const potentialType = typedefTags[0].type;
67+
/**
68+
* @param {string} potentialType
69+
*/
70+
const checkForUsedTypes = (potentialType) => {
71+
let parsedType;
72+
try {
73+
parsedType = mode === 'permissive' ?
74+
tryParseType(/** @type {string} */ (potentialType)) :
75+
parseType(/** @type {string} */ (potentialType), mode);
76+
} catch {
77+
return;
78+
}
6879

69-
let parsedType;
70-
try {
71-
parsedType = mode === 'permissive' ?
72-
tryParseType(/** @type {string} */ (potentialType)) :
73-
parseType(/** @type {string} */ (potentialType), mode)
74-
} catch {
75-
// Todo: Should handle types in @prop/erty
76-
return;
77-
}
80+
traverse(parsedType, (nde) => {
81+
const {
82+
type,
83+
value,
84+
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
85+
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
86+
usedNames.add(value);
87+
}
88+
});
89+
};
7890

79-
traverse(parsedType, (nde) => {
80-
const {
81-
type,
82-
value,
83-
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
84-
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
85-
usedNames.add(value);
86-
}
87-
});
91+
const potentialTypedefType = typedefTags[0].type;
92+
checkForUsedTypes(potentialTypedefType);
93+
94+
const tagName = /** @type {string} */ (utils.getPreferredTagName({
95+
tagName: 'property',
96+
}));
97+
const propertyTags = utils.getTags(tagName);
98+
for (const propertyTag of propertyTags) {
99+
checkForUsedTypes(propertyTag.type);
100+
}
88101

89102
for (const tag of templateTags) {
90103
const {name} = tag;

src/rules/requireTemplate.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,32 +75,50 @@ export default iterateJsdoc(({
7575
return;
7676
}
7777

78-
const potentialType = typedefTags[0].type;
78+
const usedNameToTag = new Map();
7979

80-
let parsedType;
81-
try {
82-
parsedType = mode === 'permissive' ?
83-
tryParseType(/** @type {string} */ (potentialType)) :
84-
parseType(/** @type {string} */ (potentialType), mode)
85-
} catch {
86-
// Todo: Should handle types in @prop/erty
87-
return;
88-
}
89-
90-
traverse(parsedType, (nde) => {
91-
const {
92-
type,
93-
value,
94-
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
95-
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
96-
usedNames.add(value);
80+
/**
81+
* @param {import('comment-parser').Spec} potentialTag
82+
*/
83+
const checkForUsedTypes = (potentialTag) => {
84+
let parsedType;
85+
try {
86+
parsedType = mode === 'permissive' ?
87+
tryParseType(/** @type {string} */ (potentialTag.type)) :
88+
parseType(/** @type {string} */ (potentialTag.type), mode)
89+
} catch {
90+
return;
9791
}
98-
});
92+
93+
traverse(parsedType, (nde) => {
94+
const {
95+
type,
96+
value,
97+
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
98+
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
99+
usedNames.add(value);
100+
if (!usedNameToTag.has(value)) {
101+
usedNameToTag.set(value, potentialTag);
102+
}
103+
}
104+
});
105+
};
106+
107+
const potentialTypedef = typedefTags[0];
108+
checkForUsedTypes(potentialTypedef);
109+
110+
const tagName = /** @type {string} */ (utils.getPreferredTagName({
111+
tagName: 'property',
112+
}));
113+
const propertyTags = utils.getTags(tagName);
114+
for (const propertyTag of propertyTags) {
115+
checkForUsedTypes(propertyTag);
116+
}
99117

100118
// Could check against whitelist/blacklist
101119
for (const usedName of usedNames) {
102120
if (!templateNames.includes(usedName)) {
103-
report(`Missing @template ${usedName}`, null, typedefTags[0]);
121+
report(`Missing @template ${usedName}`, null, usedNameToTag.get(usedName));
104122
}
105123
}
106124
}, {

test/rules/assertions/checkTemplateNames.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@ export default {
142142
},
143143
],
144144
},
145+
{
146+
code: `
147+
/**
148+
* @template D
149+
* @template V
150+
* @typedef Pairs
151+
* @property {V} foo
152+
*/
153+
`,
154+
errors: [
155+
{
156+
line: 3,
157+
message: '@template D not in use',
158+
},
159+
],
160+
},
145161
],
146162
valid: [
147163
{
@@ -201,5 +217,16 @@ export default {
201217
*/
202218
`,
203219
},
220+
{
221+
code: `
222+
/**
223+
* @template D
224+
* @template V
225+
* @typedef Pairs
226+
* @property {D} foo
227+
* @property {V} bar
228+
*/
229+
`,
230+
},
204231
],
205232
};

test/rules/assertions/requireTemplate.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ export default {
155155
}
156156
],
157157
},
158+
{
159+
code: `
160+
/**
161+
* @template X
162+
* @typedef {object} Pairs
163+
* @property {D} foo
164+
* @property {X} bar
165+
*/
166+
`,
167+
errors: [
168+
{
169+
line: 5,
170+
message: 'Missing @template D',
171+
},
172+
],
173+
},
158174
],
159175
valid: [
160176
{
@@ -213,5 +229,16 @@ export default {
213229
*/
214230
`,
215231
},
232+
{
233+
code: `
234+
/**
235+
* @template D
236+
* @template V
237+
* @typedef {object} Pairs
238+
* @property {D} foo
239+
* @property {V} bar
240+
*/
241+
`,
242+
},
216243
],
217244
};

0 commit comments

Comments
 (0)