mirror of
https://github.com/atom/atom.git
synced 2026-01-25 06:48:28 -05:00
Only works for TomDoc'd CoffeeScript right now. Needs to work for all languages. I had to modify handlebars to satisfy 'use strict' Also had to modify the path stuff in tdoc, which works in node but not atom. We need some way to install npm packages or something similar maybe?
167 lines
5.1 KiB
CoffeeScript
167 lines
5.1 KiB
CoffeeScript
# cdoc is a bite sized library which scans CoffeeScript
|
|
# source code and returns a js object containing class, module
|
|
# and method names, along with the comments and line numbers
|
|
# associated with them.
|
|
#
|
|
# It pairs finely with TomDoc, but really doesn't care which
|
|
# documentation format you use. As long as your class, module, and
|
|
# method definitions are preceded by a comment, cdoc will do its job.
|
|
module.exports = cdoc =
|
|
# Parses CoffeeScript source code into an object describing
|
|
# the class names and modules defined therein. Method names,
|
|
# comments, and line numbers are also included.
|
|
#
|
|
# text - The String CoffeeScript source code to parse into a js object.
|
|
#
|
|
# Returns an object like this:
|
|
#
|
|
# [
|
|
# name: "App"
|
|
# comment:"Singleton object representing the application.",
|
|
# line: 5,
|
|
# methods: [
|
|
# name: "setTitle"
|
|
# signature: "setTitle: (title) ->"
|
|
# params: [
|
|
# name: "title"
|
|
# ]
|
|
# comment: "Sets the title of the app."
|
|
# line: 7
|
|
# ,
|
|
# name: "title"
|
|
# signature: "title: ->"
|
|
# params: []
|
|
# comment: "Returns the String title of the app."
|
|
# line: 11
|
|
# ]
|
|
# name: "class Robot"
|
|
# comment: "A Robot that can walk and talk, just like a real boy."
|
|
# line: 15
|
|
# methods: [
|
|
# name: "constructor"
|
|
# signature: "constructor: (path) ->"
|
|
# params: [
|
|
# name: "path"
|
|
# ]
|
|
# comment: "Robots receive messages from a..."
|
|
# line: 20
|
|
# ]
|
|
# ]
|
|
#
|
|
# In the above, App is an object while Robot is a class.
|
|
parse: (text) ->
|
|
scopes = []
|
|
lastComment = ''
|
|
methodIndent = null
|
|
moduleIsFn = false
|
|
|
|
text.split("\n").forEach (line, lineno) =>
|
|
return if moduleIsFn
|
|
|
|
lineno++
|
|
|
|
# Skip empty lines and lines containing only a comment symbol.
|
|
return if not line.trim()
|
|
|
|
# If this line is a comment, record it and move on.
|
|
if line.trim()[0] is '#'
|
|
lastComment += line.trim().match(/^\s*\#\s?(.*)$/)[1] + "\n"
|
|
return
|
|
|
|
# detect:
|
|
# 1. module.exports = (params) ->
|
|
# 2. module.exports = (params) =>
|
|
if match = line.match /^\s*module.exports\s*=\s*((?:\((.+)\))?\s*(-|=)>)/
|
|
moduleIsFn = true
|
|
[ signature, params ] = match[1..2]
|
|
params = for param in params?.split(',') or []
|
|
{ name: param.trim() }
|
|
scopes.push
|
|
name: 'module'
|
|
signature: signature
|
|
params: params
|
|
comment: lastComment.trim()
|
|
line: lineno
|
|
|
|
# detect:
|
|
# 1. module.exports =
|
|
# 2. module.exports = MyModule =
|
|
# 3. module.exports = class MyClass
|
|
# 4. exports.MyClass = class MyClass
|
|
else if match = line.match /^\s*(?:module.exports|exports\.[\w\.]+)\s*=\s*(?:(class [\w\.]+)(?:\s+extends [\w.]+)?|([\w\.]+)\s*=)\s*$/
|
|
methodIndent = null
|
|
scopes.push
|
|
name: match[1] or match[2] or 'module'
|
|
comment: lastComment.trim()
|
|
line: lineno
|
|
methods: []
|
|
|
|
# detect: class Window
|
|
else if newScope = line.match(/^\s*class ([\w\.]+)/)?[0]
|
|
methodIndent = null
|
|
scopes.push
|
|
name: newScope
|
|
comment: lastComment.trim()
|
|
line: lineno
|
|
methods: []
|
|
|
|
# detect:
|
|
# 1. hear: ->
|
|
# 2. hear: (regex, args...) ->
|
|
# 3. hear = ->
|
|
# 4. hear = (regex, args...) ->
|
|
# also detect `@hear` and `=>` forms of the above
|
|
# as well as `exports.head = (regex, args...) ->` form
|
|
else if match = line.match /^\s*(@|exports\.)?(([\w\.]+)\s*(:|=)\s*(?:\((.*?)\))?\s*(-|=)>)\s*/
|
|
|
|
# If the indentation of this method is deeper than the
|
|
# previous method definition, and we're still in the same
|
|
# scope, bail out. Probably an inner function.
|
|
indent = line.match(/^(\s*)/)[0].length
|
|
return if methodIndent? and methodIndent < indent
|
|
methodIndent = indent
|
|
|
|
[ prefix, fn, name, type, params ] = match[1..-1]
|
|
|
|
# floating function
|
|
if type is '=' and prefix isnt 'exports.'
|
|
return
|
|
|
|
# Normalize the method signature.
|
|
# 1. exports.method = (param) ->
|
|
# exports.method: (param) ->
|
|
# 2. method: () ->
|
|
# method: ->
|
|
fn = fn.trim()
|
|
fn = fn.replace /\s+=\s+((\(|-|=))/, ': $1'
|
|
fn = fn.replace /\s*\(\)/, ''
|
|
|
|
params = for param in params?.split(',') or []
|
|
{ name: param.trim() }
|
|
|
|
if prefix is 'exports.'
|
|
modules = scopes.filter (scope) -> scope?.name is 'module'
|
|
scope = modules[0]
|
|
|
|
if not scope
|
|
scopes.push scope =
|
|
name: 'module'
|
|
comment: ""
|
|
line: 0
|
|
methods: []
|
|
else
|
|
scope = scopes[scopes.length - 1]
|
|
|
|
scope.methods ?= []
|
|
scope.methods.push
|
|
name: name
|
|
signature: fn
|
|
params: params
|
|
comment: lastComment.trim()
|
|
line: lineno
|
|
|
|
# Clear the last comment, we're done with it.
|
|
lastComment = ''
|
|
|
|
scopes
|