mirror of
				https://github.com/godotengine/godot.git
				synced 2025-11-04 07:31:16 +00:00 
			
		
		
		
	A template for `jsdoc` that generat the HTML5 public classref. The script can be run via `npm run docs` to print to stdout. You can dry run via `npm run docs -- --d dry-run` or write to file via `npm run docs -- -d /path/to/file.rst` Also update Makefile in `doc/` and add dry run test to CI.
		
			
				
	
	
		
			354 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* eslint-disable strict */
 | 
						|
 | 
						|
'use strict';
 | 
						|
 | 
						|
const fs = require('fs');
 | 
						|
 | 
						|
class JSDoclet {
 | 
						|
	constructor(doc) {
 | 
						|
		this.doc = doc;
 | 
						|
		this.description = doc['description'] || '';
 | 
						|
		this.name = doc['name'] || 'unknown';
 | 
						|
		this.longname = doc['longname'] || '';
 | 
						|
		this.types = [];
 | 
						|
		if (doc['type'] && doc['type']['names']) {
 | 
						|
			this.types = doc['type']['names'].slice();
 | 
						|
		}
 | 
						|
		this.type = this.types.length > 0 ? this.types.join('\\|') : '*';
 | 
						|
		this.variable = doc['variable'] || false;
 | 
						|
		this.kind = doc['kind'] || '';
 | 
						|
		this.memberof = doc['memberof'] || null;
 | 
						|
		this.scope = doc['scope'] || '';
 | 
						|
		this.members = [];
 | 
						|
		this.optional = doc['optional'] || false;
 | 
						|
		this.defaultvalue = doc['defaultvalue'];
 | 
						|
		this.summary = doc['summary'] || null;
 | 
						|
		this.classdesc = doc['classdesc'] || null;
 | 
						|
 | 
						|
		// Parameters (functions)
 | 
						|
		this.params = [];
 | 
						|
		this.returns = doc['returns'] ? doc['returns'][0]['type']['names'][0] : 'void';
 | 
						|
		this.returns_desc = doc['returns'] ? doc['returns'][0]['description'] : null;
 | 
						|
 | 
						|
		this.params = (doc['params'] || []).slice().map((p) => new JSDoclet(p));
 | 
						|
 | 
						|
		// Custom tags
 | 
						|
		this.tags = doc['tags'] || [];
 | 
						|
		this.header = this.tags.filter((t) => t['title'] === 'header').map((t) => t['text']).pop() || null;
 | 
						|
	}
 | 
						|
 | 
						|
	add_member(obj) {
 | 
						|
		this.members.push(obj);
 | 
						|
	}
 | 
						|
 | 
						|
	is_static() {
 | 
						|
		return this.scope === 'static';
 | 
						|
	}
 | 
						|
 | 
						|
	is_instance() {
 | 
						|
		return this.scope === 'instance';
 | 
						|
	}
 | 
						|
 | 
						|
	is_object() {
 | 
						|
		return this.kind === 'Object' || (this.kind === 'typedef' && this.type === 'Object');
 | 
						|
	}
 | 
						|
 | 
						|
	is_class() {
 | 
						|
		return this.kind === 'class';
 | 
						|
	}
 | 
						|
 | 
						|
	is_function() {
 | 
						|
		return this.kind === 'function' || (this.kind === 'typedef' && this.type === 'function');
 | 
						|
	}
 | 
						|
 | 
						|
	is_module() {
 | 
						|
		return this.kind === 'module';
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function format_table(f, data, depth = 0) {
 | 
						|
	if (!data.length) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	const column_sizes = new Array(data[0].length).fill(0);
 | 
						|
 | 
						|
	data.forEach((row) => {
 | 
						|
		row.forEach((e, idx) => {
 | 
						|
			column_sizes[idx] = Math.max(e.length, column_sizes[idx]);
 | 
						|
		});
 | 
						|
	});
 | 
						|
 | 
						|
	const indent = ' '.repeat(depth);
 | 
						|
	let sep = indent;
 | 
						|
	column_sizes.forEach((size) => {
 | 
						|
		sep += '+';
 | 
						|
		sep += '-'.repeat(size + 2);
 | 
						|
	});
 | 
						|
	sep += '+\n';
 | 
						|
	f.write(sep);
 | 
						|
 | 
						|
	data.forEach((row) => {
 | 
						|
		let row_text = `${indent}|`;
 | 
						|
		row.forEach((entry, idx) => {
 | 
						|
			row_text += ` ${entry.padEnd(column_sizes[idx])} |`;
 | 
						|
		});
 | 
						|
		row_text += '\n';
 | 
						|
		f.write(row_text);
 | 
						|
		f.write(sep);
 | 
						|
	});
 | 
						|
 | 
						|
	f.write('\n');
 | 
						|
}
 | 
						|
 | 
						|
function make_header(header, sep) {
 | 
						|
	return `${header}\n${sep.repeat(header.length)}\n\n`;
 | 
						|
}
 | 
						|
 | 
						|
function indent_multiline(text, depth) {
 | 
						|
	const indent = ' '.repeat(depth);
 | 
						|
	return text.split('\n').map((l) => (l === '' ? l : indent + l)).join('\n');
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_signature(obj, types = false, style = false) {
 | 
						|
	let out = '';
 | 
						|
	const fmt = style ? '*' : '';
 | 
						|
	obj.params.forEach((arg, idx) => {
 | 
						|
		if (idx > 0) {
 | 
						|
			if (arg.optional) {
 | 
						|
				out += ` ${fmt}[`;
 | 
						|
			}
 | 
						|
			out += ', ';
 | 
						|
		} else {
 | 
						|
			out += ' ';
 | 
						|
			if (arg.optional) {
 | 
						|
				out += `${fmt}[ `;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (types) {
 | 
						|
			out += `${arg.type} `;
 | 
						|
		}
 | 
						|
		const variable = arg.variable ? '...' : '';
 | 
						|
		const defval = arg.defaultvalue !== undefined ? `=${arg.defaultvalue}` : '';
 | 
						|
		out += `${variable}${arg.name}${defval}`;
 | 
						|
		if (arg.optional) {
 | 
						|
			out += ` ]${fmt}`;
 | 
						|
		}
 | 
						|
	});
 | 
						|
	out += ' ';
 | 
						|
	return out;
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_param(f, obj, depth = 0) {
 | 
						|
	const indent = ' '.repeat(depth * 3);
 | 
						|
	f.write(indent);
 | 
						|
	f.write(`:param ${obj.type} ${obj.name}:\n`);
 | 
						|
	f.write(indent_multiline(obj.description, (depth + 1) * 3));
 | 
						|
	f.write('\n\n');
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_attribute(f, obj, depth = 0, brief = false) {
 | 
						|
	const indent = ' '.repeat(depth * 3);
 | 
						|
	f.write(indent);
 | 
						|
	f.write(`.. js:attribute:: ${obj.name}\n\n`);
 | 
						|
 | 
						|
	if (brief) {
 | 
						|
		if (obj.summary) {
 | 
						|
			f.write(indent_multiline(obj.summary, (depth + 1) * 3));
 | 
						|
		}
 | 
						|
		f.write('\n\n');
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	f.write(indent_multiline(obj.description, (depth + 1) * 3));
 | 
						|
	f.write('\n\n');
 | 
						|
 | 
						|
	f.write(indent);
 | 
						|
	f.write(`   :type: ${obj.type}\n\n`);
 | 
						|
 | 
						|
	if (obj.defaultvalue !== undefined) {
 | 
						|
		let defval = obj.defaultvalue;
 | 
						|
		if (defval === '') {
 | 
						|
			defval = '""';
 | 
						|
		}
 | 
						|
		f.write(indent);
 | 
						|
		f.write(`   :value: \`\`${defval}\`\`\n\n`);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_function(f, obj, depth = 0) {
 | 
						|
	let prefix = '';
 | 
						|
	if (obj.is_instance()) {
 | 
						|
		prefix = 'prototype.';
 | 
						|
	}
 | 
						|
 | 
						|
	const indent = ' '.repeat(depth * 3);
 | 
						|
	const sig = make_rst_signature(obj);
 | 
						|
	f.write(indent);
 | 
						|
	f.write(`.. js:function:: ${prefix}${obj.name}(${sig})\n`);
 | 
						|
	f.write('\n');
 | 
						|
 | 
						|
	f.write(indent_multiline(obj.description, (depth + 1) * 3));
 | 
						|
	f.write('\n\n');
 | 
						|
 | 
						|
	obj.params.forEach((param) => {
 | 
						|
		make_rst_param(f, param, depth + 1);
 | 
						|
	});
 | 
						|
 | 
						|
	if (obj.returns !== 'void') {
 | 
						|
		f.write(indent);
 | 
						|
		f.write('   :return:\n');
 | 
						|
		f.write(indent_multiline(obj.returns_desc, (depth + 2) * 3));
 | 
						|
		f.write('\n\n');
 | 
						|
		f.write(indent);
 | 
						|
		f.write(`   :rtype: ${obj.returns}\n\n`);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_object(f, obj) {
 | 
						|
	let brief = false;
 | 
						|
	// Our custom header flag.
 | 
						|
	if (obj.header !== null) {
 | 
						|
		f.write(make_header(obj.header, '-'));
 | 
						|
		f.write(`${obj.description}\n\n`);
 | 
						|
		brief = true;
 | 
						|
	}
 | 
						|
 | 
						|
	// Format members table and descriptions
 | 
						|
	const data = [['type', 'name']].concat(obj.members.map((m) => [m.type, `:js:attr:\`${m.name}\``]));
 | 
						|
 | 
						|
	f.write(make_header('Properties', '^'));
 | 
						|
	format_table(f, data, 0);
 | 
						|
 | 
						|
	make_rst_attribute(f, obj, 0, brief);
 | 
						|
 | 
						|
	if (!obj.members.length) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	f.write('   **Property Descriptions**\n\n');
 | 
						|
 | 
						|
	// Properties first
 | 
						|
	obj.members.filter((m) => !m.is_function()).forEach((m) => {
 | 
						|
		make_rst_attribute(f, m, 1);
 | 
						|
	});
 | 
						|
 | 
						|
	// Callbacks last
 | 
						|
	obj.members.filter((m) => m.is_function()).forEach((m) => {
 | 
						|
		make_rst_function(f, m, 1);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_class(f, obj) {
 | 
						|
	const header = obj.header ? obj.header : obj.name;
 | 
						|
	f.write(make_header(header, '-'));
 | 
						|
 | 
						|
	if (obj.classdesc) {
 | 
						|
		f.write(`${obj.classdesc}\n\n`);
 | 
						|
	}
 | 
						|
 | 
						|
	const funcs = obj.members.filter((m) => m.is_function());
 | 
						|
	function make_data(m) {
 | 
						|
		const base = m.is_static() ? obj.name : `${obj.name}.prototype`;
 | 
						|
		const params = make_rst_signature(m, true, true);
 | 
						|
		const sig = `:js:attr:\`${m.name} <${base}.${m.name}>\` **(**${params}**)**`;
 | 
						|
		return [m.returns, sig];
 | 
						|
	}
 | 
						|
	const sfuncs = funcs.filter((m) => m.is_static());
 | 
						|
	const ifuncs = funcs.filter((m) => !m.is_static());
 | 
						|
 | 
						|
	f.write(make_header('Static Methods', '^'));
 | 
						|
	format_table(f, sfuncs.map((m) => make_data(m)));
 | 
						|
 | 
						|
	f.write(make_header('Instance Methods', '^'));
 | 
						|
	format_table(f, ifuncs.map((m) => make_data(m)));
 | 
						|
 | 
						|
	const sig = make_rst_signature(obj);
 | 
						|
	f.write(`.. js:class:: ${obj.name}(${sig})\n\n`);
 | 
						|
	f.write(indent_multiline(obj.description, 3));
 | 
						|
	f.write('\n\n');
 | 
						|
 | 
						|
	obj.params.forEach((p) => {
 | 
						|
		make_rst_param(f, p, 1);
 | 
						|
	});
 | 
						|
 | 
						|
	f.write('   **Static Methods**\n\n');
 | 
						|
	sfuncs.forEach((m) => {
 | 
						|
		make_rst_function(f, m, 1);
 | 
						|
	});
 | 
						|
 | 
						|
	f.write('   **Instance Methods**\n\n');
 | 
						|
	ifuncs.forEach((m) => {
 | 
						|
		make_rst_function(f, m, 1);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
function make_rst_module(f, obj) {
 | 
						|
	const header = obj.header !== null ? obj.header : obj.name;
 | 
						|
	f.write(make_header(header, '='));
 | 
						|
	f.write(obj.description);
 | 
						|
	f.write('\n\n');
 | 
						|
}
 | 
						|
 | 
						|
function write_base_object(f, obj) {
 | 
						|
	if (obj.is_object()) {
 | 
						|
		make_rst_object(f, obj);
 | 
						|
	} else if (obj.is_function()) {
 | 
						|
		make_rst_function(f, obj);
 | 
						|
	} else if (obj.is_class()) {
 | 
						|
		make_rst_class(f, obj);
 | 
						|
	} else if (obj.is_module()) {
 | 
						|
		make_rst_module(f, obj);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function generate(f, docs) {
 | 
						|
	const globs = [];
 | 
						|
	const SYMBOLS = {};
 | 
						|
	docs.filter((d) => !d.ignore && d.kind !== 'package').forEach((d) => {
 | 
						|
		SYMBOLS[d.name] = d;
 | 
						|
		if (d.memberof) {
 | 
						|
			const up = SYMBOLS[d.memberof];
 | 
						|
			if (up === undefined) {
 | 
						|
				console.log(d); // eslint-disable-line no-console
 | 
						|
				console.log(`Undefined symbol! ${d.memberof}`); // eslint-disable-line no-console
 | 
						|
				throw new Error('Undefined symbol!');
 | 
						|
			}
 | 
						|
			SYMBOLS[d.memberof].add_member(d);
 | 
						|
		} else {
 | 
						|
			globs.push(d);
 | 
						|
		}
 | 
						|
	});
 | 
						|
 | 
						|
	f.write('.. _doc_html5_shell_classref:\n\n');
 | 
						|
	globs.forEach((obj) => write_base_object(f, obj));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Generate documentation output.
 | 
						|
 *
 | 
						|
 * @param {TAFFY} data - A TaffyDB collection representing
 | 
						|
 *                       all the symbols documented in your code.
 | 
						|
 * @param {object} opts - An object with options information.
 | 
						|
 */
 | 
						|
exports.publish = function (data, opts) {
 | 
						|
	const docs = data().get().filter((doc) => !doc.undocumented && !doc.ignore).map((doc) => new JSDoclet(doc));
 | 
						|
	const dest = opts.destination;
 | 
						|
	if (dest === 'dry-run') {
 | 
						|
		process.stdout.write('Dry run... ');
 | 
						|
		generate({
 | 
						|
			write: function () { /* noop */ },
 | 
						|
		}, docs);
 | 
						|
		process.stdout.write('Okay!\n');
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (dest !== '' && !dest.endsWith('.rst')) {
 | 
						|
		throw new Error('Destination file must be either a ".rst" file, or an empty string (for printing to stdout)');
 | 
						|
	}
 | 
						|
	if (dest !== '') {
 | 
						|
		const f = fs.createWriteStream(dest);
 | 
						|
		generate(f, docs);
 | 
						|
	} else {
 | 
						|
		generate(process.stdout, docs);
 | 
						|
	}
 | 
						|
};
 |