Repository: tivac/modular-css
Pull request #690 (2019-11-25 23:23:32)

feat(svelte): warn on unquoted class attributes
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->
Warn users whenever an unquoted `class={css.foo}` is discovered.

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
The svelte preprocessor can create subtle bugs in cases where the user types something like `class={css.foo}`, this is fine assuming that `css.foo` is replaced with a single class but fails **hard** if there's any composition involved.

**Single-class case ✔**
```diff
-<div class={css.foo}>
+<div class=foo>
```

which is treated by the browser as `<div class="foo">`

**Multi-class case 💀**
```diff
-<div class={css.fooandbar}>
+<div class=foo bar>
```

which is treated by the browser as `<div class="foo" bar>`, which is definitely a problem.

More background on unquoted attributes is available from [Mathias Bynens](https://mathiasbynens.be/notes/unquoted-attribute-values).

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
Added tests!

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.

codecov[bot]: # [Codecov](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=h1) Report
> Merging [#690](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=desc) into [master](https://codecov.io/gh/tivac/modular-css/commit/0e913b002823711ee34017e60c4f5c323b190782?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/tivac/modular-css/pull/690/graphs/tree.svg?width=650&token=EIUJKcuTE8&height=150&src=pr)](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #690      +/-   ##
==========================================
+ Coverage   98.44%   98.45%   +0.01%     
==========================================
  Files          45       45              
  Lines        1157     1168      +11     
  Branches      179      181       +2     
==========================================
+ Hits         1139     1150      +11     
  Misses         15       15              
  Partials        3        3
```


| [Impacted Files](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/svelte/svelte.js](https://codecov.io/gh/tivac/modular-css/pull/690/diff?src=pr&el=tree#diff-cGFja2FnZXMvc3ZlbHRlL3N2ZWx0ZS5qcw==) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=footer). Last update [0e913b0...b156a21](https://codecov.io/gh/tivac/modular-css/pull/690?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/package-lock.json b/package-lock.json
index 2a8c1d3e..f572a24e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2156,6 +2156,7 @@
     "@modular-css/browserify": {
       "version": "file:packages/browserify",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "mkdirp": "^0.5.1",
         "p-each-series": "^2.0.0",
         "sink-transform": "^2.0.0",
@@ -2165,6 +2166,8 @@
     "@modular-css/cli": {
       "version": "file:packages/cli",
       "requires": {
+        "@modular-css/glob": "file:packages/glob",
+        "@modular-css/processor": "file:packages/processor",
         "meow": "^5.0.0",
         "mkdirp": "^0.5.1"
       }
@@ -2172,6 +2175,7 @@
     "@modular-css/glob": {
       "version": "file:packages/glob",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "globule": "^1.1.0"
       }
     },
@@ -2190,6 +2194,7 @@
     "@modular-css/postcss": {
       "version": "file:packages/postcss",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "mkdirp": "^0.5.1",
         "postcss": "^7.0.0"
       }
@@ -2211,6 +2216,7 @@
     "@modular-css/rollup": {
       "version": "file:packages/rollup",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "dedent": "0.7.0",
         "esutils": "^2.0.2",
         "rollup-pluginutils": "^2.0.1",
@@ -2234,8 +2240,10 @@
     "@modular-css/svelte": {
       "version": "file:packages/svelte",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "escape-string-regexp": "^2.0.0",
-        "is-url": "^1.2.4"
+        "is-url": "^1.2.4",
+        "slash": "^3.0.0"
       }
     },
     "@modular-css/test-utils": {
@@ -2245,6 +2253,7 @@
     "@modular-css/webpack": {
       "version": "file:packages/webpack",
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
         "esutils": "^2.0.2",
         "loader-utils": "^1.1.0",
         "lodash": "^4.17.0",
@@ -2255,6 +2264,10 @@
       "version": "file:packages/www",
       "dev": true,
       "requires": {
+        "@modular-css/processor": "file:packages/processor",
+        "@modular-css/rollup": "file:packages/rollup",
+        "@modular-css/shortnames": "file:packages/namer",
+        "@modular-css/svelte": "file:packages/svelte",
         "babel-eslint": "^10.0.1",
         "codemirror": "^5.42.2",
         "cssnano": "^4.1.8",
@@ -3711,7 +3724,7 @@
     },
     "browserify-aes": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
       "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
       "dev": true,
       "requires": {
@@ -3759,7 +3772,7 @@
     },
     "browserify-rsa": {
       "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
       "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
       "dev": true,
       "requires": {
@@ -4261,7 +4274,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -4465,7 +4478,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -4521,7 +4534,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -4574,7 +4587,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -4593,7 +4606,7 @@
     },
     "convert-source-map": {
       "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+      "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
       "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
       "dev": true
     },
@@ -4664,7 +4677,7 @@
     },
     "create-hash": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
       "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
       "dev": true,
       "requires": {
@@ -4677,7 +4690,7 @@
     },
     "create-hmac": {
       "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
       "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
       "dev": true,
       "requires": {
@@ -5275,7 +5288,7 @@
       "dependencies": {
         "JSONStream": {
           "version": "0.7.4",
-          "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.7.4.tgz",
+          "resolved": "http://registry.npmjs.org/JSONStream/-/JSONStream-0.7.4.tgz",
           "integrity": "sha1-c0KQ5BUR7qfCz+FR+/mlY6l7l4Y=",
           "dev": true,
           "requires": {
@@ -5291,7 +5304,7 @@
         },
         "minimist": {
           "version": "0.0.5",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz",
           "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=",
           "dev": true
         }
@@ -5332,7 +5345,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -5356,7 +5369,7 @@
     },
     "diffie-hellman": {
       "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
       "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
       "dev": true,
       "requires": {
@@ -5443,7 +5456,7 @@
     },
     "duplexer": {
       "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
       "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
       "dev": true
     },
@@ -5625,7 +5638,7 @@
     },
     "es6-promisify": {
       "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
       "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
       "dev": true,
       "requires": {
@@ -6119,7 +6132,7 @@
       "dependencies": {
         "JSONStream": {
           "version": "0.8.4",
-          "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz",
+          "resolved": "http://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz",
           "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=",
           "dev": true,
           "requires": {
@@ -6228,13 +6241,13 @@
         },
         "minimist": {
           "version": "0.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz",
           "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=",
           "dev": true
         },
         "readable-stream": {
           "version": "1.1.14",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
           "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
           "dev": true,
           "requires": {
@@ -6297,7 +6310,7 @@
           "dependencies": {
             "readable-stream": {
               "version": "1.0.34",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+              "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
               "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
               "dev": true,
               "requires": {
@@ -7198,7 +7211,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -7246,7 +7259,7 @@
         },
         "camelcase-keys": {
           "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+          "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
           "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
           "dev": true,
           "requires": {
@@ -7275,7 +7288,7 @@
         },
         "load-json-file": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
           "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
           "dev": true,
           "requires": {
@@ -7294,7 +7307,7 @@
         },
         "meow": {
           "version": "3.7.0",
-          "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+          "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
           "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
           "dev": true,
           "requires": {
@@ -7312,7 +7325,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -7347,7 +7360,7 @@
         },
         "pify": {
           "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
           "dev": true
         },
@@ -7495,7 +7508,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -7529,7 +7542,7 @@
       "dependencies": {
         "pify": {
           "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
           "dev": true
         }
@@ -7564,7 +7577,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         },
@@ -8399,7 +8412,7 @@
     },
     "is-obj": {
       "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
       "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
       "dev": true
     },
@@ -11442,7 +11455,7 @@
     },
     "minimist": {
       "version": "0.0.8",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
       "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
     },
     "minimist-options": {
@@ -11532,7 +11545,7 @@
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "requires": {
         "minimist": "0.0.8"
@@ -11731,7 +11744,7 @@
       "dependencies": {
         "semver": {
           "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
           "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
           "dev": true
         }
@@ -11776,7 +11789,7 @@
       "dependencies": {
         "buffer": {
           "version": "4.9.1",
-          "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+          "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
           "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
           "dev": true,
           "requires": {
@@ -12504,7 +12517,7 @@
     },
     "pegjs": {
       "version": "0.10.0",
-      "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
+      "resolved": "http://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
       "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=",
       "dev": true
     },
@@ -13572,7 +13585,7 @@
     },
     "readable-stream": {
       "version": "2.3.6",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
       "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
       "requires": {
         "core-util-is": "~1.0.0",
@@ -13601,7 +13614,7 @@
         },
         "readable-stream": {
           "version": "1.1.14",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
           "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
           "dev": true,
           "requires": {
@@ -14119,7 +14132,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -14197,7 +14210,7 @@
     },
     "sha.js": {
       "version": "2.4.11",
-      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
       "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
       "dev": true,
       "requires": {
@@ -14207,7 +14220,7 @@
     },
     "shasum": {
       "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
+      "resolved": "http://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
       "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
       "dev": true,
       "requires": {
@@ -14739,7 +14752,7 @@
     },
     "stream-combiner": {
       "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
+      "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
       "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
       "dev": true,
       "requires": {
@@ -14912,7 +14925,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -14953,7 +14966,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -15249,7 +15262,7 @@
     },
     "through": {
       "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
       "dev": true
     },
@@ -16032,7 +16045,7 @@
     },
     "wrap-ansi": {
       "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
       "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
       "dev": true,
       "requires": {
@@ -16068,7 +16081,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index c4e7da65..17c756ee 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -31,7 +31,8 @@
   "dependencies": {
     "@modular-css/processor": "file:../processor",
     "escape-string-regexp": "^2.0.0",
-    "is-url": "^1.2.4"
+    "is-url": "^1.2.4",
+    "slash": "^3.0.0"
   },
   "peerDependencies": {
     "svelte": ">1"
diff --git a/packages/svelte/svelte.js b/packages/svelte/svelte.js
index 7e520ee0..92e1ada1 100644
--- a/packages/svelte/svelte.js
+++ b/packages/svelte/svelte.js
@@ -1,7 +1,10 @@
 "use strict";
 
+const path = require("path");
+
 const isUrl = require("is-url");
 const escape = require("escape-string-regexp");
+const slash = require("slash");
 
 const Processor = require("@modular-css/processor");
 
@@ -9,6 +12,8 @@ const styleRegex = /<style[\S\s]*?>([\S\s]*?)<\/style>/im;
 const scriptRegex = /<script[\S\s]*?>([\S\s]*?)<\/script>/im;
 const missedRegex = /css\.\w+/gim;
 
+const prefix = `[${require("./package.json").name}]`;
+
 module.exports = (config = false) => {
     // Defined here to avoid .lastIndex bugs since /g is set
     const linkRegex = /<link\b[^<>]*?\bhref=\s*(?:"([^"]+)"|'([^']+)'|([^>\s]+))[^>]*>/gim;
@@ -16,8 +21,15 @@ module.exports = (config = false) => {
     // Use a passed processor, or set up our own if necessary
     const { processor = new Processor(config) } = config;
 
+    const { cwd } = processor.options;
+
     // eslint-disable-next-line no-console, no-empty-function
-    const log = config.verbose ? console.log.bind(console, "[svelte]") : () => {};
+    const log = config.verbose ? console.log.bind(console, prefix) : () => {};
+
+    // eslint-disable-next-line no-console
+    const warn = console.warn.bind(console, prefix, "WARN");
+
+    const relative = (file) => slash(path.relative(cwd, file));
 
     // Check for and stringify any values in the template we couldn't convert
     const missing = ({ source, file }) => {
@@ -32,12 +44,11 @@ module.exports = (config = false) => {
         const classes = missed.map((reference) => reference.replace("css.", ""));
 
         if(strict) {
-            throw new Error(`@modular-css/svelte: Unable to find .${classes.join(", .")} in "${file}"`);
+            throw new Error(`${prefix} Unable to find .${classes.join(", .")} in "${file}"`);
         }
 
         classes.forEach((key) =>
-            // eslint-disable-next-line no-console
-            console.warn(`@modular-css/svelte: Unable to find .${key} in "${file}"`)
+            warn(`Unable to find .${key} in ${file}`)
         );
 
         // Turn all missing values into strings so nothing explodes
@@ -51,7 +62,9 @@ module.exports = (config = false) => {
     // Mostly because markup() is async so tracking state is painful w/o inlining
     // the whole damn thing
     // eslint-disable-next-line max-statements, complexity
-    const markup = async ({ content, filename : html }) => {
+    const markup = async ({ content, filename }) => {
+        const file = filename ? relative(filename) : "Unknown file";
+
         let source = content;
         let dependencies = [];
 
@@ -59,7 +72,7 @@ module.exports = (config = false) => {
         const style = source.match(styleRegex);
 
         if(links && style) {
-            throw new Error("@modular-css/svelte: use <style> OR <link>, but not both");
+            throw new Error(`${prefix} Use <style> OR <link>, but not both in "${file}"`);
         }
 
         // No-op
@@ -67,7 +80,7 @@ module.exports = (config = false) => {
             return {
                 code : missing({
                     source,
-                    file : html,
+                    file : filename,
                 }),
             };
         }
@@ -75,20 +88,20 @@ module.exports = (config = false) => {
         let result;
         let css;
 
-        log("Processing", html);
+        log("Processing", file);
 
         if(style) {
-            log("extract <style>", html);
+            log("extract <style>", file);
 
             css = "<style>";
 
-            if(processor.has(html)) {
-                processor.invalidate(html);
+            if(processor.has(filename)) {
+                processor.invalidate(filename);
             }
 
             try {
                 result = await processor.string(
-                    html,
+                    filename,
                     style[1]
                 );
             } catch(e) {
@@ -125,14 +138,13 @@ module.exports = (config = false) => {
                 return {
                     code : missing({
                         source,
-                        file : html,
+                        file : filename,
                     }),
                 };
             }
 
             if(valid.length > 1) {
-                // eslint-disable-next-line no-console
-                console.warn("@modular-css/svelte will only use the first local <link> tag");
+                warn(`Only the first local <link> tag will be used`, file);
             }
 
             const [{ link, href }] = valid;
@@ -140,7 +152,7 @@ module.exports = (config = false) => {
             // Assign to file for later usage in logging
             css = href;
 
-            const external = processor.resolve(html, css);
+            const external = processor.resolve(filename, css);
 
             log("extract <link>", external);
 
@@ -179,7 +191,7 @@ module.exports = (config = false) => {
             dependencies = [ ...processor.dependencies(external), external ];
         }
 
-        log("processed styles", html);
+        log("processed styles", file);
 
         const exported = result.exports;
         const keys = Object.keys(exported);
@@ -190,25 +202,34 @@ module.exports = (config = false) => {
         if(keys.length) {
             const selectors = keys.join("|");
 
+            // Look for instances of class={css.foo} to warn about
+            const matches = source.match(new RegExp(`class={css\.(?:${selectors})}`, "g"));
+
+            if(matches) {
+                for(const match of matches) {
+                    warn(`Unquoted class attribute! ${match}`, file);
+                }
+            }
+
             source = source
                 // Replace {css.<key>} values
                 // Note extra exclusion to avoid accidentally matching ${css.<key>}
                 .replace(
                     new RegExp(`([^$]){css\\.(${selectors})}`, "gm"),
-                    (match, prefix, key) => {
+                    (match, before, key) => {
                         const replacement = Array.isArray(exported[key]) ? exported[key].join(" ") : exported[key];
 
-                        return `${prefix}${replacement}`;
+                        return `${before}${replacement}`;
                     }
                 )
 
                 // Then any remaining css.<key> values
                 .replace(
                     new RegExp(`(\\b)css\\.(${selectors})(\\b)`, "gm"),
-                    (match, prefix, key, suffix) => {
+                    (match, before, key, suffix) => {
                         const replacement = Array.isArray(exported[key]) ? exported[key].join(" ") : exported[key];
 
-                        return `${prefix}"${replacement}"${suffix}`;
+                        return `${before}"${replacement}"${suffix}`;
                     }
                 );
         }


Test patch:
diff --git a/packages/svelte/test/__snapshots__/svelte.test.js.snap b/packages/svelte/test/__snapshots__/svelte.test.js.snap
index 75ae404f..c33a61b9 100644
--- a/packages/svelte/test/__snapshots__/svelte.test.js.snap
+++ b/packages/svelte/test/__snapshots__/svelte.test.js.snap
@@ -233,15 +233,19 @@ exports[`/svelte.js should extract CSS from a <style> tag 2`] = `
 "
 `;
 
-exports[`/svelte.js should handle errors: empty css file - <link> 1`] = `"@modular-css/svelte: Unable to find .nope, .alsonope in \\"./empty.css\\""`;
+exports[`/svelte.js should handle errors: empty css file - <link> 1`] = `"[@modular-css/svelte] Unable to find .nope, .alsonope in \\"./empty.css\\""`;
 
 exports[`/svelte.js should handle errors: empty css file - <link> 2`] = `
 Array [
   Array [
-    "@modular-css/svelte: Unable to find .nope in \\"./empty.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .nope in ./empty.css",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .alsonope in \\"./empty.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .alsonope in ./empty.css",
   ],
 ]
 `;
@@ -252,15 +256,19 @@ exports[`/svelte.js should handle errors: empty css file - <link> 3`] = `
 <script>import css from \\"./empty.css\\";</script>"
 `;
 
-exports[`/svelte.js should handle errors: empty css file - <style> 1`] = `"@modular-css/svelte: Unable to find .nope, .alsonope in \\"<style>\\""`;
+exports[`/svelte.js should handle errors: empty css file - <style> 1`] = `"[@modular-css/svelte] Unable to find .nope, .alsonope in \\"<style>\\""`;
 
 exports[`/svelte.js should handle errors: empty css file - <style> 2`] = `
 Array [
   Array [
-    "@modular-css/svelte: Unable to find .nope in \\"<style>\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .nope in <style>",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .alsonope in \\"<style>\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .alsonope in <style>",
   ],
 ]
 `;
@@ -273,15 +281,19 @@ exports[`/svelte.js should handle errors: empty css file - <style> 3`] = `
 "
 `;
 
-exports[`/svelte.js should handle errors: invalid reference <script> - <link> 1`] = `"@modular-css/svelte: Unable to find .nuhuh, .alsono in \\"./invalid.css\\""`;
+exports[`/svelte.js should handle errors: invalid reference <script> - <link> 1`] = `"[@modular-css/svelte] Unable to find .nuhuh, .alsono in \\"./invalid.css\\""`;
 
 exports[`/svelte.js should handle errors: invalid reference <script> - <link> 2`] = `
 Array [
   Array [
-    "@modular-css/svelte: Unable to find .nuhuh in \\"./invalid.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .nuhuh in ./invalid.css",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .alsono in \\"./invalid.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .alsono in ./invalid.css",
   ],
 ]
 `;
@@ -299,15 +311,19 @@ import css from \\"./invalid.css\\";
 "
 `;
 
-exports[`/svelte.js should handle errors: invalid reference <script> - <style> 1`] = `"@modular-css/svelte: Unable to find .nuhuh, .alsono in \\"<style>\\""`;
+exports[`/svelte.js should handle errors: invalid reference <script> - <style> 1`] = `"[@modular-css/svelte] Unable to find .nuhuh, .alsono in \\"<style>\\""`;
 
 exports[`/svelte.js should handle errors: invalid reference <script> - <style> 2`] = `
 Array [
   Array [
-    "@modular-css/svelte: Unable to find .nuhuh in \\"<style>\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .nuhuh in <style>",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .alsono in \\"<style>\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .alsono in <style>",
   ],
 ]
 `;
@@ -324,15 +340,19 @@ exports[`/svelte.js should handle errors: invalid reference <script> - <style> 3
 "
 `;
 
-exports[`/svelte.js should handle errors: invalid reference template - <link> 1`] = `"@modular-css/svelte: Unable to find .nope, .stillnope in \\"./invalid.css\\""`;
+exports[`/svelte.js should handle errors: invalid reference template - <link> 1`] = `"[@modular-css/svelte] Unable to find .nope, .stillnope in \\"./invalid.css\\""`;
 
 exports[`/svelte.js should handle errors: invalid reference template - <link> 2`] = `
 Array [
   Array [
-    "@modular-css/svelte: Unable to find .nope in \\"./invalid.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .nope in ./invalid.css",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .stillnope in \\"./invalid.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .stillnope in ./invalid.css",
   ],
 ]
 `;
@@ -344,15 +364,19 @@ exports[`/svelte.js should handle errors: invalid reference template - <link> 3`
 <script>import css from \\"./invalid.css\\";</script>"
 `;
 
-exports[`/svelte.js should handle errors: invalid reference template - <style> 1`] = `"@modular-css/svelte: Unable to find .nope, .alsonope in \\"<style>\\""`;
+exports[`/svelte.js should handle errors: invalid reference template - <style> 1`] = `"[@modular-css/svelte] Unable to find .nope, .alsonope in \\"<style>\\""`;
 
 exports[`/svelte.js should handle errors: invalid reference template - <style> 2`] = `
 Array [
   Array [
-    "@modular-css/svelte: Unable to find .nope in \\"<style>\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .nope in <style>",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .alsonope in \\"<style>\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .alsonope in <style>",
   ],
 ]
 `;
@@ -436,12 +460,12 @@ exports[`/svelte.js should no-op if all <link>s reference a URL 1`] = `
 exports[`/svelte.js should support verbose output: <link> 1`] = `
 Array [
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "Processing",
     "packages/svelte/test/specimens/external.html",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "extract <link>",
     "packages/svelte/test/specimens/external.css",
   ],
@@ -506,17 +530,17 @@ Array [
     "packages/svelte/test/specimens/external.css",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "processed styles",
     "packages/svelte/test/specimens/external.html",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "updating source {css.<key>} references from",
     "./external.css",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "[\\"color\\",\\"flex\\",\\"wrapper\\",\\"hd\\",\\"bd\\",\\"text\\",\\"active\\"]",
   ],
   Array [
@@ -540,12 +564,12 @@ Array [
 exports[`/svelte.js should support verbose output: <style> 1`] = `
 Array [
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "Processing",
     "packages/svelte/test/specimens/style.html",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "extract <style>",
     "packages/svelte/test/specimens/style.html",
   ],
@@ -610,17 +634,17 @@ Array [
     "packages/svelte/test/specimens/style.html",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "processed styles",
     "packages/svelte/test/specimens/style.html",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "updating source {css.<key>} references from",
     "<style>",
   ],
   Array [
-    "[svelte]",
+    "[@modular-css/svelte]",
     "[\\"flex\\",\\"wrapper\\",\\"hd\\",\\"bd\\",\\"text\\",\\"active\\"]",
   ],
   Array [
@@ -641,7 +665,7 @@ Array [
 ]
 `;
 
-exports[`/svelte.js should throw on both <style> and <link> in one file 1`] = `"@modular-css/svelte: use <style> OR <link>, but not both"`;
+exports[`/svelte.js should throw on both <style> and <link> in one file 1`] = `"[@modular-css/svelte] Use <style> OR <link>, but not both in \\"packages/svelte/test/specimens/both.html\\""`;
 
 exports[`/svelte.js should use an already-created processor 1`] = `
 "<link rel=\\"stylesheet\\" href=\\"http://example.com/styles.css\\" />
@@ -682,6 +706,23 @@ entry2
 ]
 `;
 
+exports[`/svelte.js should warn about unquoted class attributes 1`] = `
+Array [
+  Array [
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unquoted class attribute! class={css.b}",
+    "packages/svelte/test/specimens/unquoted.html",
+  ],
+  Array [
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unquoted class attribute! class={css.a}",
+    "packages/svelte/test/specimens/unquoted.html",
+  ],
+]
+`;
+
 exports[`/svelte.js should warn when multiple <link> elements are in the html 1`] = `
 "<link rel=\\"stylesheet\\" href=\\"./simple2.css\\" />
 
@@ -701,10 +742,15 @@ exports[`/svelte.js should warn when multiple <link> elements are in the html 2`
 exports[`/svelte.js should warn when multiple <link> elements are in the html 3`] = `
 Array [
   Array [
-    "@modular-css/svelte will only use the first local <link> tag",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Only the first local <link> tag will be used",
+    "packages/svelte/test/specimens/multiple-link.html",
   ],
   Array [
-    "@modular-css/svelte: Unable to find .booga in \\"./simple.css\\"",
+    "[@modular-css/svelte]",
+    "WARN",
+    "Unable to find .booga in ./simple.css",
   ],
 ]
 `;
diff --git a/packages/svelte/test/specimens/unquoted.html b/packages/svelte/test/specimens/unquoted.html
new file mode 100644
index 00000000..26860be5
--- /dev/null
+++ b/packages/svelte/test/specimens/unquoted.html
@@ -0,0 +1,18 @@
+<div class={css.b}>
+    I'm not quoted lol
+    <div class={css.a}>Me either!</div>
+</div>
+
+<div class="{css.b}">
+    But I am!
+</div>
+
+<style>
+    .a {
+        color: red;
+    }
+
+    .b {
+        composes: a;
+    }
+</style>
diff --git a/packages/svelte/test/svelte.test.js b/packages/svelte/test/svelte.test.js
index 1e65f286..3a7a849b 100644
--- a/packages/svelte/test/svelte.test.js
+++ b/packages/svelte/test/svelte.test.js
@@ -30,7 +30,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -51,14 +54,17 @@ describe("/svelte.js", () => {
             try {
                 await svelte.preprocess(
                     fs.readFileSync(filename, "utf8"),
-                    Object.assign({}, preprocess, { filename })
+                    {
+                        ...preprocess,
+                        filename,
+                    },
                 );
             } catch(e) {
                 expect(e.toString()).toMatch(/\.wooga/);
             }
         }
     );
-    
+
     it("should ignore <links> that reference a URL", async () => {
         const filename = require.resolve("./specimens/url.html");
         const { preprocess, processor } = plugin({
@@ -67,7 +73,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -92,7 +101,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -117,7 +129,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -165,7 +180,10 @@ describe("/svelte.js", () => {
         await expect(
             svelte.preprocess(
                 fs.readFileSync(filename, "utf8"),
-                Object.assign({}, strict, { filename })
+                {
+                    ...strict,
+                    filename,
+                }
             )
         ).rejects.toThrowErrorMatchingSnapshot();
 
@@ -176,7 +194,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, loose, { filename })
+            {
+                ...loose,
+                filename,
+            }
         );
 
         expect(warnSpy).toHaveBeenCalled();
@@ -196,7 +217,10 @@ describe("/svelte.js", () => {
         await expect(
             svelte.preprocess(
                 fs.readFileSync(filename, "utf8"),
-                Object.assign({}, preprocess, { filename })
+                {
+                    ...preprocess,
+                    filename,
+                },
             )
         ).rejects.toThrowErrorMatchingSnapshot();
     });
@@ -217,7 +241,10 @@ describe("/svelte.js", () => {
 
         await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         await processor.output();
@@ -234,7 +261,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -256,7 +286,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -283,7 +316,10 @@ describe("/svelte.js", () => {
 
         let processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -301,7 +337,10 @@ describe("/svelte.js", () => {
 
         processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -325,7 +364,10 @@ describe("/svelte.js", () => {
 
         let processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -342,7 +384,10 @@ describe("/svelte.js", () => {
 
         processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -364,7 +409,10 @@ describe("/svelte.js", () => {
             ]
             .map((filename) => svelte.preprocess(
                 fs.readFileSync(filename, "utf8"),
-                Object.assign({}, preprocess, { filename })
+                {
+                    ...preprocess,
+                    filename,
+                },
             ))
         );
 
@@ -387,7 +435,10 @@ describe("/svelte.js", () => {
 
         const processed = await svelte.preprocess(
             fs.readFileSync(filename, "utf8"),
-            Object.assign({}, preprocess, { filename })
+            {
+                ...preprocess,
+                filename,
+            },
         );
 
         expect(processed.toString()).toMatchSnapshot();
@@ -396,4 +447,22 @@ describe("/svelte.js", () => {
 
         expect(output.css).toMatchSnapshot();
     });
+
+    it("should warn about unquoted class attributes", async () => {
+        const filename = require.resolve("./specimens/unquoted.html");
+        const { preprocess } = plugin({
+            namer,
+        });
+
+        await svelte.preprocess(
+            fs.readFileSync(filename, "utf8"),
+            {
+                ...preprocess,
+                filename,
+            },
+        );
+
+        expect(warnSpy).toHaveBeenCalled();
+        expect(warnSpy.mock.calls).toMatchSnapshot();
+    });
 });