forked from omarkhan/coffeedoc
/
coffeedoc.coffee
147 lines (123 loc) · 3.95 KB
/
coffeedoc.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
###
Documentation functions
=======================
These functions extract relevant documentation info from AST nodes as returned
by the coffeescript parser.
###
coffeescript = require('coffee-script')
exports.documentModule = (script, parser) ->
###
Given a module's source code and an AST parser, returns module information
in the form:
{
"docstring": "Module docstring",
"classes": [class1, class1...],
"functions": [func1, func2...]
}
AST parsers are defined in the `parsers.coffee` module
###
nodes = parser.getNodes(coffeescript.nodes(script))
first_obj = nodes[0]
if first_obj?.type == 'Comment'
docstring = removeLeadingWhitespace(first_obj.comment)
else
docstring = null
doc =
docstring: docstring
deps: parser.getDependencies(nodes)
classes: (documentClass(c) for c in parser.getClasses(nodes))
functions: (documentFunction(f) for f in parser.getFunctions(nodes))
return doc
getFullName = (variable) ->
###
Given a variable node, returns its full name
###
name = variable.base.value
if variable.properties.length > 0
name += '.' + (p.name.value for p in variable.properties).join('.')
return name
documentClass = (cls) ->
###
Evaluates a class object as returned by the coffeescript parser, returning
an object of the form:
{
"name": "MyClass",
"docstring": "First comment following the class definition"
"parent": "MySuperClass",
"methods": [method1, method2...]
}
###
if cls.type == 'Assign'
# Class assigned to variable -- ignore the variable definition
cls = cls.value
# Check if class is empty
emptyclass = not cls.body.expressions[0]?.base?
# Get docstring
first_obj = if emptyclass
cls.body.expressions[0]
else
cls.body.expressions[0].base?.objects[0]
if first_obj?.type == 'Comment'
docstring = removeLeadingWhitespace(first_obj.comment)
else
docstring = null
# Get methods
methods = if emptyclass
[]
else
(n for n in cls.body.expressions[0].base.objects \
when n.type == 'Assign' and n.value.type == 'Code')
if cls.parent?
parent = getFullName(cls.parent)
else
parent = null
doc =
name: getFullName(cls.variable)
docstring: docstring
parent: parent
methods: (documentFunction(m) for m in methods)
return doc
documentFunction = (func) ->
###
Evaluates a function object as returned by the coffeescript parser,
returning an object of the form:
{
"name": "myFunc",
"docstring": "First comment following the function definition",
"params": ["param1", "param2"...]
}
###
# Get docstring
first_obj = func.value.body.expressions[0]
if first_obj?.comment
docstring = removeLeadingWhitespace(first_obj.comment)
else
docstring = null
# Get params
if func.value.params
params = for p in func.value.params
if p.splat then p.name.value + '...' else p.name.value
else
params = []
doc =
name: getFullName(func.variable)
docstring: docstring
params: params
removeLeadingWhitespace = (str) ->
###
Given a string, returns it with leading whitespace removed but with
indentation preserved
###
lines = str.split('\n')
# Remove leading blank lines
while /^ *$/.test(lines[0])
lines.shift()
if lines.length == 0
return null
# Get least indented non-blank line
indentation = for line in lines
if /^ *$/.test(line) then continue
line.match(/^ */)[0].length
indentation = Math.min(indentation...)
leading_whitespace = new RegExp("^ {#{ indentation }}")
return (line.replace(leading_whitespace, '') for line in lines).join('\n')