1 | /**
|
2 | * @fileoverview Validates JSDoc comments are syntactically correct
|
3 | * @author Nicholas C. Zakas
|
4 | */
|
5 | ;
|
6 |
|
7 | //------------------------------------------------------------------------------
|
8 | // Requirements
|
9 | //------------------------------------------------------------------------------
|
10 |
|
11 | var doctrine = require("doctrine");
|
12 |
|
13 | //------------------------------------------------------------------------------
|
14 | // Rule Definition
|
15 | //------------------------------------------------------------------------------
|
16 |
|
17 | module.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 | };
|