export default class JsonCompiler { protected re_description_stop_points = new RegExp(/@(param|return|throws)/, "g"); protected re_export = new RegExp(/export.+/, "g"); protected re_for_all_members = new RegExp(/\/\*\*((\s)+\*.*)+?\s+\*\/\n(export)?( +)?(export|constructor|interface|public|protected|private) ?(((\w+ {(\n.+\?:.+;)+)\n})|(((\w+ )*{)|(\w+.+;)|((async|function)? ?(\w+)?\(.+\)?{)|((async|function)? ?(\w+)?\(((\n? + .+:.+,?)+({|(\n( +)?\).+{))))))/, "g"); protected re_ignore_block = new RegExp(/@ignore/, "g"); protected re_ignore_line = new RegExp(/@ignore-line/); protected re_is_class = new RegExp(/\* @class/); protected re_is_enum = new RegExp(/@enum +\w+/); protected re_is_function = new RegExp(/@(function|func|method) +\w+/); protected re_is_interface = new RegExp(/@interface +\w+/); protected re_is_const = new RegExp(/export? ?const +\w+(: +\w+)? += +?.+/, "g"); protected re_is_method = new RegExp(/.+(static|public|protected|private)( async)? \w+\((\n.+)?(\n +\))?.+((\n? + .+:.+,?)+{)?/); protected re_is_constructor = new RegExp(/.+constructor\((.+)?\)?/); protected re_is_property = new RegExp(/@property/); protected re_members_only = new RegExp(/\/\/\/ +@members-only/); protected re_namespace = new RegExp(/@memberof.+/); protected re__member_names = "@(class|enum|function|func|interface|method|module)";
protected current_file: string = "";
protected decoder: TextDecoder = new TextDecoder();
protected parsed: any = {};
protected parsing_members_only_file = false;
public compile(files: string[]): any { this.parsed = {};
files.forEach(file => { this.current_file = file; let fileContentsRaw = Deno.readFileSync(file); let fileContents = this.decoder.decode(fileContentsRaw);
if (this.re_members_only.test(fileContents)) { this.parsing_members_only_file = true; this.parseMembersOnlyFile(fileContents); return; }
this.parsing_members_only_file = false; this.parseClassFile(fileContents); });
return this.parsed; }
public getSection(annotation: string, docBlock: string): any { let re = new RegExp( "\\* " + annotation + "\n?.+((\n +\\* +)[^@].+)*(?:(\n +\\*?\n? +\\* + .*)+)?", "g" ); let matches = docBlock.match(re); let ret: any = {};
if (!matches) { return null; }
if (matches.length <= 0) { return null; }
if (annotation == "@description") { let description = []; matches.forEach(text => { let textBlockByLine = text.split("\n"); textBlockByLine.shift(); description = this.getDescription(textBlockByLine.join("\n")); }); return description; }
let arrayedAnnotations = ["@returns", "@return", "@throws", "@throw"]; if (arrayedAnnotations.indexOf(annotation) != -1) { let annotationBlocks = []; matches.forEach(text => { let textBlockByLine = text.split("\n"); textBlockByLine.shift(); let description = this.getDescription(textBlockByLine.join("\n")); let parsedAnnotation = this.getAnnotation(annotation, text);
annotationBlocks.push({ description: description, annotation: parsedAnnotation }); }); return annotationBlocks; }
let annotationBlocks = {}; matches.forEach(text => { let textBlockByLine = text.split("\n"); textBlockByLine.shift(); let name = this.getMemberName(text, annotation); let description = this.getDescription(textBlockByLine.join("\n")); let parsedAnnotation = this.getAnnotation(annotation, text);
annotationBlocks[name] = { name: name, description: description, annotation: parsedAnnotation }; });
return annotationBlocks; }
protected getAndCreateNamespace(docBlock: string): string { if (!this.re_namespace.test(docBlock)) { return; }
let reNamespaceResults = docBlock.match(this.re_namespace); let currentNamespace = null; reNamespaceResults.forEach(fileLine => { if (currentNamespace) { return; } if (this.re_ignore_line.test(fileLine)) { return; } currentNamespace = fileLine .trim() .replace(/\*? ?@memberof +/, "") .trim(); });
if (!this.parsed.hasOwnProperty(currentNamespace)) { this.parsed[currentNamespace] = {}; }
return currentNamespace; }
protected getAnnotation(annotation: string, docBlock: string): any { let re = new RegExp(annotation + ".+", "g"); let match = docBlock.match(re); let line = { line: null, data_type: null, name: null };
if (match) { let lineParts = match[0].split(" "); line.line = match[0]; line.data_type = lineParts[1]; line.name = lineParts[2] ? lineParts[2] : null; }
return line; }
protected getDescription(textBlock: string): string[] { let textBlockByLine = textBlock.split("\n"); let result = ""; let endOfDescription = false;
textBlockByLine.forEach(line => { if (endOfDescription) { return; }
if (line.trim() == "/**") { line = line.trim().replace("/**", ""); }
if (this.re_description_stop_points.test(line) || line.trim() == "*/") { endOfDescription = true; return; }
result += `${line}\n`; });
return this.getDescriptionInParagraphs(result); }
protected getDescriptionInParagraphs(textBlock: string): string[] { let textBlockInLines = textBlock.split("\n");
textBlockInLines = textBlockInLines.map(line => { if (line.trim() === "*") { return "---para-break---"; } return line.replace(" * ", "").trim(); });
textBlockInLines = textBlockInLines .join("\n") .split("---para-break---") .map(val => { return val.trim(); });
textBlockInLines = textBlockInLines.filter(val => { return val.trim() != ""; });
return textBlockInLines; }
protected getDocBlockDataForConst(text: string): any { return { is_exported: this.isMemberExported("const", text), name: this.getNameOfConst(text), description: this.getSection("@description", text) }; }
protected getDocBlockDataForEnum(text: string): any { let ret: any = { is_exported: this.isMemberExported("enum", text), name: this.getNameOfEnum(text), description: this.getSection("@description", text) };
return ret; }
protected getDocBlockDataForFunction(text: string): any { let ret: any = { is_exported: this.isMemberExported("function", text), name: this.getNameOfFunction(text), description: this.getSection("@description", text), params: this.getSection("@param", text), returns: this.getSection("@return", text), throws: this.getSection("@throws", text), signature: this.getSignatureOfMethod(text) };
return ret; }
protected getDocBlockDataForInterface(text: string): any { return { is_exported: this.isMemberExported("interface", text), name: this.getMemberName(text), description: this.getSection("@description", text), signature: this.getSignatureOfInterface(text) }; }
protected getDocBlockDataForMethod(text: string): any { let signature = this.getSignatureOfMethod(text);
let accessModifier = /constructor/.test(signature) ? "public" : signature.split(" ")[0];
let ret: any = { access_modifier: accessModifier, name: '', description: this.getSection("@description", text), params: this.getSection("@param", text), returns: this.getSection("@return", text), throws: this.getSection("@throws", text), signature: signature, is_async: /async/.test(signature) };
return ret; }
protected getDocBlockDataForProperty(text: string): any { let signature = this.getSignatureOfProperty(text);
let accessModifier = signature.split(" ")[0];
let ret: any = { access_modifier: accessModifier, description: this.getSection("@description", text), annotation: this.getAnnotation("@property", text), signature: signature };
return ret; }
protected getMemberName(text: string, textType?: string): string { let matches = text.match(new RegExp(this.re__member_names + ".+", "g"));
if (matches && matches.length > 0) { let memberName = text .match(new RegExp(this.re__member_names + ".+", "g"))[0] .replace(new RegExp(this.re__member_names + " +?", "g"), "") .trim(); return memberName; }
let textByLine = text.split("\n"); let line; switch (textType) { case "@param": line = textByLine[0]; return line .trim() .replace(/ ?\* /g, "") .trim() .split(" ")[2]; case "method": line = this.getMemberNameMethod(textByLine); return line .trim() .replace(/(public|protected|private) /g, "") .replace(/async /g, "") .split("(")[0]; case "property": line = textByLine[textByLine.length - 1]; return line .trim() .replace(/(public|protected|private) /g, "") .replace(":", "") .split(" ")[0]; default: break; }
return undefined; }
protected getMemberNameInterface(textByLine, index = -1, line = '') { if (index == -1) { index = textByLine.length - 1; }
line = textByLine[index] + line; line = line.trim();
let paren = new RegExp(/\{/, "g"); if (paren.test(line)) { line = line.replace("{", "{\n "); line = line.replace(/;/g, ";\n "); line = line.replace(" }", "}"); return line; }
index = index - 1;
return this.getMemberNameInterface(textByLine, index, line); }
protected getMemberNameMethod(textByLine, index = -1, line = '') { if (index == -1) { index = textByLine.length - 1; }
line = textByLine[index] + line; line = line.trim();
let paren = new RegExp(/\(/, "g"); if (paren.test(line)) { line = line.replace(/,/g, ", "); line = line.replace(/, /g, ", "); return line; }
index = index - 1;
return this.getMemberNameMethod(textByLine, index, line); }
protected getNameOfConst(text: string): string { let textByLine = text.split("\n"); return textByLine[textByLine.length - 1] .trim() .replace("export", "") .replace("const", "") .replace(/: +?/, " ") .trim() .split(" ")[0] }
protected getNameOfEnum(text: string): string { let textByLine = text.split("\n"); return textByLine[textByLine.length - 1] .trim() .replace(/ ?{/, "") .replace(/export +? ?enum +?/, ""); }
protected getNameOfFunction(text: string): string { let signature = this.getSignatureOfMethod(text); return signature .replace(/export +?/, "") .replace(/function +?/, "") .replace(/\(.+/, ""); }
protected getNameOfProperty(text: string): string { let signature = this.getSignatureOfProperty(text); return signature .replace(/(public|private|protected) +/, "") .replace(/\(.+/, ""); }
protected getSignatureOfFunction(text: string): string { let textByLine = text.split("\n"); return textByLine[textByLine.length - 1] .trim() .replace(/ ?{/, "") .replace("}", ""); }
protected getSignatureOfInterface(text: string): string { let textByLine = text.split("\n"); return this.getMemberNameInterface(textByLine); }
protected getSignatureOfMethod(text: string): string { let textByLine = text.split("\n"); let line = this.getMemberNameMethod(textByLine);
return line .trim() .replace(/ ?{/, "") .replace("}", ""); }
protected getSignatureOfProperty(text: string): string { let textByLine = text.split("\n"); return textByLine[textByLine.length - 1].trim().replace(";", ""); }
protected isMemberExported(memberType: string, text: string): boolean { let reMemberName = new RegExp(memberType); if (this.re_export.test(text)) { let exportLine = text.match(this.re_export); if (exportLine && exportLine.length > 0) { if (reMemberName.test(exportLine[0])) { return true; } } }
return false; }
protected parseMembersOnlyFile(fileContents) { let docBlocks = fileContents.match(this.re_for_all_members);
docBlocks.forEach(docBlock => { if (this.re_ignore_block.test(docBlock)) { return; }
if (this.re_is_function.test(docBlock)) { let currentNamespace = this.getAndCreateNamespace(docBlock); let memberName = this.getMemberName(docBlock); let data = this.getDocBlockDataForFunction(docBlock); data.is_function= true; if (!currentNamespace) { data.fully_qualified_name = memberName; this.parsed[memberName] = data; } else { data.fully_qualified_name = currentNamespace + "." + memberName; this.parsed[currentNamespace][memberName] = data; } return; }
if (this.re_is_enum.test(docBlock)) { let currentNamespace = this.getAndCreateNamespace(docBlock); let memberName = this.getMemberName(docBlock); let data = this.getDocBlockDataForEnum(docBlock); data.is_enum = true; if (!currentNamespace) { data.fully_qualified_name = memberName; this.parsed[memberName] = data; } else { data.fully_qualified_name = currentNamespace + "." + memberName; this.parsed[currentNamespace][memberName] = data; } return; }
if (this.re_is_interface.test(docBlock)) { let currentNamespace = this.getAndCreateNamespace(docBlock); let memberName = this.getMemberName(docBlock); let data = this.getDocBlockDataForInterface(docBlock); data.is_interface = true; if (!currentNamespace) { data.fully_qualified_name = memberName; this.parsed[memberName] = data; } else { data.fully_qualified_name = currentNamespace + "." + memberName; this.parsed[currentNamespace][memberName] = data; } return; }
if (this.re_is_const.test(docBlock)) { let currentNamespace = this.getAndCreateNamespace(docBlock); let data = this.getDocBlockDataForConst(docBlock); data.is_const = true; if (!currentNamespace) { data.fully_qualified_name = data.name; this.parsed[data.name] = data; } else { data.fully_qualified_name = currentNamespace + "." + data.name; this.parsed[currentNamespace][data.name] = data; } return; } }); }
protected parseClassFile(fileContents) { let docBlocks = fileContents.match(this.re_for_all_members);
let classMap: any = { fully_qualified_name: null, namespace: null, name: null, description: null, properties: {}, methods: {} };
docBlocks.forEach(docBlock => { if (this.re_ignore_block.test(docBlock)) { return; }
if (this.re_is_class.test(docBlock)) { classMap.namespace = this.getAndCreateNamespace(docBlock); classMap.name = this.getMemberName(docBlock); classMap.description = this.getSection("@description", docBlock); classMap.fully_qualified_name = classMap.namespace ? `${classMap.namespace}.${classMap.name}` : classMap.name; return; }
if (this.re_is_property.test(docBlock)) { let propertyName = this.getMemberName(docBlock, "property"); let data = this.getDocBlockDataForProperty(docBlock); data.name = propertyName; data.fully_qualified_name = classMap.fully_qualified_name + "." + propertyName; classMap.properties[propertyName] = data; }
if (this.re_is_constructor.test(docBlock)) { let methodName = this.getMemberName(docBlock, "method"); let data = this.getDocBlockDataForMethod(docBlock); data.name = 'constructor'; data.fully_qualified_name = classMap.fully_qualified_name + "()"; classMap.methods[methodName] = data; }
if (this.re_is_method.test(docBlock)) { let methodName = this.getMemberName(docBlock, "method"); let data = this.getDocBlockDataForMethod(docBlock); data.name = methodName; data.fully_qualified_name = classMap.fully_qualified_name + "." + methodName; classMap.methods[methodName] = data; } });
if (!classMap.namespace) { this.parsed[classMap.name] = classMap; } else { this.parsed[classMap.namespace][classMap.name] = classMap; } }}