Skip to content

Commit

Permalink
StylintBear: Implement settings
Browse files Browse the repository at this point in the history
Use the implemented settings to generate a `config_file` and use
that file if the user does not explicitly mention `stylint_config`.

Closes coala#1795
  • Loading branch information
yash-nisar committed Jun 1, 2017
1 parent 95b85f5 commit 02278c2
Show file tree
Hide file tree
Showing 12 changed files with 404 additions and 0 deletions.
207 changes: 207 additions & 0 deletions bears/stylus/StylintBear.py
@@ -0,0 +1,207 @@
import json

from coalib.bearlib.abstractions.Linter import linter
from dependency_management.requirements.NpmRequirement import NpmRequirement


@linter(executable='stylint',
output_format='regex',
output_regex=r'(?P<line>\d+):?(?P<column>\d+)?\s+.*'
r'(?P<severity>error|warning)\s+(?P<message>.+)')
class StylintBear:
"""
Attempts to catch little mistakes (duplication of rules for instance) and
enforces a code style guide on Stylus (a dynamic stylesheet language
with the `.styl` extension that is compiled into CSS) files.
The `StylintBear` is able to catch following problems:
- Duplication of rules
- Mixed spaces and tabs
- Unnecessary brackets
- Missing colon between property and value
- Naming conventions
- Trailing whitespace
"""

LANGUAGES = {'Stylus'}
REQUIREMENTS = {NpmRequirement('stylint', '1.5.9')}
AUTHORS = {'The coala developers'}
AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
LICENSE = 'AGPL-3.0'
CAN_DETECT = {'Formatting', 'Syntax', 'Redundancy'}
SEE_MORE = 'https://github.com/SimenB/stylint'

@staticmethod
def generate_config(filename, file,
allow_block_keyword: bool = False,
allow_brackets: str = 'never',
allow_colons: str = 'always',
allow_colors: str = 'always',
allow_spaces_after_commas: str = 'always',
allow_spaces_after_comments: str = 'always',
allow_trailing_whitespace: str = 'never',
allow_css_literal: str = 'never',
max_selector_depth: bool = False,
check_duplicates: bool = True,
allow_efficient_properties: str = 'always',
set_extend_preference: bool = False,
indent_size: bool = False,
allow_leading_zero: str = 'never',
set_max_errors: bool = False,
set_max_warnings: bool = False,
mixed_spaces_and_tabs: bool = False,
set_naming_convention: bool = False,
set_strict_naming_convention: bool = False,
allow_none_keyword: str = 'never',
check_no_important_keyword: bool = True,
allow_spaces_inside_parens: bool = False,
allow_placeholder: str = 'always',
prefix_vars_with_dollar: str = 'always',
allow_semicolons: str = 'never',
sort_order: str = 'alphabetical',
allow_stacked_properties: str = 'never',
check_valid_property: bool = True,
allow_zero_units: str = 'never',
z_index_normalize: bool = False):
"""
:param allow_block_keyword:
When ``always`` expect the ``@block`` keyword when defining block
variables. When ``never``, expect no ``@block`` keyword when
defining block variables. When ``false``, do not check either way.
:param allow_brackets:
When ``always``, expect ``{}`` when declaring a selector. When
``never``, expect no brackets when declaring a selector.
:param allow_colons:
When ``always``, expect ``:`` when declaring a property. When
``never``, expect no ``:`` when declaring a property.
:param allow_colors:
When ``always``, enforce variables when defining hex values.
:param allow_spaces_after_commas:
Enforce or disallow spaces after commas.
:param allow_spaces_after_comments:
Enforce or disallow spaces after line comments.
:param allow_css_literal:
By default Stylint ignores ``@css`` blocks. If set to true however,
it will throw a warning if ``@css`` is used.
:param max_selector_depth:
Set the max selector depth. If set to 4, max selector depth will
be 4 indents. Pseudo selectors like ``&:first-child`` or
``&:hover`` won't count towards the limit.
:param check_duplicates:
Checks if selector or property duplicated unnecessarily. By
default, only checks on a file-by-file basis, but if
``globalDupes: true`` is set, then it will also check for
duplicates globally (for root values only).
:param allow_efficient_properties:
Check for places where properties can be written more efficiently.
:param set_extend_preference:
Pass in either ``@extend`` or ``@extends`` and then enforce that.
Both are valid in Stylus. It doesn't really matter which one
you use.
:param indent_size:
This works in conjunction with ``max_selector_depth``. If you
indent with spaces this is the number of spaces you indent with.
If you use hard tabs, set this value to ``false``.
:param allow_leading_zero:
Enforce or disallow unnecessary leading zeroes on decimal points.
:param set_max_errors:
Set 'max' number of Errors.
:param set_max_warnings:
Set 'max' number of Warnings.
:param mixed_spaces_and_tabs:
Returns ``true`` if mixed spaces and tabs are found. If a number is
passed to indent_size, it assumes soft tabs (ie, spaces)
, and if ``false`` is passed to indentPref it assumes hard tabs.
:param set_naming_convention:
Enforce a particular naming convention when declaring classes, ids,
and variables. Throws a warning if you don't follow the convention.
:param set_strict_naming_convention:
By default, ``set_naming_convention`` only looks at variable names.
If ``set_strict_naming_convention`` is set to true,
``set_naming_convention`` will also look at class and id names.
:param allow_none_keyword:
If ``always`` check for places where ``none`` used instead of 0. If
``never`` check for places where 0 could be used instead of none.
:param check_no_important_keyword:
If ``true``, show warning when ``!important`` is found.
:param allow_spaces_inside_parens:
Enforce or disallow use of extra spaces inside parens.
:param allow_placeholder:
Enforce extending placeholder vars when using ``@extend(s)``.
:param prefix_vars_with_dollar:
Enforce use of ``$`` when defining a variable. In Stylus using a
``$`` when defining a variable is optional, but is a good idea
if you want to prevent ambiguity. Not including the ``$`` sets up
situations where you wonder: "Is this a variable or a value?"
For instance: padding ``$default`` is easier to understand than
padding ``default``.
:param allow_semicolons:
Enforce or disallow semicolons.
:param sort_order:
Enforce a particular sort order when declaring properties. Throws
a warning if you don't follow the order. If set to ``false``, allow
any order.
:param allow_stacked_properties:
No one-liners. Enforce putting properties on new lines.
:param check_valid_property:
Check that a property is valid CSS or HTML.
:param allow_zero_units:
Looks for instances of ``0px``. You don't need the px. Checks all
units, not just px.
:param z_index_normalize:
Enforce some (very) basic z-index sanity. Any number passed in
will be used as the base for your z-index values. Throws an error
if your value is not normalized.
"""
options = {
'blocks': allow_block_keyword,
'brackets': allow_brackets,
'colons': allow_colons,
'colors': allow_colors,
'commaSpace': allow_spaces_after_commas,
'commentSpace': allow_spaces_after_comments,
'cssLiteral': allow_css_literal,
'depthLimit': max_selector_depth,
'duplicates': check_duplicates,
'efficient': allow_efficient_properties,
'extendPref': set_extend_preference,
'indentPref': indent_size,
'leadingZero': allow_leading_zero,
'maxErrors': set_max_errors,
'maxWarnings': set_max_warnings,
'mixed': mixed_spaces_and_tabs,
'namingConvention': set_naming_convention,
'namingConventionStrict': set_strict_naming_convention,
'none': allow_none_keyword,
'noImportant': check_no_important_keyword,
'parenSpace': allow_spaces_inside_parens,
'placeholders': allow_placeholder,
'prefixVarsWithDollar': prefix_vars_with_dollar,
'semicolons': allow_semicolons,
'sortOrder': sort_order,
'stackedProperties': allow_stacked_properties,
'trailingWhitespace': allow_trailing_whitespace,
'valid': check_valid_property,
'zeroUnits': allow_zero_units,
'zIndexNormalize': z_index_normalize,
'groupOutputByFile': True}

options['reporterOptions'] = {
'columns': ['lineData', 'severity', 'description', 'rule'],
'columnSplitter': ' ',
'showHeaders': False,
'truncate': True
}

return json.dumps(options)

@staticmethod
def create_arguments(filename, file, config_file, stylint_config: str=''):
"""
:param stylint_config:
The location of the ``.stylintrc`` config file.
"""
if stylint_config:
return '--config', stylint_config, filename
else:
return '--config', config_file, filename
158 changes: 158 additions & 0 deletions tests/stylus/StylintBearTest.py
@@ -0,0 +1,158 @@
import os
from queue import Queue

from bears.stylus.StylintBear import StylintBear
from coalib.results.Result import Result
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
from coalib.settings.Section import Section
from coalib.testing.BearTestHelper import generate_skip_decorator
from coalib.testing.LocalBearTestHelper import LocalBearTestHelper


def get_testfile_path(name):
return os.path.join(os.path.dirname(__file__),
'stylint_test_files',
name)


def load_testfile(name):
with open(get_testfile_path(name)) as f:
return f.readlines()


@generate_skip_decorator(StylintBear)
class StylintBearTest(LocalBearTestHelper):

def setUp(self):
self.uut = StylintBear(Section('name'), Queue())

def test_bad_missing_colon(self):
filename = 'test_bad_missing_colon.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='missing colon between property '
'and value colons',
file=get_testfile_path(filename),
line=2,
column=6,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_duplicates(self):
filename = 'test_bad_duplicates.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='duplicate property or selector, '
'consider merging duplicates',
file=get_testfile_path(filename),
line=4,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_no_important(self):
filename = 'test_bad_no_important.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='!important is disallowed '
'noImportant',
file=get_testfile_path(filename),
line=2,
column=9,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_brackets(self):
filename = 'test_bad_brackets.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='unnecessary bracket brackets',
file=get_testfile_path(filename),
line=1,
column=13,
severity=RESULT_SEVERITY.NORMAL),
Result.from_values('StylintBear',
message='unnecessary bracket brackets',
file=get_testfile_path(filename),
line=3,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_semicolon(self):
filename = 'test_bad_semicolon.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='unnecessary semicolon found '
'semicolons',
file=get_testfile_path(filename),
line=2,
column=19,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_alphabetical_order(self):
filename = 'test_bad_alphabetical_order.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='prefer alphabetical when sorting '
'properties sortOrder',
file=get_testfile_path(filename),
line=3,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_trailing_whitespace(self):
filename = 'test_bad_trailing_whitespace.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='trailing whitespace '
'trailingWhitespace',
file=get_testfile_path(filename),
line=3,
column=22,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename))

def test_bad_mixed_spaces_tabs(self):
filename = 'test_bad_mixed_spaces_tabs.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[Result.from_values('StylintBear',
message='mixed spaces and tabs',
file=get_testfile_path(filename),
line=3,
column=0,
severity=RESULT_SEVERITY.NORMAL)],
filename=get_testfile_path(filename),
settings={'stylint_config': get_testfile_path('.stylintrc')})

def test_valid_file(self):
filename = 'test_valid_file.styl'
file_contents = load_testfile(filename)
self.check_results(
self.uut,
file_contents,
[],
filename=get_testfile_path(filename))
5 changes: 5 additions & 0 deletions tests/stylus/stylint_test_files/.stylintrc
@@ -0,0 +1,5 @@
{
"depthLimit": 4,
"indentPref": 4,
"mixed": true
}
@@ -0,0 +1,3 @@
.bankTitle
padding-right: 2em
font-weight: bold
3 changes: 3 additions & 0 deletions tests/stylus/stylint_test_files/test_bad_brackets.styl
@@ -0,0 +1,3 @@
.pageContent {
padding: 1em
}
5 changes: 5 additions & 0 deletions tests/stylus/stylint_test_files/test_bad_duplicates.styl
@@ -0,0 +1,5 @@
test = @block
margin: 0

test =
margin: auto
2 changes: 2 additions & 0 deletions tests/stylus/stylint_test_files/test_bad_missing_colon.styl
@@ -0,0 +1,2 @@
test = @block
margin 0
@@ -0,0 +1,3 @@
test = @block
margin: 0
padding-right: 2em
2 changes: 2 additions & 0 deletions tests/stylus/stylint_test_files/test_bad_no_important.styl
@@ -0,0 +1,2 @@
.im-important
margin 0 !important

0 comments on commit 02278c2

Please sign in to comment.