Metadata | |
---|---|
cEP | 5 |
Version | 2.2 |
Title | coala Configuration |
Authors | Lasse Schuirmann mailto:[email protected], |
Adhika Setya Pramudita mailto:[email protected] | |
Status | Implementation Due |
Type | Feature |
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.
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 theall
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 whataspects
of their code should be analyzed.aspects
could beSpelling
orSmell
and are namespaced such thatRedundancy.Clone
will be possible as well.aspects
can be defined inclusive (analyze those aspects, with theaspects
settings) or exclusive (analyze all but those aspects, with theignore_aspects
setting). - Specifying
bears
manually will still be possible as it eases use especially for bear developers.
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
.
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, ...)
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 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.
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.
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.
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.
If multiple bears provide the same aspects, then coala will pick one of them with the following prioritization:
- Could fix the problem, not only detect it.
- 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.
- 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.
- Alphabetical order.
coala will emit a log message that state the name and reason for each picked bear.