1 |
|
2 | const doctrine = require("doctrine");
|
3 | const util = require('util')
|
4 |
|
5 | module.exports = {
|
6 | create (context) {
|
7 |
|
8 |
|
9 | // rule implementation ...
|
10 | const source = context.getSourceCode();
|
11 |
|
12 | // console.log(source)
|
13 | /**
|
14 | * Report the error message
|
15 | * @param {ASTNode} node node to report
|
16 | * @returns {void}
|
17 | */
|
18 | function report(node) {
|
19 | context.report({node, message: "Missing JSDoc comment."});
|
20 | }
|
21 |
|
22 | function checkJSDoc(nodes) {
|
23 | nodes.forEach(jsdocNode => {
|
24 | // console.log(jsdocNode.value)
|
25 | const leadingComments = jsdocNode.leadingComments
|
26 | if (!leadingComments) {
|
27 | context.report({node: jsdocNode, message: "Missing JSDoc."});
|
28 | return
|
29 | }
|
30 | const l = leadingComments.length
|
31 | const comment = leadingComments[l - 1].value
|
32 | const params = Object.create(null)
|
33 |
|
34 | let jsdoc, hasReturn
|
35 | try {
|
36 | jsdoc = doctrine.parse(comment, {
|
37 | strict: true,
|
38 | unwrap: true,
|
39 | sloppy: true
|
40 | });
|
41 | } catch (ex) {
|
42 |
|
43 | if (/braces/i.test(ex.message)) {
|
44 | context.report({node: jsdocNode, message: "JSDoc type missing brace."});
|
45 | } else {
|
46 | context.report({node: jsdocNode, message: "JSDoc syntax error."});
|
47 | }
|
48 |
|
49 | return
|
50 | }
|
51 |
|
52 | // check for functions missing description
|
53 | if (!jsdoc.description) {
|
54 | context.report({
|
55 | node: jsdocNode,
|
56 | message: "Missing JSDoc description for function."
|
57 | });
|
58 | }
|
59 |
|
60 | jsdoc.tags.forEach(tag => {
|
61 | switch (tag.title.toLowerCase()) {
|
62 | case 'param':
|
63 | if (!tag.type) {
|
64 | context.report({
|
65 | node: jsdocNode,
|
66 | message: "Missing JSDoc parameter type for '{{name}}'.",
|
67 | data: {name: tag.name}
|
68 | });
|
69 | }
|
70 |
|
71 | if (!tag.description) {
|
72 | context.report({
|
73 | node: jsdocNode,
|
74 | message: "Missing JSDoc parameter description for '{{name}}'.",
|
75 | data: {name: tag.name}
|
76 | });
|
77 | }
|
78 |
|
79 | if (params[tag.name]) {
|
80 | context.report({
|
81 | node: jsdocNode,
|
82 | message: "Duplicate JSDoc parameter '{{name}}'.",
|
83 | data: {name: tag.name}
|
84 | });
|
85 | } else if (tag.name.indexOf(".") === -1) {
|
86 | params[tag.name] = 1;
|
87 | }
|
88 | break;
|
89 | // case 'return':
|
90 | // case 'returns': // 如果有@returns,检测@returns 是否正确
|
91 | // hasReturn = true
|
92 | // if(!tag.type){
|
93 | // context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
|
94 | // }
|
95 | // if (!tag.description) {
|
96 | // context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
|
97 | // }
|
98 | // break;
|
99 | }
|
100 | })
|
101 |
|
102 |
|
103 | // check for functions missing @returns
|
104 | /*if(!hasReturn){
|
105 | context.report({
|
106 | node: jsdocNode,
|
107 | message: "Missing JSDoc @{{returns}} for function."
|
108 | })
|
109 | }*/
|
110 |
|
111 |
|
112 | // check the parameters
|
113 | const jsdocParams = Object.keys(params);
|
114 |
|
115 | if (jsdocNode.value && jsdocNode.value.params) {
|
116 | jsdocNode.value.params.forEach((param, i) => {
|
117 | if (param.type === "AssignmentPattern") {
|
118 | param = param.left;
|
119 | }
|
120 |
|
121 | const name = param.name;
|
122 |
|
123 | // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
|
124 | if (param.type === "Identifier") {
|
125 | if (jsdocParams[i] && (name !== jsdocParams[i])) {
|
126 | context.report({
|
127 | node: jsdocNode,
|
128 | message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
|
129 | data: {
|
130 | name,
|
131 | jsdocName: jsdocParams[i]
|
132 | }
|
133 | });
|
134 | } else if (!params[name]) {
|
135 | context.report({
|
136 | node: jsdocNode, message: "Missing JSDoc for parameter '{{name}}'.", data: {
|
137 | name
|
138 | }
|
139 | });
|
140 | }
|
141 | }
|
142 | });
|
143 | }
|
144 | })
|
145 | }
|
146 |
|
147 | const default_opt = ['created', 'data']
|
148 | // console.log(context.options[0]['obj-doc'])
|
149 | let opt = context.options[0] && context.options[0]['ignoreMethods'] || []
|
150 |
|
151 | opt = default_opt.concat(opt)
|
152 |
|
153 | const blacklist = new Set();
|
154 |
|
155 | opt.forEach(item => blacklist.add(item))
|
156 |
|
157 | /**
|
158 | * 获取需要检测的method节点
|
159 | * @param node
|
160 | * @returns {*}
|
161 | */
|
162 | function getJSDocNode(node) {
|
163 |
|
164 | let methodNodes = []
|
165 | if (util.isArray(node.properties)) {
|
166 | node.properties.forEach(item => {
|
167 | if (item.key && blacklist.has(item.key.name)) return;
|
168 | if (item.method) methodNodes.push(item)
|
169 | })
|
170 | }
|
171 |
|
172 | return methodNodes
|
173 | }
|
174 |
|
175 |
|
176 | return {
|
177 | ReturnStatement: function (node) {
|
178 | // at a ReturnStatement node while going down
|
179 | // console.log(node)
|
180 | },
|
181 | // at a function expression node while going up:
|
182 | // "FunctionExpression:exit": checkLastSegment,
|
183 | // "ArrowFunctionExpression:exit": checkLastSegment,
|
184 | ObjectExpression: function (node) {
|
185 | let methodNodes = getJSDocNode(node)
|
186 | if (methodNodes) {
|
187 | checkJSDoc(methodNodes)
|
188 | }
|
189 | },
|
190 | onCodePathStart: function (codePath, node) {
|
191 | // console.log(node)
|
192 |
|
193 | // at the start of analyzing a code path
|
194 | },
|
195 | onCodePathEnd: function (codePath, node) {
|
196 | // at the end of analyzing a code path
|
197 | }
|
198 | }
|
199 | }
|
200 | } |
\ | No newline at end of file |