Ejemplo n.º 1
0
    def addConstructor(self, valueNode, commentNode=None):
        entry = self.construct = {
            "line" : (commentNode or valueNode).line
        }

        if commentNode is None:
            commentNode = valueNode

        # Root doc comment is optional for constructors
        comment = getDocComment(commentNode)
        if comment and comment.hasContent():
            html = comment.getHtml(self.highlight)
            entry["doc"] = html
            entry["summary"] = Text.extractSummary(html)

        if comment and comment.tags:
            entry["tags"] = comment.tags

        entry["init"] = self.main["name"]

        funcParams = getParamNamesFromFunction(valueNode)
        if funcParams:
            entry["params"] = {}
            for paramPos, paramName in enumerate(funcParams):
                entry["params"][paramName] = {
                    "position" : paramPos
                }

            # Use comment for enrich existing data
            comment = getDocComment(commentNode)
            if comment:
                if not comment.params:
                    self.warn("Documentation for parameters of constructor are missing", valueNode.line)
                    for paramName in funcParams:
                        entry["params"][paramName]["errornous"] = True

                else:
                    for paramName in funcParams:
                        if paramName in comment.params:
                            entry["params"][paramName].update(comment.params[paramName])
                        else:
                            entry["params"][paramName]["errornous"] = True
                            self.warn("Missing documentation for parameter %s in constructor" % paramName, valueNode.line)

            else:
                entry["errornous"] = True
Ejemplo n.º 2
0
    def addEvent(self, name, valueNode, commentNode, collection):
        entry = collection[name] = {
            "line" : (commentNode or valueNode).line
        }

        if valueNode.type == "dot":
            entry["type"] = assembleDot(valueNode)
        elif valueNode.type == "identifier":
            entry["type"] = valueNode.value

            # Try to resolve identifier with local variable assignment
            assignments, values = findAssignments(valueNode.value, valueNode)
            if assignments:

                # We prefer the same comment node as before as in these
                # szenarios a reference might be used for different event types
                if not findCommentNode(commentNode):
                    commentNode = assignments[0]

                self.addEvent(name, values[0], commentNode, collection)
                return

        comment = getDocComment(commentNode)
        if comment:

            if comment.tags:
                entry["tags"] = comment.tags

            # Prefer type but fall back to returns (if the developer has made an error here)
            if comment.type:
                entry["type"] = comment.type
            elif comment.returns:
                entry["type"] = comment.returns[0]

            if comment.hasContent():
                html = comment.getHtml(self.highlight)
                entry["doc"] = html
                entry["summary"] = Text.extractSummary(html)
            else:
                self.warn("Comment contains invalid HTML", commentNode.line)
                entry["errornous"] = True

        else:
            self.warn("Invalid doc comment", commentNode.line)
            entry["errornous"] = True
Ejemplo n.º 3
0
    def setMain(self, mainType, mainNode, exportName):

        callComment = getDocComment(mainNode)

        entry = self.main = {
            "type" : mainType,
            "name" : exportName,
            "line" : mainNode.line
        }

        if callComment:

            if callComment.text:
                html = callComment.getHtml(self.highlight)
                entry["doc"] = html
                entry["summary"] = Text.extractSummary(html)

            if hasattr(callComment, "tags"):
                entry["tags"] = callComment.tags

        if callComment is None or not callComment.text:
            entry["errornous"] = True
            self.warn('Missing comment on "%s" namespace' % exportName, mainNode.line)
Ejemplo n.º 4
0
    def __process(self,
                  apiData,
                  classFilter=None,
                  internals=False,
                  privates=False,
                  printErrors=True,
                  highlightCode=True):

        knownClasses = set(list(apiData))

        #
        # Attaching Links to Source Code (Lines)
        # Building Documentation Summaries
        #

        Console.info("Adding Source Links...")

        for className in apiData:
            classApi = apiData[className]

            constructData = getattr(classApi, "construct", None)
            if constructData is not None:
                if "line" in constructData:
                    constructData["sourceLink"] = "source:%s~%s" % (
                        className, constructData["line"])

            for section in ("properties", "events", "statics", "members"):
                sectionData = getattr(classApi, section, None)

                if sectionData is not None:
                    for name in sectionData:
                        if "line" in sectionData[name]:
                            sectionData[name][
                                "sourceLink"] = "source:%s~%s" % (
                                    className, sectionData[name]["line"])

        #
        # Including Mixins / IncludedBy
        #

        Console.info("Resolving Mixins...")
        Console.indent()

        # Just used temporary to keep track of which classes are merged
        mergedClasses = set()

        def getApi(className):
            classApi = apiData[className]

            if className in mergedClasses:
                return classApi

            classIncludes = getattr(classApi, "includes", None)
            if classIncludes:
                for mixinName in classIncludes:
                    if mixinName not in apiData:
                        Console.error("Invalid mixin %s in class %s",
                                      className, mixinName)
                        continue

                    mixinApi = apiData[mixinName]
                    if not hasattr(mixinApi, "includedBy"):
                        mixinApi.includedBy = set()

                    mixinApi.includedBy.add(className)
                    mergeMixin(className, mixinName, classApi,
                               getApi(mixinName))

            mergedClasses.add(className)

            return classApi

        for className in apiData:
            apiData[className] = getApi(className)

        Console.outdent()

        #
        # Checking links
        #

        Console.info("Checking Links...")

        additionalTypes = ("Call", "Identifier", "Map", "Integer", "Node",
                           "Element", "Class", "Exception", "Uri")

        def checkInternalLink(link, className):
            match = internalLinkParse.match(link)
            if not match:
                return 'Invalid link "#%s"' % link

            if match.group(3) is not None:
                className = match.group(3)

            if not className in knownClasses and not className in apiData:
                return 'Invalid class in link "#%s"' % link

            # Accept all section/item values for named classes,
            # as it might be pretty complicated to verify this here.
            if className not in apiData:
                return True

            classApi = apiData[className]
            sectionName = match.group(2)
            itemName = match.group(5)

            if itemName is None:
                return True

            if sectionName is not None:
                if sectionName not in linkMap:
                    return 'Invalid section in link "#%s"' % link

                section = getattr(classApi, linkMap[sectionName], None)
                if section is None:
                    return 'Invalid section in link "#%s"' % link
                else:
                    if itemName in section:
                        return True

                    return 'Invalid item in link "#%s"' % link

            for sectionName in ("statics", "members", "properties", "events"):
                section = getattr(classApi, sectionName, None)
                if section and itemName in section:
                    return True

            return 'Invalid item link "#%s"' % link

        def checkLinksInItem(item):

            # Process types
            if "type" in item:

                if item["type"] == "Function":

                    # Check param types
                    if "params" in item:
                        for paramName in item["params"]:
                            paramEntry = item["params"][paramName]
                            if "type" in paramEntry:
                                for paramTypeEntry in paramEntry["type"]:
                                    if not paramTypeEntry[
                                            "name"] in knownClasses and not paramTypeEntry[
                                                "name"] in additionalTypes and not (
                                                    "builtin" in paramTypeEntry
                                                    or "pseudo"
                                                    in paramTypeEntry):
                                        item["errornous"] = True
                                        Console.error(
                                            'Invalid param type "%s" in %s' %
                                            (paramTypeEntry["name"],
                                             className))

                                    if not "pseudo" in paramTypeEntry and paramTypeEntry[
                                            "name"] in knownClasses:
                                        paramTypeEntry["linkable"] = True

                    # Check return types
                    if "returns" in item:
                        for returnTypeEntry in item["returns"]:
                            if not returnTypeEntry[
                                    "name"] in knownClasses and not returnTypeEntry[
                                        "name"] in additionalTypes and not (
                                            "builtin" in returnTypeEntry
                                            or "pseudo" in returnTypeEntry):
                                item["errornous"] = True
                                Console.error(
                                    'Invalid return type "%s" in %s' %
                                    (returnTypeEntry["name"], className))

                            if not "pseudo" in returnTypeEntry and returnTypeEntry[
                                    "name"] in knownClasses:
                                returnTypeEntry["linkable"] = True

                elif not item["type"] in knownClasses and not item[
                        "type"] in builtinTypes and not item[
                            "type"] in pseudoTypes and not item[
                                "type"] in additionalTypes:
                    item["errornous"] = True
                    Console.error('Invalid type "%s" in %s' %
                                  (item["type"], className))

            # Process doc
            if "doc" in item:

                def processInternalLink(match):
                    linkUrl = match.group(2)

                    if linkUrl.startswith("#"):
                        linkCheck = checkInternalLink(linkUrl[1:], className)
                        if linkCheck is not True:
                            item["errornous"] = True

                            if sectionName:
                                Console.error(
                                    "%s in %s:%s~%s" %
                                    (linkCheck, sectionName, className, name))
                            else:
                                Console.error("%s in %s" %
                                              (linkCheck, className))

                linkExtract.sub(processInternalLink, item["doc"])

        Console.indent()

        # Process APIs
        for className in apiData:
            classApi = apiData[className]

            sectionName = None
            constructData = getattr(classApi, "construct", None)
            if constructData is not None:
                checkLinksInItem(constructData)

            for sectionName in ("properties", "events", "statics", "members"):
                section = getattr(classApi, sectionName, None)

                if section is not None:
                    for name in section:
                        checkLinksInItem(section[name])

        Console.outdent()

        #
        # Filter Internals/Privates
        #

        Console.info("Filtering Items...")

        def isVisible(entry):
            if "visibility" in entry:
                visibility = entry["visibility"]
                if visibility == "private" and not privates:
                    return False
                if visibility == "internal" and not internals:
                    return False

            return True

        def filterInternalsPrivates(classApi, field):
            data = getattr(classApi, field, None)
            if data:
                for name in list(data):
                    if not isVisible(data[name]):
                        del data[name]

        for className in apiData:
            filterInternalsPrivates(apiData[className], "statics")
            filterInternalsPrivates(apiData[className], "members")

        #
        # Connection Interfaces / ImplementedBy
        #

        Console.info("Connecting Interfaces...")
        Console.indent()

        for className in apiData:
            classApi = getApi(className)

            if not hasattr(classApi, "main"):
                continue

            classType = classApi.main["type"]
            if classType == "core.Class":

                classImplements = getattr(classApi, "implements", None)
                if classImplements:

                    for interfaceName in classImplements:
                        interfaceApi = apiData[interfaceName]
                        implementedBy = getattr(interfaceApi, "implementedBy",
                                                None)
                        if not implementedBy:
                            implementedBy = interfaceApi.implementedBy = []

                        implementedBy.append(className)
                        connectInterface(className, interfaceName, classApi,
                                         interfaceApi)

        Console.outdent()

        #
        # Merging Named Classes
        #

        Console.info("Merging Named Classes...")
        Console.indent()

        for className in list(apiData):
            classApi = apiData[className]
            destName = classApi.main["name"]

            if destName is not None and destName != className:

                Console.debug("Extending class %s with %s", destName,
                              className)

                if destName in apiData:
                    destApi = apiData[destName]
                    destApi.main["from"].append(className)

                else:
                    destApi = apiData[destName] = Data.ApiData(
                        destName, highlight=highlightCode)
                    destApi.main = {
                        "type": "Extend",
                        "name": destName,
                        "from": [className]
                    }

                # If there is a "main" tag found in the class use its API description
                if "tags" in classApi.main and classApi.main[
                        "tags"] is not None and "main" in classApi.main["tags"]:
                    if "doc" in classApi.main:
                        destApi.main["doc"] = classApi.main["doc"]

                classApi.main["extension"] = True

                # Read existing data
                construct = getattr(classApi, "construct", None)
                statics = getattr(classApi, "statics", None)
                members = getattr(classApi, "members", None)

                if construct is not None:
                    if hasattr(destApi, "construct"):
                        Console.warn(
                            "Overriding constructor in extension %s by %s",
                            destName, className)

                    destApi.construct = copy.copy(construct)

                if statics is not None:
                    if not hasattr(destApi, "statics"):
                        destApi.statics = {}

                    for staticName in statics:
                        destApi.statics[staticName] = copy.copy(
                            statics[staticName])
                        destApi.statics[staticName]["from"] = className
                        destApi.statics[staticName][
                            "fromLink"] = "static:%s~%s" % (className,
                                                            staticName)

                if members is not None:
                    if not hasattr(destApi, "members"):
                        destApi.members = {}

                    for memberName in members:
                        destApi.members[memberName] = copy.copy(
                            members[memberName])
                        destApi.members[memberName]["from"] = className
                        destApi.members[memberName][
                            "fromLink"] = "member:%s~%s" % (className,
                                                            memberName)

        Console.outdent()

        #
        # Connecting Uses / UsedBy
        #

        Console.info("Collecting Use Patterns...")

        # This matches all uses with the known classes and only keeps them if matched
        allClasses = set(list(apiData))
        for className in apiData:
            uses = apiData[className].uses

            # Rebuild use list
            cleanUses = set()
            for use in uses:
                if use != className and use in allClasses:
                    cleanUses.add(use)

                    useEntry = apiData[use]
                    if not hasattr(useEntry, "usedBy"):
                        useEntry.usedBy = set()

                    useEntry.usedBy.add(className)

            apiData[className].uses = cleanUses

        #
        # Building Search Index
        #

        Console.info("Building Search Index...")
        search = {}

        def addSearch(classApi, field):
            data = getattr(classApi, field, None)
            if data:
                for name in data:
                    if name not in search:
                        search[name] = set()

                    search[name].add(className)

        for className in apiData:

            classApi = apiData[className]

            addSearch(classApi, "statics")
            addSearch(classApi, "members")
            addSearch(classApi, "properties")
            addSearch(classApi, "events")

        #
        # Post Process (dict to sorted list)
        #

        Console.info("Post Processing Data...")

        for className in sorted(apiData):
            classApi = apiData[className]

            convertTags(classApi.main)

            construct = getattr(classApi, "construct", None)
            if construct:
                convertFunction(construct)
                convertTags(construct)

            for section in ("statics", "members", "properties", "events"):
                items = getattr(classApi, section, None)
                if items:
                    sortedList = []
                    for itemName in sorted(items):
                        item = items[itemName]
                        item["name"] = itemName

                        if "type" in item and item["type"] == "Function":
                            convertFunction(item)

                        convertTags(item)
                        sortedList.append(item)

                    setattr(classApi, section, sortedList)

        #
        # Collecting Package Docs
        #

        Console.info("Collecting Package Docs...")
        Console.indent()

        # Inject existing package docs into api data
        for project in self.__session.getProjects():
            docs = project.getDocs()

            for packageName in docs:
                if self.__isIncluded(packageName, classFilter):
                    Console.debug("Creating package documentation %s",
                                  packageName)
                    apiData[packageName] = docs[packageName].getApi()

        # Fill missing package docs
        for className in sorted(apiData):
            splits = className.split(".")
            packageName = splits[0]
            for split in splits[1:]:
                if packageName not in apiData:
                    Console.warn("Missing package documentation %s",
                                 packageName)
                    apiData[packageName] = Data.ApiData(
                        packageName, highlight=highlightCode)
                    apiData[packageName].main = {
                        "type": "Package",
                        "name": packageName
                    }

                packageName = "%s.%s" % (packageName, split)

        # Now register all classes in their parent namespace/package
        for className in sorted(apiData):
            splits = className.split(".")
            packageName = ".".join(splits[:-1])
            if packageName:
                package = apiData[packageName]
                # debug("- Registering class %s in parent %s", className, packageName)

                entry = {
                    "name": splits[-1],
                    "link": className,
                }

                classMain = apiData[className].main
                if "doc" in classMain and classMain["doc"]:
                    summary = Text.extractSummary(classMain["doc"])
                    if summary:
                        entry["summary"] = summary

                if "type" in classMain and classMain["type"]:
                    entry["type"] = classMain["type"]

                if not hasattr(package, "content"):
                    package.content = [entry]
                else:
                    package.content.append(entry)

        Console.outdent()

        #
        # Writing API Index
        #

        Console.debug("Building Index...")
        index = {}

        for className in sorted(apiData):

            classApi = apiData[className]
            mainInfo = classApi.main

            # Create structure for className
            current = index
            for split in className.split("."):
                if split not in current:
                    current[split] = {}

                current = current[split]

            # Store current type
            current["$type"] = mainInfo["type"]

            # Keep information if
            if hasattr(classApi, "content"):
                current["$content"] = True

        #
        # Return
        #

        return apiData, index, search
Ejemplo n.º 5
0
    def __process(
        self, apiData, classFilter=None, internals=False, privates=False, printErrors=True, highlightCode=True
    ):

        knownClasses = set(list(apiData))

        #
        # Attaching Links to Source Code (Lines)
        # Building Documentation Summaries
        #

        Console.info("Adding Source Links...")

        for className in apiData:
            classApi = apiData[className]

            constructData = getattr(classApi, "construct", None)
            if constructData is not None:
                if "line" in constructData:
                    constructData["sourceLink"] = "source:%s~%s" % (className, constructData["line"])

            for section in ("properties", "events", "statics", "members"):
                sectionData = getattr(classApi, section, None)

                if sectionData is not None:
                    for name in sectionData:
                        if "line" in sectionData[name]:
                            sectionData[name]["sourceLink"] = "source:%s~%s" % (className, sectionData[name]["line"])

        #
        # Including Mixins / IncludedBy
        #

        Console.info("Resolving Mixins...")
        Console.indent()

        # Just used temporary to keep track of which classes are merged
        mergedClasses = set()

        def getApi(className):
            classApi = apiData[className]

            if className in mergedClasses:
                return classApi

            classIncludes = getattr(classApi, "includes", None)
            if classIncludes:
                for mixinName in classIncludes:
                    if mixinName not in apiData:
                        Console.error("Invalid mixin %s in class %s", className, mixinName)
                        continue

                    mixinApi = apiData[mixinName]
                    if not hasattr(mixinApi, "includedBy"):
                        mixinApi.includedBy = set()

                    mixinApi.includedBy.add(className)
                    mergeMixin(className, mixinName, classApi, getApi(mixinName))

            mergedClasses.add(className)

            return classApi

        for className in apiData:
            apiData[className] = getApi(className)

        Console.outdent()

        #
        # Checking links
        #

        Console.info("Checking Links...")

        additionalTypes = ("Call", "Identifier", "Map", "Integer", "Node", "Element", "Class", "Exception", "Uri")

        def checkInternalLink(link, className):
            match = internalLinkParse.match(link)
            if not match:
                return 'Invalid link "#%s"' % link

            if match.group(3) is not None:
                className = match.group(3)

            if not className in knownClasses and not className in apiData:
                return 'Invalid class in link "#%s"' % link

            # Accept all section/item values for named classes,
            # as it might be pretty complicated to verify this here.
            if className not in apiData:
                return True

            classApi = apiData[className]
            sectionName = match.group(2)
            itemName = match.group(5)

            if itemName is None:
                return True

            if sectionName is not None:
                if sectionName not in linkMap:
                    return 'Invalid section in link "#%s"' % link

                section = getattr(classApi, linkMap[sectionName], None)
                if section is None:
                    return 'Invalid section in link "#%s"' % link
                else:
                    if itemName in section:
                        return True

                    return 'Invalid item in link "#%s"' % link

            for sectionName in ("statics", "members", "properties", "events"):
                section = getattr(classApi, sectionName, None)
                if section and itemName in section:
                    return True

            return 'Invalid item link "#%s"' % link

        def checkLinksInItem(item):

            # Process types
            if "type" in item:

                if item["type"] == "Function":

                    # Check param types
                    if "params" in item:
                        for paramName in item["params"]:
                            paramEntry = item["params"][paramName]
                            if "type" in paramEntry:
                                for paramTypeEntry in paramEntry["type"]:
                                    if (
                                        not paramTypeEntry["name"] in knownClasses
                                        and not paramTypeEntry["name"] in additionalTypes
                                        and not ("builtin" in paramTypeEntry or "pseudo" in paramTypeEntry)
                                    ):
                                        item["errornous"] = True
                                        Console.error(
                                            'Invalid param type "%s" in %s' % (paramTypeEntry["name"], className)
                                        )

                                    if not "pseudo" in paramTypeEntry and paramTypeEntry["name"] in knownClasses:
                                        paramTypeEntry["linkable"] = True

                    # Check return types
                    if "returns" in item:
                        for returnTypeEntry in item["returns"]:
                            if (
                                not returnTypeEntry["name"] in knownClasses
                                and not returnTypeEntry["name"] in additionalTypes
                                and not ("builtin" in returnTypeEntry or "pseudo" in returnTypeEntry)
                            ):
                                item["errornous"] = True
                                Console.error('Invalid return type "%s" in %s' % (returnTypeEntry["name"], className))

                            if not "pseudo" in returnTypeEntry and returnTypeEntry["name"] in knownClasses:
                                returnTypeEntry["linkable"] = True

                elif (
                    not item["type"] in knownClasses
                    and not item["type"] in builtinTypes
                    and not item["type"] in pseudoTypes
                    and not item["type"] in additionalTypes
                ):
                    item["errornous"] = True
                    Console.error('Invalid type "%s" in %s' % (item["type"], className))

            # Process doc
            if "doc" in item:

                def processInternalLink(match):
                    linkUrl = match.group(2)

                    if linkUrl.startswith("#"):
                        linkCheck = checkInternalLink(linkUrl[1:], className)
                        if linkCheck is not True:
                            item["errornous"] = True

                            if sectionName:
                                Console.error("%s in %s:%s~%s" % (linkCheck, sectionName, className, name))
                            else:
                                Console.error("%s in %s" % (linkCheck, className))

                linkExtract.sub(processInternalLink, item["doc"])

        Console.indent()

        # Process APIs
        for className in apiData:
            classApi = apiData[className]

            sectionName = None
            constructData = getattr(classApi, "construct", None)
            if constructData is not None:
                checkLinksInItem(constructData)

            for sectionName in ("properties", "events", "statics", "members"):
                section = getattr(classApi, sectionName, None)

                if section is not None:
                    for name in section:
                        checkLinksInItem(section[name])

        Console.outdent()

        #
        # Filter Internals/Privates
        #

        Console.info("Filtering Items...")

        def isVisible(entry):
            if "visibility" in entry:
                visibility = entry["visibility"]
                if visibility == "private" and not privates:
                    return False
                if visibility == "internal" and not internals:
                    return False

            return True

        def filterInternalsPrivates(classApi, field):
            data = getattr(classApi, field, None)
            if data:
                for name in list(data):
                    if not isVisible(data[name]):
                        del data[name]

        for className in apiData:
            filterInternalsPrivates(apiData[className], "statics")
            filterInternalsPrivates(apiData[className], "members")

        #
        # Connection Interfaces / ImplementedBy
        #

        Console.info("Connecting Interfaces...")
        Console.indent()

        for className in apiData:
            classApi = getApi(className)

            if not hasattr(classApi, "main"):
                continue

            classType = classApi.main["type"]
            if classType == "core.Class":

                classImplements = getattr(classApi, "implements", None)
                if classImplements:

                    for interfaceName in classImplements:
                        interfaceApi = apiData[interfaceName]
                        implementedBy = getattr(interfaceApi, "implementedBy", None)
                        if not implementedBy:
                            implementedBy = interfaceApi.implementedBy = []

                        implementedBy.append(className)
                        connectInterface(className, interfaceName, classApi, interfaceApi)

        Console.outdent()

        #
        # Merging Named Classes
        #

        Console.info("Merging Named Classes...")
        Console.indent()

        for className in list(apiData):
            classApi = apiData[className]
            destName = classApi.main["name"]

            if destName is not None and destName != className:

                Console.debug("Extending class %s with %s", destName, className)

                if destName in apiData:
                    destApi = apiData[destName]
                    destApi.main["from"].append(className)

                else:
                    destApi = apiData[destName] = Data.ApiData(destName, highlight=highlightCode)
                    destApi.main = {"type": "Extend", "name": destName, "from": [className]}

                # If there is a "main" tag found in the class use its API description
                if "tags" in classApi.main and classApi.main["tags"] is not None and "main" in classApi.main["tags"]:
                    if "doc" in classApi.main:
                        destApi.main["doc"] = classApi.main["doc"]

                classApi.main["extension"] = True

                # Read existing data
                construct = getattr(classApi, "construct", None)
                statics = getattr(classApi, "statics", None)
                members = getattr(classApi, "members", None)

                if construct is not None:
                    if hasattr(destApi, "construct"):
                        Console.warn("Overriding constructor in extension %s by %s", destName, className)

                    destApi.construct = copy.copy(construct)

                if statics is not None:
                    if not hasattr(destApi, "statics"):
                        destApi.statics = {}

                    for staticName in statics:
                        destApi.statics[staticName] = copy.copy(statics[staticName])
                        destApi.statics[staticName]["from"] = className
                        destApi.statics[staticName]["fromLink"] = "static:%s~%s" % (className, staticName)

                if members is not None:
                    if not hasattr(destApi, "members"):
                        destApi.members = {}

                    for memberName in members:
                        destApi.members[memberName] = copy.copy(members[memberName])
                        destApi.members[memberName]["from"] = className
                        destApi.members[memberName]["fromLink"] = "member:%s~%s" % (className, memberName)

        Console.outdent()

        #
        # Connecting Uses / UsedBy
        #

        Console.info("Collecting Use Patterns...")

        # This matches all uses with the known classes and only keeps them if matched
        allClasses = set(list(apiData))
        for className in apiData:
            uses = apiData[className].uses

            # Rebuild use list
            cleanUses = set()
            for use in uses:
                if use != className and use in allClasses:
                    cleanUses.add(use)

                    useEntry = apiData[use]
                    if not hasattr(useEntry, "usedBy"):
                        useEntry.usedBy = set()

                    useEntry.usedBy.add(className)

            apiData[className].uses = cleanUses

        #
        # Building Search Index
        #

        Console.info("Building Search Index...")
        search = {}

        def addSearch(classApi, field):
            data = getattr(classApi, field, None)
            if data:
                for name in data:
                    if name not in search:
                        search[name] = set()

                    search[name].add(className)

        for className in apiData:

            classApi = apiData[className]

            addSearch(classApi, "statics")
            addSearch(classApi, "members")
            addSearch(classApi, "properties")
            addSearch(classApi, "events")

        #
        # Post Process (dict to sorted list)
        #

        Console.info("Post Processing Data...")

        for className in sorted(apiData):
            classApi = apiData[className]

            convertTags(classApi.main)

            construct = getattr(classApi, "construct", None)
            if construct:
                convertFunction(construct)
                convertTags(construct)

            for section in ("statics", "members", "properties", "events"):
                items = getattr(classApi, section, None)
                if items:
                    sortedList = []
                    for itemName in sorted(items):
                        item = items[itemName]
                        item["name"] = itemName

                        if "type" in item and item["type"] == "Function":
                            convertFunction(item)

                        convertTags(item)
                        sortedList.append(item)

                    setattr(classApi, section, sortedList)

        #
        # Collecting Package Docs
        #

        Console.info("Collecting Package Docs...")
        Console.indent()

        # Inject existing package docs into api data
        for project in self.__session.getProjects():
            docs = project.getDocs()

            for packageName in docs:
                if self.__isIncluded(packageName, classFilter):
                    Console.debug("Creating package documentation %s", packageName)
                    apiData[packageName] = docs[packageName].getApi()

        # Fill missing package docs
        for className in sorted(apiData):
            splits = className.split(".")
            packageName = splits[0]
            for split in splits[1:]:
                if packageName not in apiData:
                    Console.warn("Missing package documentation %s", packageName)
                    apiData[packageName] = Data.ApiData(packageName, highlight=highlightCode)
                    apiData[packageName].main = {"type": "Package", "name": packageName}

                packageName = "%s.%s" % (packageName, split)

        # Now register all classes in their parent namespace/package
        for className in sorted(apiData):
            splits = className.split(".")
            packageName = ".".join(splits[:-1])
            if packageName:
                package = apiData[packageName]
                # debug("- Registering class %s in parent %s", className, packageName)

                entry = {"name": splits[-1], "link": className}

                classMain = apiData[className].main
                if "doc" in classMain and classMain["doc"]:
                    summary = Text.extractSummary(classMain["doc"])
                    if summary:
                        entry["summary"] = summary

                if "type" in classMain and classMain["type"]:
                    entry["type"] = classMain["type"]

                if not hasattr(package, "content"):
                    package.content = [entry]
                else:
                    package.content.append(entry)

        Console.outdent()

        #
        # Writing API Index
        #

        Console.debug("Building Index...")
        index = {}

        for className in sorted(apiData):

            classApi = apiData[className]
            mainInfo = classApi.main

            # Create structure for className
            current = index
            for split in className.split("."):
                if split not in current:
                    current[split] = {}

                current = current[split]

            # Store current type
            current["$type"] = mainInfo["type"]

            # Keep information if
            if hasattr(classApi, "content"):
                current["$content"] = True

        #
        # Return
        #

        return apiData, index, search
Ejemplo n.º 6
0
    def addEntry(self, name, valueNode, commentNode, collection):

        #
        # Use already existing type or get type from node info
        #
        if name in collection:
            entry = collection[name]
        else:
            entry = collection[name] = {
                "type" : nodeTypeToDocType[valueNode.type]
            }


        #
        # Store generic data like line number and visibility
        #
        entry["line"] = valueNode.line
        entry["visibility"] = getVisibility(name)

        if name.upper() == name:
            entry["constant"] = True


        #
        # Complex structured types are processed in two steps
        #
        if entry["type"] == "Call" or entry["type"] == "Hook":

            commentNode = findCommentNode(commentNode)
            if commentNode:

                comment = getDocComment(commentNode)
                if comment:

                    # Static type definition
                    if comment.type:
                        entry["type"] = comment.type
                        self.addEntry(name, valueNode, commentNode, collection)
                        return

                    else:

                        # Maybe type function: We need to ignore returns etc. which are often
                        # the parent of the comment.
                        funcValueNode = findFunction(commentNode)
                        if funcValueNode:

                            # Switch to function type for re-analysis
                            entry["type"] = "Function"
                            self.addEntry(name, funcValueNode, commentNode, collection)
                            return

            if entry["type"] == "Call":

                callFunction = None

                if valueNode[0].type == "function":
                    callFunction = valueNode[0]

                elif valueNode[0].type == "identifier":
                    assignNodes, assignValues = findAssignments(valueNode[0].value, valueNode[0])
                    if assignNodes:
                        callFunction = assignValues[0]

                if callFunction:
                    # We try to analyze what the first return node returns
                    returnNode = findReturn(callFunction)
                    if returnNode and len(returnNode) > 0:
                        returnValue = returnNode[0]
                        entry["type"] = nodeTypeToDocType[returnValue.type]
                        self.addEntry(name, returnValue, returnValue, collection)

            elif entry["type"] == "Hook":

                thenEntry = valueNode[1]
                thenType = nodeTypeToDocType[thenEntry.type]
                if not thenType in ("void", "null"):
                    entry["type"] = thenType
                    self.addEntry(name, thenEntry, thenEntry, collection)

                # Try second item for better data then null/void
                else:
                    elseEntry = valueNode[2]
                    elseType = nodeTypeToDocType[elseEntry.type]
                    entry["type"] = elseType
                    self.addEntry(name, elseEntry, elseEntry, collection)

            return


        #
        # Try to resolve identifiers
        #
        if entry["type"] == "Identifier":

            assignTypeNode, assignCommentNode = resolveIdentifierNode(valueNode)
            if assignTypeNode is not None:
                entry["type"] = nodeTypeToDocType[assignTypeNode.type]

                # Prefer comment from assignment, not from value if available
                self.addEntry(name, assignTypeNode, assignCommentNode, collection)
                return


        #
        # Processes special types:
        #
        # - Plus: Whether a string or number is created
        # - Object: Figures out the class instance which is created
        #
        if entry["type"] == "Plus":
            entry["type"] = detectPlusType(valueNode)

        elif entry["type"] == "Object":
            entry["type"] = detectObjectType(valueNode)


        #
        # Add human readable value
        #
        valueNodeHumanValue = valueToString(valueNode)
        if valueNodeHumanValue != entry["type"] and not valueNodeHumanValue in ("Other", "Call"):
            entry["value"] = valueNodeHumanValue


        #
        # Read data from comment and add documentation
        #
        comment = getDocComment(commentNode)
        if comment:

            if comment.tags:
                entry["tags"] = comment.tags

            if comment.type:
                entry["type"] = comment.type

            if comment.hasContent():
                html = comment.getHtml(self.highlight)
                entry["doc"] = html
                entry["summary"] = Text.extractSummary(html)
            else:
                entry["errornous"] = True

            if comment.tags:
                entry["tags"] = comment.tags

        else:
            entry["errornous"] = True


        #
        # Add additional data for function types (params, returns)
        #
        if entry["type"] == "Function":

            # Add basic param data
            funcParams = getParamNamesFromFunction(valueNode)
            if funcParams:
                entry["params"] = {}
                for paramPos, paramName in enumerate(funcParams):
                    entry["params"][paramName] = {
                        "position" : paramPos
                    }

            # Detect return type automatically
            returnNode = findReturn(valueNode)
            if returnNode and len(returnNode) > 0:
                autoReturnType = nodeTypeToDocType[returnNode[0].type]
                if autoReturnType == "Plus":
                    autoReturnType = detectPlusType(returnNode[0])
                elif autoReturnType in ("Call", "Object"):
                    autoReturnType = "var"

                autoReturnEntry = {
                    "name" : autoReturnType,
                    "auto" : True
                }

                if autoReturnType in builtinTypes:
                    autoReturnEntry["builtin"] = True

                if autoReturnType in pseudoTypes:
                    autoReturnEntry["pseudo"] = True

                entry["returns"] = [autoReturnEntry]

            # Use comment for enrich existing data
            if comment:
                if comment.returns:
                    entry["returns"] = comment.returns

                if funcParams:
                    if not comment.params:
                        for paramName in funcParams:
                            entry["params"][paramName]["errornous"] = True

                    else:
                        for paramName in funcParams:
                            if paramName in comment.params:
                                entry["params"][paramName].update(comment.params[paramName])
                            else:
                                entry["params"][paramName]["errornous"] = True
Ejemplo n.º 7
0
    def addProperty(self, name, valueNode, commentNode, collection):

        entry = collection[name] = {
            "line": (commentNode or valueNode).line
        }
        comment = getDocComment(commentNode)

        if comment is None or not comment.text:
            entry["errornous"] = True
            self.warn('Missing or empty comment on property "%s"' % name, valueNode.line)

        else:
            html = comment.getHtml(self.highlight)
            entry["doc"] = html
            entry["summary"] = Text.extractSummary(html)

        if comment and comment.tags:
            entry["tags"] = comment.tags

        # Copy over value
        ptype = getKeyValue(valueNode, "type")
        if ptype and ptype.type == "string":
            entry["type"] = ptype.value

        pfire = getKeyValue(valueNode, "fire")
        if pfire and pfire.type == "string":
            entry["fire"] = pfire.value

        # Produce nice output for init value
        pinit = getKeyValue(valueNode, "init")
        if pinit:
            entry["init"] = valueToString(pinit)

        # Handle nullable, default value is true when an init value is there. Otherwise false.
        pnullable = getKeyValue(valueNode, "nullable")
        if pnullable:
            entry["nullable"] = pnullable.type == "true"
        elif pinit is not None and pinit.type != "null":
            entry["nullable"] = False
        else:
            entry["nullable"] = True

        # Just store whether an apply routine was defined
        papply = getKeyValue(valueNode, "apply")
        if papply and papply.type == "function":
            entry["apply"] = True

        # Multi Properties
        pthemeable = getKeyValue(valueNode, "themeable")
        if pthemeable and pthemeable.type == "true":
            entry["themeable"] = True

        pinheritable = getKeyValue(valueNode, "inheritable")
        if pinheritable and pinheritable.type == "true":
            entry["inheritable"] = True

        pgroup = getKeyValue(valueNode, "group")
        if pgroup and len(pgroup) > 0:
            entry["group"] = [child.value for child in pgroup]

            pshorthand = getKeyValue(valueNode, "shorthand")
            if pshorthand and pshorthand.type == "true":
                entry["shorthand"] = True