Skip to content

Commit

Permalink
feat(valid-types): Add more type checks
Browse files Browse the repository at this point in the history
  • Loading branch information
l1bbcsg committed Jul 4, 2019
1 parent 84fb449 commit f5c485d
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 20 deletions.
19 changes: 18 additions & 1 deletion src/iterateJsdoc.js
Expand Up @@ -9,11 +9,21 @@ const parseComment = (commentNode, indent) => {
// @see https://github.com/yavorskiy/comment-parser/issues/21
parsers: [
commentParser.PARSERS.parse_tag,
commentParser.PARSERS.parse_type,
(str, data) => {
if (data.tag === 'see') {
// @see can't contain types, only names or links which might be confused with types
return null;
}

return commentParser.PARSERS.parse_type(str, data);
},
(str, data) => {
if (['return', 'returns', 'throws', 'exception'].includes(data.tag)) {
return null;
}
if (data.tag === 'see' && str.match(/{@link.+?}/)) {
return null;
}

return commentParser.PARSERS.parse_name(str, data);
},
Expand Down Expand Up @@ -145,12 +155,19 @@ const getUtils = (
utils.isTagWithType = (tagName) => {
return jsdocUtils.isTagWithType(tagName);
};
utils.isPotentiallyEmptyTypeTag = (tagName) => {
return jsdocUtils.isPotentiallyEmptyTypeTag(tagName);
};

utils.passesEmptyNamepathCheck = (tag) => {
return !tag.name && allowEmptyNamepaths &&
jsdocUtils.isPotentiallyEmptyNamepathTag(tag.tag);
};

utils.isTagWithMandatoryNamepathOrType = (tagName) => {
return jsdocUtils.isTagWithMandatoryNamepathOrType(tagName);
};

utils.hasDefinedTypeReturnTag = (tag) => {
return jsdocUtils.hasDefinedTypeReturnTag(tag);
};
Expand Down
35 changes: 34 additions & 1 deletion src/jsdocUtils.js
Expand Up @@ -186,20 +186,26 @@ const potentiallyEmptyNamepathTags = [
'event',
'callback',
'class', 'constructor',
'extends', 'augments',
'constant', 'const',
'function', 'func', 'method',
'interface',
'member', 'var',
'mixin',
'namespace',
'listens', 'fires', 'emits'
'listens', 'fires', 'emits',
'see',

// GCC syntax allows typedef to be named through variable declaration rather than jsdoc name
'typedef'
];

const isPotentiallyEmptyNamepathTag = (tag) => {
return potentiallyEmptyNamepathTags.includes(tag);
};

let tagsWithTypes = [
'augments', 'extends',
'class',
'constant',
'enum',
Expand Down Expand Up @@ -234,10 +240,35 @@ const tagsWithTypesAliases = [

tagsWithTypes = tagsWithTypes.concat(tagsWithTypesAliases, closureTagsWithTypes);

const potentiallyEmptyTypeTags = [
'class', 'constructor',
'constant', 'const',
'extends', 'augments',
'namespace',
'param', 'arg',
'return', 'returns',
'throws', 'exception',
'yields', 'yield',
'package', 'private', 'protected', 'public', 'static'
];

const isPotentiallyEmptyTypeTag = (tag) => {
return potentiallyEmptyTypeTags.includes(tag);
};

const isTagWithType = (tagName) => {
return tagsWithTypes.includes(tagName);
};

const tagsWithMandatoryNamepathOrType = [
'augments', 'extends',
'param', 'arg',
'typedef'
];
const isTagWithMandatoryNamepathOrType = (tagName) => {
return tagsWithMandatoryNamepathOrType.includes(tagName);
};

const LOOP_STATEMENTS = ['WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement'];

const STATEMENTS_WITH_CHILDREN = [
Expand Down Expand Up @@ -530,6 +561,8 @@ export default {
isNamepathDefiningTag,
isNamepathTag,
isPotentiallyEmptyNamepathTag,
isPotentiallyEmptyTypeTag,
isTagWithMandatoryNamepathOrType,
isTagWithType,
isValidTag,
parseClosureTemplateTag
Expand Down
30 changes: 25 additions & 5 deletions src/rules/validTypes.js
Expand Up @@ -52,6 +52,15 @@ export default iterateJsdoc(({
return true;
};

const hasType = Boolean(tag.type);
const mustHaveType = utils.isTagWithType(tag.tag) && !utils.isPotentiallyEmptyTypeTag(tag.tag);

const hasNamePath = Boolean(tag.name);
const mustHaveNamepath = utils.isNamepathTag(tag.tag) && !utils.passesEmptyNamepathCheck(tag);

const hasEither = hasType || hasNamePath;
const mustHaveEither = utils.isTagWithMandatoryNamepathOrType(tag.tag);

if (tag.tag === 'borrows') {
const thisNamepath = tag.description.replace(asExpression, '');

Expand All @@ -66,13 +75,24 @@ export default iterateJsdoc(({

validTypeParsing(thatNamepath);
}
} else if (utils.isNamepathTag(tag.tag)) {
if (utils.passesEmptyNamepathCheck(tag)) {
} else {
if (mustHaveEither && !hasEither) {
report(`Tag @${tag.tag} must have either a type or namepath`);

return;
}
validTypeParsing(tag.name, tag.tag);
} else if (tag.type && utils.isTagWithType(tag.tag)) {
validTypeParsing(tag.type);

if (hasType) {
validTypeParsing(tag.type);
} else if (mustHaveType) {
report(`Tag @${tag.tag} must have a type`);
}

if (hasNamePath) {
validTypeParsing(tag.name, tag.tag);
} else if (mustHaveNamepath) {
report(`Tag @${tag.tag} must have a namepath`);
}
}
});
}, {
Expand Down
123 changes: 110 additions & 13 deletions test/rules/assertions/validTypes.js
Expand Up @@ -161,14 +161,82 @@ export default {
}
`,
errors: [{
line: 3,
message: 'Syntax error in type: '
line: 2,
message: 'Tag @callback must have a namepath'
}],
settings: {
jsdoc: {
allowEmptyNamepaths: false
}
}
},
{
code: `
/**
* @constant {str%ng}
*/
const FOO = 'foo';
`,
errors: [
{
line: 3,
message: 'Syntax error in type: str%ng'
}
]
},
{
code: `
/**
* @typedef {str%ng} UserString
*/
`,
errors: [
{
line: 3,
message: 'Syntax error in type: str%ng'
}
]
},
{
code: `
/**
* @typedef {string} UserStr%ng
*/
`,
errors: [
{
line: 3,
message: 'Syntax error in type: UserStr%ng'
}
]
},
{
code: `
/**
* @extends
*/
class Bar {};
`,
errors: [
{
line: 2,
message: 'Tag @extends must have either a type or namepath'
}
]
},
{
code: `
/**
* @type
*/
let foo;
`,
errors: [
{
line: 2,
message: 'Tag @type must have a type'
}
]
}
],
valid: [
Expand Down Expand Up @@ -222,16 +290,6 @@ export default {
}
`
},
{
code: `
/**
* @see foo%
*/
function quux() {
}
`
},
{
code: `
/**
Expand All @@ -245,7 +303,7 @@ export default {
{
code: `
/**
* @callback
* @callback foo
*/
function quux() {
Expand Down Expand Up @@ -312,6 +370,45 @@ export default {
}
`
},
{
code: `
/**
* @constant {string}
*/
const FOO = 'foo';
`
},
{
code: `
/**
* @extends Foo
*/
class Bar {};
`
},
{
code: `
/**
* @extends {Foo<String>}
*/
class Bar {};
`
},
{
code: `
/**
* @typedef {number|string} UserDefinedType
*/
`
},
{
code: `
/**
* @typedef {number|string}
*/
let UserDefinedGCCType;
`
}
]
};

0 comments on commit f5c485d

Please sign in to comment.