-
Notifications
You must be signed in to change notification settings - Fork 3.5k
/
Copy pathcomment-analyzer.mjs
164 lines (150 loc) · 5.56 KB
/
comment-analyzer.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* Script to parse the JSON output of TypeDoc
* and check for missing comments on public
* members.
* @see https://github.com/TypeStrong/typedoc/blob/master/src/lib/models/reflections/kind.ts#L7
*/
const KINDS = {
0x1: "PROJECT",
0x2: "MODULE",
0x4: "NAMESPACE",
0x8: "ENUM",
0x10: "ENUM_MEMBER",
0x20: "VARIABLE",
0x40: "FUNCTION",
0x80: "CLASS",
0x100: "INTERFACE",
0x200: "CONSTRUCTOR",
0x400: "PROPERTY",
0x800: "METHOD",
0x1000: "CALL_SIGNATURE",
0x2000: "INDEX_SIGNATURE",
0x4000: "CONSTRUCTOR_SIGNATURE",
0x8000: "PARAMETER",
0x10000: "TYPE_LITERAL",
0x20000: "TYPE_PARAMETER",
0x40000: "ACCESSOR",
0x80000: "GET_SIGNATURE",
0x100000: "SET_SIGNATURE",
0x200000: "TYPE_ALIAS",
0x400000: "REFERENCE",
};
const kindToCheckFunction = {
CLASS: (child, parent) => checkBaseComments("CLASS", child, parent),
PROPERTY: checkPropertyComments,
METHOD: checkMethodComments,
INTERFACE: (child, parent) => checkBaseComments("INTERFACE", child, parent),
};
const TestResultType = {
PASS: "PASS",
MISSING_COMMENT: "Missing comment",
MISSING_PARAM_COMMENT: "Missing param documentation",
};
function getKind(child) {
const kind = KINDS[child.kind];
if (kind === undefined) {
throw new Error(`Unknown kind: ${child.kind}`);
}
return kind;
}
function checkBaseComments(type, child, parent) {
return traverseChildrenLookingForComments(child, parent);
}
function isInternal(child) {
return child.comment?.modifierTags?.find((tag) => tag === "@internal");
}
function traverseChildrenLookingForComments(child, parent, isSignature = false) {
if (!child) {
return;
}
const result = {
componentName: child.name,
componentType: getKind(child),
parentName: parent?.name,
fileName: child.sources[0]?.fileName + ":" + child.sources[0]?.line + ":" + child.sources[0]?.character,
url: child.sources[0]?.url,
};
// underscored names are ignored
if (child.name.startsWith("_")) {
result.result = TestResultType.PASS;
return result;
}
if (isInternal(child)) {
result.result = TestResultType.PASS;
return result;
}
// check if parent is a class and this is a method
if (isInternal(parent) && (getKind(parent) === "CLASS" || getKind(parent) === "INTERFACE") && !child.comment) {
result.result = TestResultType.PASS;
return result;
}
if (child.comment) {
if (child.parameters) {
result.missingParamNames = result.missingParamNames || [];
for (const param of child.parameters) {
if (!param.comment) {
result.missingParamNames.push(param.name);
result.result = TestResultType.MISSING_PARAM_COMMENT;
}
}
if (result.result === TestResultType.MISSING_PARAM_COMMENT) return result;
}
result.result = TestResultType.PASS;
return result;
} else if (child.signatures || child.type?.declaration?.signatures) {
const signatureResult = traverseChildrenLookingForComments(child.signatures?.length > 0 ? child.signatures[0] : child.type.declaration.signatures[0], parent, true);
// const signatureResults = child.signatures[0]
// .map((sig) => traverseChildrenLookingForComments(sig, parent, true))
// .flat()
// .filter((sigResult) => {
// return sigResult.result !== TestResultType.PASS;
// });
return signatureResult;
}
result.result = TestResultType.MISSING_COMMENT;
return result;
}
function isVisible(child, parent) {
const parentKind = getKind(parent);
return child.flags.isPublic || (!child.flags.isPrivate && !child.flags.isProtected && parentKind === "INTERFACE");
}
function checkPropertyComments(child, parent) {
if (isVisible(child, parent)) {
return traverseChildrenLookingForComments(child, parent);
}
}
function checkMethodComments(child, parent) {
if (isVisible(child, parent)) {
return traverseChildrenLookingForComments(child, parent);
}
}
function sourceInNodeModules(child) {
return child.sources && child.sources[0].fileName.includes("node_modules");
}
function addErrorToArray(error, errorArray) {
if (error) {
errorArray.push(error);
}
// console.log(error);
}
// Define a recursive function to iterate over the children
function checkCommentsOnChild(child, parent, namesToCheck = []) {
if (isInternal(child)) return [];
const errors = [];
// Check if the child is a declaration
if ((namesToCheck.length === 0 || namesToCheck.includes(child.name)) && !sourceInNodeModules(child)) {
const childKind = getKind(child);
if (childKind in kindToCheckFunction) {
addErrorToArray(kindToCheckFunction[childKind](child, parent), errors);
}
}
// If the child has its own children, recursively call this function on them
if (child.children) {
child.children.forEach((grandchild) => addErrorToArray(checkCommentsOnChild(grandchild, child, namesToCheck), errors));
}
// console.log(errors.flat().filter((e) => e.result !== TestResultType.PASS));
return errors.flat().filter((e) => e.result !== TestResultType.PASS);
}
export const commentAnalyzer = (data, namesToCheck = []) => {
return checkCommentsOnChild(data, null, namesToCheck);
};