This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bandolier-template/commands/codedoc.js

230 lines
5.7 KiB

// you may not need all of these but they come up a lot
import fs from "fs";
import assert from "assert";
import logging from '../lib/logging.js';
import { mkdir, mkdir_to } from "../lib/builderator.js";
import glob from "fast-glob";
import path from "path";
import template from "lodash/template.js";
import * as acorn from "acorn";
import * as acorn_walk from "acorn-walk";
const log = logging.create(import.meta.url);
export const description = "Describe your command here."
// your command uses the npm package commander's options format
export const options = [
]
// example of a positional argument, it's the 1st argument to main
export const argument = ["source", "source directory"];
// put required options in the required variable
export const required = [
["--output <string>", "Save to file rather than stdout."],
]
// handy function for checking things are good and aborting
const check = (test, fail_message) => {
if(!test) {
log.error(fail_message);
process.exit(1);
}
}
const dump = (obj) => {
return JSON.stringify(obj, null, 4);
}
class ParseWalker {
constructor(comments) {
this.comments = comments;
this.exported = [];
}
handle_class(root) {
const new_class = {
isa: "class",
name: root.declaration.id.name,
line_start: root.loc.start.line,
methods: [],
}
new_class.comment = this.find_comment(new_class.line_start);
acorn_walk.simple(root, {
ClassDeclaration: (cls_node) => {
assert(cls_node.id.name === new_class.name, "Name of class changed!");
this.exported.push(new_class);
},
MethodDefinition: (meth_node) => {
const new_method = {
isa: "method",
static: meth_node.static,
async: meth_node.value.async,
generator: meth_node.value.generator,
name: meth_node.key.name,
line_start: meth_node.loc.start.line,
params: this.handle_params(meth_node.value.params),
comment: this.find_comment(meth_node.loc.start.line)
}
new_class.methods.push(new_method);
}
});
}
handle_params(param_list) {
const result = [];
for(let param of param_list) {
acorn_walk.simple(param, {
Identifier: (_node) => {
result.push({isa: "identifier", name: _node.name});
},
AssignmentPattern: (_node) => {
result.push({
isa: "assignment",
name: _node.left.name,
right: {
type: _node.right.type.toLowerCase(),
raw: _node.right.raw,
value: _node.right.value,
name: _node.right.name,
},
});
}
});
}
return result;
}
add_export(id, exp) {
exp.name = id.name;
exp.line_start = id.loc.start.line;
exp.comment = this.find_comment(exp.line_start);
this.exported.push(exp);
}
handle_arrow_func(id, arrow) {
this.add_export(id, {
isa: "function",
async: arrow.async,
generator: arrow.generator,
expression: arrow.expression,
params: this.handle_params(arrow.params),
});
}
handle_variable(root) {
const declare = root.declaration.declarations[0];
// console.log("VARIABLE", declare);
const id = declare.id;
if(declare.init.type === "ArrowFunctionExpression") {
this.handle_arrow_func(id, declare.init);
} else {
acorn_walk.simple(root, {
Literal: (_node) => {
this.add_export(id, {
isa: _node.type.toLowerCase(),
value: _node.value,
raw: _node.raw
});
},
CallExpression: (_node) => {
this.add_export(id, {
isa: _node.type.toLowerCase(),
identifier: _node.callee.name,
arguments: this.handle_params(_node.arguments)
});
},
MemberExpression: (_node) => {
this.add_export(id, {
isa: _node.type.toLowerCase(),
object: _node.object.name,
property: _node.property.name,
computed: _node.computed,
options: _node.optional,
});
}
});
}
}
/**
* Find the nearest comment to this line, giving
* about 2 lines of slack.
*/
find_comment(line) {
for(let c of this.comments) {
const distance = c.end - line;
if(!c.found && distance < 0 && distance > -3) {
c.found = true;
return c;
}
}
return undefined;
}
handle_export(_node) {
switch(_node.declaration.type) {
case "ClassDeclaration":
this.handle_class(_node);
break;
case "VariableDeclaration": {
this.handle_variable(_node);
break;
}
default:
console.log(">>>", _node.declaration.type);
}
}
}
export const main = async (arg, opts) => {
const code = fs.readFileSync(arg);
mkdir(opts.output);
let comments = [];
const acorn_opts = {
sourceType: "module",
ecmaVersion: "2023",
locations: true,
sourceFile: arg,
ranges: true,
onComment: comments
}
const parsed = acorn.parse(code, acorn_opts);
comments = comments.filter(c => c.type === "Block").map(c => {
return {
start: c.loc.start.line,
end: c.loc.end.line,
value: c.value,
type: "comment",
found: false,
}
});
const walker = new ParseWalker(comments);
// acorn is stupid and they grab a reference to the functions so that _removes_
// this from the object, instead of just...calling walker.function() like a normal person
acorn_walk.simple(parsed, {
ExportNamedDeclaration: (_node) => walker.handle_export(_node),
});
console.log(dump({
exports: walker.exported,
orphan_comments: walker.comments.filter(c => !c.found)
}));
process.exit(0);
}