├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── index.js └── utils │ └── vendorPrefixes.js ├── index.js ├── jest-setup.js ├── package-lock.json ├── package.json └── utils ├── matchesStringOrRegExp.js └── vendorPrefixes.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | env: 4 | CI: true 5 | 6 | jobs: 7 | run: 8 | name: Node ${{ matrix.node }} on ${{ matrix.os }} 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | node: [20] 15 | os: [ubuntu-latest, windows-latest] 16 | 17 | steps: 18 | - name: Clone repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Set Node.js version 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node }} 25 | 26 | - name: Install npm dependencies 27 | run: npm ci 28 | 29 | - name: Lint 30 | run: npm run lint 31 | 32 | - name: Run jest tests with coverage 33 | run: npm run jest -- --runInBand --coverage 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.8.0 2 | 3 | - Added: support for stylelint version 16. 4 | 5 | ## v2.7.0 6 | 7 | - Added: support for stylelint version 15. 8 | 9 | ## v2.6.0 10 | 11 | - Added: support for CSS Logical Properties (https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties). 12 | 13 | ## v2.5.0 14 | 15 | - Added: support for stylelint version 14. 16 | 17 | ## v2.4.0 18 | 19 | - Removed: PostCSS dependency. 20 | 21 | ## v2.3.0 22 | 23 | - Added: support for stylelint version 13. 24 | 25 | ## v2.2.0 26 | 27 | - Added: support for stylelint versions 11 and 12. 28 | 29 | ## v2.1.0 30 | 31 | - Added: more ignored properties based on the list from https://github.com/captainbrosset/useless-css-properties and the documentation for those properties on the Mozilla Developer Network website. 32 | 33 | ## v2.0.0 34 | 35 | - Added: support for stylelint version 10. 36 | - Breaking change: Dropped Node.js 4 support. 37 | 38 | ## v1.1.0 39 | 40 | - Added: support for stylelint 9. 41 | 42 | ## v1.0.2 43 | 44 | - Fixed: a bug where a display property in a block would cancel warnings from the previous block. 45 | 46 | ## v1.0.1 47 | 48 | - Fixed: utils folder not being included with the release. 49 | 50 | ## v1.0.0 51 | 52 | - Intial release 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Krister Kari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stylelint-declaration-block-no-ignored-properties 2 | 3 | [![NPM version](https://img.shields.io/npm/v/stylelint-declaration-block-no-ignored-properties.svg)](https://www.npmjs.com/package/stylelint-declaration-block-no-ignored-properties) 4 | [![Build Status](https://github.com/kristerkari/stylelint-declaration-block-no-ignored-properties/workflows/Tests/badge.svg)](https://github.com/kristerkari/stylelint-declaration-block-no-ignored-properties/actions?workflow=Tests) 5 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) 6 | [![Downloads per month](https://img.shields.io/npm/dm/stylelint-declaration-block-no-ignored-properties.svg)](http://npmcharts.com/compare/stylelint-declaration-block-no-ignored-properties) 7 | 8 | Original rule: [stylelint/declaration-block-no-ignored-properties](https://github.com/stylelint/stylelint/tree/7.13.0/lib/rules/declaration-block-no-ignored-properties). 9 | 10 | Disallow property values that are ignored due to another property value in the same rule. 11 | 12 | ```css 13 | a { display: inline; width: 100px; } 14 | /** ↑ 15 | * This property */ 16 | ``` 17 | 18 | Certain property value pairs rule out other property value pairs, causing them to be ignored by the browser. For example, when an element has display: inline, any further declarations about width, height and margin-top properties will be ignored. Sometimes this is confusing: maybe you forgot that your margin-top will have no effect because the element has display: inline, so you spend a while struggling to figure out what you've done wrong. This rule protects against that confusion by ensuring that within a single rule you don't use property values that are ruled out by other property values in that same rule. 19 | 20 | The rule complains when it finds: 21 | 22 | - `display: inline` used with `width`, `height`, `margin`, `margin-top`, `margin-bottom`, `overflow` (and all variants). 23 | - `display: list-item` used with `vertical-align`. 24 | - `display: block` used with `vertical-align`. 25 | - `display: flex` used with `vertical-align`. 26 | - `display: table` used with `vertical-align`. 27 | - `display: table-*` (except `table-caption`) used with `margin`. 28 | - `display: table-*` (except `table-cell`) used with `padding`. 29 | - `display: table-*` (except `table-cell`) used with `vertical-align`. 30 | - `display: table-(row|row-group)` used with `width`, `min-width` or `max-width`. 31 | - `display: table-(column|column-group)` used with `height`, `min-height` or `max-height`. 32 | - `float: left` and `float: right` used with `vertical-align`. 33 | - `position: static` used with `top`, `right`, `bottom`, `left` or `z-index`. 34 | - `position: absolute` used with `float`, `clear` or `vertical-align`. 35 | - `position: fixed` used with `float`, `clear` or `vertical-align`. 36 | - `list-style-type: none` used with `list-style-image`. 37 | - `overflow: visible` used with `resize`. 38 | 39 | ## Installation 40 | 41 | ``` 42 | npm install stylelint-declaration-block-no-ignored-properties --save-dev 43 | ``` 44 | 45 | ## Usage 46 | 47 | ```js 48 | // .stylelintrc 49 | { 50 | "plugins": [ 51 | "stylelint-declaration-block-no-ignored-properties" 52 | ], 53 | "rules": { 54 | "plugin/declaration-block-no-ignored-properties": true, 55 | } 56 | } 57 | ``` 58 | 59 | ## Options 60 | 61 | ### `true` 62 | 63 | The following patterns are considered violations: 64 | 65 | ```css 66 | a { display: inline; width: 100px; } 67 | ``` 68 | 69 | `display: inline` causes `width` to be ignored. 70 | 71 | ```css 72 | a { display: inline; height: 100px; } 73 | ``` 74 | 75 | `display: inline` causes `height` to be ignored. 76 | 77 | ```css 78 | a { display: inline; margin: 10px; } 79 | ``` 80 | 81 | `display: inline` causes `margin` to be ignored. 82 | 83 | ```css 84 | a { display: block; vertical-align: baseline; } 85 | ``` 86 | 87 | `display: block` causes `vertical-align` to be ignored. 88 | 89 | ```css 90 | a { display: flex; vertical-align: baseline; } 91 | ``` 92 | 93 | `display: flex` causes `vertical-align` to be ignored. 94 | 95 | ```css 96 | a { position: absolute; vertical-align: baseline; } 97 | ``` 98 | 99 | `position: absolute` causes `vertical-align` to be ignored. 100 | 101 | ```css 102 | a { float: left; vertical-align: baseline; } 103 | ``` 104 | 105 | `float: left` causes `vertical-align` to be ignored. 106 | 107 | The following patterns are *not* considered violations: 108 | 109 | ```css 110 | a { display: inline; margin-left: 10px; } 111 | ``` 112 | 113 | ```css 114 | a { display: inline; margin-right: 10px; } 115 | ``` 116 | 117 | ```css 118 | a { display: inline; padding: 10px; } 119 | ``` 120 | 121 | ```css 122 | a { display: inline-block; width: 100px; } 123 | ``` 124 | 125 | Although `display: inline` causes `width` to be ignored, `inline-block` works with `width`. 126 | 127 | ```css 128 | a { display: table-cell; vertical-align: baseline; } 129 | ``` 130 | 131 | Although `display: block` causes `vertical-align` to be ignored, `table-cell` works with `vertical-align`. 132 | 133 | ```css 134 | a { position: relative; vertical-align: baseline; } 135 | ``` 136 | 137 | Although `position: absolute` causes `vertical-align` to be ignored, `relative` works with `vertical-align`. 138 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | const messages = require("..").messages; 2 | const ruleName = require("..").ruleName; 3 | 4 | const rule = require(".."); 5 | 6 | testRule(rule, { 7 | ruleName, 8 | config: [true], 9 | 10 | accept: [ 11 | { 12 | code: "a { display: inline; }", 13 | }, 14 | { 15 | code: "a { display: inline; color: red; }", 16 | }, 17 | { 18 | code: "a { display: inline; padding: 10px; }", 19 | }, 20 | { 21 | code: "a { display: inline; margin-left: 10px; }", 22 | }, 23 | { 24 | code: "a { display: inline; margin-right: 10px; }", 25 | }, 26 | { 27 | code: "a { display: inline; vertical-align: baseline; }", 28 | }, 29 | { 30 | code: "a { display: inline-block; }", 31 | }, 32 | { 33 | code: "a { display: inline-block; color: red; }", 34 | }, 35 | { 36 | code: "a { display: inline-block; width: 10px; }", 37 | }, 38 | { 39 | code: "a { display: inline-block; height: 10px; }", 40 | }, 41 | { 42 | code: "a { display: inline-block; margin: 10px; }", 43 | }, 44 | { 45 | code: "a { display: inline-block; padding: 10px; }", 46 | }, 47 | { 48 | code: "a { display: inline-block; vertical-align: baseline; }", 49 | }, 50 | { 51 | code: "a { display: inline-table; vertical-align: baseline; }", 52 | }, 53 | { 54 | code: "a { display: inline-flex; vertical-align: baseline; }", 55 | }, 56 | { 57 | code: "a { display: block; }", 58 | }, 59 | { 60 | code: "a { display: block; color: red; }", 61 | }, 62 | { 63 | code: "a { display: block; width: 10px; }", 64 | }, 65 | { 66 | code: "a { display: block; height: 10px; }", 67 | }, 68 | { 69 | code: "a { display: block; margin: 10px; }", 70 | }, 71 | { 72 | code: "a { display: block; padding: 10px; }", 73 | }, 74 | { 75 | code: "a { display: block; float: left; }", 76 | }, 77 | { 78 | code: "a { display: table-cell; width: 100%; }", 79 | }, 80 | { 81 | code: "a { display: table-cell; height: 100%; }", 82 | }, 83 | { 84 | code: "a { display: table-cell; padding: 10px; }", 85 | }, 86 | { 87 | code: "a { display: table; margin: 10px; }", 88 | }, 89 | { 90 | code: "a { display: table; margin-top: 10px; }", 91 | }, 92 | { 93 | code: "a { display: table; margin-right: 10px; }", 94 | }, 95 | { 96 | code: "a { display: table; margin-bottom: 10px; }", 97 | }, 98 | { 99 | code: "a { display: table; margin-left: 10px; }", 100 | }, 101 | { 102 | code: "a { display: table-caption; margin: 10px; }", 103 | }, 104 | { 105 | code: "a { display: table-caption; margin-top: 10px; }", 106 | }, 107 | { 108 | code: "a { display: table-caption; margin-right: 10px; }", 109 | }, 110 | { 111 | code: "a { display: table-caption; margin-bottom: 10px; }", 112 | }, 113 | { 114 | code: "a { display: table-caption; margin-left: 10px; }", 115 | }, 116 | { 117 | code: "a { display: inline-table; margin: 10px; }", 118 | }, 119 | { 120 | code: "a { display: inline-table; margin-top: 10px; }", 121 | }, 122 | { 123 | code: "a { display: inline-table; margin-right: 10px; }", 124 | }, 125 | { 126 | code: "a { display: inline-table; margin-bottom: 10px; }", 127 | }, 128 | { 129 | code: "a { display: inline-table; margin-left: 10px; }", 130 | }, 131 | { 132 | code: "a { display: table-cell; padding: 10px; }", 133 | }, 134 | { 135 | code: "a { display: table-cell; padding-top: 10px; }", 136 | }, 137 | { 138 | code: "a { display: table-cell; padding-right: 10px; }", 139 | }, 140 | { 141 | code: "a { display: table-cell; padding-bottom: 10px; }", 142 | }, 143 | { 144 | code: "a { display: table-cell; padding-left: 10px; }", 145 | }, 146 | { 147 | code: "a { display: table-cell; vertical-align: baseline; }", 148 | }, 149 | { 150 | code: "a { position: static; display: block; }", 151 | }, 152 | { 153 | code: "a { position: static; width: 100%; }", 154 | }, 155 | { 156 | code: "a { position: static; vertical-align: baseline; }", 157 | }, 158 | { 159 | code: "a { position: relative; vertical-align: baseline; }", 160 | }, 161 | { 162 | code: "a { position: absolute; top: 0px; }", 163 | }, 164 | { 165 | code: "a { position: absolute; right: 0px; }", 166 | }, 167 | { 168 | code: "a { position: absolute; bottom: 0px; }", 169 | }, 170 | { 171 | code: "a { position: absolute; left: 0px; }", 172 | }, 173 | { 174 | code: "a { position: fixed; top: 0px; }", 175 | }, 176 | { 177 | code: "a { position: fixed; right: 0px; }", 178 | }, 179 | { 180 | code: "a { position: fixed; bottom: 0px; }", 181 | }, 182 | { 183 | code: "a { position: fixed; left: 0px; }", 184 | }, 185 | { 186 | code: "a { display: inline; &:hover { width: 100px; } }", 187 | }, 188 | { 189 | code: "a { display: inline; &::before { width: 100px; } }", 190 | }, 191 | { 192 | code: "a { display: inline; display: inline-block; width: 100px; }", 193 | }, 194 | { 195 | code: "a { display: inline; width: 100px; display: inline-block; }", 196 | }, 197 | { 198 | code: ".a { display: inline; width: 100px; display: inline-block; } .b { display: inline; width: 100px; display: inline-block; }", 199 | }, 200 | ], 201 | 202 | reject: [ 203 | { 204 | code: "a { display: inline; width: 100px; }", 205 | message: messages.rejected("width", "display: inline"), 206 | line: 1, 207 | column: 22, 208 | description: 209 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 210 | }, 211 | { 212 | code: ".a { display: inline; width: 100px; } .b { display: inline; }", 213 | message: messages.rejected("width", "display: inline"), 214 | line: 1, 215 | column: 23, 216 | description: 217 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 218 | }, 219 | { 220 | code: ".a { display: block; width: 100px; } .b { display: inline; width: 100px; }", 221 | message: messages.rejected("width", "display: inline"), 222 | line: 1, 223 | column: 60, 224 | description: 225 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 226 | }, 227 | { 228 | code: "a { dIsPlAy: iNlInE; wIdTh: 100pX; }", 229 | message: messages.rejected("wIdTh", "dIsPlAy: iNlInE"), 230 | line: 1, 231 | column: 22, 232 | description: 233 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 234 | }, 235 | { 236 | code: "a { DISPLAY: INLINE; WIDTH: 100PX; }", 237 | message: messages.rejected("WIDTH", "DISPLAY: INLINE"), 238 | line: 1, 239 | column: 22, 240 | description: 241 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 242 | }, 243 | { 244 | code: "a { display: inline; min-width: 100px; }", 245 | message: messages.rejected("min-width", "display: inline"), 246 | line: 1, 247 | column: 22, 248 | description: "display: inline rules out min-width", 249 | }, 250 | { 251 | code: "a { display: inline; max-width: 100px; }", 252 | message: messages.rejected("max-width", "display: inline"), 253 | line: 1, 254 | column: 22, 255 | description: "display: inline rules out max-width", 256 | }, 257 | { 258 | code: "a { display: inline; height: 100px; }", 259 | message: messages.rejected("height", "display: inline"), 260 | line: 1, 261 | column: 22, 262 | description: 263 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 264 | }, 265 | { 266 | code: "a { display: inline; min-height: 100px; }", 267 | message: messages.rejected("min-height", "display: inline"), 268 | line: 1, 269 | column: 22, 270 | description: "display: inline rules out min-height", 271 | }, 272 | { 273 | code: "a { display: inline; max-height: 100px; }", 274 | message: messages.rejected("max-height", "display: inline"), 275 | line: 1, 276 | column: 22, 277 | description: "display: inline rules out max-height", 278 | }, 279 | { 280 | code: "a { display: inline; margin: 100px; }", 281 | message: messages.rejected("margin", "display: inline"), 282 | line: 1, 283 | column: 22, 284 | description: 285 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 286 | }, 287 | { 288 | code: "a { display: inline; margin-top: 100px; }", 289 | message: messages.rejected("margin-top", "display: inline"), 290 | line: 1, 291 | column: 22, 292 | description: 293 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 294 | }, 295 | { 296 | code: "a { display: inline; margin-bottom: 100px; }", 297 | message: messages.rejected("margin-bottom", "display: inline"), 298 | line: 1, 299 | column: 22, 300 | description: 301 | "display: inline rules out width, height, margin-top and margin-bottom, and float", 302 | }, 303 | { 304 | code: "a { display: inline; overflow: scroll; }", 305 | message: messages.rejected("overflow", "display: inline"), 306 | line: 1, 307 | column: 22, 308 | description: "display: inline rules out overflow", 309 | }, 310 | { 311 | code: "a { display: inline; overflow-x: scroll; }", 312 | message: messages.rejected("overflow-x", "display: inline"), 313 | line: 1, 314 | column: 22, 315 | description: "display: inline rules out overflow-x", 316 | }, 317 | { 318 | code: "a { display: inline; overflow-y: scroll; }", 319 | message: messages.rejected("overflow-y", "display: inline"), 320 | line: 1, 321 | column: 22, 322 | description: "display: inline rules out overflow-x", 323 | }, 324 | { 325 | code: "a { display: inline; inline-size: 100px; }", 326 | message: messages.rejected("inline-size", "display: inline"), 327 | line: 1, 328 | column: 22, 329 | description: "display: inline rules out inline-size", 330 | }, 331 | { 332 | code: "a { display: inline; min-inline-size: 100px; }", 333 | message: messages.rejected("min-inline-size", "display: inline"), 334 | line: 1, 335 | column: 22, 336 | description: "display: inline rules out min-inline-size", 337 | }, 338 | { 339 | code: "a { display: inline; max-inline-size: 100px; }", 340 | message: messages.rejected("max-inline-size", "display: inline"), 341 | line: 1, 342 | column: 22, 343 | description: "display: inline rules out max-inline-size", 344 | }, 345 | { 346 | code: "a { display: inline; block-size: 100px; }", 347 | message: messages.rejected("block-size", "display: inline"), 348 | line: 1, 349 | column: 22, 350 | description: "display: inline rules out block-size", 351 | }, 352 | { 353 | code: "a { display: inline; min-block-size: 100px; }", 354 | message: messages.rejected("min-block-size", "display: inline"), 355 | line: 1, 356 | column: 22, 357 | description: "display: inline rules out min-block-size", 358 | }, 359 | { 360 | code: "a { display: inline; max-block-size: 100px; }", 361 | message: messages.rejected("max-block-size", "display: inline"), 362 | line: 1, 363 | column: 22, 364 | description: "display: inline rules out max-block-size", 365 | }, 366 | { 367 | code: "a { display: inline; margin-block-start: 100px; }", 368 | message: messages.rejected("margin-block-start", "display: inline"), 369 | line: 1, 370 | column: 22, 371 | description: "display: inline rules out margin-block-start", 372 | }, 373 | { 374 | code: "a { display: inline; margin-block-end: 100px; }", 375 | message: messages.rejected("margin-block-end", "display: inline"), 376 | line: 1, 377 | column: 22, 378 | description: "display: inline rules out margin-block-end", 379 | }, 380 | { 381 | code: "a { display: inline; overflow-block: 100px; }", 382 | message: messages.rejected("overflow-block", "display: inline"), 383 | line: 1, 384 | column: 22, 385 | description: "display: inline rules out overflow-block", 386 | }, 387 | { 388 | code: "a { display: inline; overflow-inline: 100px; }", 389 | message: messages.rejected("overflow-inline", "display: inline"), 390 | line: 1, 391 | column: 22, 392 | description: "display: inline rules out overflow-inline", 393 | }, 394 | { 395 | code: "a { display: block; vertical-align: baseline; }", 396 | message: messages.rejected("vertical-align", "display: block"), 397 | line: 1, 398 | column: 21, 399 | description: "display: block rules out baseline", 400 | }, 401 | { 402 | code: "a { display: flex; vertical-align: baseline; }", 403 | message: messages.rejected("vertical-align", "display: flex"), 404 | line: 1, 405 | column: 20, 406 | description: "display: flex rules out baseline", 407 | }, 408 | { 409 | code: "a { display: list-item; vertical-align: baseline; }", 410 | message: messages.rejected("vertical-align", "display: list-item"), 411 | line: 1, 412 | column: 25, 413 | description: "display: list-item rules out baseline", 414 | }, 415 | { 416 | code: "a { display: table; vertical-align: baseline; }", 417 | message: messages.rejected("vertical-align", "display: table"), 418 | line: 1, 419 | column: 21, 420 | description: "display: table rules out baseline", 421 | }, 422 | { 423 | code: "a { display: table-row-group; vertical-align: baseline; }", 424 | message: messages.rejected("vertical-align", "display: table-row-group"), 425 | line: 1, 426 | column: 31, 427 | description: "display: table-row-group rules out baseline", 428 | }, 429 | { 430 | code: "a { display: table-row-group; inline-size: 10px; }", 431 | message: messages.rejected("inline-size", "display: table-row-group"), 432 | line: 1, 433 | column: 31, 434 | description: "display: table-row-group rules out inline-size", 435 | }, 436 | { 437 | code: "a { display: table-row-group; min-inline-size: 10px; }", 438 | message: messages.rejected("min-inline-size", "display: table-row-group"), 439 | line: 1, 440 | column: 31, 441 | description: "display: table-row-group rules out min-inline-size", 442 | }, 443 | { 444 | code: "a { display: table-row-group; max-inline-size: 10px; }", 445 | message: messages.rejected("max-inline-size", "display: table-row-group"), 446 | line: 1, 447 | column: 31, 448 | description: "display: table-row-group rules out max-inline-size", 449 | }, 450 | { 451 | code: "a { display: table-column; vertical-align: baseline; }", 452 | message: messages.rejected("vertical-align", "display: table-column"), 453 | line: 1, 454 | column: 28, 455 | description: "display: table-column rules out baseline", 456 | }, 457 | { 458 | code: "a { display: table-column; block-size: 10px; }", 459 | message: messages.rejected("block-size", "display: table-column"), 460 | line: 1, 461 | column: 28, 462 | description: "display: table-column rules out block-size", 463 | }, 464 | { 465 | code: "a { display: table-column; min-block-size: 10px; }", 466 | message: messages.rejected("min-block-size", "display: table-column"), 467 | line: 1, 468 | column: 28, 469 | description: "display: table-column rules out min-block-size", 470 | }, 471 | { 472 | code: "a { display: table-column; max-block-size: 10px; }", 473 | message: messages.rejected("max-block-size", "display: table-column"), 474 | line: 1, 475 | column: 28, 476 | description: "display: table-column rules out max-block-size", 477 | }, 478 | { 479 | code: "a { display: table-column-group; vertical-align: baseline; }", 480 | message: messages.rejected( 481 | "vertical-align", 482 | "display: table-column-group" 483 | ), 484 | line: 1, 485 | column: 34, 486 | description: "display: table-column-group rules out baseline", 487 | }, 488 | { 489 | code: "a { display: table-column-group; block-size: 10px; }", 490 | message: messages.rejected("block-size", "display: table-column-group"), 491 | line: 1, 492 | column: 34, 493 | description: "display: table-column-group rules out block-size", 494 | }, 495 | { 496 | code: "a { display: table-column-group; min-block-size: 10px; }", 497 | message: messages.rejected( 498 | "min-block-size", 499 | "display: table-column-group" 500 | ), 501 | line: 1, 502 | column: 34, 503 | description: "display: table-column-group rules out min-block-size", 504 | }, 505 | { 506 | code: "a { display: table-column-group; max-block-size: 10px; }", 507 | message: messages.rejected( 508 | "max-block-size", 509 | "display: table-column-group" 510 | ), 511 | line: 1, 512 | column: 34, 513 | description: "display: table-column-group rules out max-block-size", 514 | }, 515 | { 516 | code: "a { display: table-header-group; vertical-align: baseline; }", 517 | message: messages.rejected( 518 | "vertical-align", 519 | "display: table-header-group" 520 | ), 521 | line: 1, 522 | column: 34, 523 | description: "display: table-header-group rules out baseline", 524 | }, 525 | { 526 | code: "a { display: table-footer-group; vertical-align: baseline; }", 527 | message: messages.rejected( 528 | "vertical-align", 529 | "display: table-footer-group" 530 | ), 531 | line: 1, 532 | column: 34, 533 | description: "display: table-footer-group rules out baseline", 534 | }, 535 | { 536 | code: "a { display: table-row; vertical-align: baseline; }", 537 | message: messages.rejected("vertical-align", "display: table-row"), 538 | line: 1, 539 | column: 25, 540 | description: "display: table-row rules out baseline", 541 | }, 542 | { 543 | code: "a { display: table-row; inline-size: 100px; }", 544 | message: messages.rejected("inline-size", "display: table-row"), 545 | line: 1, 546 | column: 25, 547 | description: "display: table-row rules out inline-size", 548 | }, 549 | { 550 | code: "a { display: table-row; min-inline-size: 100px; }", 551 | message: messages.rejected("min-inline-size", "display: table-row"), 552 | line: 1, 553 | column: 25, 554 | description: "display: table-row rules out min-inline-size", 555 | }, 556 | { 557 | code: "a { display: table-row; max-inline-size: 100px; }", 558 | message: messages.rejected("max-inline-size", "display: table-row"), 559 | line: 1, 560 | column: 25, 561 | description: "display: table-row rules out max-inline-size", 562 | }, 563 | { 564 | code: "a { display: table-caption; vertical-align: baseline; }", 565 | message: messages.rejected("vertical-align", "display: table-caption"), 566 | line: 1, 567 | column: 29, 568 | description: "display: table-caption rules out baseline", 569 | }, 570 | { 571 | code: "a { position: absolute; vertical-align: baseline; }", 572 | message: messages.rejected("vertical-align", "position: absolute"), 573 | line: 1, 574 | column: 25, 575 | description: "position: absolute rules out baseline", 576 | }, 577 | { 578 | code: "a { position: fixed; vertical-align: baseline; }", 579 | message: messages.rejected("vertical-align", "position: fixed"), 580 | line: 1, 581 | column: 22, 582 | description: "position: fixed rules out baseline", 583 | }, 584 | { 585 | code: "a { float: left; vertical-align: baseline; }", 586 | message: messages.rejected("vertical-align", "float: left"), 587 | line: 1, 588 | column: 18, 589 | description: "float: left rules out baseline", 590 | }, 591 | { 592 | code: "a { float: left; vertical-align: baseline; } a { float: left }", 593 | message: messages.rejected("vertical-align", "float: left"), 594 | line: 1, 595 | column: 18, 596 | description: "float: left rules out baseline", 597 | }, 598 | { 599 | code: "a { float: right; vertical-align: baseline; }", 600 | message: messages.rejected("vertical-align", "float: right"), 601 | line: 1, 602 | column: 19, 603 | description: "float: right rules out baseline", 604 | }, 605 | { 606 | code: "a { display: table-row; width: 10px; }", 607 | message: messages.rejected("width", "display: table-row"), 608 | line: 1, 609 | column: 25, 610 | description: "display: table-row rules out width", 611 | }, 612 | { 613 | code: "a { display: table-row; min-width: 10px; }", 614 | message: messages.rejected("min-width", "display: table-row"), 615 | line: 1, 616 | column: 25, 617 | description: "display: table-row rules out min-width", 618 | }, 619 | { 620 | code: "a { display: table-row; max-width: 10px; }", 621 | message: messages.rejected("max-width", "display: table-row"), 622 | line: 1, 623 | column: 25, 624 | description: "display: table-row rules out max-width", 625 | }, 626 | { 627 | code: "a { display: table-row-group; width: 10px; }", 628 | message: messages.rejected("width", "display: table-row-group"), 629 | line: 1, 630 | column: 31, 631 | description: "display: table-row-group rules out width", 632 | }, 633 | { 634 | code: "a { display: table-row-group; min-width: 10px; }", 635 | message: messages.rejected("min-width", "display: table-row-group"), 636 | line: 1, 637 | column: 31, 638 | description: "display: table-row-group rules out min-width", 639 | }, 640 | { 641 | code: "a { display: table-row-group; max-width: 10px; }", 642 | message: messages.rejected("max-width", "display: table-row-group"), 643 | line: 1, 644 | column: 31, 645 | description: "display: table-row-group rules out max-width", 646 | }, 647 | { 648 | code: "a { display: table-column; height: 10px; }", 649 | message: messages.rejected("height", "display: table-column"), 650 | line: 1, 651 | column: 28, 652 | description: "display: table-column rules out height", 653 | }, 654 | { 655 | code: "a { display: table-column; min-height: 10px; }", 656 | message: messages.rejected("min-height", "display: table-column"), 657 | line: 1, 658 | column: 28, 659 | description: "display: table-column rules out min-width", 660 | }, 661 | { 662 | code: "a { display: table-column; max-height: 10px; }", 663 | message: messages.rejected("max-height", "display: table-column"), 664 | line: 1, 665 | column: 28, 666 | description: "display: table-column rules out max-width", 667 | }, 668 | { 669 | code: "a { display: table-column-group; height: 10px; }", 670 | message: messages.rejected("height", "display: table-column-group"), 671 | line: 1, 672 | column: 34, 673 | description: "display: table-column-group rules out height", 674 | }, 675 | { 676 | code: "a { display: table-column-group; min-height: 10px; }", 677 | message: messages.rejected("min-height", "display: table-column-group"), 678 | line: 1, 679 | column: 34, 680 | description: "display: table-column-group rules out min-height", 681 | }, 682 | { 683 | code: "a { display: table-column-group; max-height: 10px; }", 684 | message: messages.rejected("max-height", "display: table-column-group"), 685 | line: 1, 686 | column: 34, 687 | description: "display: table-column-group rules out max-height", 688 | }, 689 | { 690 | code: "a { position: static; top: 1px; }", 691 | message: messages.rejected("top", "position: static"), 692 | line: 1, 693 | column: 23, 694 | description: "position: static rules out top, right, bottom, and left", 695 | }, 696 | { 697 | code: "a { position: static; right: 1px; }", 698 | message: messages.rejected("right", "position: static"), 699 | line: 1, 700 | column: 23, 701 | description: "position: static rules out top, right, bottom, and left", 702 | }, 703 | { 704 | code: "a { position: static; bottom: 1px; }", 705 | message: messages.rejected("bottom", "position: static"), 706 | line: 1, 707 | column: 23, 708 | description: "position: static rules out top, right, bottom, and left", 709 | }, 710 | { 711 | code: "a { position: static; left: 1px; }", 712 | message: messages.rejected("left", "position: static"), 713 | line: 1, 714 | column: 23, 715 | description: "position: static rules out top, right, bottom, and left", 716 | }, 717 | { 718 | code: "a { position: static; z-index: 1; }", 719 | message: messages.rejected("z-index", "position: static"), 720 | line: 1, 721 | column: 23, 722 | description: "position: static rules out z-index", 723 | }, 724 | { 725 | code: "a { position: static; inset-block-start: 10px; }", 726 | message: messages.rejected("inset-block-start", "position: static"), 727 | line: 1, 728 | column: 23, 729 | description: "position: static rules out inset-block-start", 730 | }, 731 | { 732 | code: "a { position: static; inset-inline-end: 10px; }", 733 | message: messages.rejected("inset-inline-end", "position: static"), 734 | line: 1, 735 | column: 23, 736 | description: "position: static rules out inset-inline-end", 737 | }, 738 | { 739 | code: "a { position: static; inset-block-end: 10px; }", 740 | message: messages.rejected("inset-block-end", "position: static"), 741 | line: 1, 742 | column: 23, 743 | description: "position: static rules out inset-block-end", 744 | }, 745 | { 746 | code: "a { position: static; inset-inline-start: 10px; }", 747 | message: messages.rejected("inset-inline-start", "position: static"), 748 | line: 1, 749 | column: 23, 750 | description: "position: static rules out inset-inline-start", 751 | }, 752 | { 753 | code: "a { position: absolute; float: left; }", 754 | message: messages.rejected("float", "position: absolute"), 755 | line: 1, 756 | column: 25, 757 | description: "position: absolute rules out float", 758 | }, 759 | { 760 | code: "a { position: absolute; clear: left; }", 761 | message: messages.rejected("clear", "position: absolute"), 762 | line: 1, 763 | column: 25, 764 | description: "position: absolute rules out clear", 765 | }, 766 | { 767 | code: "a { position: fixed; float: left; }", 768 | message: messages.rejected("float", "position: fixed"), 769 | line: 1, 770 | column: 22, 771 | description: "position: absolute rules out float", 772 | }, 773 | { 774 | code: "a { position: fixed; clear: left; }", 775 | message: messages.rejected("clear", "position: fixed"), 776 | line: 1, 777 | column: 22, 778 | description: "position: absolute rules out float", 779 | }, 780 | { 781 | code: "a { list-style-type: none; list-style-image: url('starsolid.gif'); }", 782 | message: messages.rejected("list-style-image", "list-style-type: none"), 783 | line: 1, 784 | column: 28, 785 | description: "list-style-type: none rules out list-style-image", 786 | }, 787 | { 788 | code: "a { overflow: visible; resize: both; }", 789 | message: messages.rejected("resize", "overflow: visible"), 790 | line: 1, 791 | column: 24, 792 | description: "overflow: visible rules out resize", 793 | }, 794 | ], 795 | }); 796 | 797 | // table-* rules out margin 798 | const tableTypesForIgnoredMargin = 799 | "row|row-group|column|column-group|header-group|footer-group|cell"; 800 | 801 | tableTypesForIgnoredMargin.split("|").forEach((type) => { 802 | const column = 22 + type.length; 803 | testRule(rule, { 804 | ruleName, 805 | config: [true], 806 | 807 | reject: [ 808 | { 809 | code: `a { display: table-${type}; margin: 10px; }`, 810 | message: messages.rejected("margin", `display: table-${type}`), 811 | line: 1, 812 | column, 813 | description: `display: table-${type} rules out margin`, 814 | }, 815 | { 816 | code: `a { display: table-${type}; margin-top: 10px; }`, 817 | message: messages.rejected("margin-top", `display: table-${type}`), 818 | line: 1, 819 | column, 820 | description: `display: table-${type} rules out margin-top`, 821 | }, 822 | { 823 | code: `a { display: table-${type}; margin-right: 10px; }`, 824 | message: messages.rejected("margin-right", `display: table-${type}`), 825 | line: 1, 826 | column, 827 | description: `display: table-${type} rules out margin-right`, 828 | }, 829 | { 830 | code: `a { display: table-${type}; margin-bottom: 10px; }`, 831 | message: messages.rejected("margin-bottom", `display: table-${type}`), 832 | line: 1, 833 | column, 834 | description: `display: table-${type} rules out margin-bottom`, 835 | }, 836 | { 837 | code: `a { display: table-${type}; margin-left: 10px; }`, 838 | message: messages.rejected("margin-left", `display: table-${type}`), 839 | line: 1, 840 | column, 841 | description: `display: table-${type} rules out margin-left`, 842 | }, 843 | { 844 | code: `a { display: table-${type}; margin-block-start: 10px; }`, 845 | message: messages.rejected( 846 | "margin-block-start", 847 | `display: table-${type}` 848 | ), 849 | line: 1, 850 | column, 851 | description: `display: table-${type} rules out margin-block-start`, 852 | }, 853 | { 854 | code: `a { display: table-${type}; margin-inline-end: 10px; }`, 855 | message: messages.rejected( 856 | "margin-inline-end", 857 | `display: table-${type}` 858 | ), 859 | line: 1, 860 | column, 861 | description: `display: table-${type} rules out margin-inline-end`, 862 | }, 863 | { 864 | code: `a { display: table-${type}; margin-block-end: 10px; }`, 865 | message: messages.rejected( 866 | "margin-block-end", 867 | `display: table-${type}` 868 | ), 869 | line: 1, 870 | column, 871 | description: `display: table-${type} rules out margin-block-end`, 872 | }, 873 | { 874 | code: `a { display: table-${type}; margin-inline-start: 10px; }`, 875 | message: messages.rejected( 876 | "margin-inline-start", 877 | `display: table-${type}` 878 | ), 879 | line: 1, 880 | column, 881 | description: `display: table-${type} rules out margin-inline-start`, 882 | }, 883 | ], 884 | }); 885 | }); 886 | 887 | // table-* rules out padding 888 | const tableTypesForIgnoredPadding = 889 | "row|row-group|column|column-group|header-group|footer-group"; 890 | 891 | tableTypesForIgnoredPadding.split("|").forEach((type) => { 892 | const column = 22 + type.length; 893 | testRule(rule, { 894 | ruleName, 895 | config: [true], 896 | 897 | reject: [ 898 | { 899 | code: `a { display: table-${type}; padding: 10px; }`, 900 | message: messages.rejected("padding", `display: table-${type}`), 901 | line: 1, 902 | column, 903 | description: `display: table-${type} rules out padding`, 904 | }, 905 | { 906 | code: `a { display: table-${type}; padding-top: 10px; }`, 907 | message: messages.rejected("padding-top", `display: table-${type}`), 908 | line: 1, 909 | column, 910 | description: `display: table-${type} rules out padding-top`, 911 | }, 912 | { 913 | code: `a { display: table-${type}; padding-right: 10px; }`, 914 | message: messages.rejected("padding-right", `display: table-${type}`), 915 | line: 1, 916 | column, 917 | description: `display: table-${type} rules out padding-right`, 918 | }, 919 | { 920 | code: `a { display: table-${type}; padding-bottom: 10px; }`, 921 | message: messages.rejected("padding-bottom", `display: table-${type}`), 922 | line: 1, 923 | column, 924 | description: `display: table-${type} rules out padding-bottom`, 925 | }, 926 | { 927 | code: `a { display: table-${type}; padding-left: 10px; }`, 928 | message: messages.rejected("padding-left", `display: table-${type}`), 929 | line: 1, 930 | column, 931 | description: `display: table-${type} rules out padding-left`, 932 | }, 933 | { 934 | code: `a { display: table-${type}; padding-block-start: 10px; }`, 935 | message: messages.rejected( 936 | "padding-block-start", 937 | `display: table-${type}` 938 | ), 939 | line: 1, 940 | column, 941 | description: `display: table-${type} rules out padding-block-start`, 942 | }, 943 | { 944 | code: `a { display: table-${type}; padding-inline-end: 10px; }`, 945 | message: messages.rejected( 946 | "padding-inline-end", 947 | `display: table-${type}` 948 | ), 949 | line: 1, 950 | column, 951 | description: `display: table-${type} rules out padding-inline-end`, 952 | }, 953 | { 954 | code: `a { display: table-${type}; padding-block-end: 10px; }`, 955 | message: messages.rejected( 956 | "padding-block-end", 957 | `display: table-${type}` 958 | ), 959 | line: 1, 960 | column, 961 | description: `display: table-${type} rules out padding-block-end`, 962 | }, 963 | { 964 | code: `a { display: table-${type}; padding-inline-start: 10px; }`, 965 | message: messages.rejected( 966 | "padding-inline-start", 967 | `display: table-${type}` 968 | ), 969 | line: 1, 970 | column, 971 | description: `display: table-${type} rules out padding-inline-start`, 972 | }, 973 | ], 974 | }); 975 | }); 976 | -------------------------------------------------------------------------------- /__tests__/utils/vendorPrefixes.js: -------------------------------------------------------------------------------- 1 | const vendorPrefixes = require("../../utils/vendorPrefixes"); 2 | 3 | describe("utils/vendorPrefixes", () => { 4 | it("should return the vendor prefix when it is specified", () => { 5 | expect(vendorPrefixes.prefix("-moz-tab-size")).toBe("-moz-"); 6 | expect(vendorPrefixes.prefix("-webkit-tab-size")).toBe("-webkit-"); 7 | expect(vendorPrefixes.prefix("-whatever-tab-size")).toBe("-whatever-"); 8 | }); 9 | 10 | it("should return an empty string is the prefix is not present", () => { 11 | expect(vendorPrefixes.prefix("tab-size")).toBe(""); 12 | }); 13 | 14 | it("should return the unprefixed version of CSS rule", () => { 15 | expect(vendorPrefixes.unprefixed("-moz-tab-size")).toBe("tab-size"); 16 | expect(vendorPrefixes.unprefixed("tab-size")).toBe("tab-size"); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const matchesStringOrRegExp = require("./utils/matchesStringOrRegExp"); 2 | const stylelint = require("stylelint"); 3 | const vendorPrefixes = require("./utils/vendorPrefixes"); 4 | const report = stylelint.utils.report; 5 | const ruleMessages = stylelint.utils.ruleMessages; 6 | const validateOptions = stylelint.utils.validateOptions; 7 | 8 | const ruleName = "plugin/declaration-block-no-ignored-properties"; 9 | 10 | const messages = ruleMessages(ruleName, { 11 | rejected: (ignore, cause) => `Unexpected "${ignore}" with "${cause}"`, 12 | }); 13 | 14 | const ignored = [ 15 | { 16 | property: "display", 17 | value: "inline", 18 | ignoredProperties: [ 19 | "width", 20 | "min-width", 21 | "max-width", 22 | "height", 23 | "min-height", 24 | "max-height", 25 | "margin", 26 | "margin-top", 27 | "margin-bottom", 28 | "overflow", 29 | "overflow-x", 30 | "overflow-y", 31 | "inline-size", 32 | "min-inline-size", 33 | "max-inline-size", 34 | "block-size", 35 | "min-block-size", 36 | "max-block-size", 37 | "margin-block-start", 38 | "margin-block-end", 39 | "overflow-block", 40 | "overflow-inline", 41 | ], 42 | }, 43 | { 44 | property: "display", 45 | value: "list-item", 46 | ignoredProperties: ["vertical-align"], 47 | }, 48 | { 49 | property: "display", 50 | value: "block", 51 | ignoredProperties: ["vertical-align"], 52 | }, 53 | { 54 | property: "display", 55 | value: "flex", 56 | ignoredProperties: ["vertical-align"], 57 | }, 58 | { 59 | property: "display", 60 | value: "table", 61 | ignoredProperties: ["vertical-align"], 62 | }, 63 | { 64 | property: "display", 65 | value: 66 | "/^table-(row|row-group|column|column-group|header-group|footer-group|cell)$/", 67 | ignoredProperties: [ 68 | "margin", 69 | "margin-top", 70 | "margin-right", 71 | "margin-bottom", 72 | "margin-left", 73 | "margin-block-start", 74 | "margin-inline-end", 75 | "margin-block-end", 76 | "margin-inline-start", 77 | ], 78 | }, 79 | { 80 | property: "display", 81 | value: 82 | "/^table-(row|row-group|column|column-group|header-group|footer-group)$/", 83 | ignoredProperties: [ 84 | "padding", 85 | "padding-top", 86 | "padding-right", 87 | "padding-bottom", 88 | "padding-left", 89 | "padding-block-start", 90 | "padding-inline-end", 91 | "padding-block-end", 92 | "padding-inline-start", 93 | ], 94 | }, 95 | { 96 | property: "display", 97 | value: 98 | "/^table-(row|row-group|column|column-group|header-group|footer-group|caption)$/", 99 | ignoredProperties: ["vertical-align"], 100 | }, 101 | { 102 | property: "display", 103 | value: "/^table-(row|row-group)$/", 104 | ignoredProperties: [ 105 | "width", 106 | "min-width", 107 | "max-width", 108 | "inline-size", 109 | "min-inline-size", 110 | "max-inline-size", 111 | ], 112 | }, 113 | { 114 | property: "display", 115 | value: "/^table-(column|column-group)$/", 116 | ignoredProperties: [ 117 | "height", 118 | "min-height", 119 | "max-height", 120 | "block-size", 121 | "min-block-size", 122 | "max-block-size", 123 | ], 124 | }, 125 | { 126 | property: "float", 127 | value: "left", 128 | ignoredProperties: ["vertical-align"], 129 | }, 130 | { 131 | property: "float", 132 | value: "right", 133 | ignoredProperties: ["vertical-align"], 134 | }, 135 | { 136 | property: "position", 137 | value: "static", 138 | ignoredProperties: [ 139 | "top", 140 | "right", 141 | "bottom", 142 | "left", 143 | "z-index", 144 | "inset-block-start", 145 | "inset-inline-end", 146 | "inset-block-end", 147 | "inset-inline-start", 148 | ], 149 | }, 150 | { 151 | property: "position", 152 | value: "absolute", 153 | ignoredProperties: ["float", "clear", "vertical-align"], 154 | }, 155 | { 156 | property: "position", 157 | value: "fixed", 158 | ignoredProperties: ["float", "clear", "vertical-align"], 159 | }, 160 | { 161 | property: "list-style-type", 162 | value: "none", 163 | ignoredProperties: ["list-style-image"], 164 | }, 165 | { 166 | property: "overflow", 167 | value: "visible", 168 | ignoredProperties: ["resize"], 169 | }, 170 | ]; 171 | 172 | const rule = (actual) => { 173 | return (root, result) => { 174 | const validOptions = validateOptions(result, ruleName, { actual }); 175 | 176 | if (!validOptions) { 177 | return; 178 | } 179 | 180 | root.walkRules((rule) => { 181 | const uniqueDecls = {}; 182 | rule.walkDecls((decl) => { 183 | uniqueDecls[decl.prop] = decl; 184 | }); 185 | 186 | function check(prop, index) { 187 | const decl = uniqueDecls[prop]; 188 | const value = decl.value; 189 | const unprefixedProp = vendorPrefixes.unprefixed(prop); 190 | const unprefixedValue = vendorPrefixes.unprefixed(value); 191 | 192 | ignored.forEach((ignore) => { 193 | const matchProperty = matchesStringOrRegExp( 194 | unprefixedProp.toLowerCase(), 195 | ignore.property 196 | ); 197 | const matchValue = matchesStringOrRegExp( 198 | unprefixedValue.toLowerCase(), 199 | ignore.value 200 | ); 201 | 202 | if (!matchProperty || !matchValue) { 203 | return; 204 | } 205 | 206 | const ignoredProperties = ignore.ignoredProperties; 207 | 208 | decl.parent.nodes.forEach((node, nodeIndex) => { 209 | if ( 210 | !node.prop || 211 | ignoredProperties.indexOf(node.prop.toLowerCase()) === -1 || 212 | index === nodeIndex 213 | ) { 214 | return; 215 | } 216 | 217 | report({ 218 | message: messages.rejected(node.prop, decl.toString()), 219 | node, 220 | result, 221 | ruleName, 222 | }); 223 | }); 224 | }); 225 | } 226 | 227 | Object.keys(uniqueDecls).forEach(check); 228 | }); 229 | }; 230 | }; 231 | 232 | module.exports = stylelint.createPlugin(ruleName, rule); 233 | module.exports.ruleName = ruleName; 234 | module.exports.messages = messages; 235 | -------------------------------------------------------------------------------- /jest-setup.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const stylelint = require("stylelint"); 3 | 4 | global.testRule = (rule, schema) => { 5 | expect.extend({ 6 | toHaveMessage(testCase) { 7 | if (testCase.message === undefined) { 8 | return { 9 | message: () => 10 | 'Expected "reject" test case to have a "message" property', 11 | pass: false, 12 | }; 13 | } 14 | 15 | return { 16 | pass: true, 17 | }; 18 | }, 19 | }); 20 | 21 | describe(schema.ruleName, () => { 22 | const stylelintConfig = { 23 | plugins: ["./index"], 24 | rules: { 25 | [schema.ruleName]: schema.config, 26 | }, 27 | }; 28 | 29 | const passingTestCases = schema.accept || []; 30 | 31 | if (passingTestCases && passingTestCases.length) { 32 | describe("accept", () => { 33 | passingTestCases.forEach((testCase) => { 34 | const spec = testCase.only ? it.only : it; 35 | describe(JSON.stringify(schema.config), () => { 36 | describe(JSON.stringify(testCase.code), () => { 37 | spec(testCase.description || "no description", () => { 38 | const options = { 39 | code: testCase.code, 40 | config: stylelintConfig, 41 | syntax: schema.syntax, 42 | }; 43 | return stylelint.lint(options).then((output) => { 44 | expect(output.results[0].warnings).toEqual([]); 45 | expect(output.results[0].parseErrors).toEqual([]); 46 | if (!schema.fix) return; 47 | 48 | // Check the fix 49 | return stylelint 50 | .lint(Object.assign({ fix: true }, options)) 51 | .then((output) => { 52 | const fixedCode = getOutputCss(output); 53 | expect(fixedCode).toBe(testCase.code); 54 | }); 55 | }); 56 | }); 57 | }); 58 | }); 59 | }); 60 | }); 61 | } 62 | 63 | if (schema.reject && schema.reject.length) { 64 | describe("reject", () => { 65 | schema.reject.forEach((testCase) => { 66 | const spec = testCase.only ? it.only : it; 67 | describe(JSON.stringify(schema.config), () => { 68 | describe(JSON.stringify(testCase.code), () => { 69 | spec(testCase.description || "no description", () => { 70 | const options = { 71 | code: testCase.code, 72 | config: stylelintConfig, 73 | syntax: schema.syntax, 74 | }; 75 | return stylelint.lint(options).then((output) => { 76 | const warning = output.results[0].warnings[0]; 77 | 78 | expect(output.results[0].parseErrors).toEqual([]); 79 | expect(testCase).toHaveMessage(); 80 | 81 | if (testCase.message !== undefined) { 82 | expect(_.get(warning, "text")).toBe(testCase.message); 83 | } 84 | if (testCase.line !== undefined) { 85 | expect(_.get(warning, "line")).toBe(testCase.line); 86 | } 87 | if (testCase.column !== undefined) { 88 | expect(_.get(warning, "column")).toBe(testCase.column); 89 | } 90 | 91 | if (!schema.fix) return; 92 | 93 | if (!testCase.fixed) { 94 | throw new Error( 95 | "If using { fix: true } in test schema, all reject cases must have { fixed: .. }" 96 | ); 97 | } 98 | 99 | // Check the fix 100 | return stylelint 101 | .lint(Object.assign({ fix: true }, options)) 102 | .then((output) => { 103 | const fixedCode = getOutputCss(output); 104 | expect(fixedCode).toBe(testCase.fixed); 105 | expect(fixedCode).not.toBe(testCase.code); 106 | }); 107 | }); 108 | }); 109 | }); 110 | }); 111 | }); 112 | }); 113 | } 114 | }); 115 | }; 116 | 117 | function getOutputCss(output) { 118 | const result = output.results[0]._postcssResult; 119 | const css = result.root.toString(result.opts.syntax); 120 | return css; 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylelint-declaration-block-no-ignored-properties", 3 | "description": "Disallow property values that are ignored due to another property value in the same rule.", 4 | "version": "2.8.0", 5 | "author": { 6 | "name": "Krister Kari", 7 | "url": "https://github.com/kristerkari/" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/kristerkari/stylelint-declaration-block-no-ignored-properties/issues" 11 | }, 12 | "devDependencies": { 13 | "babel-eslint": "^10.1.0", 14 | "cross-env": "^7.0.3", 15 | "eslint": "^7.31.0", 16 | "eslint-plugin-sort-requires": "^2.1.0", 17 | "jest-cli": "^29.3.1", 18 | "lint-staged": "^15.2.0", 19 | "lodash": "^4.17.21", 20 | "np": "^10.0.5", 21 | "prettier": "^3.0.0", 22 | "stylelint": "^16.1.0" 23 | }, 24 | "peerDependencies": { 25 | "stylelint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" 26 | }, 27 | "engines": { 28 | "node": ">=6" 29 | }, 30 | "eslintConfig": { 31 | "parser": "babel-eslint", 32 | "extends": "eslint:recommended", 33 | "parserOptions": { 34 | "ecmaVersion": 6 35 | }, 36 | "env": { 37 | "es6": true, 38 | "jest": true, 39 | "node": true 40 | }, 41 | "plugins": [ 42 | "sort-requires" 43 | ], 44 | "globals": { 45 | "testRule": true 46 | }, 47 | "rules": { 48 | "eqeqeq": "error", 49 | "no-use-before-define": [ 50 | "error", 51 | "nofunc" 52 | ], 53 | "sort-requires/sort-requires": "error", 54 | "strict": [ 55 | "error", 56 | "global" 57 | ], 58 | "arrow-spacing": "error", 59 | "no-var": "error", 60 | "object-shorthand": "error", 61 | "prefer-const": "error", 62 | "template-curly-spacing": "error" 63 | } 64 | }, 65 | "files": [ 66 | "index.js", 67 | "utils", 68 | "CHANGELOG.md", 69 | "README.md" 70 | ], 71 | "homepage": "https://github.com/kristerkari/stylelint-declaration-block-no-ignored-properties#readme", 72 | "jest": { 73 | "clearMocks": true, 74 | "collectCoverage": false, 75 | "collectCoverageFrom": [ 76 | "index.js" 77 | ], 78 | "coverageDirectory": "./coverage/", 79 | "coverageReporters": [ 80 | "lcov", 81 | "text" 82 | ], 83 | "coverageThreshold": { 84 | "global": { 85 | "branches": 75, 86 | "functions": 75, 87 | "lines": 75, 88 | "statements": 75 89 | } 90 | }, 91 | "testEnvironment": "node", 92 | "setupFiles": [ 93 | "./jest-setup.js" 94 | ] 95 | }, 96 | "keywords": [ 97 | "css", 98 | "less", 99 | "lint", 100 | "linter", 101 | "scss", 102 | "stylelint", 103 | "stylelint-plugin", 104 | "sugarss" 105 | ], 106 | "license": "MIT", 107 | "lint-staged": { 108 | "**/*.{js,json}": [ 109 | "prettier --write", 110 | "git add" 111 | ] 112 | }, 113 | "main": "index.js", 114 | "repository": { 115 | "type": "git", 116 | "url": "git+https://github.com/kristerkari/stylelint-declaration-block-no-ignored-properties.git" 117 | }, 118 | "scripts": { 119 | "jest": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest", 120 | "lint": "eslint . --ignore-path .gitignore", 121 | "precommit": "lint-staged", 122 | "pretest": "npm run lint", 123 | "prettify": "prettier --write '**/*.{js,json}'", 124 | "release": "np", 125 | "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest --coverage" 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /utils/matchesStringOrRegExp.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Compares a string to a second value that, if it fits a certain convention, 5 | * is converted to a regular expression before the comparison. 6 | * If it doesn't fit the convention, then two strings are compared. 7 | * 8 | * Any strings starting and ending with `/` are interpreted 9 | * as regular expressions. 10 | */ 11 | module.exports = function matchesStringOrRegExp( 12 | input /*: string | Array*/, 13 | comparison /*: string | Array*/ 14 | ) /*: false | { match: string, pattern: string}*/ { 15 | if (!Array.isArray(input)) { 16 | return testAgainstStringOrArray(input, comparison); 17 | } 18 | 19 | for (const inputItem of input) { 20 | const testResult = testAgainstStringOrArray(inputItem, comparison); 21 | if (testResult) { 22 | return testResult; 23 | } 24 | } 25 | 26 | return false; 27 | }; 28 | 29 | function testAgainstStringOrArray(value, comparison) { 30 | if (!Array.isArray(comparison)) { 31 | return testAgainstString(value, comparison); 32 | } 33 | 34 | for (const comparisonItem of comparison) { 35 | const testResult = testAgainstString(value, comparisonItem); 36 | if (testResult) { 37 | return testResult; 38 | } 39 | } 40 | return false; 41 | } 42 | 43 | function testAgainstString(value, comparison) { 44 | const firstComparisonChar = comparison[0]; 45 | const lastComparisonChar = comparison[comparison.length - 1]; 46 | const secondToLastComparisonChar = comparison[comparison.length - 2]; 47 | 48 | const comparisonIsRegex = 49 | firstComparisonChar === "/" && 50 | (lastComparisonChar === "/" || 51 | (secondToLastComparisonChar === "/" && lastComparisonChar === "i")); 52 | 53 | const hasCaseInsensitiveFlag = 54 | comparisonIsRegex && lastComparisonChar === "i"; 55 | 56 | if (comparisonIsRegex) { 57 | const valueMatches = hasCaseInsensitiveFlag 58 | ? new RegExp(comparison.slice(1, -2), "i").test(value) 59 | : new RegExp(comparison.slice(1, -1)).test(value); 60 | return valueMatches ? { match: value, pattern: comparison } : false; 61 | } 62 | 63 | return value === comparison ? { match: value, pattern: comparison } : false; 64 | } 65 | -------------------------------------------------------------------------------- /utils/vendorPrefixes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains helpers for working with vendor prefixes. 3 | * 4 | * @namespace vendor 5 | */ 6 | const vendor = { 7 | /** 8 | * Returns the vendor prefix extracted from an input string. 9 | * 10 | * @param {string} prop String with or without vendor prefix. 11 | * 12 | * @return {string} vendor prefix or empty string 13 | * 14 | * @example 15 | * vendorPrefixes.prefix('-moz-tab-size') //=> '-moz-' 16 | * vendorPrefixes.prefix('tab-size') //=> '' 17 | */ 18 | prefix(prop) { 19 | const match = prop.match(/^(-\w+-)/); 20 | if (match) { 21 | return match[0]; 22 | } 23 | 24 | return ""; 25 | }, 26 | 27 | /** 28 | * Returns the input string stripped of its vendor prefix. 29 | * 30 | * @param {string} prop String with or without vendor prefix. 31 | * 32 | * @return {string} String name without vendor prefixes. 33 | * 34 | * @example 35 | * vendorPrefixes.unprefixed('-moz-tab-size') //=> 'tab-size' 36 | */ 37 | unprefixed(prop) { 38 | return prop.replace(/^-\w+-/, ""); 39 | } 40 | }; 41 | 42 | module.exports = vendor; 43 | --------------------------------------------------------------------------------