## # Module dependencies. dox = require './dox' ## # Parse comments in the given string of `coffee`. # # @param {String} coffee # @param {Object} options # @return {Array} # @see exports.parseComment # @api public exports.parseCommentsCoffee = (coffee, options = {}) -> coffee = coffee.replace /\r\n/gm, '\n' comments = [] raw = options.raw buf = '' line_number = 1 indent = undefined buf_line_number = undefined getCodeContext = -> lines = buf.split('\n') # skip start empty lines while lines.length>0 and lines[0].trim() is '' lines.splice 0, 1 buf_line_number++ if lines.length isnt 0 # get expected indent indentre = new RegExp('^' + lines[0].match(/^(\s*)/)[1]) # remove 'indent' from beginning for each line for line, i in lines # skip empty line if line.trim() is '' continue lines[i] = line = line.replace indentre, '' # find line of same or little indent to stop there if i isnt 0 and not line.match /^\s/ break # cut below lines lines.length = i code = lines.join('\n').trim() # add code to previous comment comment = comments[comments.length - 1] if comment # find parent i = comments.length - 2 while i >= 0 if comments[i].indent.search(comment.indent)<0 break i-- comment.ctx = parseCodeContextCoffee code, if i>=0 then comments[i] else null comment.tags.forEach (tag) -> if tag.type is 'class' comment.ctx or= {} comment.ctx.type = 'class' return if comment.ctx and comment.ctx.type is 'class' comment.class_code = code comment.class_codeStart = buf_line_number else comment.code = code comment.codeStart = buf_line_number buf = '' addBuf = -> buf += coffee[pos] if '\n' is coffee[pos] line_number++ return true return false addBufLine = -> while pos < len if addBuf() break pos++ addComment = -> comment = dox.parseComment buf, options comment.ignore = ignore comment.indent = indent comments.push comment buf = '' buf_line_number = line_number pos = 0 len = coffee.length while pos < coffee.length # block comment if coffee.slice(pos, pos+3) is '###' indent = buf.match(/([ \t]*)$/)[1] # code following previous comment getCodeContext() pos += 3 if '\n' is coffee[pos] line_number++ ignore = '!' is coffee[pos] pos++ while pos < len if coffee.slice(pos, pos+3) is '###' pos += 3 if '\n' is coffee[pos] line_number++ buf = buf.replace /^[ \t]*[\*\#] ?/gm, '' addComment() break addBuf() pos++ # doxygen style comment else if '#' is coffee[pos] and '#' is coffee[pos+1] indent = buf.match(/([ \t]*)$/)[1] # code following previous comment getCodeContext() pos += 2 ignore = '!' is coffee[pos] if '\n' is coffee[pos] line_number++ else pos++ addBufLine() while 1 pos++ # check whether line comment j = pos while j < len if '#' is coffee[j] break if ' ' isnt coffee[j] and '\t' isnt coffee[j] break j++ if '#' isnt coffee[j] buf = buf.replace /^[ \t]*#{1,2} {0,1}/gm, '' addComment() pos-- break # add this line if comment addBufLine() # line comment else if '#' is coffee[pos] addBufLine() # buffer code else addBuf() pos++ if comments.length is 0 comments.push tags: [], description: full: '', summary: '', body: '' isPrivate: false # trailing code getCodeContext() return comments ## # Parse the context from the given `str` of coffee. # # This method attempts to discover the context # for the comment based on it's code. Currently # supports: # # - function statements # - function expressions # - prototype methods # - prototype properties # - methods # - properties # - declarations # # @param {String} str # @return {Object} # @api public parseCodeContextCoffee = (str, parent) -> str = str.split('\n')[0] # function expression if /^(\w+) *= *(\(.*\)|) *[-=]>/.exec(str) return { type: 'function' name: RegExp.$1 string: RegExp.$1 + '()' } # prototype method else if /^(\w+)::(\w+) *= *(\(.*\)|) *[-=]>/.exec(str) return { type: 'method' constructor: RegExp.$1 cons: RegExp.$1 name: RegExp.$2 string: RegExp.$1 + '::' + RegExp.$2 + '()' } # prototype property else if /^(\w+)::(\w+) *= *([^\n]+)/.exec(str) return { type: 'property' constructor: RegExp.$1 cons: RegExp.$1 name: RegExp.$2 value: RegExp.$3 string: RegExp.$1 + '::' + RegExp.$2 } # method else if /^[\w.]*?(\w+)\.(\w+) *= *(\(.*\)|) *[-=]>/.exec(str) return { type: 'method' receiver: RegExp.$1 name: RegExp.$2 string: RegExp.$1 + '.' + RegExp.$2 + '()' } # property else if /^[\w.]*?(\w+)\.(\w+) *= *([^\n]+)/.exec(str) return { type: 'property' receiver: RegExp.$1 name: RegExp.$2 value: RegExp.$3 string: RegExp.$1 + '.' + RegExp.$2 } # declaration else if /^(\w+) *= *([^\n]+)/.exec(str) return { type: 'declaration' name: RegExp.$1 value: RegExp.$2 string: RegExp.$1 } if parent and parent.ctx and parent.ctx.type is 'class' class_name = parent.ctx.name # CoffeeScript class syntax if /\bclass +(\w+)/.exec(str) return { type: 'class' name: RegExp.$1 string: 'class ' + RegExp.$1 } # prototype method else if /^(\w+) *: *(\(.*\)|) *[-=]>/.exec(str) if class_name return { type: 'method' constructor: class_name cons: class_name name: RegExp.$1 string: class_name + '::' + RegExp.$1 + '()' is_constructor: RegExp.$1 is 'constructor' } else return { type: 'method' name: RegExp.$1 string: RegExp.$1 + '()' } # prototype property else if /^(\w+) *: *([^\n]+)/.exec(str) if class_name return { type: 'property' constructor: class_name cons: class_name name: RegExp.$1 value: RegExp.$2 string: class_name + '::' + RegExp.$1 } else return { type: 'property' name: RegExp.$1 value: RegExp.$2 string: RegExp.$1 } else if not class_name # method else if /^@(\w+) *: *(\(.*\)|) *[-=]>/.exec(str) return { type: 'method' receiver: class_name name: RegExp.$1 string: class_name + '.' + RegExp.$1 + '()' } # property else if /^@(\w+) *: *([^\n]+)/.exec(str) return { type: 'property' receiver: class_name name: RegExp.$1 value: RegExp.$2 string: class_name + '.' + RegExp.$1 } else return { class_name: class_name }