UNPKG

4.67 kBJavaScriptView Raw
1/**
2 * @fileoverview Validates JSDoc comments are syntactically correct
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11var doctrine = require("doctrine");
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17module.exports = function(context) {
18
19 var options = context.options[0] || {},
20 prefer = options.prefer || {};
21
22 //--------------------------------------------------------------------------
23 // Helpers
24 //--------------------------------------------------------------------------
25
26 /**
27 * Validate the JSDoc node and output warnings if anything is wrong.
28 * @param {ASTNode} node The AST node to check.
29 * @returns {void}
30 */
31 function checkJSDoc(node) {
32 var jsdocNode = context.getJSDocComment(node),
33 hasReturns = false,
34 hasConstructor = false,
35 params = Object.create(null),
36 jsdoc;
37
38 // make sure only to validate JSDoc comments
39 if (jsdocNode) {
40
41 try {
42 jsdoc = doctrine.parse(jsdocNode.value, {
43 strict: true,
44 unwrap: true,
45 sloppy: true
46 });
47 } catch (ex) {
48
49 if (/braces/i.test(ex.message)) {
50 context.report(jsdocNode, "JSDoc type missing brace.");
51 } else {
52 context.report(jsdocNode, "JSDoc syntax error.");
53 }
54
55 return;
56 }
57
58 jsdoc.tags.forEach(function(tag) {
59
60 switch (tag.title) {
61
62 case "param":
63 if (!tag.type) {
64 context.report(jsdocNode, "Missing JSDoc parameter type for '{{name}}'.", { name: tag.name });
65 }
66
67 if (!tag.description) {
68 context.report(jsdocNode, "Missing JSDoc parameter description for '{{name}}'.", { name: tag.name });
69 }
70
71 if (params[tag.name]) {
72 context.report(jsdocNode, "Duplicate JSDoc parameter '{{name}}'.", { name: tag.name });
73 } else {
74 params[tag.name] = 1;
75 }
76 break;
77
78 case "return":
79 case "returns":
80 hasReturns = true;
81 if (!tag.type) {
82 context.report(jsdocNode, "Missing JSDoc return type.");
83 }
84
85 if (tag.type.name !== "void" && !tag.description) {
86 context.report(jsdocNode, "Missing JSDoc return description.");
87 }
88 break;
89
90 case "constructor":
91 hasConstructor = true;
92 break;
93
94 }
95
96 // check tag preferences
97 if (prefer.hasOwnProperty(tag.title)) {
98 context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] });
99 }
100
101 });
102
103 // check for functions missing @returns
104 if (!hasReturns && !hasConstructor) {
105 context.report(jsdocNode, "Missing JSDoc @returns for function.");
106 }
107
108 // check the parameters
109 var jsdocParams = Object.keys(params);
110
111 node.params.forEach(function(param, i) {
112 var name = param.name;
113
114 if (jsdocParams[i] && (name !== jsdocParams[i])) {
115 context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", {
116 name: name,
117 jsdocName: jsdocParams[i]
118 });
119 } else if (!params[name]) {
120 context.report(jsdocNode, "Missing JSDoc for parameter '{{name}}'.", {
121 name: name
122 });
123 }
124 });
125
126 }
127
128 }
129
130 //--------------------------------------------------------------------------
131 // Public
132 //--------------------------------------------------------------------------
133
134 return {
135 "FunctionExpression": checkJSDoc,
136 "FunctionDeclaration": checkJSDoc
137 };
138
139};