Permalink
301 lines (237 sloc) 12 KB

coala Configuration

Metadata
cEP 5
Version 2.2
Title coala Configuration
Authors Lasse Schuirmann mailto:lasse.schuirmann@gmail.com,
Adhika Setya Pramudita mailto:adhika.setyap@gmail.com
Status Implementation Due
Type Feature

Problem

One main pain point for our users is that coala is hard to configure. It tries to provide full options to a full set of tools. At the same time we try to provide an abstraction over the tools by e.g. giving the same setting names to similar bears across languages so the user can reuse the same configuration for other languages too.

By integrating lots of different existing tools with coala we have gained a huge set of possible analysis but it makes it very hard to understand and configure without knowledge about a particular tool. Also the conflict of bears versus linter bears that effectively offer multiple "checks" or sets of analysis rises: it is confusing to have to use multiple bears for one language for a set of analysis that is provided by only one bear for another language.

Other problems of the configuration file are, that some duplication is involved, especially when dealing with settings that are used for file collection.

Proposal

One important aspect of coala and its usability is that the configuration of a new language is as easy as possible. There should be no learning involved. Naturally, one very good aspect of a good configuration would be that it can be used for a new language without changing anything. Also, namespaces are a honking great idea and explicit is better than implicit.

The following changes are proposed and illustrated in the mockup following this paragraph:

  • Implicit default section inheritance will be removed. (It will be kept with warning for some time to allow people to do the switch.)
  • Explicit inheritance is possible by giving sections namespaces (all.python would inherit from the all section.)
  • Values can be added to inherited values. E.g. ignore += ./vendor would take the inherited value and add , ./vendor to it. Note that additions within a section will not be possible! The configuration should describe a state and not a program and must remain flow insensitive.
  • To specify, what analysis should be run the user will not have to care about bears. Instead, an aspects setting will be provided allowing the user to specify what aspects of their code should be analyzed. aspects could be Spelling or Smell and are namespaced such that Redundancy.Clone will be possible as well. aspects can be defined inclusive (analyze those aspects, with the aspects settings) or exclusive (analyze all but those aspects, with the ignore_aspects setting).
  • Specifying bears manually will still be possible as it eases use especially for bear developers.

Mockup

The following is a mockup of how a configuration could look like.

[all]
# Values can be added to inherited ones only, not within the section because
# a configuration should describe a state and not involve operations.
# A system wide coafile can define venv, .git, ... and we would recommend to
# always use += for ignore even in the default section to inherit those values.
# += always concatenates with a comma.
ignore += ./vendor

max_line_length = 80

# This inherits all settings from the `all` section. There is no implicit
# inheritance.
[all.python]
language = Python
files = **.py
aspects = Smell, Redundancy.Clone  # Inclusive

[all.c]
language = C
files = **.(c|h)
ignore_aspects = Redundancy  # Exclusive

An aspect could be writen by its fully qualified name (Root.Redundancy.Clone) or partially qualified name (Redundancy.Clone or even Clone) and is case insensitive. Note that writing by partial name could result in ambiguity. This could be resolved by writing the expanded partially qualified name in such way to remove the ambiguity or just write its fully qualified name.

In case of multiple aspects have the same taste name, ambiguity must be resolved by prefixing the taste name with its aspect name. For example, max_length is a taste of Shortlog and LineLength. Thus the Shortlog would be defined like Shortlog.max_length = 50.

Bear API

Bears will receive the aspects that should be checked as a parameter and results will get metadata to indicate what aspect has been checked.

from coalib.bears.LocalBear import LocalBear
from coalib.results.Result import Result
from coalib.bearlib.aspects import Root


# To define the aspects and languages for a bear using the bear metaclass
class RedundancyBear(LocalBear, aspects={
        'detect': [Root.Redundancy.Clone],
        'fix': [Root.Redundancy.UnusedImport]
}, languages=['C', 'Python']):

    # aspect instances are passed as parameter
    def run(self, filename, file, aspects):
        # No documentation required anymore for the bears.

        if unused_imports(file):
            # A bear may yield results for any aspect even if it's not selected.
            # coala will filter out only selected aspects.
            yield Result.from_values(aspect=Root.Redundancy.UnusedImport, ...)

        # Bears can save performance by only performing needed checks
        aspect = aspects.get(Root.Redundancy.Clone, False)
        if aspect:
            # Bears can access settings right from the aspect instance
            min_clone_tokens = aspect.min_clone_tokens

            # The aspect is tied to the result. coala now knows everything from
            # the aspect documentation!
            yield Result.from_values(aspect=aspect, ...)

Naming of aspects and tastes

aspects

An aspect should just talk about a property of something, without qualifying it. It should be named something like Length rather than TooLong. Then it should be given the tastes.

For e.g. an aspect can be named as Metadata.CommitMessage.Shortlog.Length to represent the length property of the shortlog (first line) of a commit message.

tastes

tastes are the values that are used to initialize the aspect settings. It should not be something like allow or check which enables or disables an aspect, rather it should be something more like a value which describes an aspect in a specific, measurable way. It can be given as much context as needed but not more.

For e.g. for the aspect Metadata.CommitMessage.Shortlog. FirstCharacterCasing, set the taste shortlog_starts_upper_case as true if the shortlog must begin with a upper case letter consistently, and false if the opposite is true.

Note: aspects and tastes are orthogonal concepts and should never overlap. Therefore extreme caution must be taken while naming aspects and tastes to avoid any discrepancy and inconsistency in the future.

aspectclass

The concept of aspects allows us to implement a consistency check and reduce documentation redundancy. Instead of documenting settings and results in every bear, we can create a new aspects class. A working implementation of the aspects concept is available here.

An aspect can be defined as follows:

from coalib.bearlib.aspects import Root


@Root.subaspect  # Every aspect is inherited from Root
class Metadata:  # The description can be given as a documentation comment
    """
    This describes any aspect that is related to metadata that is not your
    source code.
    """


@Metadata.subaspect  #  Now we can build a tree of aspects
class CommitMessage:
    """
    Your commit message is important documentation associated with your
    source code. It can help you to identify bugs (e.g. through
    `git bisect`) or find missing information about unknown source code
    (through `git blame`).

    Commit messages are also sometimes used to generate - or write
    manually - release notes.
    """


@CommitMessage.subaspect
class Shortlog:
    """
    Your commit shortlog is the first line of your commit message. It is the
    most crucial part and summarizes the change in the shortest possible manner.
    """

# This is a leaf of the tree! It has to be well documented and bears will only
# implement leaves. However, users may use just Metadata.Shortlog to check for
# groups of aspects. This simplifies browsing aspect documentation:
#
# Whether the user wants to see/configure aspects precisely or roughly, he can
# just go deeper into the tree as needed.
@Shortlog.subaspect
class ColonExistence:
    """
    Some projects force you to use colons in the commit message shortlog (first
    line).
    """
    class docs:
        example = """
        FIX: Describe change further
        context: Describe change further
        """
        example_language = "English"
        importance_reason = """
        The colon can be a useful separator for a context (e.g. a filename) so
        the commit message makes more sense to the reader or a classification
        (e.g. FIX, ...) or others. Some projects prefer not using colons
        specifically: consistency is key.
        """
        fix_suggestions = """
        Add or remove the colon according to the commit message guidelines.
        """

    def style_paragraph(self):
        if self.shortlog_colon:
            return """
            Your commit message must have a colon. You can use the colon to
            give your commit message some context, for example::

                coafile: Add colon shortlog check
            """
        else:
            return """
            Your commit message must not contain a colon. A colon interrupts
            the readability of your commit message::

                Removed colon shortlog check in coafile

            This allows your commit messages to resemble natural language
            more closely.
            """

    def result_message(self):
        if self.shortlog_colon:
            return "Missing colon in commit message shortlog."
        else:
            return "Colon found in commit message shortlog."

    shortlog_colon = Setting[bool](
        "Whether or not the shortlog has to contain a colon.",
        (True, False), default=True)

This allows to show more information in results. Bears can give a custom result message; however, by default the message can be generated from the settings of the aspects with the result_message function.

aspects can also define a style_paragraph function that returns a small paragraph describing how the user should write their source code according to the given settings. This will be used to generate a full style definition from a coala configuration.

Bears are expected to use all of the settings they get via the aspects.

Implementation

The implementation will have to be two phased as we should ideally deprecate the old way of using bears, keep it around for few releases and then phase it out and remove unneeded source code.

Gathering Metadata

To get all bears for a set of aspects, all bears will have to be collected. It can then be filtered against its metadata to get only bears that analyze the given aspects. This information should be cached to improve the performance.

Bear Picking Strategy

If multiple bears provide the same aspects, then coala will pick one of them with the following prioritization:

  1. Could fix the problem, not only detect it.
  2. Minimizing number of bears. For performance reason, it's faster to run 1 bear that can handle 10 aspect rather than 10 bears for 10 aspect.
  3. Run under the most common runtime. Rather than have 2 external bear run with NodeJS and 2 with Ruby, try to get 4 that run under NodeJS.
  4. Alphabetical order.

coala will emit a log message that state the name and reason for each picked bear.