From 3dfa452a22ee3857811a01266fdefcdea4b6673c Mon Sep 17 00:00:00 2001 From: Ilya Chudin Date: Fri, 26 Mar 2021 13:46:13 +0400 Subject: [PATCH] Add no-invalid-position-at-import-rule (#5202) --- docs/user-guide/rules/list.md | 1 + lib/rules/index.js | 3 + .../README.md | 51 ++++++++ .../__tests__/index.js | 113 ++++++++++++++++++ .../index.js | 52 ++++++++ 5 files changed, 220 insertions(+) create mode 100644 lib/rules/no-invalid-position-at-import-rule/README.md create mode 100644 lib/rules/no-invalid-position-at-import-rule/__tests__/index.js create mode 100644 lib/rules/no-invalid-position-at-import-rule/index.js diff --git a/docs/user-guide/rules/list.md b/docs/user-guide/rules/list.md index f3934ebb2e..5ab5df0221 100644 --- a/docs/user-guide/rules/list.md +++ b/docs/user-guide/rules/list.md @@ -79,6 +79,7 @@ Grouped first by the following categories and then by the [_thing_](http://apps. - [`no-empty-source`](../../../lib/rules/no-empty-source/README.md): Disallow empty sources. - [`no-extra-semicolons`](../../../lib/rules/no-extra-semicolons/README.md): Disallow extra semicolons (Autofixable). - [`no-invalid-double-slash-comments`](../../../lib/rules/no-invalid-double-slash-comments/README.md): Disallow double-slash comments (`//...`) which are not supported by CSS. +- [`no-invalid-position-at-import-rule`](../../../lib/rules/no-invalid-position-at-import-rule/README.md): Disallow invalid position `@import` rules within a stylesheet. ## Limit language features diff --git a/lib/rules/index.js b/lib/rules/index.js index 395ac241c0..5c966a243e 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -242,6 +242,9 @@ const rules = { 'no-invalid-double-slash-comments': importLazy(() => require('./no-invalid-double-slash-comments'), )(), + 'no-invalid-position-at-import-rule': importLazy(() => + require('./no-invalid-position-at-import-rule'), + )(), 'no-missing-end-of-source-newline': importLazy(() => require('./no-missing-end-of-source-newline'), )(), diff --git a/lib/rules/no-invalid-position-at-import-rule/README.md b/lib/rules/no-invalid-position-at-import-rule/README.md new file mode 100644 index 0000000000..77cbe4546d --- /dev/null +++ b/lib/rules/no-invalid-position-at-import-rule/README.md @@ -0,0 +1,51 @@ +# no-invalid-position-at-import-rule + +Disallow invalid position `@import` rules within a stylesheet. + + +```css +a {} +@import 'foo.css'; +/** ↑ + * This @import */ +``` + +Any `@import` rules must precede all other valid at-rules and style rules in a stylesheet (ignoring `@charset`), or else the `@import` rule is invalid. + +## Options + +### `true` + +The following patterns are considered violations: + + +```css +a {} +@import 'foo.css'; +``` + + +```css +@media print {} +@import 'foo.css'; +``` + +The following patterns are _not_ considered violations: + + +```css +@import 'foo.css'; +a {} +``` + + +```css +/* some comment */ +@import 'foo.css'; +``` + + +```css +@charset 'utf-8'; +@import 'foo.css'; +``` diff --git a/lib/rules/no-invalid-position-at-import-rule/__tests__/index.js b/lib/rules/no-invalid-position-at-import-rule/__tests__/index.js new file mode 100644 index 0000000000..8699f3d36c --- /dev/null +++ b/lib/rules/no-invalid-position-at-import-rule/__tests__/index.js @@ -0,0 +1,113 @@ +'use strict'; + +const stripIndent = require('common-tags').stripIndent; + +const { messages, ruleName } = require('..'); + +testRule({ + ruleName, + config: [true], + + accept: [ + { + code: stripIndent` + @import 'foo.css'; + a {} + `, + description: '@import on first line', + }, + { + code: stripIndent` + /* some comment */ + @import 'foo.css'; + `, + description: '@import after comment', + }, + { + code: stripIndent` + @charset 'utf-8'; + @import 'foo.css'; + `, + description: '@import after @charset ', + }, + { + code: stripIndent` + @import 'foo.css'; + @import 'bar.css'; + `, + description: '@import after another @import', + }, + { + code: stripIndent` + @CHARSET 'utf-8'; + @imPORT 'foo.css'; + @import 'bar.css'; + `, + description: 'case insensitive', + }, + ], + + reject: [ + { + code: stripIndent` + a {} + @import 'foo.css'; + `, + message: messages.rejected, + description: '@import after selector', + line: 2, + column: 1, + }, + { + code: stripIndent` + @media print {} + @import url('foo.css'); + `, + message: messages.rejected, + description: '@import after another at-rule', + line: 2, + column: 1, + }, + { + code: stripIndent` + @media print {} + @imPort URl('foo.css'); + `, + message: messages.rejected, + description: 'case insensitive', + line: 2, + column: 1, + }, + { + code: stripIndent` + @import 'foo.css'; + a {} + @import 'bar.css'; + `, + message: messages.rejected, + description: 'only second @import reported', + line: 3, + column: 1, + }, + { + code: stripIndent` + a {} + @import 'foo.css'; + @import 'bar.css'; + `, + warnings: [ + { + message: messages.rejected, + line: 2, + column: 1, + }, + { + message: messages.rejected, + line: 3, + column: 1, + }, + ], + description: 'all @import reported', + }, + ], +}); diff --git a/lib/rules/no-invalid-position-at-import-rule/index.js b/lib/rules/no-invalid-position-at-import-rule/index.js new file mode 100644 index 0000000000..810230fad6 --- /dev/null +++ b/lib/rules/no-invalid-position-at-import-rule/index.js @@ -0,0 +1,52 @@ +// @ts-nocheck + +'use strict'; + +const report = require('../../utils/report'); +const ruleMessages = require('../../utils/ruleMessages'); +const validateOptions = require('../../utils/validateOptions'); + +const ruleName = 'no-invalid-position-at-import-rule'; + +const messages = ruleMessages(ruleName, { + rejected: 'Unexpected invalid position @import rule', +}); + +function rule(actual) { + return (root, result) => { + const validOptions = validateOptions(result, ruleName, { actual }); + + if (!validOptions) { + return; + } + + let invalidPosition = false; + + root.walk((node) => { + const nodeName = node.name && node.name.toLowerCase(); + + if (node.type === 'comment' || (node.type === 'atrule' && nodeName === 'charset')) { + return; + } + + if (node.type === 'atrule' && nodeName === 'import') { + if (invalidPosition) { + report({ + message: messages.rejected, + node, + result, + ruleName, + }); + } + + return; + } + + invalidPosition = true; + }); + }; +} + +rule.ruleName = ruleName; +rule.messages = messages; +module.exports = rule;