Skip to content

Commit 9cc656b

Browse files
authored
feat: emit warnings on missing keys (#445)
Also adds strict mode which will throw errors on missing keys.
1 parent 5facdff commit 9cc656b

File tree

11 files changed

+224
-40
lines changed

11 files changed

+224
-40
lines changed

docs/svelte.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,13 @@ module.exports = {
8989
]
9090
};
9191
```
92+
93+
## Options
94+
95+
### `strict`
96+
97+
If `true` whenever a missing replacement is found like `{css.doesnotexist}` an error will be throw aborting the file processing. Defaults to `false`.
98+
99+
### Shared Options
100+
101+
All options are passed to the underlying `Processor` instance, see [Options](https://github.com/tivac/modular-css/blob/master/docs/api.md#options).

packages/svelte/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,10 @@ module.exports = {
129129
130130
## Options
131131
132+
### `strict`
133+
134+
If `true` whenever a missing replacement is found like `{css.doesnotexist}` an error will be throw aborting the file processing. Defaults to `false`.
135+
136+
### Shared Options
137+
132138
All options are passed to the underlying `Processor` instance, see [Options](https://github.com/tivac/modular-css/blob/master/docs/api.md#options).

packages/svelte/src/markup.js

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,46 @@ const slash = require("slash");
99
const styleRegex = /<style[\S\s]*?>([\S\s]*?)<\/style>/im;
1010
const scriptRegex = /<script[\S\s]*?>([\S\s]*?)<\/script>/im;
1111
const linkRegex = /<link\b[^<>]*?\bhref=\s*(?:"([^"]+)"|'([^']+)'|([^>\s]+))[^>]*>/im;
12+
const missedRegex = /css\.\w+/gim;
1213

13-
function updateCss({ content, result }) {
14+
function updateCss({ processor, content, result, filename }) {
1415
const exported = result.files[result.file].exports;
1516

17+
const code = content
18+
// Replace simple {css.<key>} values first
19+
.replace(
20+
new RegExp(`{css\\.(${Object.keys(exported).join("|")})}`, "gm"),
21+
(match, key) => exported[key].join(" ")
22+
)
23+
// Then any remaining bare css.<key> values
24+
.replace(
25+
new RegExp(`(\\b)css\\.(${Object.keys(exported).join("|")})(\\b)`, "gm"),
26+
(match, prefix, key, suffix) => `${prefix}"${exported[key].join(" ")}"${suffix}`
27+
);
28+
29+
// Check for any values in the template we couldn't convert
30+
const missed = code.match(missedRegex);
31+
32+
if(missed) {
33+
const { strict } = processor.options;
34+
35+
if(strict) {
36+
throw new Error(`modular-css-svelte: Unable to find "${missed.join(", ")}" in ${filename}`);
37+
}
38+
39+
missed.forEach((key) =>
40+
// eslint-disable-next-line no-console
41+
console.warn(`modular-css-svelte: Unable to find "${key}" in ${filename}`)
42+
);
43+
}
44+
1645
return {
17-
code : content
18-
// Replace simple {css.<key>} values first
19-
.replace(
20-
new RegExp(`{css\\.(${Object.keys(exported).join("|")})}`, "gm"),
21-
(match, key) => exported[key].join(" ")
22-
)
23-
// Then any remaining bare css.<key> values
24-
.replace(
25-
new RegExp(`(\\b)css\\.(${Object.keys(exported).join("|")})(\\b)`, "gm"),
26-
(match, prefix, key, suffix) => `${prefix}"${exported[key].join(" ")}"${suffix}`
27-
),
46+
code,
2847
};
2948
}
3049

3150
async function extractLink({ processor, content, filename, link }) {
51+
// This looks weird, but it's to support multiple types of quotation marks
3252
const href = link[1] || link[2] || link[3];
3353

3454
const external = slash(resolve(path.dirname(filename), href));
@@ -41,11 +61,13 @@ async function extractLink({ processor, content, filename, link }) {
4161
// To get rollup to watch the CSS file we need to inject an import statement
4262
// if a <script> block already exists hijack it otherwise inject a simple one
4363
if(script) {
64+
const [ tag, contents ] = script;
65+
4466
content = content.replace(
45-
script[0],
46-
script[0].replace(script[1], dedent(`
67+
tag,
68+
tag.replace(contents, dedent(`
4769
import css from ${JSON.stringify(href)};
48-
${script[1]}
70+
${contents}
4971
`))
5072
);
5173
} else {
@@ -58,7 +80,12 @@ async function extractLink({ processor, content, filename, link }) {
5880

5981
const result = await processor.file(external);
6082

61-
return updateCss({ content, result });
83+
return updateCss({
84+
processor,
85+
content,
86+
result,
87+
filename : href,
88+
});
6289
}
6390

6491
async function extractStyle({ processor, content, filename, style }) {
@@ -67,16 +94,20 @@ async function extractStyle({ processor, content, filename, style }) {
6794
style[1]
6895
);
6996

70-
return updateCss({ content, result });
97+
return updateCss({
98+
processor,
99+
content,
100+
result,
101+
filename : "<style>",
102+
});
71103
}
72104

73-
/* eslint-disable max-statements */
74105
module.exports = (processor) => ({ content, filename }) => {
75106
const link = content.match(linkRegex);
76107
const style = content.match(styleRegex);
77108

78109
if(link && style) {
79-
throw new Error("modular-css-svelte supports <style> OR <link>, but not both");
110+
throw new Error("modular-css-svelte: use <style> OR <link>, but not both");
80111
}
81112

82113
if(style) {

packages/svelte/svelte.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ const markup = require("./src/markup.js");
66
const style = require("./src/style.js");
77

88
module.exports = function(args) {
9-
const processor = new Processor(args);
9+
const config = Object.assign(
10+
Object.create(null),
11+
{ strict : false },
12+
args
13+
);
14+
15+
const processor = new Processor(config);
1016

1117
return {
1218
processor,

packages/svelte/test/__snapshots__/svelte.test.js.snap

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,91 @@ exports[`/svelte.js should extract CSS from a <style> tag 2`] = `
205205
"
206206
`;
207207
208+
exports[`/svelte.js should handle invalid references in "<script>" (inline: false, strict: false) 1`] = `
209+
Array [
210+
Array [
211+
"modular-css-svelte: Unable to find \\"css.nuhuh\\" in ./invalid.css",
212+
],
213+
]
214+
`;
215+
216+
exports[`/svelte.js should handle invalid references in "<script>" (inline: false, strict: false) 2`] = `
217+
"
218+
219+
<h2 class=\\"yup\\">Yup</h2>
220+
221+
<script>import css from \\"./invalid.css\\";
222+
223+
console.log(css.nuhuh);</script>
224+
"
225+
`;
226+
227+
exports[`/svelte.js should handle invalid references in "<script>" (inline: false, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nuhuh\\" in ./invalid.css"`;
228+
229+
exports[`/svelte.js should handle invalid references in "<script>" (inline: true, strict: false) 1`] = `
230+
Array [
231+
Array [
232+
"modular-css-svelte: Unable to find \\"css.nuhuh\\" in <style>",
233+
],
234+
]
235+
`;
236+
237+
exports[`/svelte.js should handle invalid references in "<script>" (inline: true, strict: false) 2`] = `
238+
"<h2 class=\\"yup\\">Yup</h2>
239+
240+
<style>/* replaced by modular-css */</style>
241+
242+
<script>
243+
console.log(css.nuhuh);
244+
</script>
245+
"
246+
`;
247+
248+
exports[`/svelte.js should handle invalid references in "<script>" (inline: true, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nuhuh\\" in <style>"`;
249+
250+
exports[`/svelte.js should handle invalid references in "template" (inline: false, strict: false) 1`] = `
251+
Array [
252+
Array [
253+
"modular-css-svelte: Unable to find \\"css.nope\\" in ./invalid.css",
254+
],
255+
]
256+
`;
257+
258+
exports[`/svelte.js should handle invalid references in "template" (inline: false, strict: false) 2`] = `
259+
"
260+
261+
<h1 class=\\"{css.nope}\\">Nope</h1>
262+
<h2 class=\\"yup\\">Yup</h2>
263+
<script>
264+
import css from \\"./invalid.css\\";
265+
</script>"
266+
`;
267+
268+
exports[`/svelte.js should handle invalid references in "template" (inline: false, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nope\\" in ./invalid.css"`;
269+
270+
exports[`/svelte.js should handle invalid references in "template" (inline: true, strict: false) 1`] = `
271+
Array [
272+
Array [
273+
"modular-css-svelte: Unable to find \\"css.nope\\" in <style>",
274+
],
275+
]
276+
`;
277+
278+
exports[`/svelte.js should handle invalid references in "template" (inline: true, strict: false) 2`] = `
279+
"<h1 class=\\"{css.nope}\\">Nope</h1>
280+
<h2 class=\\"yup\\">Yup</h2>
281+
282+
<style>/* replaced by modular-css */</style>
283+
"
284+
`;
285+
286+
exports[`/svelte.js should handle invalid references in "template" (inline: true, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nope\\" in <style>"`;
287+
208288
exports[`/svelte.js should ignore files without <style> blocks 1`] = `
209289
"<h1>Hello</h1>
210290
<script>console.log(\\"output\\")</script>"
211291
`;
212292
213293
exports[`/svelte.js should ignore files without <style> blocks 2`] = `""`;
214294
215-
exports[`/svelte.js should ignore invalid {css.<key>} 1`] = `
216-
"<h1 class=\\"{css.nope}\\">Hello</h1>
217-
<h2 class=\\"yup\\">World</h2>
218-
<style>/* replaced by modular-css */</style>"
219-
`;
295+
exports[`/svelte.js should throw on both <style> and <link> in one file 1`] = `"modular-css-svelte: use <style> OR <link>, but not both"`;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<link rel="stylesheet" href="./invalid.css" />
2+
3+
<h2 class="{css.yup}">Yup</h2>
4+
5+
<script>
6+
console.log(css.nuhuh);
7+
</script>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<link rel="stylesheet" href="./invalid.css" />
2+
3+
<h1 class="{css.nope}">Nope</h1>
4+
<h2 class="{css.yup}">Yup</h2>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<h2 class="{css.yup}">Yup</h2>
2+
3+
<style>
4+
.yup { color: red; }
5+
</style>
6+
7+
<script>
8+
console.log(css.nuhuh);
9+
</script>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<h1 class="{css.nope}">Nope</h1>
2+
<h2 class="{css.yup}">Yup</h2>
3+
4+
<style>
5+
.yup { color: red; }
6+
</style>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.yup {
2+
color: yellow;
3+
}

packages/svelte/test/svelte.test.js

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,37 +72,63 @@ describe("/svelte.js", () => {
7272
expect(output.css).toMatchSnapshot();
7373
});
7474

75-
it("should ignore invalid {css.<key>}", async () => {
75+
it.each`
76+
title | inline | strict | specimen
77+
${"<script>"} | ${true} | ${true} | ${"invalid-inline-script.html"}
78+
${"template"} | ${true} | ${true} | ${"invalid-inline-template.html"}
79+
${"<script>"} | ${true} | ${false} | ${"invalid-inline-script.html"}
80+
${"template"} | ${true} | ${false} | ${"invalid-inline-template.html"}
81+
${"<script>"} | ${false} | ${false} | ${"invalid-external-script.html"}
82+
${"template"} | ${false} | ${false} | ${"invalid-external-template.html"}
83+
${"<script>"} | ${false} | ${true} | ${"invalid-external-script.html"}
84+
${"template"} | ${false} | ${true} | ${"invalid-external-template.html"}
85+
`("should handle invalid references in $title (inline: $inline, strict: $strict)", async ({ strict, specimen }) => {
86+
const spy = jest.spyOn(global.console, "warn");
87+
88+
spy.mockImplementation(() => { /* NO-OP */ });
89+
90+
const filename = require.resolve(`./specimens/${specimen}`);
91+
7692
const { preprocess } = plugin({
7793
namer,
94+
strict,
7895
});
96+
97+
if(strict) {
98+
await expect(
99+
svelte.preprocess(
100+
fs.readFileSync(filename, "utf8"),
101+
Object.assign({}, preprocess, { filename })
102+
)
103+
).rejects.toThrowErrorMatchingSnapshot();
104+
105+
return;
106+
}
79107

80108
const processed = await svelte.preprocess(
81-
dedent(`
82-
<h1 class="{css.nope}">Hello</h1>
83-
<h2 class="{css.yup}">World</h2>
84-
<style>.yup { color: red; }</style>
85-
`),
86-
Object.assign({}, preprocess, { filename : require.resolve("./specimens/style.html") })
109+
fs.readFileSync(filename, "utf8"),
110+
Object.assign({}, preprocess, { filename })
87111
);
88112

113+
expect(spy).toHaveBeenCalled();
114+
expect(spy.mock.calls).toMatchSnapshot();
115+
89116
expect(processed.toString()).toMatchSnapshot();
90117
});
91118

92-
it("should throw on both <style> and <link> in one file", () => {
119+
it("should throw on both <style> and <link> in one file", async () => {
93120
const { preprocess } = plugin({
94121
css : "./packages/svelte/test/output/svelte.css",
95122
namer,
96123
});
97124

98125
const filename = require.resolve("./specimens/both.html");
99126

100-
return svelte.preprocess(
101-
fs.readFileSync(filename, "utf8"),
102-
Object.assign({}, preprocess, { filename })
103-
)
104-
.catch((error) =>
105-
expect(error.message).toMatch("modular-css-svelte supports <style> OR <link>, but not both")
106-
);
127+
await expect(
128+
svelte.preprocess(
129+
fs.readFileSync(filename, "utf8"),
130+
Object.assign({}, preprocess, { filename })
131+
)
132+
).rejects.toThrowErrorMatchingSnapshot();
107133
});
108134
});

0 commit comments

Comments
 (0)