Replace SCSS Lint with Stylelint

SCSS Lint was based on Ruby Sass, which has been deprecated since 2018
and doesn't support some of the latest features in Dart Sass.

Since we're going to migrate to Dart Sass, we're also migrating to
Stylelint. In order to provide all the funcionality SCSS Lint had, we
also need to install a few Stylelint plugins. We also need to install
the `postcss-scss` package so Stylelint can read SCSS files.

We're still getting the linting errors we used to get in
`legislation_process.scss` because of the `max-nesting-depth` and
`selector-max-compound-selectors` rules, as well as the warnings we used
to get in `layout.scss` because the `ssb-whatsapp_app` HTML class
contains undescores in their name.

We're also getting a couple of new linting errors, which could be false
positives:

* The `scss/no-unused-private-members` rule reports errors in
  `_consul_settings.scss` because of the `$-zf-*` variables, but I'm not
  too worried about this one because these lines won't be necessary after
  updating Foundation.
* The `scss/selector-no-redundant-nesting-selector` reports an error in
  `datepicker_overrides.scss`, but removing the unnecessary nesting
  selector would make it harder to understand the code (assuming it'd
  work in the first place).

We've changed some files due to few differences between SCSS Lint rules
and their Stylelint equivalents:

* The `@stylistic/string-quotes` rule detects a case in `admin.scss`
  that StringQuotes didn't detect.
* The `function-url-quotes` rule detects a case in `mixins/icons.scss`
  that UrlQuotes didn't detect.
* The `@stylistic/declaration-bang-space-before` rule detects a case in
  `sdg/goals/show.scss` that BangFormat didn't detect.

There are also a couple of rules that don't behave exactly like they
used to:

* The equivalents of SpaceBetweenParens and SpaceAfterComma don't cover
  parenthesis or commas in mixin parameters; we haven't found rules that
  detect these cases.
* DisableLinterReason probably has an equivalent that behaves
  differently but, since we never disable linters inline, we aren't
  adding its equivalent rule.

Note we're removing the SpaceAfterVariableColon rule because its
equivalent, `scss/dollar-variable-colon-space-after`, reports cases
where we add spaces to indent several variable assignments (which we do
a lot in the `_consul_settings.scss` file). We might add this rule again
if we stop aligning consecutive assignments.

We're also removing the QualifyingElement rule because its equivalent,
`selector-no-qualifying-type: true`, behaves differently. For example,
in this code:

```
a.qualifying {
  color: inherit;
}

p {
  &.qualifying {
    color: inherit;
  }
}
```

With the QualifyingElement from SCSS Lint, the first rule is incorrect
but the second one is correct. With the selector-no-qualifying-type rule
from Stylelint, on the other hand, both rules are incorrect.

Personally, I never liked the QualifyingElement rule, and we were
working around it anyway, so we aren't applying
selector-no-qualifying-type.

For reference, here's a full list of the SCSS Lint rules we had enabled
and their Stylelint equivalents.

BangFormat
"@stylistic/declaration-bang-space-after": "never"
"@stylistic/declaration-bang-space-before": "always"

BorderZero
declaration-property-value-disallowed-list:
  border:
    - none

ColorKeyword
color-named: "never"

DebugStatement
at-rule-disallowed-list:
  - debug

DeclarationOrder
order/order:
  - dollar-variables
  - custom-properties
  - type: at-rule
    name: extend
  - type: at-rule
    name: include
    hasBlock: false
  - declarations
  - type: at-rule
    name: include
    hasBlock: true
  - rules

ElsePlacement # Apparently replaced by the combination of:
scss/at-else-closing-brace-space-after: always-intermediate
scss/at-else-empty-line-before: never
scss/at-if-closing-brace-space-after: always-intermediate
scss/at-if-closing-brace-newline-after: always-last-in-chain

EmptyLineBetweenBlocks:
  ignore_single_line_blocks: true
rule-empty-line-before:
  - "always-multi-line"
  - ignore:
    - after-comment
    - first-nested

EmptyRule:
block-no-empty: true

FinalNewline
"@stylistic/no-missing-end-of-source-newline": true

HexLength
color-hex-length: "short"

HexNotation
@stylistic/color-hex-case: "lower"

HexValidation
color-no-invalid-hex: true

IdSelector
selector-max-id: 0

ImportPath
scss/load-no-partial-leading-underscore: true
scss/at-import-partial-extension: never

Indentation
"@stylistic/indentation": 2

LeadingZero
@stylistic/number-leading-zero: "always"

NameFormat
scss/at-function-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/at-mixin-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/dollar-variable-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/percent-placeholder-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"

NestingDepth
max-nesting-depth: 4

PlaceholderInExtend
scss/at-extend-no-missing-placeholder: true

PrivateNamingConvention
scss/no-unused-private-members: true

PropertySpelling
property-no-unknown: true

PseudoElement
selector-pseudo-element-colon-notation: "double"
selector-pseudo-element-no-unknown: true

SelectorDepth
selector-max-compound-selectors: 5

SelectorFormat # Not always followed; ssb-whatsapp_app
custom-property-pattern: "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$"
selector-class-pattern: "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$"

Shorthand
shorthand-property-no-redundant-values: true

SingleLinePerProperty
"@stylistic/declaration-block-semicolon-newline-after": always-multi-line

SingleLinePerSelector
@stylistic/selector-list-comma-newline-after": always

SpaceAfterComma
"@stylistic/value-list-comma-space-after": always

SpaceAfterPropertyColon
"@stylistic/declaration-colon-space-after": always-single-line

SpaceAfterPropertyName
"@stylistic/declaration-colon-space-before": never

SpaceAfterVariableName
scss/dollar-variable-colon-space-before: never

SpaceAroundOperator
scss/operator-no-unspaced: true

SpaceBeforeBrace
@stylistic/block-opening-brace-space-before: always

SpaceBetweenParens
"@stylistic/function-parentheses-space-inside": never
"@stylistic/selector-attribute-brackets-space-inside": never
"@stylistic/selector-pseudo-class-parentheses-space-inside": never
"@stylistic/media-feature-parentheses-space-inside": never

StringQuotes
"@stylistic/string-quotes": double

TrailingSemicolon
"@stylistic/declaration-block-trailing-semicolon": always

TrailingWhitespace # Note it was enabled by the gem and not by us
"@stylistic/no-eol-whitespace": true

TrailingZero
"@stylistic/number-no-trailing-zeros": true

UnnecessaryMantissa
"@stylistic/number-no-trailing-zeros": true

UnnecessaryParentReference
scss/selector-no-redundant-nesting-selector: true

UrlQuotes
function-url-quotes: always

VendorPrefixes
value-no-vendor-prefix: true
selector-no-vendor-prefix: true
property-no-vendor-prefix: true
at-rule-no-vendor-prefix: true
media-feature-name-no-vendor-prefix: true

ZeroUnit:
length-zero-no-unit: true
This commit is contained in:
Javi Martín
2024-03-25 04:55:09 +01:00
parent ea26f39589
commit e210682ac0
11 changed files with 1614 additions and 225 deletions

View File

@@ -13,6 +13,13 @@ jobs:
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
bundler-cache: true bundler-cache: true
- name: Setup NPM
uses: actions/setup-node@v4
with:
cache: "npm"
node-version-file: ".node-version"
- name: Install node packages
run: npm clean-install
- name: Run pronto - name: Run pronto
run: | run: |
if [[ ${{ github.event.pull_request.head.repo.full_name }} == ${{ github.event.pull_request.base.repo.full_name }} && ${{ github.actor }} != dependabot* ]]; then if [[ ${{ github.event.pull_request.head.repo.full_name }} == ${{ github.event.pull_request.base.repo.full_name }} && ${{ github.actor }} != dependabot* ]]; then

1
.pronto_stylelint.yml Normal file
View File

@@ -0,0 +1 @@
stylelint_executable: "npx stylelint"

View File

@@ -1,212 +0,0 @@
linters:
BangFormat:
enabled: true
space_before_bang: true
space_after_bang: false
BorderZero:
enabled: true
convention: zero
ChainedClasses:
enabled: false
ColorKeyword:
enabled: true
ColorVariable:
enabled: false
Comment:
enabled: false
DebugStatement:
enabled: true
DeclarationOrder:
enabled: true
DisableLinterReason:
enabled: true
DuplicateProperty:
enabled: false
ElsePlacement:
enabled: true
style: same_line
EmptyLineBetweenBlocks:
enabled: true
ignore_single_line_blocks: true
EmptyRule:
enabled: true
ExtendDirective:
enabled: false
FinalNewline:
enabled: true
present: true
HexLength:
enabled: true
style: short
HexNotation:
enabled: true
style: lowercase
HexValidation:
enabled: true
IdSelector:
enabled: true
ImportantRule:
enabled: false
ImportPath:
enabled: true
leading_underscore: false
filename_extension: false
Indentation:
enabled: true
allow_non_nested_indentation: true
character: space
width: 2
LeadingZero:
enabled: true
style: include_zero
MergeableSelector:
enabled: false
force_nesting: false
NameFormat:
enabled: true
convention: hyphenated_lowercase
allow_leading_underscore: true
NestingDepth:
enabled: true
max_depth: 4
PlaceholderInExtend:
enabled: true
exclude: "app/assets/stylesheets/mixins/icons.scss"
PrivateNamingConvention:
enabled: true
prefix: _
PropertyCount:
enabled: false
PropertySortOrder:
enabled: false
PropertySpelling:
enabled: true
extra_properties: []
PropertyUnits:
enabled: false
PseudoElement:
enabled: true
QualifyingElement:
enabled: true
allow_element_with_attribute: false
allow_element_with_class: false
allow_element_with_id: false
SelectorDepth:
enabled: true
max_depth: 5
SelectorFormat:
enabled: true
convention: hyphenated_lowercase
Shorthand:
enabled: true
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: false
SingleLinePerSelector:
enabled: true
SpaceAfterComma:
enabled: true
SpaceAfterPropertyColon:
enabled: true
style: one_space
SpaceAfterPropertyName:
enabled: true
SpaceAfterVariableColon:
enabled: true
style: at_least_one_space
SpaceAfterVariableName:
enabled: true
SpaceAroundOperator:
enabled: true
style: one_space
SpaceBeforeBrace:
enabled: true
style: space
allow_single_line_padding: true
SpaceBetweenParens:
enabled: true
spaces: 0
StringQuotes:
enabled: true
style: double_quotes
TrailingSemicolon:
enabled: true
TrailingZero:
enabled: true
TransitionAll:
enabled: false
UnnecessaryMantissa:
enabled: true
UnnecessaryParentReference:
enabled: true
UrlFormat:
enabled: false
UrlQuotes:
enabled: true
VariableForProperty:
enabled: false
VendorPrefixes:
enabled: true
identifier_list: base
include: []
exclude: []
ZeroUnit:
enabled: true

90
.stylelintrc.yml Normal file
View File

@@ -0,0 +1,90 @@
customSyntax: postcss-scss
ignoreFiles:
- app/assets/stylesheets/pdf_fonts.scss.erb
- app/assets/stylesheets/print.css
- app/assets/stylesheets/browserslist
- app/assets/stylesheets/fonts.scss
plugins:
- stylelint-order
- stylelint-scss
- "@stylistic/stylelint-plugin"
rules:
at-rule-disallowed-list:
- debug
at-rule-no-vendor-prefix: true
block-no-empty: true
color-hex-length: "short"
color-named: "never"
color-no-invalid-hex: true
custom-property-pattern: "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$"
declaration-property-value-disallowed-list:
border:
- none
function-url-quotes: always
length-zero-no-unit: true
max-nesting-depth: 4
media-feature-name-no-vendor-prefix: true
property-no-unknown: true
property-no-vendor-prefix: true
rule-empty-line-before:
- "always-multi-line"
- ignore:
- after-comment
- first-nested
selector-class-pattern: "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$"
selector-max-compound-selectors: 5
selector-max-id: 0
selector-no-vendor-prefix: true
selector-pseudo-element-colon-notation: "double"
selector-pseudo-element-no-unknown: true
shorthand-property-no-redundant-values: true
value-no-vendor-prefix: true
order/order:
- type: at-rule
name: extend
- type: at-rule
name: include
hasBlock: false
- declarations
- type: at-rule
name: include
hasBlock: true
- rules
scss/at-else-closing-brace-space-after: always-intermediate
scss/at-else-empty-line-before: never
scss/at-extend-no-missing-placeholder: true
scss/at-function-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/at-if-closing-brace-newline-after: always-last-in-chain
scss/at-if-closing-brace-space-after: always-intermediate
scss/at-import-partial-extension: never
scss/at-mixin-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/dollar-variable-colon-space-before: never
scss/dollar-variable-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/load-no-partial-leading-underscore: true
scss/no-unused-private-members: true
scss/operator-no-unspaced: true
scss/percent-placeholder-pattern: "^(-?[a-z][a-z0-9]*)(-[a-z0-9]+)*$"
scss/selector-no-redundant-nesting-selector: true
"@stylistic/declaration-bang-space-after": "never"
"@stylistic/declaration-bang-space-before": "always"
"@stylistic/declaration-block-semicolon-newline-after": always-multi-line
"@stylistic/declaration-block-trailing-semicolon": always
"@stylistic/declaration-colon-space-after": always-single-line
"@stylistic/declaration-colon-space-before": never
"@stylistic/function-parentheses-space-inside": never-single-line
"@stylistic/indentation":
- 2
- ignore:
- value
"@stylistic/media-feature-parentheses-space-inside": never
"@stylistic/no-eol-whitespace": true
"@stylistic/no-missing-end-of-source-newline": true
"@stylistic/number-no-trailing-zeros": true
"@stylistic/selector-attribute-brackets-space-inside": never
"@stylistic/selector-pseudo-class-parentheses-space-inside": never
"@stylistic/string-quotes": double
"@stylistic/value-list-comma-space-after": always-single-line
"@stylistic/block-opening-brace-space-before": always
"@stylistic/color-hex-case": "lower"
"@stylistic/number-leading-zero": "always"
"@stylistic/selector-list-comma-newline-after": always

View File

@@ -97,7 +97,7 @@ group :development do
gem "pronto-erb_lint", "~> 0.1.6", require: false gem "pronto-erb_lint", "~> 0.1.6", require: false
gem "pronto-eslint", "~> 0.11.1", require: false gem "pronto-eslint", "~> 0.11.1", require: false
gem "pronto-rubocop", "~> 0.11.5", require: false gem "pronto-rubocop", "~> 0.11.5", require: false
gem "pronto-scss", "~> 0.11.0", require: false gem "pronto-stylelint", "~> 0.10.3", require: false
gem "rubocop", "~> 1.61.0", require: false gem "rubocop", "~> 1.61.0", require: false
gem "rubocop-capybara", "~> 2.20.0", require: false gem "rubocop-capybara", "~> 2.20.0", require: false
gem "rubocop-factory_bot", "~> 2.25.1", require: false gem "rubocop-factory_bot", "~> 2.25.1", require: false
@@ -105,7 +105,6 @@ group :development do
gem "rubocop-rails", "~> 2.23.1", require: false gem "rubocop-rails", "~> 2.23.1", require: false
gem "rubocop-rspec", "~> 2.27.0", require: false gem "rubocop-rspec", "~> 2.27.0", require: false
gem "rvm1-capistrano3", "~> 1.4.0", require: false gem "rvm1-capistrano3", "~> 1.4.0", require: false
gem "scss_lint", "~> 0.60.0", require: false
gem "spring", "~> 4.1.3" gem "spring", "~> 4.1.3"
gem "web-console", "~> 4.2.1" gem "web-console", "~> 4.2.1"
end end

View File

@@ -435,9 +435,9 @@ GEM
pronto-rubocop (0.11.5) pronto-rubocop (0.11.5)
pronto (~> 0.11.0) pronto (~> 0.11.0)
rubocop (>= 0.63.1, < 2.0) rubocop (>= 0.63.1, < 2.0)
pronto-scss (0.11.0) pronto-stylelint (0.10.3)
pronto (~> 0.11.0) pronto (>= 0.10, < 0.12)
scss_lint (~> 0.43, >= 0.43.0) rugged (>= 0.24, < 2.0)
public_suffix (4.0.7) public_suffix (4.0.7)
puma (5.6.8) puma (5.6.8)
nio4r (~> 2.0) nio4r (~> 2.0)
@@ -586,8 +586,6 @@ GEM
sawyer (0.9.2) sawyer (0.9.2)
addressable (>= 2.3.5) addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3) faraday (>= 0.17.3, < 3)
scss_lint (0.60.0)
sass (~> 3.5, >= 3.5.5)
selenium-webdriver (4.16.0) selenium-webdriver (4.16.0)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@@ -743,7 +741,7 @@ DEPENDENCIES
pronto-erb_lint (~> 0.1.6) pronto-erb_lint (~> 0.1.6)
pronto-eslint (~> 0.11.1) pronto-eslint (~> 0.11.1)
pronto-rubocop (~> 0.11.5) pronto-rubocop (~> 0.11.5)
pronto-scss (~> 0.11.0) pronto-stylelint (~> 0.10.3)
puma (~> 5.6.8) puma (~> 5.6.8)
rails (= 6.1.7.7) rails (= 6.1.7.7)
recipient_interceptor (~> 0.3.1) recipient_interceptor (~> 0.3.1)
@@ -761,7 +759,6 @@ DEPENDENCIES
rvm1-capistrano3 (~> 1.4.0) rvm1-capistrano3 (~> 1.4.0)
sassc-rails (~> 2.1.2) sassc-rails (~> 2.1.2)
savon (~> 2.15.0) savon (~> 2.15.0)
scss_lint (~> 0.60.0)
selenium-webdriver (~> 4.16.0) selenium-webdriver (~> 4.16.0)
simplecov (~> 0.22.0) simplecov (~> 0.22.0)
simplecov-lcov (~> 0.8.0) simplecov-lcov (~> 0.8.0)

View File

@@ -976,7 +976,7 @@ table {
vertical-align: middle; vertical-align: middle;
} }
&[aria-expanded='false'] { &[aria-expanded="false"] {
@include hollow-button; @include hollow-button;
+ .columns-selector-wrapper { + .columns-selector-wrapper {
@@ -984,7 +984,7 @@ table {
} }
} }
&[aria-expanded='true'] { &[aria-expanded="true"] {
@include regular-button; @include regular-button;
} }
} }

View File

@@ -5,7 +5,7 @@
} }
%svg-icon { %svg-icon {
@supports (mask-image: url()) and (--custom-property-name: custom-property-value) { @supports (mask-image: url("")) and (--custom-property-name: custom-property-value) {
background: currentcolor; background: currentcolor;
content: "" !important; content: "" !important;
height: 1em; height: 1em;

1500
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,5 +10,12 @@
"leaflet.markercluster": "^1.5.3", "leaflet.markercluster": "^1.5.3",
"markdown-it": "^12.3.2", "markdown-it": "^12.3.2",
"motion-ui": "^2.0.3" "motion-ui": "^2.0.3"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^2.1.0",
"postcss-scss": "^4.0.9",
"stylelint": "^16.3.1",
"stylelint-order": "^6.0.4",
"stylelint-scss": "^6.2.1"
} }
} }