jsduckify
Version:
Enables the use of Sencha's JSDuck for documenting CoffeeScript projects.
235 lines (211 loc) • 10.2 kB
HTML
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js">/* <CoffeeScript>
{DoublyLinkedList} = require('doubly-linked-list')
_space = (level, multiplier = 1) ->
return new Array(level * multiplier + 1).join(' ')
_contains = (s, s2) ->
return s.indexOf(s2) >= 0
_stripLeadingBlanks = (filename, line, count) ->
startOfLine = line.slice(0, count)
minLength = Math.min(startOfLine.length, count)
unless startOfLine == _space(minLength)
console.error("Non whitespace found within the indent space for the docstring in file: #{filename}. At line:\n" +
"#{line}"
)
return line.slice(count)
_convertLinesFromCSToJS = (filename, start, end, indent) ->
current = start
while current != end.after
current.value = _space(indent) + ' * ' + _stripLeadingBlanks(filename, current.value, indent)
current = current.after
_convertDocstringFromCSToJS = (filename, docstringStart, docstringEnd) ->
# Figure out the number of indention characters
indent = docstringStart.value.indexOf('###')
unless indent == docstringEnd.value.indexOf('###')
console.error("Starting and ending docstrings do not have the same indention in file #{filename}.\n" +
"start:\n#{docstringStart.value}\n" + "end:\n#{docstringEnd.value}"
)
docstringStart.insertBefore('</CoffeeScript> * /') # ends the JS comment wrapping the entire file
docstringStart.value = _space(indent) + '/ **'
_convertLinesFromCSToJS(filename, docstringStart.after, docstringEnd, indent)
docstringEnd.value = _space(indent) + ' * /'
docstringEnd.insertAfter('/ * <CoffeeScript>') # restarts the JS comment wrapping the entire file
_processNameAndMember = (nameLine, memberLine, exportsAPI, prefix, fullNameForCurrentClass) ->
# !TODO: (round 2) Marks it as processed in the fileDocumentation
a = nameLine.value.match(/@(class|method|property)\s([\$|\w]+)\b/)
type = a[1]
name = a[2]
fullName = null
for e in exportsAPI
if name in e.fullName.split('.')
e.processed = true
fullName = prefix + '.' + e.fullName
break
space = nameLine.value.indexOf('@')
unless fullName?
fullName = prefix + '.' + name
nameArray = fullName.split('.')
baseName = nameArray[nameArray.length - 1]
if baseName.indexOf('_') == 0
nameLine.insertAfter(_space(space) + '@private') # !TODO: This will add it twice if it's already there but OK.
nameLine.value = _space(space)
switch type
when 'class'
nameLine.value += "@class #{fullName}"
fullNameForCurrentClass = fullName
when 'method', 'property'
nameLine.value += "@#{type} #{name}"
unless memberLine? # !TODO: Maybe I should manipulate the existing memberLine
if fullNameForCurrentClass?
member = fullNameForCurrentClass
else
member = prefix
memberLine = nameLine.insertAfter(_space(space) + "@member #{member}")
return fullNameForCurrentClass
_processDocstring = (filename, docstringStart, docstringEnd, currentClass, exportsAPI, prefix) ->
foundSomething = false
# Look for the @class, @method, @constructor, or @property JSDuck tags.
current = docstringStart.after
nameLine = null
type = null
memberLine = null
while current? and current != docstringEnd
s = current.value
if _contains(s, "@class")
if type?
throw new Error("Found @class when docstring already has an @#{type} in: #{filename}.")
else
type = 'class'
nameLine = current
if _contains(s, "@method")
if type?
throw new Error("Found @method when docstring already has an @#{type} in: #{filename}.")
else
type = 'method'
nameLine = current
if _contains(s, "@property")
if type?
throw new Error("Found @property when docstring already has an @#{type} in: #{filename}.")
else
type = 'property'
nameLine = current
if _contains(s, "@constructor")
if type?
unless type == 'class'
throw new Error("Cannot define an @constructor inside an @#{type} docstring in: #{filename}.")
# We can essentially do nothing because the @constructor is already part of the @class docstring
else
unless currentClass.end?
throw new Error("I have no idea where to associate this constructor.")
type = 'constructor'
nameLine = current
if _contains(s, "@member")
memberLine = current
current = current.after
if type == 'class'
currentClass.start = docstringStart
currentClass.end = docstringEnd
fullNameForCurrentClass = _processNameAndMember(nameLine, memberLine, exportsAPI, prefix, currentClass.fullName)
currentClass.fullName = fullNameForCurrentClass
if type == 'constructor'
# * Append the current docstring to the end of the currentClass
constructorBodyStart = docstringStart.after
constructorBodyStart.before.remove()
docstringStart = constructorBodyStart
constructorBodyEnd = docstringEnd.before
constructorBodyEnd.after.remove()
docstringEnd = constructorBodyEnd
# Move the constructor to the bottom of the class
indent = currentClass.end.before.value.indexOf('*') - 1
if indent < 0
throw new Error("Expected * to be at the top of the docstring in #{filename}")
indentConstructor = constructorBodyStart.value.indexOf('@')
if indentConstructor < 0
throw new Error("Expected @constructor to be at the top of the docstring in #{filename}")
newLine = _space(indent) + ' * '
currentClass.end.insertBefore(newLine) # Blank line between Class and Constructor
current = constructorBodyStart.before
while current != constructorBodyEnd and current?
current = current.after
newLine = _space(indent) + ' * ' + _stripLeadingBlanks(filename, current.value, indentConstructor)
currentClass.end.insertBefore(newLine)
current.before.remove()
if type in ['property', 'method']
_processNameAndMember(nameLine, memberLine, exportsAPI, prefix, currentClass.fullName)
# * (round 2) Mark it as @static if it is according to fileDocumentation.
# * (round 2) Uses fileDocumentation to specify the @membership.
# (round 2) For each element in fileDocumentation that is not marked as processed, it tries to locate it and create
# the appropriate JSDuck header. Note, in my old code, I was able to identify parameters.
foundSomething = type?
if foundSomething and type != 'constructor'
_convertDocstringFromCSToJS(filename, docstringStart, docstringEnd)
return foundSomething
</CoffeeScript> */
<span id='jsduckify-method-duckifyFile'>/**
</span> * @method duckifyFile
* @member jsduckify
* Once it's used, it's marked as such in the exportsAPI
*
* @param {String} sourceFileString CoffeeScript source
* @param {Array} exportsAPI each row contains {baseName, fullName, type}
* @param {String} [prefix] the root for this documentation
*
* @return {String} duckified source file
*/
/* <CoffeeScript>
exports.duckifyFile = (filename, sourceFileString, exportsAPI, prefix = '', isMain = false) ->
# !TODO: (round 2) Runs documentFile on it creating fileDocumentation. This runs the CoffeeScript compiler extracting classes
# and functions. It can distinguish static methods from instance methods within your classes. It can also identify
# functions not associated with a CoffeeScript class. However, it cannot identify any properties.
# fileDocumentation = documentFile(sourceFileString)
# Breaks the file down into lines.
sourceFileString = sourceFileString.replace(/\/\* /g,'/ *') # This is a hack but good enough
sourceFileString = sourceFileString.replace(/\*\//g,'* /')
lineList = new DoublyLinkedList(sourceFileString.split('\n'))
# Turn the entire file into a big JavaScript block comment
lineList.unshift('/ * <CoffeeScript>')
lineList.push('</CoffeeScript> * /')
# Walks through the file line-by-line looking for docstrings starting with ### and ending with ###.
foundSomething = false
current = lineList.head
currentClassDocstring = {}
while current?
if current.value.match(/(\n|\r|^)\s*###/)?
# !TODO: If a starting markers is not on its own line, make it so.
docstringStart = current
current = current.after
while current? and !current.value.match(/(\n|\r|^)\s*###/)?
current = current.after
unless current?
throw new Error("Found a docstring start without a corresponding docstring end in file: #{filename}")
docstringEnd = current
if isMain
_convertDocstringFromCSToJS(filename, docstringStart, docstringEnd)
foundSomething = true
else if _processDocstring(filename, docstringStart, docstringEnd, currentClassDocstring, exportsAPI, prefix)
foundSomething = true
current = current.after
if foundSomething
fileString = lineList.toArray().join('\n')
else
fileString = null
return fileString
</CoffeeScript> */</pre>
</body>
</html>