jsx
Version:
a faster, safer, easier JavaScript
549 lines (475 loc) • 16.9 kB
JSX
/*
* Copyright (c) 2012 DeNA Co., Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import "./classdef.jsx";
import "./type.jsx";
import "./parser.jsx";
import "./compiler.jsx";
import "./util.jsx";
import "./meta.jsx";
class DocCommentNode {
var _description : string;
function constructor () {
this._description = "";
}
function getDescription () : string {
return this._description;
}
function appendDescription (s : string) : void {
s = s.trim();
// append
if (s != "") {
if (this._description != "") {
this._description += " ";
}
this._description += s;
}
}
}
class DocCommentParameter extends DocCommentNode {
var _token : Token;
function constructor (token : Token) {
super();
this._token = token;
}
function getToken () : Token {
return this._token;
}
function getParamName () : string {
return this._token.getValue();
}
}
class DocCommentTag extends DocCommentNode {
var _tagName : string;
function constructor (tagName : string) {
super();
this._tagName = tagName;
}
function getTagName () : string {
return this._tagName;
}
}
class DocComment extends DocCommentNode {
var _params : DocCommentParameter[];
var _tags : DocCommentTag[];
function constructor () {
super();
this._params = new DocCommentParameter[];
this._tags = new DocCommentTag[];
}
function getParams () : DocCommentParameter[] {
return this._params;
}
function getTags () : DocCommentTag[] {
return this._tags;
}
function getTagByName (tagName : string) : DocCommentTag {
for (var i = 0; i < this._tags.length; ++i) {
if (this._tags[i].getTagName() == tagName) {
return this._tags[i];
}
}
return null;
}
function getTagsByName (tagName : string) : DocCommentTag[] {
var tags = new DocCommentTag[];
for (var i = 0; i < this._tags.length; ++i) {
if (this._tags[i].getTagName() == tagName) {
tags.push(this._tags[i]);
}
}
return tags;
}
}
class DocumentGenerator {
var _compiler : Compiler;
var _templatePath : string; // directory
var _outputPath : string; // directory
var _pathFilter : function(:string):boolean;
var _resourceFiles : string[];
var _classDefToHTMLCache = new TypedMap.<ClassDefinition,string>;
/**
* @param compiler Compiler instance
* @param templatePath a directory which has document resources (template.html and style.css)
* @param outputPath the output directory
*/
function constructor (compiler : Compiler, templatePath : string, outputPath : string) {
this._compiler = compiler;
this._templatePath = templatePath;
this._outputPath = outputPath;
this._resourceFiles = new string[];
this._pathFilter = null;
}
function setResourceFiles (files : string[]) : DocumentGenerator {
this._resourceFiles = files;
return this;
}
function setPathFilter (pathFilter : function(:string):boolean) : DocumentGenerator {
this._pathFilter = pathFilter;
return this;
}
function buildDoc () : void {
var platform = this._compiler.getPlatform();
// resource files are copied regardless of the template
this._resourceFiles.forEach((file) -> {
platform.save(
this._outputPath + "/" + file,
platform.load(this._templatePath + "/" + file));
});
// output each file
this._compiler.getParsers().forEach(function (parser) {
var encodedFilename = platform.encodeFilename(parser.getPath());
if (this._pathFilter(encodedFilename)) {
var outputFile = this._outputPath + "/" + parser.getPath() + ".html";
var html = this._buildDocOfFile(parser);
platform.save(outputFile, html);
}
});
}
function _buildDocOfFile (parser : Parser) : string {
var htmlFile = this._templatePath + "/template.html";
return this._compiler.getPlatform().load(htmlFile).replace(
/<%JSX:(.*?)%>/g,
function (matched) {
var key = matched.substring(6, matched.length-2);
switch (key) {
case "BASE_HREF":
// convert each component of dirname to ..
return parser.getPath().replace(/\/[^\/]+$/, "").replace(/[^\/]+/g, "..");
case "TITLE":
return this._escape(parser.getPath());
case "BODY":
return this._buildBodyOfFile(parser);
case "FOOTER":
return this._buildFooterOfFile(parser);
default:
throw new Error("unknown template key:" + key + " in file: " + htmlFile);
}
});
}
function _buildBodyOfFile (parser : Parser) : string {
var _ = "";
?<div class="jsxdoc">
?<div class="file">
?<h1><?= this._escape(parser.getPath()) ?></h1>
?<?= this._descriptionToHTML(parser.getDocComment()) ?>
?</div><!--/file-->
?<?= this._buildListOfClasses(parser) ?>
?</div><!--/jsxdoc-->
return _;
}
function _buildFooterOfFile (parser : Parser) : string {
var _ = "";
var docComment = parser.getDocComment();
if (docComment) {
var version = docComment.getTagByName("version");
if (version) {
?<p>This is <strong><?= this._escape(parser.getPath()) ?> version <?= this._escape(version.getDescription()) ?></strong>.</p>
}
var author = docComment.getTagByName("author");
if (author) {
var d = author.getDescription();
var endWithDot = (d.charAt(d.length - 1) == ".");
?<p>Copyright © <?= this._escape(d) + (endWithDot ? "" : ".") ?></p>
}
}
?<p class="jsxdoc-notice">This document was automatically generated by <a href="http://jsx.github.io/">JSX</a> <?= Meta.VERSION_STRING ?><br />
?at <?= this._escape( (new Date).toISOString() ) ?>.</p>
return _;
}
function _buildListOfClasses (parser : Parser) : string {
var _ = "";
?<div class="classes">
parser.getTemplateClassDefs().forEach(function (classDef) {
if (! this._isPrivate(classDef)) {
?<?= this._buildDocOfClass(parser, classDef) ?>
}
});
parser.getClassDefs().forEach(function (classDef) {
if (! (classDef instanceof InstantiatedClassDefinition) && ! this._isPrivate(classDef)) {
?<?= this._buildDocOfClass(parser, classDef) ?>
}
});
?</div>
return _;
}
function _buildDocOfClass (parser : Parser, classDef : ClassDefinition) : string {
var typeName = "class";
if ((classDef.flags() & ClassDefinition.IS_INTERFACE) != 0) {
typeName = "interface";
} else if ((classDef.flags() & ClassDefinition.IS_MIXIN) != 0) {
typeName = "mixin";
}
var typeArgs = classDef instanceof TemplateClassDefinition ? (classDef as TemplateClassDefinition).getTypeArguments() : new Token[];
var _ = "";
?<div class="class" id="class-<?= this._escape(classDef.className()) ?>">
?<h2><?= this._flagsToHTML(classDef.flags()) + " " + this._escape(typeName + " " + classDef.className()) + this._formalTypeArgsToHTML(typeArgs) ?></h2>
?<?= this._descriptionToHTML(classDef.getDocComment()) ?>
if (this._hasPublicProperties(classDef)) {
classDef.forEachMemberVariable(function (varDef) {
if (! this._isPrivate(varDef)) {
?<div class="member property">
?<h3>
?<?= this._flagsToHTML(varDef.flags()) ?> var <?= varDef.name() ?> : <?= this._typeToHTML(parser, varDef.getType()) ?>
?</h3>
?<?= this._descriptionToHTML(varDef.getDocComment()) ?>
?</div>
}
return true;
});
}
classDef.forEachMemberFunction(function (funcDef) {
if (! (funcDef instanceof InstantiatedMemberFunctionDefinition) && this._isConstructor(funcDef) && (funcDef.flags() & ClassDefinition.IS_DELETE) == 0) {
?<?= this._buildDocOfFunction(parser, funcDef) ?>
}
return true;
});
if (this._hasPublicFunctions(classDef)) {
classDef.forEachMemberFunction(function (funcDef) {
if (! (funcDef instanceof InstantiatedMemberFunctionDefinition || this._isConstructor(funcDef) || this._isPrivate(funcDef))) {
?<?= this._buildDocOfFunction(parser, funcDef) ?>
}
return true;
});
}
?</div>
return _;
}
function _buildDocOfFunction (parser : Parser, funcDef : MemberFunctionDefinition) : string {
var _ = "";
var funcName = this._isConstructor(funcDef) ? "new " + funcDef.getClassDef().className() : this._flagsToHTML(funcDef.flags()) + " function " + funcDef.name();
var args = funcDef.getArguments();
var argsHTML = args.map.<string>(function (arg) {
return this._escape(arg.getName().getValue()) + " : " + this._typeToHTML(parser, arg.getType());
}).join(", ");
?<div class="member function">
?<h3>
?<?= this._escape(funcName) + this._formalTypeArgsToHTML(funcDef instanceof TemplateFunctionDefinition ? (funcDef as TemplateFunctionDefinition).getTypeArguments() : new Token[]) ?>(<?= argsHTML ?>)
if (! this._isConstructor(funcDef)) {
? : <?= this._typeToHTML(parser, funcDef.getReturnType()) ?>
}
?</h3>
?<?= this._descriptionToHTML(funcDef.getDocComment()) ?>
if (this._argsHasDocComment(funcDef)) {
?<table class="arguments">
args.forEach(function (arg) {
var argName = arg.getName().getValue();
?<tr>
?<td class="param-name"><?= this._escape(argName) ?></td>
?<td class="param-desc"><?= this._argumentDescriptionToHTML(argName, funcDef.getDocComment()) ?></td>
?</tr>
});
?</table>
}
?</div>
return _;
}
function _descriptionToHTML (docComment : DocComment) : string {
var _ = "";
if (docComment != null) {
if (docComment.getDescription() != "") {
?<div class="description">
?<?= docComment.getDescription() ?>
?</div>
}
var seeTags = docComment.getTagsByName("see");
if (seeTags.length > 0) {
?<ul class="see">
seeTags.forEach((tag) -> {
?<li><?= this._autoLink(tag.getDescription()) ?></li>
});
?</ul>
}
}
return _;
}
function _autoLink (str : string) : string {
var uri = /^https?:\/\/[A-Za-z0-9\-\._~:\/?#\[\]@!$&'()*+,;=]+/;
return str.replace(uri, (matched) -> {
return Util.format('<a href="%1">%1</a>', [matched]);
});
}
function _argumentDescriptionToHTML (name : string, docComment : DocComment) : string {
return docComment != null ? this._getDescriptionOfNamedArgument(docComment, name): "";
}
function _formalTypeArgsToHTML (typeArgs : Token[]) : string {
if (typeArgs.length == 0) {
return "";
}
return ".<"
+ typeArgs.map.<string>(function (typeArg) { return this._escape(typeArg.getValue()); }).join(", ")
+ ">";
}
function _typeToHTML (parser : Parser, type : Type) : string {
// TODO create links for object types
if (type instanceof ObjectType) {
var classDef = type.getClassDef();
if (classDef != null) {
return this._classDefToHTML(parser, classDef);
} else if (type instanceof ParsedObjectType && (type as ParsedObjectType).getTypeArguments().length != 0) {
classDef = (type as ParsedObjectType).getQualifiedName().getTemplateClass(parser);
if (classDef != null) {
return this._classDefToHTML(parser, classDef)
+ ".<"
+ (type as ParsedObjectType).getTypeArguments().map.<string>(function (type) { return this._typeToHTML(parser, type); }).join(", ")
+ ">";
}
}
} else if (type instanceof FunctionType) {
return "function "
+ "("
+ (type as ResolvedFunctionType).getArgumentTypes().map.<string>(function (type) {
return ":" + this._typeToHTML(parser, type);
}).join(", ")
+ ") : " + this._typeToHTML(parser, (type as ResolvedFunctionType).getReturnType());
} else if (type instanceof VariableLengthArgumentType) {
return "..." + this._typeToHTML(parser, (type as VariableLengthArgumentType).getBaseType());
}
return this._escape(type.toString());
}
function _classDefToHTML (parser : Parser, classDef : ClassDefinition) : string {
// instantiated classes should be handled separately
if (classDef instanceof InstantiatedClassDefinition) {
return this._classDefToHTML(parser, (classDef as InstantiatedClassDefinition).getTemplateClass())
+ ".<"
+ (classDef as InstantiatedClassDefinition).getTypeArguments().map.<string>(function (type) { return this._typeToHTML(parser, type); }).join(", ")
+ ">";
}
// lokup the cache
var result = this._classDefToHTMLCache.get(classDef);
if (result != null) {
return result;
}
// determine the parser to which the classDef belongs
function determineParserOfClassDef () : Parser {
var parsers = this._compiler.getParsers();
for (var i = 0; i < parsers.length; ++i) {
if (classDef instanceof TemplateClassDefinition) {
var templateClassDefs = parsers[i].getTemplateClassDefs();
for (var j = 0; j < templateClassDefs.length; ++j) {
templateClassDefs = templateClassDefs.concat(templateClassDefs[j].getTemplateInnerClasses());
}
if (templateClassDefs.indexOf(classDef as TemplateClassDefinition) != -1) {
return parsers[i];
}
} else {
var classDefs = parsers[i].getClassDefs();
for (var j = 0; j < classDefs.length; ++j) {
classDefs = classDefs.concat(classDefs[j].getInnerClasses());
}
if (classDefs.indexOf(classDef) != -1) {
return parsers[i];
}
}
}
throw new Error("could not determine the parser to which the class belongs:" + classDef.className());
};
var parserOfClassDef = determineParserOfClassDef();
// return text if we cannot linkify the class name
if (! this._pathFilter(parserOfClassDef.getPath())) {
return this._escape(classDef.className());
}
// linkify and return
var _ = "";
?<a href="<?= this._escape(parserOfClassDef.getPath()) ?>.html#class-<?= this._escape(classDef.className()) ?>"><?= this._escape(classDef.className()) ?></a>
_ = _.trim();
this._classDefToHTMLCache.set(classDef, _);
return _;
}
function _flagsToHTML (flags : number) : string {
var strs = new string[];
// does not expose internal properties
if ((flags & ClassDefinition.IS_STATIC) != 0)
strs.push("static");
if ((flags & ClassDefinition.IS_CONST) != 0)
strs.push("const");
if ((flags & ClassDefinition.IS_ABSTRACT) != 0)
strs.push("abstract");
if ((flags & ClassDefinition.IS_FINAL) != 0)
strs.push("final");
if ((flags & ClassDefinition.IS_OVERRIDE) != 0)
strs.push("override");
if ((flags & ClassDefinition.IS_INLINE) != 0)
strs.push("inline");
return strs.join(" ");
}
function _escape (str : string) : string {
return str.replace(/[<>&'"]/g, function (ch) {
return {
"<": "<",
">": ">",
"&": "&",
"'": "'",
"\"": """
}[ch];
});
}
function _hasPublicProperties (classDef : ClassDefinition) : boolean {
return ! classDef.forEachMemberVariable(function (varDef) {
if (! this._isPrivate(varDef)) {
return false;
}
return true;
});
}
function _hasPublicFunctions (classDef : ClassDefinition) : boolean {
return ! classDef.forEachMemberFunction(function (funcDef) {
if (funcDef instanceof InstantiatedMemberFunctionDefinition
|| this._isConstructor(funcDef)
|| this._isPrivate(funcDef)) {
return true;
}
return false;
});
}
function _argsHasDocComment (funcDef : MemberFunctionDefinition) : boolean {
var docComment = funcDef.getDocComment();
if (docComment == null) {
return false;
}
var args = funcDef.getArguments();
for (var argIndex = 0; argIndex < args.length; ++argIndex) {
if (this._getDescriptionOfNamedArgument(docComment, args[argIndex].getName().getValue()) != "") {
return true;
}
}
return false;
}
function _getDescriptionOfNamedArgument (docComment : DocComment, argName : string) : string {
var params = docComment.getParams();
for (var paramIndex = 0; paramIndex < params.length; ++paramIndex) {
if (params[paramIndex].getParamName() == argName) {
return params[paramIndex].getDescription();
}
}
return "";
}
function _isConstructor (funcDef : MemberFunctionDefinition) : boolean {
return funcDef.name() == "constructor"
&& (funcDef.flags() & ClassDefinition.IS_STATIC) == 0;
}
function _isPrivate (classDef : ClassDefinition) : boolean {
return classDef.className().charAt(0) == "_";
}
function _isPrivate (memberDef : MemberDefinition) : boolean {
return memberDef.name().charAt(0) == "_";
}
}