├── .commitlintrc.js ├── .editorconfig ├── .gitattributes ├── .github ├── issue_template.md └── workflows │ ├── commitlint.yml │ └── node.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .prettierrc ├── .releaserc ├── CHANGELOG.md ├── README.md ├── eslint.config.js ├── jest.config.js ├── lib ├── index.browser-env.test.js ├── index.js └── index.test.js ├── package.json └── pnpm-lock.yaml /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 'body-max-line-length': [0] }, 4 | }; 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | .* text eol=lf 10 | *.css text eol=lf 11 | *.html text eol=lf 12 | *.js text eol=lf 13 | *.json text eol=lf 14 | *.md text eol=lf 15 | *.sh text eol=lf 16 | *.txt text eol=lf 17 | *.xml text eol=lf 18 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | Before creating an issue: 2 | 3 | 1. As mentioned in the readme, this plugin uses [doiuse](https://github.com/anandthakker/doiuse) to detect browser support. If you're having an issue with detection, make sure it isn't a doiuse issue by testing with doiuse directly. If the issue also occurs when using doiuse directly it should be fixed in doiuse and there's no use in opening an issue for it in this repo. 4 | 2. If you're encountering a bug, please accompany your bug report with a PR that adds a failing test that demonstrates the bug. 5 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: commitlint 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 0 12 | - uses: wagoid/commitlint-github-action@v5 13 | with: 14 | configFile: .commitlintrc.js 15 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: node 2 | 3 | on: [push] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - run: corepack enable 11 | - run: corepack pnpm i 12 | - run: corepack pnpm run lint:js 13 | - run: corepack pnpm run lint:prettier 14 | 15 | test: 16 | needs: [lint] 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | node-version: [18.x, 20.x] 21 | steps: 22 | - uses: actions/checkout@v4 23 | - run: corepack enable 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: corepack pnpm i 28 | - run: corepack pnpm run test 29 | 30 | coverage: 31 | needs: [test] 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - run: corepack enable 36 | - run: corepack pnpm i 37 | - run: corepack pnpm run test:coverage 38 | - uses: codecov/codecov-action@v3 39 | 40 | release: 41 | needs: [test] 42 | if: github.ref == 'refs/heads/master' 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | fetch-depth: 0 48 | persist-credentials: false 49 | - run: corepack enable 50 | - run: corepack pnpm i 51 | - run: corepack pnpm run semantic-release 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 54 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules 3 | npm-debug.log 4 | yarn-error.log 5 | package-lock.json 6 | 7 | # Coveralls coverage 8 | /coverage 9 | 10 | # System files 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit $1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx --no-install lint-staged --quiet 2 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.js": ["prettier --write", "eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "semi": true 7 | } 8 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": "master", 3 | "plugins": [ 4 | ["@semantic-release/commit-analyzer", { 5 | "preset": "conventionalcommits" 6 | }], 7 | "@semantic-release/release-notes-generator", 8 | "@semantic-release/changelog", 9 | "@semantic-release/npm", 10 | "@semantic-release/git", 11 | "@semantic-release/github" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [8.0.5](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v8.0.4...v8.0.5) (2025-10-13) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * resolve browsers option by default to improve performance ([6010f82](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/6010f82e503621267dd76da2f0bde989ce549a2c)) 7 | 8 | ## [8.0.4](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v8.0.3...v8.0.4) (2025-01-28) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * report on whole node, rather than a specific index ([a1920b2](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/a1920b2292f8ea97a407b711887941c38b38e12f)) 14 | 15 | ## [8.0.3](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v8.0.2...v8.0.3) (2025-01-27) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * providing the line argument in the utils.report() function is deprecated ([ee1175c](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/ee1175cc1139286ea8c63062d8d710186e4f6935)) 21 | 22 | ## [8.0.2](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v8.0.1...v8.0.2) (2024-11-01) 23 | 24 | ### Bug Fixes 25 | 26 | - cannot read properties of undefined ([5e3a9f1](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/5e3a9f192f1fc8a5370d4e23115f0d40ed625278)) 27 | 28 | ## [8.0.1](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v8.0.0...v8.0.1) (2024-01-25) 29 | 30 | ### Bug Fixes 31 | 32 | - remove dependency on lodash ([a9d5f68](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/a9d5f68c02142e3cc114183e73737eaf89d4ee49)) 33 | 34 | # [8.0.0](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v7.0.0...v8.0.0) (2024-01-02) 35 | 36 | ### chore 37 | 38 | - bump to next major ([80a9a10](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/80a9a10ae2366b77f7c895855fc3a7f762278e47)) 39 | 40 | ### Features 41 | 42 | - support stylelint 16 ([#296](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/issues/296)) ([adcd166](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/adcd166692177df231df6433664c5c9b7d0b335b)) 43 | 44 | ### BREAKING CHANGES 45 | 46 | - dropped support for node 16 and stylelint 14, 15 47 | 48 | # [7.1.0](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/compare/v7.0.0...v7.1.0) (2024-01-02) 49 | 50 | ### Features 51 | 52 | - support stylelint 16 ([#296](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/issues/296)) ([adcd166](https://github.com/RJWadley/stylelint-no-unsupported-browser-features/commit/adcd166692177df231df6433664c5c9b7d0b335b)) 53 | 54 | # [7.0.0](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v6.1.0...v7.0.0) (2023-07-07) 55 | 56 | ### chore 57 | 58 | - **deps:** upgrade to doiuse 6 ([0368c23](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/0368c238da6d5d5880d6b6c251374b9c8499b2e4)) 59 | 60 | ### BREAKING CHANGES 61 | 62 | - **deps:** dropped support for node 14 and 15 63 | 64 | # [6.1.0](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v6.0.1...v6.1.0) (2023-02-13) 65 | 66 | ### Features 67 | 68 | - **peerdep:** add stylelint 15 support ([6e5a7eb](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/6e5a7eb850f62f82612cb65080183396045be485)) 69 | 70 | ## [6.0.1](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v6.0.0...v6.0.1) (2022-10-01) 71 | 72 | ### Bug Fixes 73 | 74 | - **node:** broaden supported node engines range ([f066420](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/f066420426f4e3fca8ef264668a1683e95ee220b)) 75 | 76 | # [6.0.0](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v5.0.4...v6.0.0) (2022-09-30) 77 | 78 | ### chore 79 | 80 | - **peerdeps:** drop support for stylelint v13 ([31887ef](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/31887ef81fd76fe71392ed8a498e155cc24c5e45)) 81 | 82 | ### Continuous Integration 83 | 84 | - **node:** drop node 12 support ([89ba32d](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/89ba32db76ca22ce9720a8cc87bb81966202cadf)) 85 | 86 | ### BREAKING CHANGES 87 | 88 | - **node:** dropped support for node 12 89 | - **peerdeps:** dropped support for stylelint v13 90 | 91 | ## [5.0.4](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v5.0.3...v5.0.4) (2022-09-13) 92 | 93 | ### Bug Fixes 94 | 95 | - **postcss:** upgrade postcss to latest to address user upgrade errors ([a4dce08](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/a4dce08f49b2edba86806ed145fa65c688ff8320)) 96 | 97 | ## [5.0.3](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v5.0.2...v5.0.3) (2022-02-19) 98 | 99 | ### Bug Fixes 100 | 101 | - fix detecting a mix of supported/unsupported rules ([9c63dd0](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/9c63dd04d93ad64acd52b28b66a6cddd5ce22dc0)) 102 | 103 | ## [5.0.2](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v5.0.1...v5.0.2) (2021-09-13) 104 | 105 | ### Bug Fixes 106 | 107 | - **deps:** increment postcss version ([b50dc11](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/b50dc11dac853e73f91c8d5bf26439110c90e0f0)) 108 | 109 | ## [5.0.1](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v5.0.0...v5.0.1) (2021-05-22) 110 | 111 | ### Bug Fixes 112 | 113 | - **installation:** remove husky lifecycle script ([cb081ab](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/cb081ab84c3deaacd2713264b23360c269342150)) 114 | 115 | # [5.0.0](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/compare/v4.1.4...v5.0.0) (2021-05-22) 116 | 117 | ### Bug Fixes 118 | 119 | - **dependencies:** fix doiuse warning ([c79336f](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/c79336fb1418d793a1d9887272c6faae791ae8d6)), closes [#149](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/issues/149) 120 | 121 | ### Code Refactoring 122 | 123 | - **support:** drop support for node 10 ([72e07bc](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/72e07bca6918775bd8795cc4f220d9b3cdc9b119)) 124 | - **support:** drop support for stylelint v11 and v12 ([4d5a108](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/commit/4d5a1083cd512e0a775e5e5b8ab82097979d417f)) 125 | 126 | ### BREAKING CHANGES 127 | 128 | - **support:** support for node 10 dropped 129 | - **support:** support for stylelint v11, v12 dropped 130 | 131 | ### 4.1.4 132 | 133 | - update doiuse to fix yargs vulnerability warning 134 | 135 | ### 4.1.3 136 | 137 | - update ignorePartialSupport option validation 138 | 139 | ### 4.1.2 140 | 141 | - fix ignorePartialSupport type error when secondary options were omitted 142 | 143 | ### 4.1.1 144 | 145 | - patch: bump postcss from 7.0.35 to 8.1.4 146 | 147 | ### 4.1.0 148 | 149 | - feature `ignorePartialSupport` added (default: `false`). When enabled: 150 | - Rules that only trigger partial violations will be ignored. 151 | - Rules that trigger both partial and full violations will only report on the full support violations. 152 | - Rules that trigger only full support violations will not be affected. 153 | 154 | ### 4.0.0 155 | 156 | - upgrade dependencies 157 | - BREAKING: dropped support for node 6 and 8 158 | 159 | ### 3.0.2 160 | 161 | - update doiuse to v4.2.0 162 | 163 | ### 3.0.1 164 | 165 | - update postcss to v7.0.0 166 | 167 | ### 3.0.0 168 | 169 | - breaking: dropped node 4 support 170 | - breaking: updated doiuse to 4.1.0 (underlying browserslist update might break builds) 171 | - moved to stylelint 9 for testing 172 | 173 | ### 2.0.0 174 | 175 | - move stylelint to peerdependencies 176 | 177 | ### 1.0.1 178 | 179 | - bugfix: choked on parsing less with import statements, fixed by upgrading to doiuse 4.0.0 180 | 181 | ### 1.0.0 182 | 183 | - update to doiuse 3.0.0 184 | 185 | ### 0.1.1 186 | 187 | - update to postcss 6.0.1 188 | 189 | ### 0.1.0 190 | 191 | - initial release 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stylelint-no-unsupported-browser-features 2 | 3 | [![npm version](https://badgen.net/npm/v/stylelint-no-unsupported-browser-features)](https://www.npmjs.com/package/stylelint-no-unsupported-browser-features) 4 | [![ci](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/workflows/node/badge.svg)](https://github.com/rjwadley/stylelint-no-unsupported-browser-features/actions) 5 | [![codecov](https://codecov.io/gh/rjwadley/stylelint-no-unsupported-browser-features/branch/master/graph/badge.svg?token=oFkzh0LZme)](https://codecov.io/gh/rjwadley/stylelint-no-unsupported-browser-features) 6 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 7 | 8 | > disallow features that aren't supported by your target browser audience 9 | 10 | ![screenshot](https://i.imgur.com/YVrqG6P.png) 11 | 12 | This plugin checks if the CSS you're using is supported by the browsers you're targeting. It uses [doiuse](https://github.com/anandthakker/doiuse) to detect browser support. Doiuse itself checks your code against the [caniuse](http://caniuse.com/) database and uses [browserslist](https://github.com/ai/browserslist) to get the list of browsers you want to support. Doiuse and this plugin are only compatible with standard css syntax, so syntaxes like `scss`, `less` and others aren't supported. 13 | 14 | ## Installation 15 | 16 | ```bash 17 | $ npm install stylelint-no-unsupported-browser-features 18 | ``` 19 | 20 | Stylelint is a [peerdependency](https://nodejs.org/en/blog/npm/peer-dependencies/) of this plugin, so you'll have to install stylelint as well: 21 | 22 | ```bash 23 | $ npm install stylelint 24 | ``` 25 | 26 | ## Usage 27 | 28 | 1. Add `"stylelint-no-unsupported-browser-features"` to your stylelint config plugins array 29 | 2. Add `"plugin/no-unsupported-browser-features"` to your stylelint config rules 30 | 3. Enable the rule by setting it to `true`, or pass optional extra configuration 31 | 32 | ## Options 33 | 34 | - `browsers`: optional. Accepts an array of browsers you want to support. For example `['> 1%', 'Last 2 versions']`. See [browserslist](https://github.com/ai/browserslist) for documentation. 35 | - `ignore`: optional. Accepts an array of features to ignore. For example: `['rem', 'css-table']`. Feature names can be found in the error messages. 36 | - `ignorePartialSupport`: optional, off by default. Accepts a boolean. When enabled: 37 | - Rules that only trigger partial violations will be ignored. 38 | - Rules that trigger both partial and full violations will only report on the full support violations. 39 | - Rules that trigger only full support violations will not be affected. 40 | 41 | So for example, in a `.stylelintrc`: 42 | 43 | ```json 44 | { 45 | "plugins": ["stylelint-no-unsupported-browser-features"], 46 | "rules": { 47 | "plugin/no-unsupported-browser-features": [ 48 | true, 49 | { 50 | "browsers": ["> 1%", "Last 2 versions"], 51 | "ignore": ["rem"], 52 | "ignorePartialSupport": true 53 | } 54 | ] 55 | } 56 | } 57 | ``` 58 | 59 | ## Recommendations 60 | 61 | This is a good rule to use with "warning"-level severity, because its primary purpose is to warn you that you are using features not all browsers fully support and therefore ought to provide fallbacks. But the warning will continue even if you have a fallback in place (it doesn't know); so you probably do not want this rule to break your build. Instead, consider it a friendly reminder to double-check certain spots for fallbacks. 62 | 63 | Also, doiuse uses browserslist to get the list of browsers you want to support. Browserslist accepts a `browserslist` file at the root of your project with a list of browsers that you want to support. Since there are other projects that can use this file (like [autoprefixer](https://github.com/postcss/autoprefixer) or [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat)) the simplest solution is to define your intended browser support in this file. There are a lot of different ways to define this list. Check out the browserslist [documentation](https://github.com/browserslist/browserslist#config-file) for more options. 64 | 65 | For the above setup you could use the following config: 66 | 67 | `./.stylelintrc` 68 | 69 | ```json 70 | { 71 | "plugins": ["stylelint-no-unsupported-browser-features"], 72 | "rules": { 73 | "plugin/no-unsupported-browser-features": [ 74 | true, 75 | { 76 | "severity": "warning" 77 | } 78 | ] 79 | } 80 | } 81 | ``` 82 | 83 | `./browserslist`: 84 | 85 | ```text 86 | > 5% 87 | Last 2 versions 88 | ``` 89 | 90 | ## Known issues 91 | 92 | - [Visual Studio Code](https://code.visualstudio.com) users leveraging stylelint-no-unsupported-browser-features through the official [stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) extension will need to restart VSCode after making changes to their browserslist configuration file. It seems that either VSCode or the extension are causing browserlist config files to be cached and are not watching for changes in the file. If you are relying on the `browsers` property within the rules section of `.stylelintrc` you can ignore this issue. Changes to the `browsers` property are discovered immediately. 93 | 94 | ## License 95 | 96 | MIT 97 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import pluginJs from '@eslint/js'; 3 | 4 | /** @type {import('eslint').Linter.Config[]} */ 5 | export default [{ languageOptions: { globals: globals.node } }, pluginJs.configs.recommended]; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @satisfies {import('jest').Config} */ 2 | const config = { 3 | preset: 'jest-preset-stylelint', 4 | runner: 'jest-light-runner', 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /lib/index.browser-env.test.js: -------------------------------------------------------------------------------- 1 | /* global testRule */ 2 | 3 | import rule from './index.js'; 4 | 5 | const { ruleName } = rule; 6 | 7 | /** 8 | * This test suite uses https://github.com/stylelint/jest-preset-stylelint, 9 | * as recommended in https://stylelint.io/developer-guide/plugins#testing. 10 | * 11 | * It does not test configurations that would cause the plugin code itself 12 | * to error. It only asserts whether the rule acceptance and rejections 13 | * work as expected for a given config and code. 14 | * 15 | * This specific test suite does not pass a "browsers" configuration setting, 16 | * Instead it relies on browserslist resolving the supported browsers. We're 17 | * setting the environment vars BROWSERSLIST_DISABLE_CACHE and 18 | * BROWSERSLIST='IE 8' in the package.json scripts so that the browser will 19 | * resolve to IE 8. 20 | * 21 | * See also: https://github.com/browserslist/browserslist#environment-variables 22 | */ 23 | 24 | testRule({ 25 | plugins: ['./lib'], 26 | ruleName, 27 | config: true, 28 | 29 | accept: [ 30 | { 31 | code: 'div { display: inline-block; }', 32 | description: 'allow display:inline-block for IE 8', 33 | }, 34 | { 35 | code: 'div { display: table; }', 36 | description: 'allow display:table for IE 8', 37 | }, 38 | ], 39 | reject: [ 40 | { 41 | code: 'div { cursor: no-drop; }', 42 | description: 'disallow css3-cursors for IE 8', 43 | message: `Unexpected browser feature "css3-cursors" is only partially supported by IE 8 (plugin/no-unsupported-browser-features)`, 44 | line: 1, 45 | column: 7, 46 | }, 47 | { 48 | code: 'div { opacity: 0; }', 49 | description: 'disallow opacity for IE 8', 50 | message: `Unexpected browser feature "css-opacity" is only partially supported by IE 8 (plugin/no-unsupported-browser-features)`, 51 | line: 1, 52 | column: 7, 53 | }, 54 | { 55 | code: 'div { width: 5rem; }', 56 | description: 'disallow rems for IE 8', 57 | message: `Unexpected browser feature "rem" is not supported by IE 8 (plugin/no-unsupported-browser-features)`, 58 | line: 1, 59 | column: 7, 60 | }, 61 | { 62 | code: 'div { display: flex; }', 63 | description: 'disallow display:flex for IE 8', 64 | message: `Unexpected browser feature "flexbox" is not supported by IE 8 (plugin/no-unsupported-browser-features)`, 65 | line: 1, 66 | column: 7, 67 | }, 68 | ], 69 | }); 70 | 71 | testRule({ 72 | plugins: ['./lib'], 73 | ruleName, 74 | config: [ 75 | true, 76 | { 77 | ignore: ['rem', 'flexbox'], 78 | }, 79 | ], 80 | 81 | accept: [ 82 | { 83 | code: 'div { width: 5rem; }', 84 | description: 'allow rems for IE 8 if ignored', 85 | }, 86 | { 87 | code: 'div { display: flex; }', 88 | description: 'allow display:flex for IE 8 if ignored', 89 | }, 90 | ], 91 | }); 92 | 93 | testRule({ 94 | plugins: ['./lib'], 95 | ruleName, 96 | config: [ 97 | true, 98 | { 99 | ignorePartialSupport: true, 100 | }, 101 | ], 102 | 103 | accept: [ 104 | { 105 | code: 'div { opacity: 0; }', 106 | description: 'allow opacity for IE 8 if partial support is ignored', 107 | }, 108 | { 109 | code: 'div { cursor: no-drop; }', 110 | description: 'allow css3-cursors for IE 8 if partial support is ignored', 111 | }, 112 | ], 113 | }); 114 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import stylelint from 'stylelint'; 2 | import browserslist from 'browserslist'; 3 | import doiuse from 'doiuse'; 4 | import { Result } from 'postcss'; 5 | 6 | /** 7 | * Plugin settings 8 | */ 9 | 10 | const ruleName = 'plugin/no-unsupported-browser-features'; 11 | const messages = stylelint.utils.ruleMessages(ruleName, { 12 | rejected: (details) => `Unexpected browser feature ${details}`, 13 | }); 14 | 15 | /** 16 | * Options 17 | */ 18 | 19 | function isString(value) { 20 | return typeof value === 'string'; 21 | } 22 | 23 | function isBoolean(value) { 24 | return typeof value === 'boolean'; 25 | } 26 | 27 | const optionsSchema = { 28 | browsers: [isString], 29 | ignore: [isString], 30 | ignorePartialSupport: isBoolean, 31 | }; 32 | 33 | const defaultBrowsers = browserslist(); 34 | 35 | /** 36 | * Utilities 37 | */ 38 | 39 | function cleanWarningText(warningText, ignorePartialSupport) { 40 | // Get index of feature Id 41 | const featureIdIndex = warningText.lastIndexOf('('); 42 | 43 | // Get feature Id, then replace brackets with quotes 44 | const featureId = warningText.slice(featureIdIndex, warningText.length).replace(/\(|\)/g, '"'); 45 | 46 | // Get start of support text i.e. "x not supported by...", or "y only partially supported by..." 47 | const browserSupportStartIndex = 48 | warningText.indexOf('not') !== -1 ? warningText.indexOf('not') : warningText.indexOf('only'); 49 | 50 | // Get browser support text, then strip brackets. 51 | let browserSupport = warningText 52 | .slice(browserSupportStartIndex, featureIdIndex - 1) 53 | .replace(/\(|\)|:/g, ''); 54 | 55 | // Check if there's partial support for anything 56 | const andIndex = browserSupport.indexOf(' and'); 57 | 58 | // If there's an and, and partialSupport should be ignored, remove the partial support string 59 | if (ignorePartialSupport && andIndex !== -1) { 60 | browserSupport = browserSupport.slice(0, andIndex); 61 | } 62 | 63 | const cleanedWarningText = `${featureId} is ${browserSupport}`; 64 | 65 | return cleanedWarningText; 66 | } 67 | 68 | /** 69 | * The main plugin rule 70 | */ 71 | 72 | function ruleFunction(on, options) { 73 | return (root, result) => { 74 | const validOptions = stylelint.utils.validateOptions(result, ruleName, { 75 | actual: options, 76 | possible: optionsSchema, 77 | optional: true, 78 | }); 79 | 80 | if (!validOptions) { 81 | return; 82 | } 83 | 84 | const usedFeatures = {}; 85 | const doiuseResult = new Result(); 86 | const doiuseOptions = {}; 87 | 88 | if (options) { 89 | Object.keys(optionsSchema).forEach((optionsKey) => { 90 | doiuseOptions[optionsKey] = options[optionsKey]; 91 | }); 92 | } 93 | 94 | if (!doiuseOptions.browsers) { 95 | doiuseOptions.browsers = defaultBrowsers; 96 | } 97 | 98 | doiuseOptions.onFeatureUsage = (info) => { 99 | // Use the node as key to store feature information 100 | usedFeatures[info.usage] = info.featureData; 101 | }; 102 | 103 | const { ignorePartialSupport } = doiuseOptions; 104 | 105 | doiuse(doiuseOptions).postcss(root, doiuseResult); 106 | doiuseResult.warnings().forEach((doiuseWarning) => { 107 | const featureData = usedFeatures[doiuseWarning.node]; 108 | if (featureData && ignorePartialSupport && featureData.partial && !featureData.missing) { 109 | return; 110 | } 111 | 112 | stylelint.utils.report({ 113 | ruleName, 114 | result, 115 | message: messages.rejected(cleanWarningText(doiuseWarning.text, ignorePartialSupport)), 116 | node: doiuseWarning.node, 117 | }); 118 | }); 119 | }; 120 | } 121 | 122 | ruleFunction.ruleName = ruleName; 123 | ruleFunction.messages = messages; 124 | 125 | const plugin = stylelint.createPlugin(ruleName, ruleFunction); 126 | 127 | export default plugin; 128 | -------------------------------------------------------------------------------- /lib/index.test.js: -------------------------------------------------------------------------------- 1 | /* global testRule */ 2 | 3 | import rule from './index.js'; 4 | 5 | const { ruleName } = rule; 6 | 7 | /** 8 | * This test suite uses https://github.com/stylelint/jest-preset-stylelint, 9 | * as recommended in https://stylelint.io/developer-guide/plugins#testing. 10 | * 11 | * It does not test configurations that would cause the plugin code itself 12 | * to error. It only asserts whether the rule acceptance and rejections 13 | * work as expected for a given config and code. 14 | */ 15 | 16 | /** 17 | * browsers option: single browser 18 | */ 19 | 20 | // IE 6 21 | testRule({ 22 | plugins: ['./lib'], 23 | ruleName, 24 | config: [ 25 | true, 26 | { 27 | browsers: ['IE 6'], 28 | }, 29 | ], 30 | 31 | reject: [ 32 | { 33 | code: 'div { display: table; }', 34 | description: 'disallow display:table for IE 6', 35 | message: `Unexpected browser feature "css-table" is not supported by IE 6 (plugin/no-unsupported-browser-features)`, 36 | line: 1, 37 | column: 7, 38 | }, 39 | ], 40 | }); 41 | 42 | // IE 8 43 | testRule({ 44 | plugins: ['./lib'], 45 | ruleName, 46 | config: [ 47 | true, 48 | { 49 | browsers: ['IE 8'], 50 | }, 51 | ], 52 | 53 | accept: [ 54 | { 55 | code: 'div { display: table; }', 56 | description: 'allow display:table for IE 8', 57 | }, 58 | ], 59 | }); 60 | 61 | // IE 9 62 | testRule({ 63 | plugins: ['./lib'], 64 | ruleName, 65 | config: [ 66 | true, 67 | { 68 | browsers: ['IE 9'], 69 | }, 70 | ], 71 | 72 | reject: [ 73 | { 74 | code: 'div { width: 10rem; }', 75 | description: 'disallow rems for IE 9', 76 | message: `Unexpected browser feature "rem" is only partially supported by IE 9 (plugin/no-unsupported-browser-features)`, 77 | line: 1, 78 | column: 7, 79 | }, 80 | ], 81 | }); 82 | 83 | /** 84 | * browsers option: multiple browsers 85 | */ 86 | 87 | // IE 8 and IE 9 88 | testRule({ 89 | plugins: ['./lib'], 90 | ruleName, 91 | config: [ 92 | true, 93 | { 94 | browsers: ['IE 8', 'IE 9'], 95 | }, 96 | ], 97 | 98 | accept: [ 99 | { 100 | code: 'div { display: table; }', 101 | description: 'allow display:table for IE 8 and IE 9', 102 | }, 103 | ], 104 | }); 105 | 106 | // IE 6 and IE 7 107 | testRule({ 108 | plugins: ['./lib'], 109 | ruleName, 110 | config: [ 111 | true, 112 | { 113 | browsers: ['IE 6', 'IE 7'], 114 | }, 115 | ], 116 | 117 | reject: [ 118 | { 119 | code: 'div { display: table; }', 120 | description: 'disallow display:table for IE 6 and IE 7', 121 | message: `Unexpected browser feature "css-table" is not supported by IE 6,7 (plugin/no-unsupported-browser-features)`, 122 | line: 1, 123 | column: 7, 124 | }, 125 | ], 126 | }); 127 | 128 | /** 129 | * ignore option 130 | */ 131 | 132 | // IE 6 with flexbox ignored 133 | testRule({ 134 | plugins: ['./lib'], 135 | ruleName, 136 | config: [ 137 | true, 138 | { 139 | browsers: ['IE 6'], 140 | ignore: ['flexbox'], 141 | }, 142 | ], 143 | 144 | accept: [ 145 | { 146 | code: 'div { display: flex; }', 147 | description: 'allow display:flex for IE 6 if ignored', 148 | }, 149 | ], 150 | }); 151 | 152 | // IE 6 with flexbox and table ignored 153 | testRule({ 154 | plugins: ['./lib'], 155 | ruleName, 156 | config: [ 157 | true, 158 | { 159 | browsers: ['IE 6'], 160 | ignore: ['flexbox', 'css-table'], 161 | }, 162 | ], 163 | 164 | accept: [ 165 | { 166 | code: 'div { display: flex; } a { display: table; }', 167 | description: 'allow display:flex and display:table for IE 6 if both ignored', 168 | }, 169 | ], 170 | }); 171 | 172 | /** 173 | * ignorePartialSupport option 174 | */ 175 | 176 | // IE 11 with partial support ignored 177 | testRule({ 178 | plugins: ['./lib'], 179 | ruleName, 180 | config: [ 181 | true, 182 | { 183 | browsers: ['IE 11'], 184 | ignorePartialSupport: true, 185 | }, 186 | ], 187 | 188 | accept: [ 189 | { 190 | code: 'div { display: flex; }', 191 | description: 'allow display:flex for IE 11 if partial support ignored', 192 | }, 193 | ], 194 | }); 195 | 196 | // IE 7 and IE 10 with partial support ignored 197 | testRule({ 198 | plugins: ['./lib'], 199 | ruleName, 200 | config: [ 201 | true, 202 | { 203 | browsers: ['IE 7', 'IE 10'], 204 | ignorePartialSupport: true, 205 | }, 206 | ], 207 | 208 | reject: [ 209 | { 210 | code: 'div { width: 10rem; }', 211 | description: 'remove the partial support warning for a rule with mixed support', 212 | message: `Unexpected browser feature "rem" is not supported by IE 7 (plugin/no-unsupported-browser-features)`, 213 | line: 1, 214 | column: 7, 215 | }, 216 | ], 217 | }); 218 | 219 | // IE 6 with partial support ignored 220 | testRule({ 221 | plugins: ['./lib'], 222 | ruleName, 223 | config: [ 224 | true, 225 | { 226 | browsers: ['IE 6'], 227 | ignorePartialSupport: true, 228 | }, 229 | ], 230 | 231 | reject: [ 232 | { 233 | code: 'div { width: 10rem; }', 234 | description: 'not ignore a rule which is fully unsupported', 235 | message: `Unexpected browser feature "rem" is not supported by IE 6 (plugin/no-unsupported-browser-features)`, 236 | line: 1, 237 | column: 7, 238 | }, 239 | ], 240 | }); 241 | 242 | // IE 11 mix of not supported and partially supported 243 | testRule({ 244 | plugins: ['./lib'], 245 | ruleName, 246 | config: [ 247 | true, 248 | { 249 | browsers: ['IE 11'], 250 | ignorePartialSupport: true, 251 | }, 252 | ], 253 | 254 | reject: [ 255 | { 256 | code: 'div { display: flex;\nposition: sticky; }', 257 | description: 'handle unsupported and partially supported separately', 258 | message: `Unexpected browser feature "css-sticky" is not supported by IE 11 (plugin/no-unsupported-browser-features)`, 259 | line: 2, 260 | column: 1, 261 | }, 262 | ], 263 | }); 264 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylelint-no-unsupported-browser-features", 3 | "version": "8.0.5", 4 | "description": "Disallow features that are unsupported by the browsers that you are targeting", 5 | "homepage": "https://github.com/RJWadley/stylelint-no-unsupported-browser-features#readme", 6 | "scripts": { 7 | "codecov": "codecov", 8 | "test": "cross-env BROWSERSLIST_DISABLE_CACHE=true BROWSERSLIST='IE 8' jest", 9 | "test:coverage": "cross-env BROWSERSLIST_DISABLE_CACHE=true BROWSERSLIST='IE 8' jest --coverage", 10 | "lint:prettier": "prettier --list-different '**/*.js'", 11 | "lint:js": "eslint '**/*.js'", 12 | "semantic-release": "semantic-release", 13 | "prepare": "husky" 14 | }, 15 | "packageManager": "pnpm@9.15.4", 16 | "peerDependencies": { 17 | "stylelint": "^16.0.2" 18 | }, 19 | "dependencies": { 20 | "browserslist": "^4.26.3", 21 | "doiuse": "^6.0.5", 22 | "postcss": "^8.4.32" 23 | }, 24 | "devDependencies": { 25 | "@commitlint/cli": "19.6.1", 26 | "@commitlint/config-conventional": "19.6.0", 27 | "@eslint/js": "^9.19.0", 28 | "@semantic-release/changelog": "6.0.3", 29 | "@semantic-release/commit-analyzer": "13.0.1", 30 | "@semantic-release/git": "10.0.1", 31 | "@semantic-release/github": "11.0.1", 32 | "@semantic-release/npm": "12.0.1", 33 | "@semantic-release/release-notes-generator": "14.0.3", 34 | "codecov": "3.8.3", 35 | "cross-env": "7.0.3", 36 | "eslint": "^9.19.0", 37 | "globals": "^15.14.0", 38 | "husky": "9.1.7", 39 | "jest": "29.7.0", 40 | "jest-light-runner": "^0.6.0", 41 | "jest-preset-stylelint": "7.2.0", 42 | "lint-staged": "15.4.3", 43 | "prettier": "3.4.2", 44 | "semantic-release": "24.2.1", 45 | "stylelint": "16.14.1" 46 | }, 47 | "author": "RJWadley", 48 | "license": "MIT", 49 | "engines": { 50 | "node": ">=18.12.0" 51 | }, 52 | "type": "module", 53 | "exports": "./lib/index.js", 54 | "files": [ 55 | "lib/index.js" 56 | ], 57 | "repository": { 58 | "type": "git", 59 | "url": "git+https://github.com/RJWadley/stylelint-no-unsupported-browser-features.git" 60 | }, 61 | "bugs": { 62 | "url": "https://github.com/RJWadley/stylelint-no-unsupported-browser-features/issues" 63 | }, 64 | "keywords": [ 65 | "stylelint", 66 | "stylelint-plugin", 67 | "css", 68 | "doiuse", 69 | "linter", 70 | "browser", 71 | "support" 72 | ], 73 | "renovate": { 74 | "extends": [ 75 | "config:js-lib", 76 | ":dependencyDashboardApproval", 77 | ":enableVulnerabilityAlerts" 78 | ] 79 | } 80 | } 81 | --------------------------------------------------------------------------------