Repository: stylelint-scss/stylelint-scss
Pull request #1019 (2024-07-08 12:40:21)

Fix `partial-no-import` message to include more accurate rule name
By this change, the `partial-no-import` rule message will be properly associated with the rule name. See the examples below:

Before:

```sh-session
$ echo '@import "file.scss"' | npx stylelint

<input css ZECpLj>
       ✖  The 'partial-no-import' rule won't work if linting in a code string without an actual file

✖ 1 problem (1 error, 0 warnings)
```

After:

```sh-session
$ echo '@import "file.scss"' | npx stylelint

<input css PE6AAM>
  1:2  ✖  This rule won't work if linting in a code string without an actual file  scss/partial-no-import

✖ 1 problem (1 error, 0 warnings)
```

In addition, this change updates `jest-preset-stylelint` to use `testRule()` instead of `postcss`.
Because `testRule()` is closer to the actual use of `stylelint` and easier to avoid any potential troubles.
See https://github.com/stylelint/jest-preset-stylelint/releases/tag/7.1.0


ybiquitous: For your information, this `partial-no-import` test would fail with the `stylelint` HEAD revision due to the direct use of `postcss`. That's why I recommend using `testRule()`.

```diff
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
     "postcss-less": "^6.0.0",
     "postcss-scss": "^4.0.9",
     "prettier": "^3.3.2",
-    "stylelint": "^16.6.1"
+    "stylelint": "https://github.com/stylelint/stylelint.git"
   },
   "files": [
     "src/**/*.js",
```

```sh-session
$ npm run --ignore-scripts test -- partial-no-import
...
Test Suites: 1 failed, 1 total
Tests:       9 failed, 11 passed, 20 total
Snapshots:   0 total
Time:        46.995 s
Ran all test suites matching /partial-no-import/i.
```

Patch:
diff --git a/README.md b/README.md
index a646b7d..49921b8 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,6 @@ Please also see the [example configs](./docs/examples/) for special cases.
 
 ### `@`-import
 
-- [`at-import-partial-extension`](./src/rules/at-import-partial-extension/README.md): Require or disallow extension in `@import` commands (Autofixable).
 - [`at-import-partial-extension-allowed-list`](./src/rules/at-import-partial-extension-allowed-list/README.md): Specify a list of allowed file extensions for partial names in `@import` commands.
 - [`at-import-partial-extension-disallowed-list`](./src/rules/at-import-partial-extension-disallowed-list/README.md): Specify a list of disallowed file extensions for partial names in `@import` commands.
 
@@ -183,6 +182,7 @@ Please also see the [example configs](./docs/examples/) for special cases.
 ### Load
 
 - [`load-no-partial-leading-underscore`](./src/rules/load-no-partial-leading-underscore/README.md): Disallow leading underscore in partial names in `@import`, `@use`, `@forward`, and [`meta.load-css`](https://sass-lang.com/documentation/modules/meta/#load-css) `$url` parameter.
+- [`load-partial-extension`](./src/rules/load-partial-extension/README.md): Require or disallow extension in `@import`, `@use`, `@forward`, and [`meta.load-css`] commands (Autofixable).
 
 ### General / Sheet
 
@@ -197,6 +197,7 @@ Please also see the [example configs](./docs/examples/) for special cases.
 These rules are deprecated — we won't fix bugs nor add options, and we will remove them in the next major release. We recommend you use a pretty printer (like Prettier) alongside Stylelint rather than these rules.
 
 - [`at-import-no-partial-leading-underscore`](https://github.com/stylelint-scss/stylelint-scss/blob/v5.2.1/src/rules/at-import-no-partial-leading-underscore/README.md): Disallow leading underscore in partial names in `@import`.
+- [`at-import-partial-extension`](https://github.com/stylelint-scss/stylelint-scss/blob/v6.3.0/src/rules/at-import-partial-extension/README.md): Require or disallow extension in `@import` commands.
 - [`at-import-partial-extension-blacklist`](https://github.com/stylelint-scss/stylelint-scss/blob/v6.1.0/src/rules/at-import-partial-extension-blacklist/README.md): Specify a blacklist of disallowed file extensions for partial names in `@import` commands.
 - [`at-import-partial-extension-whitelist`](https://github.com/stylelint-scss/stylelint-scss/blob/v6.1.0/src/rules/at-import-partial-extension-whitelist/README.md): Specify a whitelist of allowed file extensions for partial names in `@import` commands.
 
diff --git a/package-lock.json b/package-lock.json
index 76893fe..dbf0ae3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,7 @@
         "github-contributors-list": "^1.2.5",
         "husky": "^9.0.11",
         "jest": "^29.7.0",
-        "jest-preset-stylelint": "^7.0.1",
+        "jest-preset-stylelint": "^7.1.0",
         "lint-staged": "^14.0.1",
         "np": "^10.0.6",
         "postcss": "^8.4.39",
@@ -5224,9 +5224,9 @@
       }
     },
     "node_modules/jest-preset-stylelint": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/jest-preset-stylelint/-/jest-preset-stylelint-7.0.1.tgz",
-      "integrity": "sha512-umVx7qx/O2pTMdZvG8HcMmExo/7rXpYsNK/22GKQ6ulLBxfgVSINLtkav77ZH505Uy6g0tDoEbWuPoWe5vJuMQ==",
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/jest-preset-stylelint/-/jest-preset-stylelint-7.1.0.tgz",
+      "integrity": "sha512-0chPt4l2B1xIMMoTQPhwqcbH1ANBx6W+IhGpMDvPWffIdI5pE7pgti3XVOh9bBBgFutcSY0MCD8yEFABEY07VQ==",
       "dev": true,
       "funding": [
         {
@@ -5238,6 +5238,7 @@
           "url": "https://github.com/sponsors/stylelint"
         }
       ],
+      "license": "MIT",
       "engines": {
         "node": ">=18.12.0"
       },
@@ -13647,9 +13648,9 @@
       "requires": {}
     },
     "jest-preset-stylelint": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/jest-preset-stylelint/-/jest-preset-stylelint-7.0.1.tgz",
-      "integrity": "sha512-umVx7qx/O2pTMdZvG8HcMmExo/7rXpYsNK/22GKQ6ulLBxfgVSINLtkav77ZH505Uy6g0tDoEbWuPoWe5vJuMQ==",
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/jest-preset-stylelint/-/jest-preset-stylelint-7.1.0.tgz",
+      "integrity": "sha512-0chPt4l2B1xIMMoTQPhwqcbH1ANBx6W+IhGpMDvPWffIdI5pE7pgti3XVOh9bBBgFutcSY0MCD8yEFABEY07VQ==",
       "dev": true,
       "requires": {}
     },
diff --git a/package.json b/package.json
index 31e0002..71efb98 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
     "github-contributors-list": "^1.2.5",
     "husky": "^9.0.11",
     "jest": "^29.7.0",
-    "jest-preset-stylelint": "^7.0.1",
+    "jest-preset-stylelint": "^7.1.0",
     "lint-staged": "^14.0.1",
     "np": "^10.0.6",
     "postcss": "^8.4.39",
diff --git a/src/rules/at-import-partial-extension/index.js b/src/rules/at-import-partial-extension/index.js
index d4feed7..804dba3 100644
--- a/src/rules/at-import-partial-extension/index.js
+++ b/src/rules/at-import-partial-extension/index.js
@@ -14,7 +14,8 @@ const messages = utils.ruleMessages(ruleName, {
 });
 
 const meta = {
-  url: ruleUrl(ruleName)
+  url: ruleUrl(ruleName),
+  deprecated: true
 };
 
 // https://drafts.csswg.org/mediaqueries/#media-types
@@ -47,6 +48,16 @@ function rule(expectation, _, context) {
       return;
     }
 
+    result.warn(
+      "'at-import-partial-extension has been deprecated, " +
+        "and will be removed in '7.0'. Use 'load-partial-extension' instead.",
+      {
+        stylelintType: "deprecation",
+        stylelintReference:
+          "https://github.com/stylelint-scss/stylelint-scss/blob/v6.3.0/src/rules/at-import-partial-extension/README.md"
+      }
+    );
+
     root.walkAtRules("import", atRule => {
       const paths = atRule.params
         .split(/["']\s*,/)
diff --git a/src/rules/index.js b/src/rules/index.js
index e9a2b56..21a218a 100644
--- a/src/rules/index.js
+++ b/src/rules/index.js
@@ -15,6 +15,7 @@ const rules = {
   "at-if-no-null": require("./at-if-no-null"),
   "at-import-no-partial-leading-underscore": require("./at-import-no-partial-leading-underscore"),
   "load-no-partial-leading-underscore": require("./load-no-partial-leading-underscore"),
+  "load-partial-extension": require("./load-partial-extension"),
   "at-import-partial-extension-allowed-list": require("./at-import-partial-extension-allowed-list"),
   "at-import-partial-extension-blacklist": require("./at-import-partial-extension-blacklist"),
   "at-import-partial-extension-disallowed-list": require("./at-import-partial-extension-disallowed-list"),
diff --git a/src/rules/load-partial-extension/README.md b/src/rules/load-partial-extension/README.md
new file mode 100644
index 0000000..456c6a6
--- /dev/null
+++ b/src/rules/load-partial-extension/README.md
@@ -0,0 +1,159 @@
+# load-partial-extension
+
+Require or disallow extension in `@import`, `@use`, `@forward`, and [`meta.load-css`] commands.
+
+```scss
+@use "file.scss";
+/**       ↑
+ * This extension */
+```
+
+```scss
+@use "sass:meta";
+
+body {
+  @include meta.load-css("fff.scss",
+                  /**         ↑
+                  * This extension */
+      $with: ("border-contrast": true));
+}
+```
+
+The [`fix` option](https://stylelint.io/user-guide/usage/options#fix) can automatically fix all of the problems reported by this rule only when `"never"` is given.
+
+The rule ignores [cases](https://sass-lang.com/documentation/at-rules/import) when Sass considers an `@import` command just a plain CSS import:
+
+- If the file’s extension is `.css`.
+- If the filename begins with `http://` (or any other protocol).
+- If the filename is a `url()`.
+- If the `@import` has any media queries.
+
+## Options
+
+`string`: `"always"|"never"`
+
+### `"always"`
+
+The following patterns are considered warnings:
+
+```scss
+@import "foo";
+```
+
+```scss
+@use "path/fff";
+```
+
+```scss
+@forward "path/fff";
+```
+
+```scss
+@use "path\\fff";
+```
+
+```scss
+@use "df/fff", "1.SCSS";
+```
+
+The following patterns are _not_ considered warnings:
+
+```scss
+@use "fff.scss";
+```
+
+```scss
+@forward "path/fff.scss";
+```
+
+```scss
+@import url("path/_file.css"); /* has url(), so doesn't count as a partial @import */
+```
+
+```scss
+@use "file.css"; /* Has ".css" extension, so doesn't count as a partial @import */
+```
+
+```scss
+/* Both are URIs, so don't count as partial @imports */
+@use "http://_file.scss";
+@use "//_file.scss";
+```
+
+```scss
+@import "file.scss" screen; /* Has a media query, so doesn't count as a partial @import */
+```
+
+### `"never"`
+
+The following patterns are considered warnings:
+
+```scss
+@import "foo.scss";
+```
+
+```scss
+@use "path/fff.less";
+```
+
+```scss
+@forward "path/fff.scss";
+```
+
+```scss
+@use "path\\fff.ruthless";
+```
+
+```scss
+@use "df/fff", "1.SCSS";
+```
+
+```scss
+@use "sass:meta";
+
+body {
+  @include meta.load-css("foo.scss", 
+    $with: ("border-contrast": true));
+}
+```
+
+The following patterns are _not_ considered warnings:
+
+```scss
+@use "foo";
+```
+
+```scss
+@use "path/fff";
+```
+
+```scss
+@import url("path/_file.css"); /* has url(), so doesn't count as a partial @import */
+```
+
+```scss
+@use "file.css"; /* Has ".css" extension, so doesn't count as a partial @import */
+```
+
+```scss
+@forward "path/fff";
+```
+
+```scss
+/* Both are URIs, so don't count as partial @imports */
+@import "http://_file.scss";
+@import "//_file.scss";
+```
+
+```scss
+@import "file.scss" screen; /* Has a media query, so doesn't count as a partial @import */
+```
+
+```scss
+@use "sass:meta";
+
+body {
+  @include meta.load-css("path/foo", 
+    $with: ("border-contrast": true));
+}
+```
diff --git a/src/rules/load-partial-extension/index.js b/src/rules/load-partial-extension/index.js
new file mode 100644
index 0000000..768f110
--- /dev/null
+++ b/src/rules/load-partial-extension/index.js
@@ -0,0 +1,138 @@
+"use strict";
+
+const nodeJsPath = require("path");
+const { utils } = require("stylelint");
+const atRuleParamIndex = require("../../utils/atRuleParamIndex");
+const namespace = require("../../utils/namespace");
+const ruleUrl = require("../../utils/ruleUrl");
+
+const ruleName = namespace("load-partial-extension");
+
+const messages = utils.ruleMessages(ruleName, {
+  expected: (rule = "import") => `Expected @${rule} to have an extension`,
+  rejected: (ext, rule = "import") =>
+    `Unexpected extension ".${ext}" in @${rule}`
+});
+
+const meta = {
+  url: ruleUrl(ruleName)
+};
+
+// https://drafts.csswg.org/mediaqueries/#media-types
+const mediaQueryTypes = [
+  "all",
+  "print",
+  "screen",
+  "speech",
+  "tv",
+  "tty",
+  "projection",
+  "handheld",
+  "braille",
+  "embossed",
+  "aural"
+];
+
+const mediaQueryTypesRE = new RegExp(`(${mediaQueryTypes.join("|")})$`, "i");
+const hasArgumentsRegExp = /\(\s*([^)]+?)\s*\)/;
+const loadAtRules = ["import", "use", "forward", "include"];
+const stripPath = path =>
+  path.replace(/^\s*(["'])\s*/, "").replace(/\s*(["'])\s*$/, "");
+
+function rule(expectation, _, context) {
+  return (root, result) => {
+    const validOptions = utils.validateOptions(result, ruleName, {
+      actual: expectation,
+      possible: ["always", "never"]
+    });
+
+    if (!validOptions) {
+      return;
+    }
+
+    root.walkAtRules(atRule => {
+      if (loadAtRules.includes(atRule.name)) {
+        let name = atRule.name;
+        let paths = atRule.params
+          .split(/["']\s*,/)
+          .filter(path => !mediaQueryTypesRE.test(path.trim()));
+
+        const isLoadCss = !!atRule.params.match(/load-css/);
+        if (isLoadCss) {
+          name = "meta.load-css";
+          paths = [
+            hasArgumentsRegExp
+              .exec(atRule.params)[0]
+              .split(",")[0]
+              .replace(/[()]/g, "")
+          ];
+        }
+
+        // Processing comma-separated lists of import paths
+        paths.forEach(path => {
+          // Stripping trailing quotes and whitespaces, if any
+          const pathStripped = stripPath(path);
+
+          // Skipping importing CSS: url(), ".css", URI with a protocol
+          if (
+            pathStripped.slice(0, 4) === "url(" ||
+            pathStripped.slice(-4) === ".css" ||
+            pathStripped.search("//") !== -1 ||
+            pathStripped.search(":") !== -1
+          ) {
+            return;
+          }
+
+          const extension = nodeJsPath.extname(pathStripped).slice(1);
+
+          if (!extension && expectation === "always") {
+            utils.report({
+              message: messages.expected(name),
+              node: atRule,
+              result,
+              ruleName,
+              word: pathStripped
+            });
+
+            return;
+          }
+
+          const isScssPartial = extension === "scss";
+          if (extension && isScssPartial && expectation === "never") {
+            if (context.fix) {
+              const extPattern = new RegExp(`\\.${extension}(['" ]*)$`, "g");
+              if (isLoadCss) {
+                atRule.params = atRule.params.replace(
+                  path,
+                  path.replace(extPattern, "$1")
+                );
+              }
+              atRule.params = atRule.params.replace(extPattern, "$1");
+
+              return;
+            }
+
+            const dotExt = `.${extension}`;
+            const index =
+              atRuleParamIndex(atRule) + atRule.params.lastIndexOf(dotExt);
+
+            utils.report({
+              message: messages.rejected(extension, name),
+              node: atRule,
+              index,
+              endIndex: index + dotExt.length,
+              result,
+              ruleName
+            });
+          }
+        });
+      }
+    });
+  };
+}
+
+rule.ruleName = ruleName;
+rule.messages = messages;
+rule.meta = meta;
+
+module.exports = rule;
diff --git a/src/rules/partial-no-import/index.js b/src/rules/partial-no-import/index.js
index cf9aa23..b14659a 100644
--- a/src/rules/partial-no-import/index.js
+++ b/src/rules/partial-no-import/index.js
@@ -8,7 +8,9 @@ const ruleUrl = require("../../utils/ruleUrl");
 const ruleName = namespace("partial-no-import");
 
 const messages = utils.ruleMessages(ruleName, {
-  expected: "Unexpected @import in a partial"
+  expected: "Unexpected @import in a partial",
+  expectedActualFile:
+    "This rule won't work if linting in a code string without an actual file"
 });
 
 const meta = {
@@ -26,9 +28,14 @@ function rule(on) {
     }
 
     if (root.source.input.file === undefined || !root.source.input.file) {
-      result.warn(
-        "The 'partial-no-import' rule won't work if linting in a code string without an actual file."
-      );
+      utils.report({
+        message: messages.expectedActualFile,
+        node: root,
+        index: 1,
+        endIndex: 2,
+        result,
+        ruleName
+      });
 
       return;
     }


Test patch:
diff --git a/src/rules/load-partial-extension/__tests__/index.js b/src/rules/load-partial-extension/__tests__/index.js
new file mode 100644
index 0000000..277f1a3
--- /dev/null
+++ b/src/rules/load-partial-extension/__tests__/index.js
@@ -0,0 +1,1460 @@
+"use strict";
+
+const { messages, ruleName } = require("..");
+
+// Always: Import
+testRule({
+  ruleName,
+  config: ["always"],
+  customSyntax: "postcss-scss",
+
+  accept: [
+    {
+      code: `
+      @import "fff.scss";
+    `,
+      description: "Single file, .scss extension"
+    },
+    {
+      code: `
+      @import "path/fff.scss";
+    `,
+      description: "Single file, path with dir, .scss extension"
+    },
+    {
+      code: `
+      @import "df\\fff.scss";
+    `,
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @import 'fff.scss';
+    `,
+      description: "Single file, .scss extension, single quotes."
+    },
+    {
+      code: `
+      @import "fff.foo";
+    `,
+      description: "Single file, .foo extension."
+    },
+    {
+      code: `
+      @import " fff.scss1 ";
+    `,
+      description: "Single file, extension, trailing whitespaces."
+    },
+    {
+      code: `
+      @import "fff.scss", "fff.moi";
+    `,
+      description: "Multiple files with extensions."
+    },
+    {
+      code: `
+      @import url("path/_file.css");
+    `,
+      description: "Import CSS with url()."
+    },
+    {
+      code: `
+      @import "_file.css";
+    `,
+      description: "Import CSS by extension."
+    },
+    {
+      code: `
+      @import "http://_file.scss";
+    `,
+      description: "Import CSS from the web, http://."
+    },
+    {
+      code: `
+      @import " https://_file.scss ";
+    `,
+      description:
+        "Import CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @import 'https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap';
+    `,
+      description: "Import CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @import "//_file.scss";
+    `,
+      description: "Import CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @import "_file.scss" screen;
+    `,
+      description: "Import CSS (with media queries)."
+    },
+    {
+      code: `
+      @import "_file.scss"screen;
+    `,
+      description: "Import CSS (with media queries)."
+    },
+    {
+      code: `
+      @import "_file.scss "screen;
+    `,
+      description:
+        "Import CSS (with media queries), trailing space inside quotes."
+    },
+    {
+      code: `
+      @import url(_lol.scss) screen;
+    `,
+      description: "Import CSS (with media queries - url + media)."
+    },
+    {
+      code: `
+      @import url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap');
+    `,
+      description: "Import CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @import _file.scss tv, screen;
+    `,
+      description: "Import CSS (with media queries - multiple)."
+    },
+    {
+      code: `
+      @import _file.scss tv,screen;
+    `,
+      description: "Import CSS (with media queries - multiple, no spaces)."
+    },
+    {
+      code: `
+      @import "screen.scss";
+    `,
+      description: "Import with a name that matches a media query type."
+    },
+    {
+      code: `
+      @import "colors.variables.scss";
+    `,
+      description: "Import a filename with a dot."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @import "fff";
+    `,
+      line: 2,
+      column: 16,
+      endLine: 2,
+      endColumn: 19,
+      message: messages.expected(),
+      description: "Single file, no extension."
+    },
+    {
+      code: `
+      @import "fff",
+          "fff.ruthless";
+    `,
+      line: 2,
+      column: 16,
+      endLine: 2,
+      endColumn: 19,
+      message: messages.expected(),
+      description: "Multiple files, one without extension."
+    },
+    {
+      code: `
+      @import "fff", "score";
+    `,
+      warnings: [
+        {
+          line: 2,
+          column: 16,
+          endLine: 2,
+          endColumn: 19,
+          message: messages.expected()
+        },
+        {
+          line: 2,
+          column: 23,
+          endLine: 2,
+          endColumn: 28,
+          message: messages.expected()
+        }
+      ],
+      description: "Two files, no extensions."
+    }
+  ]
+});
+
+// Always : Use
+testRule({
+  ruleName,
+  config: ["always"],
+  customSyntax: "postcss-scss",
+
+  accept: [
+    {
+      code: `
+      @use "fff.scss";
+    `,
+      description: "Single file, .scss extension"
+    },
+    {
+      code: `
+      @use "path/fff.scss";
+    `,
+      description: "Single file, path with dir, .scss extension"
+    },
+    {
+      code: `
+      @use "df\\fff.scss";
+    `,
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @use 'fff.scss';
+    `,
+      description: "Single file, .scss extension, single quotes."
+    },
+    {
+      code: `
+      @use "fff.foo";
+    `,
+      description: "Single file, .foo extension."
+    },
+    {
+      code: `
+      @use " fff.scss1 ";
+    `,
+      description: "Single file, extension, trailing whitespaces."
+    },
+    {
+      code: `
+      @use url("path/_file.css");
+    `,
+      description: "use CSS with url()."
+    },
+    {
+      code: `
+      @use "_file.css";
+    `,
+      description: "use CSS by extension."
+    },
+    {
+      code: `
+      @use "http://_file.scss";
+    `,
+      description: "use CSS from the web, http://."
+    },
+    {
+      code: `
+      @use " https://_file.scss ";
+    `,
+      description:
+        "use CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @use url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap');
+    `,
+      description: "use CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @use "//_file.scss";
+    `,
+      description: "use CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @use "colors.variables.scss";
+    `,
+      description: "use a filename with a dot."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @use "fff";
+    `,
+      line: 2,
+      column: 13,
+      endLine: 2,
+      endColumn: 16,
+      message: messages.expected("use"),
+      description: "Single file, no extension."
+    }
+  ]
+});
+
+// Always : forward
+testRule({
+  ruleName,
+  config: ["always"],
+  customSyntax: "postcss-scss",
+
+  accept: [
+    {
+      code: `
+      @forward "fff.scss";
+    `,
+      description: "Single file, .scss extension"
+    },
+    {
+      code: `
+      @forward "path/fff.scss";
+    `,
+      description: "Single file, path with dir, .scss extension"
+    },
+    {
+      code: `
+      @forward "df\\fff.scss";
+    `,
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @forward 'fff.scss';
+    `,
+      description: "Single file, .scss extension, single quotes."
+    },
+    {
+      code: `
+      @forward "fff.foo";
+    `,
+      description: "Single file, .foo extension."
+    },
+    {
+      code: `
+      @forward " fff.scss1 ";
+    `,
+      description: "Single file, extension, trailing whitespaces."
+    },
+    {
+      code: `
+      @forward "http://_file.scss";
+    `,
+      description: "forward CSS from the web, http://."
+    },
+    {
+      code: `
+      @forward " https://_file.scss ";
+    `,
+      description:
+        "forward CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @forward "//_file.scss";
+    `,
+      description: "forward CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @forward "colors.variables.scss";
+    `,
+      description: "forward a filename with a dot."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @forward "fff";
+    `,
+      line: 2,
+      column: 17,
+      endLine: 2,
+      endColumn: 20,
+      message: messages.expected("forward"),
+      description: "Single file, no extension."
+    },
+    {
+      code: `
+      @forward "fff",
+          "fff.ruthless";
+    `,
+      line: 2,
+      column: 17,
+      endLine: 2,
+      endColumn: 20,
+      message: messages.expected("forward"),
+      description: "Multiple files, one without extension."
+    },
+    {
+      code: `
+      @forward "fff", "score";
+    `,
+      warnings: [
+        {
+          line: 2,
+          column: 17,
+          endLine: 2,
+          endColumn: 20,
+          message: messages.expected("forward")
+        },
+        {
+          line: 2,
+          column: 24,
+          endLine: 2,
+          endColumn: 29,
+          message: messages.expected("forward")
+        }
+      ],
+      description: "Two files, no extensions."
+    }
+  ]
+});
+
+// Always : meta.load-css
+testRule({
+  ruleName,
+  config: ["always"],
+  customSyntax: "postcss-scss",
+
+  accept: [
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, .scss extension"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("paths/fff.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, path with dir, .scss extension"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("df\\fff.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css('fff.scss', $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, .scss extension, single quotes."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff.foo", $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, .foo extension."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(" fff.scss1 ", $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, extension, trailing whitespaces."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("path/_file.css", $with: ("border-contrast": true));
+      }
+    `,
+      description: "forward CSS with url()."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("_file.css", $with: ("border-contrast": true));
+      }
+    `,
+      description: "forward CSS by extension."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("http://_file.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "forward CSS from the web, http://."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(" https://_file.scss ", $with: ("border-contrast": true));
+      }
+    `,
+      description:
+        "forward CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("//_file.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "forward CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap'), $with: ("border-contrast": true));
+      }
+    `,
+      description: "forward CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("colors.variables.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "forward a filename with a dot."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 33,
+      endLine: 4,
+      endColumn: 36,
+      message: messages.expected("meta.load-css"),
+      description: "Single file, no extension."
+    }
+  ]
+});
+
+// Never: Import
+testRule({
+  ruleName,
+  config: ["never"],
+  customSyntax: "postcss-scss",
+  fix: true,
+
+  accept: [
+    {
+      code: `
+      @import "fff";
+    `,
+      description: "Single file, no extension, double quotes."
+    },
+    {
+      code: `
+      @import 'fff';
+    `,
+      description: "Single file, no extension, single quotes."
+    },
+    {
+      code: `
+      @import ' fff ';
+    `,
+      description: "Single file, no extension, trailing spaces inside quotes."
+    },
+    {
+      code: `
+      @import "fff", "score";
+    `,
+      description: "Two files, no extension, double quotes."
+    },
+    {
+      code: `
+      @import "screen";
+    `,
+      description: "Import with a name that matches a media query type."
+    },
+    {
+      code: `
+      @import url("path/_file.css");
+    `,
+      description: "Import CSS with url()."
+    },
+    {
+      code: `
+      @import "_file.css";
+    `,
+      description: "Import CSS by extension."
+    },
+    {
+      code: `
+      @import "http://_file.scss";
+    `,
+      description: "Import CSS from the web, http://."
+    },
+    {
+      code: `
+      @import " https://_file.scss ";
+    `,
+      description:
+        "Import CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @import 'https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap';
+    `,
+      description: "Import CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @import "//_file.scss";
+    `,
+      description: "Import CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @import "_file.scss" screen;
+    `,
+      description: "Import CSS (with media queries)."
+    },
+    {
+      code: `
+      @import "_file.scss"screen;
+    `,
+      description: "Import CSS (with media queries)."
+    },
+    {
+      code: `
+      @import "_file.scss "screen;
+    `,
+      description:
+        "Import CSS (with media queries), trailing space inside quotes."
+    },
+    {
+      code: `
+      @import url(_lol.scss) screen;
+    `,
+      description: "Import CSS (with media queries - url + media)."
+    },
+    {
+      code: `
+      @import url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap');
+    `,
+      description: "Import CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @import _file.scss tv, screen;
+    `,
+      description: "Import CSS (with media queries - multiple)."
+    },
+    {
+      code: `
+      @import _file.scss tv,screen;
+    `,
+      description: "Import CSS (with media queries - multiple, no spaces)."
+    },
+    {
+      code: `
+      @import "colors.variables";
+    `,
+      description: "Import a style file with a dot in the name."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @import "fff.scss";
+     `,
+      fixed: `
+      @import "fff";
+     `,
+      line: 2,
+      column: 19,
+      endLine: 2,
+      endColumn: 24,
+      message: messages.rejected("scss"),
+      description: "Single file, .scss extension."
+    },
+    {
+      code: `
+      @import "screen.scss";
+    `,
+      fixed: `
+      @import "screen";
+    `,
+      line: 2,
+      column: 22,
+      endLine: 2,
+      endColumn: 27,
+      message: messages.rejected("scss", "import"),
+      description: "Single file with media query type as name, .scss extension."
+    },
+    {
+      code: `
+      @import "fff.scss ";
+    `,
+      fixed: `
+      @import "fff ";
+    `,
+      line: 2,
+      column: 19,
+      endLine: 2,
+      endColumn: 24,
+      message: messages.rejected("scss"),
+      description: "Single file, has extension, space at the end."
+    },
+    {
+      code: `
+      @import " fff.scss ";
+    `,
+      fixed: `
+      @import " fff ";
+    `,
+      line: 2,
+      column: 20,
+      endLine: 2,
+      endColumn: 25,
+      message: messages.rejected("scss"),
+      description: "Single file, has extension, trailing spaces."
+    },
+    {
+      code: `
+      @import "df/fff.scss";
+    `,
+      fixed: `
+      @import "df/fff";
+    `,
+      line: 2,
+      column: 22,
+      endLine: 2,
+      endColumn: 27,
+      message: messages.rejected("scss"),
+      description: "Single file, path with dir, has extension."
+    },
+    {
+      code: `
+      @import "df\\fff.scss";
+    `,
+      fixed: `
+      @import "df\\fff";
+    `,
+      line: 2,
+      column: 22,
+      endLine: 2,
+      endColumn: 27,
+      message: messages.rejected("scss"),
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @import "df/fff", '_1.scss';
+    `,
+      fixed: `
+      @import "df/fff", '_1';
+    `,
+      line: 2,
+      column: 28,
+      endLine: 2,
+      endColumn: 33,
+      message: messages.rejected("scss"),
+      description: "Two files, path with dir, has extension."
+    },
+    {
+      code: `
+      @import "colors.variables.scss";
+    `,
+      fixed: `
+      @import "colors.variables";
+    `,
+      line: 2,
+      column: 32,
+      endLine: 2,
+      endColumn: 37,
+      message: messages.rejected("scss"),
+      description: "Single file, has .scss extension and a dot in the name."
+    },
+    {
+      code: `
+      @import "component.scss-theme.scss";
+    `,
+      fixed: `
+      @import "component.scss-theme";
+    `,
+      line: 2,
+      column: 36,
+      endLine: 2,
+      endColumn: 41,
+      message: messages.rejected("scss"),
+      description:
+        "Single file, has .scss extension and a .scss in the filename."
+    }
+  ]
+});
+
+// Never: Use
+testRule({
+  ruleName,
+  config: ["never"],
+  customSyntax: "postcss-scss",
+  fix: true,
+
+  accept: [
+    {
+      code: `
+      @use "fff";
+    `,
+      description: "Single file, no extension, double quotes."
+    },
+    {
+      code: `
+      @use 'fff';
+    `,
+      description: "Single file, no extension, single quotes."
+    },
+    {
+      code: `
+      @use ' fff ';
+    `,
+      description: "Single file, no extension, trailing spaces inside quotes."
+    },
+    {
+      code: `
+      @use "_file.css";
+    `,
+      description: "use CSS by extension."
+    },
+    {
+      code: `
+      @use "http://_file.scss";
+    `,
+      description: "use CSS from the web, http://."
+    },
+    {
+      code: `
+      @use " https://_file.scss ";
+    `,
+      description:
+        "use CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @use 'https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap';
+    `,
+      description: "use CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @use "//_file.scss";
+    `,
+      description: "use CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @use url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap');
+    `,
+      description: "use CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @use "colors.variables";
+    `,
+      description: "use a style file with a dot in the name."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @use "fff.scss";
+     `,
+      fixed: `
+      @use "fff";
+     `,
+      line: 2,
+      column: 16,
+      endLine: 2,
+      endColumn: 21,
+      message: messages.rejected("scss", "use"),
+      description: "Single file, .scss extension."
+    },
+    {
+      code: `
+      @use "fff.scss";
+     `,
+      fixed: `
+      @use "fff";
+     `,
+      line: 2,
+      column: 16,
+      endLine: 2,
+      endColumn: 21,
+      message: messages.rejected("scss", "use"),
+      description: "Single file, .scss extension."
+    },
+    {
+      code: `
+      @use "fff.scss ";
+    `,
+      fixed: `
+      @use "fff ";
+    `,
+      line: 2,
+      column: 16,
+      endLine: 2,
+      endColumn: 21,
+      message: messages.rejected("scss", "use"),
+      description: "Single file, has extension, space at the end."
+    },
+    {
+      code: `
+      @use " fff.scss ";
+    `,
+      fixed: `
+      @use " fff ";
+    `,
+      line: 2,
+      column: 17,
+      endLine: 2,
+      endColumn: 22,
+      message: messages.rejected("scss", "use"),
+      description: "Single file, has extension, trailing spaces."
+    },
+    {
+      code: `
+      @use "df/fff.scss";
+    `,
+      fixed: `
+      @use "df/fff";
+    `,
+      line: 2,
+      column: 19,
+      endLine: 2,
+      endColumn: 24,
+      message: messages.rejected("scss", "use"),
+      description: "Single file, path with dir, has extension."
+    },
+    {
+      code: `
+      @use "df\\fff.scss";
+    `,
+      fixed: `
+      @use "df\\fff";
+    `,
+      line: 2,
+      column: 19,
+      endLine: 2,
+      endColumn: 24,
+      message: messages.rejected("scss", "use"),
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @use "df/fff", '_1.scss';
+    `,
+      fixed: `
+      @use "df/fff", '_1';
+    `,
+      line: 2,
+      column: 25,
+      endLine: 2,
+      endColumn: 30,
+      message: messages.rejected("scss", "use"),
+      description: "Two files, path with dir, has extension."
+    },
+    {
+      code: `
+      @use "colors.variables.scss";
+    `,
+      fixed: `
+      @use "colors.variables";
+    `,
+      line: 2,
+      column: 29,
+      endLine: 2,
+      endColumn: 34,
+      message: messages.rejected("scss", "use"),
+      description: "Single file, has .scss extension and a dot in the name."
+    },
+    {
+      code: `
+      @use "component.scss-theme.scss";
+    `,
+      fixed: `
+      @use "component.scss-theme";
+    `,
+      line: 2,
+      column: 33,
+      endLine: 2,
+      endColumn: 38,
+      message: messages.rejected("scss", "use"),
+      description:
+        "Single file, has .scss extension and a .scss in the filename."
+    }
+  ]
+});
+
+// Never: forward
+testRule({
+  ruleName,
+  config: ["never"],
+  customSyntax: "postcss-scss",
+  fix: true,
+
+  accept: [
+    {
+      code: `
+      @forward "fff";
+    `,
+      description: "Single file, no extension, double quotes."
+    },
+    {
+      code: `
+      @forward 'fff';
+    `,
+      description: "Single file, no extension, single quotes."
+    },
+    {
+      code: `
+      @forward ' fff ';
+    `,
+      description: "Single file, no extension, trailing spaces inside quotes."
+    },
+    {
+      code: `
+      @forward "_file.css";
+    `,
+      description: "forward CSS by extension."
+    },
+    {
+      code: `
+      @forward "http://_file.scss";
+    `,
+      description: "forward CSS from the web, http://."
+    },
+    {
+      code: `
+      @forward " https://_file.scss ";
+    `,
+      description:
+        "forward CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @forward "//_file.scss";
+    `,
+      description: "forward CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @forward "colors.variables";
+    `,
+      description: "forward a style file with a dot in the name."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @forward "fff.scss";
+     `,
+      fixed: `
+      @forward "fff";
+     `,
+      line: 2,
+      column: 20,
+      endLine: 2,
+      endColumn: 25,
+      message: messages.rejected("scss", "forward"),
+      description: "Single file, .scss extension."
+    },
+    {
+      code: `
+      @forward "fff.scss";
+     `,
+      fixed: `
+      @forward "fff";
+     `,
+      line: 2,
+      column: 20,
+      endLine: 2,
+      endColumn: 25,
+      message: messages.rejected("scss", "forward"),
+      description: "Single file, .scss extension."
+    },
+    {
+      code: `
+      @forward "fff.scss ";
+    `,
+      fixed: `
+      @forward "fff ";
+    `,
+      line: 2,
+      column: 20,
+      endLine: 2,
+      endColumn: 25,
+      message: messages.rejected("scss", "forward"),
+      description: "Single file, has extension, space at the end."
+    },
+    {
+      code: `
+      @forward " fff.scss ";
+    `,
+      fixed: `
+      @forward " fff ";
+    `,
+      line: 2,
+      column: 21,
+      endLine: 2,
+      endColumn: 26,
+      message: messages.rejected("scss", "forward"),
+      description: "Single file, has extension, trailing spaces."
+    },
+    {
+      code: `
+      @forward "df/fff.scss";
+    `,
+      fixed: `
+      @forward "df/fff";
+    `,
+      line: 2,
+      column: 23,
+      endLine: 2,
+      endColumn: 28,
+      message: messages.rejected("scss", "forward"),
+      description: "Single file, path with dir, has extension."
+    },
+    {
+      code: `
+      @forward "df\\fff.scss";
+    `,
+      fixed: `
+      @forward "df\\fff";
+    `,
+      line: 2,
+      column: 23,
+      endLine: 2,
+      endColumn: 28,
+      message: messages.rejected("scss", "forward"),
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @forward "colors.variables.scss";
+    `,
+      fixed: `
+      @forward "colors.variables";
+    `,
+      line: 2,
+      column: 33,
+      endLine: 2,
+      endColumn: 38,
+      message: messages.rejected("scss", "forward"),
+      description: "Single file, has .scss extension and a dot in the name."
+    },
+    {
+      code: `
+      @forward "component.scss-theme.scss";
+    `,
+      fixed: `
+      @forward "component.scss-theme";
+    `,
+      line: 2,
+      column: 37,
+      endLine: 2,
+      endColumn: 42,
+      message: messages.rejected("scss", "forward"),
+      description:
+        "Single file, has .scss extension and a .scss in the filename."
+    }
+  ]
+});
+
+// Never: meta.load-css
+testRule({
+  ruleName,
+  config: ["never"],
+  customSyntax: "postcss-scss",
+  fix: true,
+
+  accept: [
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff", $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, no extension, double quotes."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css('fff', $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, no extension, single quotes."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(' fff ', $with: ("border-contrast": true));
+      }
+    `,
+      description: "Single file, no extension, trailing spaces inside quotes."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(url("path/_file.css"), $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css CSS with url()."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("_file.css", $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css CSS by extension."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("http://_file.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css CSS from the web, http://."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(" https://_file.scss ", $with: ("border-contrast": true));
+      }
+    `,
+      description:
+        "meta.load-css CSS from the web, https://, trailing spaces inside quotes"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap', $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("//_file.scss", $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css CSS from the web, no protocol."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap'), $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css CSS from the web, https:// with comma"
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("colors.variables", $with: ("border-contrast": true));
+      }
+    `,
+      description: "meta.load-css a style file with a dot in the name."
+    }
+  ],
+
+  reject: [
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff.scss", $with: ("border-contrast": true));
+      }
+     `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff", $with: ("border-contrast": true));
+      }
+     `,
+      line: 4,
+      column: 36,
+      endLine: 4,
+      endColumn: 41,
+      message: messages.rejected("scss", "meta.load-css"),
+      description: "Single file, .scss extension."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff.scss  ", $with: ("border-contrast": true));
+      }
+    `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("fff  ", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 36,
+      endLine: 4,
+      endColumn: 41,
+      message: messages.rejected("scss", "meta.load-css"),
+      description: "Single file, has extension, space at the end."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(" fff.scss ", $with: ("border-contrast": true));
+      }
+    `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css(" fff ", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 37,
+      endLine: 4,
+      endColumn: 42,
+      message: messages.rejected("scss", "meta.load-css"),
+      description: "Single file, has extension, trailing spaces."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("df/fff.scss", $with: ("border-contrast": true));
+      }
+    `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("df/fff", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 39,
+      endLine: 4,
+      endColumn: 44,
+      message: messages.rejected("scss", "meta.load-css"),
+      description: "Single file, path with dir, has extension."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("df\\fff.scss", $with: ("border-contrast": true));
+      }
+    `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("df\\fff", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 39,
+      endLine: 4,
+      endColumn: 44,
+      message: messages.rejected("scss", "meta.load-css"),
+      description:
+        "Single file, path with dir, has extension, windows delimiters."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("colors.variables.scss", $with: ("border-contrast": true));
+      }
+    `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("colors.variables", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 49,
+      endLine: 4,
+      endColumn: 54,
+      message: messages.rejected("scss", "meta.load-css"),
+      description: "Single file, has .scss extension and a dot in the name."
+    },
+    {
+      code: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("component.scss-theme.scss", $with: ("border-contrast": true));
+      }
+    `,
+      fixed: `
+      @use "sass:meta";
+      .a {
+        @include meta.load-css("component.scss-theme", $with: ("border-contrast": true));
+      }
+    `,
+      line: 4,
+      column: 53,
+      endLine: 4,
+      endColumn: 58,
+      message: messages.rejected("scss", "meta.load-css"),
+      description:
+        "Single file, has .scss extension and a .scss in the filename."
+    }
+  ]
+});
diff --git a/src/rules/partial-no-import/__tests__/index.js b/src/rules/partial-no-import/__tests__/index.js
index 7bb32cd..3628908 100644
--- a/src/rules/partial-no-import/__tests__/index.js
+++ b/src/rules/partial-no-import/__tests__/index.js
@@ -1,319 +1,204 @@
 "use strict";
 
 const path = require("path");
-const postcss = require("postcss");
 const rule = require("..");
-const { messages } = rule;
-
-function logError(err) {
-  console.log(err.stack); // eslint-disable-line no-console
-}
-
-test("No file specified", done => {
-  expect.assertions(2);
-
-  postcss([rule()])
-    .process("@import 'file.scss';", { from: undefined })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      expect(warnings[0].text).toBe(
-        "The 'partial-no-import' rule won't work if linting in a code string without an actual file."
-      );
-      done();
-    })
-    .catch(logError);
-});
-
-test("Import a file from non-partial .scss", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import 'file.scss';", {
-      from: path.join(__dirname, "test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a file from a partial .scss", done => {
-  expect.assertions(2);
-  postcss([rule()])
-    .process("@import 'file.scss';", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      expect(warnings[0]).toMatchObject({
-        text: messages.expected,
-        line: 1,
-        column: 10,
-        endLine: 1,
-        endColumn: 19
-      });
-      done();
-    });
-});
-
-test("Import a file from a partial .scss 2", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process('@import "file.scss";', {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      done();
-    });
-});
-
-test("Ignores empty imports (Sass will throw an error instead)", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import '';", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Ignores empty imports (Sass will throw an error instead) 2", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process('@import " ";', {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a file from a partial .scss; omitting extension", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import 'file';", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      done();
-    });
-});
-
-test("Import comma separated files from a partial .scss; omitting extension", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import 'file','file2';", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(2);
-      done();
-    });
-});
-
-test("Import comma separated files from a partial .scss; omitting extension 2", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process('@import "file" , "file2";', {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(2);
-      done();
-    });
-});
-
-// Exceptions
-test("Import a file from CSS", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import 'file.scss';", {
-      from: path.join(__dirname, "_test.css")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a CSS from a partial .scss", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import 'file.css';", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a CSS (url) from a partial .scss", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import url('file.scss');", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a CSS (with protocol) from a partial .scss", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import '//file.scss;'", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a CSS file (font URL with https) from a partial .scss", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process(
-      "@import 'https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700';",
-      {
-        from: path.join(__dirname, "_test.scss")
-      }
-    )
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Import a local file and a CSS file (font URL with https) from a partial .scss (warn for local file, but not https import)", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process(
-      "@import 'file', 'https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700';",
-      {
-        from: path.join(__dirname, "_test.scss")
-      }
-    )
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      done();
-    });
-});
-
-test("Import a local file and a CSS file (font URL with https) from a partial .scss (warn for local file, but not https import) 2", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process(
-      '@import "file", "https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700";',
-      {
-        from: path.join(__dirname, "_test.scss")
-      }
-    )
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      done();
-    });
-});
-
-test("Import a local file and a CSS file (font URL with https) from a partial .scss (warn for local file, but not https import) 3", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process(
-      '@import "https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700", "file";',
-      {
-        from: path.join(__dirname, "_test.scss")
-      }
-    )
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(1);
-      done();
-    });
-});
-
-test("Import a CSS (with media) from a partial .scss", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process("@import 'file.scss' screen", {
-      from: path.join(__dirname, "_test.scss")
-    })
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
-});
-
-test("Multiple imports in a partial.", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process(
-      '@import "_bootstrap/variables";\n@import "_font-awesome/variables";',
-      {
-        from: path.join(__dirname, "_variables.scss")
-      }
-    )
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(2);
-      done();
-    });
-});
-
-test("Import from a non-partial SCSS-file.", done => {
-  expect.assertions(1);
-  postcss([rule()])
-    .process(
-      '@import "bootstrap/variables";\n@import "font-awesome/variables";',
-      {
-        from: path.join(__dirname, "_der", "variables.scss")
-      }
-    )
-    .then(result => {
-      const warnings = result.warnings();
-
-      expect(warnings).toHaveLength(0);
-      done();
-    });
+const { messages, ruleName } = rule;
+
+testRule({
+  ruleName,
+  config: [true],
+
+  accept: [
+    {
+      description: "Import a file from non-partial .scss",
+      code: "@import 'file.scss';",
+      codeFilename: path.join(__dirname, "test.scss")
+    },
+    {
+      description: "Ignores empty imports (Sass will throw an error instead)",
+      code: "@import '';",
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description: "Ignores empty imports (Sass will throw an error instead) 2",
+      code: '@import " ";',
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description: "Import a file from CSS",
+      code: "@import 'file.scss';",
+      codeFilename: path.join(__dirname, "_test.css")
+    },
+    {
+      description: "Import a CSS from a partial .scss",
+      code: "@import 'file.css';",
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description: "Import a CSS (url) from a partial .scss",
+      code: "@import url('file.css');",
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description: "Import a CSS (with protocol) from a partial .scss",
+      code: "@import '//file.css';",
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description:
+        "Import a CSS file (font URL with https) from a partial .scss",
+      code: "@import 'https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700';",
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description: "Import a CSS (with media) from a partial .scss",
+      code: "@import 'file.scss' screen",
+      codeFilename: path.join(__dirname, "_test.scss")
+    },
+    {
+      description: "Import from a non-partial SCSS-file.",
+      code: '@import "bootstrap/variables";\n@import "font-awesome/variables";',
+      codeFilename: path.join(__dirname, "_der", "variables.scss")
+    }
+  ],
+
+  reject: [
+    {
+      description: "No file specified",
+      code: "@import 'file.scss';",
+      message: messages.expectedActualFile,
+      line: 1,
+      column: 2,
+      endLine: 1,
+      endColumn: 3
+    },
+    {
+      description: "Import a file from a partial .scss",
+      code: "@import 'file.scss';",
+      codeFilename: path.join(__dirname, "_test.scss"),
+      message: messages.expected,
+      line: 1,
+      column: 10,
+      endLine: 1,
+      endColumn: 19
+    },
+    {
+      description: "Import a file from a partial .scss 2",
+      code: '@import "file.scss";',
+      codeFilename: path.join(__dirname, "_test.scss"),
+      message: messages.expected,
+      line: 1,
+      column: 10,
+      endLine: 1,
+      endColumn: 19
+    },
+    {
+      description: "Import a file from a partial .scss; omitting extension",
+      code: "@import 'file';",
+      codeFilename: path.join(__dirname, "_test.scss"),
+      message: messages.expected,
+      line: 1,
+      column: 10,
+      endLine: 1,
+      endColumn: 14
+    },
+    {
+      description:
+        "Import comma separated files from a partial .scss; omitting extension",
+      code: "@import 'file','file2';",
+      codeFilename: path.join(__dirname, "_test.scss"),
+      warnings: [
+        {
+          message: messages.expected,
+          line: 1,
+          column: 10,
+          endLine: 1,
+          endColumn: 14
+        },
+        {
+          message: messages.expected,
+          line: 1,
+          column: 17,
+          endLine: 1,
+          endColumn: 22
+        }
+      ]
+    },
+    {
+      description:
+        "Import comma separated files from a partial .scss; omitting extension 2",
+      code: '@import "file" , "file2";',
+      codeFilename: path.join(__dirname, "_test.scss"),
+      warnings: [
+        {
+          message: messages.expected,
+          line: 1,
+          column: 10,
+          endLine: 1,
+          endColumn: 14
+        },
+        {
+          message: messages.expected,
+          line: 1,
+          column: 19,
+          endLine: 1,
+          endColumn: 24
+        }
+      ]
+    },
+    {
+      description:
+        "Import a local file and a CSS file (font URL with https) from a partial .scss (warn for local file, but not https import)",
+      code: "@import 'file', 'https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700';",
+      codeFilename: path.join(__dirname, "_test.scss"),
+      message: messages.expected,
+      line: 1,
+      column: 10,
+      endLine: 1,
+      endColumn: 14
+    },
+    {
+      description:
+        "Import a local file and a CSS file (font URL with https) from a partial .scss (warn for local file, but not https import) 2",
+      code: '@import "file", "https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700";',
+      codeFilename: path.join(__dirname, "_test.scss"),
+      message: messages.expected,
+      line: 1,
+      column: 10,
+      endLine: 1,
+      endColumn: 14
+    },
+    {
+      description:
+        "Import a local file and a CSS file (font URL with https) from a partial .scss (warn for local file, but not https import) 3",
+      code: '@import "https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700", "file";',
+      codeFilename: path.join(__dirname, "_test.scss"),
+      message: messages.expected,
+      line: 1,
+      column: 79,
+      endLine: 1,
+      endColumn: 83
+    },
+    {
+      description: "Multiple imports from a partial .scss",
+      code: '@import "_bootstrap/variables";\n@import "_font-awesome/variables";',
+      codeFilename: path.join(__dirname, "_variables.scss"),
+      warnings: [
+        {
+          message: messages.expected,
+          line: 1,
+          column: 10,
+          endLine: 1,
+          endColumn: 30
+        },
+        {
+          message: messages.expected,
+          line: 2,
+          column: 10,
+          endLine: 2,
+          endColumn: 33
+        }
+      ]
+    }
+  ]
 });